commit
3565cd0fc0
25 changed files with 2020 additions and 587 deletions
27
README.md
27
README.md
|
@ -1,12 +1,14 @@
|
||||||
# NS-USBloader
|
# NS-USBloader
|
||||||
|
|
||||||
NS-USBloader is a PC-side tinfoil NSP USB uploader. Replacement for default usb_install_pc.py
|
NS-USBloader is a PC-side TinFoil/GoldLeaf NSP USB uploader. Replacement for default usb_install_pc.py and GoldTree.
|
||||||
|
|
||||||
With GUI and cookies.
|
With GUI and cookies.
|
||||||
|
|
||||||
Read more: https://developersu.blogspot.com/2019/02/ns-usbloader-en.html
|
Read more: https://developersu.blogspot.com/2019/02/ns-usbloader-en.html
|
||||||
|
|
||||||
Here is the version of 'not perfect byt anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR).
|
Here is the version of 'not perfect but anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR).
|
||||||
|
Ok, I'm almost sure that this version has bugs. I don't remember where I downloaded it. But it works for me somehow..
|
||||||
|
Let's rephrase, if you have working version of TinFoil DO NOT use this one.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ Install JRE/JDK 8 or higher (openJDK is good. Oracle's one is also good). JavaFX
|
||||||
|
|
||||||
See 'Linux' section.
|
See 'Linux' section.
|
||||||
|
|
||||||
Set 'Security & Privacy' if needed.
|
Set 'Security & Privacy' settings if needed.
|
||||||
|
|
||||||
### Windows:
|
### Windows:
|
||||||
|
|
||||||
|
@ -56,7 +58,22 @@ Set 'Security & Privacy' if needed.
|
||||||
## Known bugs
|
## Known bugs
|
||||||
* Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't selected anything on NS).
|
* Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't selected anything on NS).
|
||||||
|
|
||||||
|
## NOTES
|
||||||
|
Table 'Status' = 'Uploaded' does not means that file installed. It means that it has been sent to NS without any issues! That's what this app about.
|
||||||
|
Handling successful/failed installation is a purpose of the other side application (TinFoil/GoldLeaf). (And they don't provide any feedback interfaces so I can't detect success/failure.)
|
||||||
|
|
||||||
## TODO:
|
## TODO:
|
||||||
- [x] macOS QA by [Konstanin Kelemen](https://github.com/konstantin-kelemen). Appreciate assistance of [Vitaliy Natarov](https://github.com/SebastianUA).
|
- [x] macOS QA v0.1
|
||||||
|
- [ ] macOS QA v0.2 (partly)
|
||||||
- [x] Windows support
|
- [x] Windows support
|
||||||
- [ ] code refactoring
|
- [ ] code refactoring (almost. todo: printLog() )
|
||||||
|
- [x] GoldLeaf support
|
||||||
|
- [ ] XCI support
|
||||||
|
- [ ] File order sort (non-critical)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
Appreciate assistance and support of both Vitaliy and Konstantin. Without you all this magic would not have happened.
|
||||||
|
|
||||||
|
[Konstanin Kelemen](https://github.com/konstantin-kelemen)
|
||||||
|
|
||||||
|
[Vitaliy Natarov](https://github.com/SebastianUA)
|
42
pom.xml
42
pom.xml
|
@ -5,12 +5,50 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>loper</groupId>
|
<groupId>loper</groupId>
|
||||||
<artifactId>NS-USBloader</artifactId>
|
|
||||||
<version>0.1-SNAPSHOT</version>
|
|
||||||
<name>NS-USBloader</name>
|
<name>NS-USBloader</name>
|
||||||
|
|
||||||
|
<artifactId>ns-usbloader</artifactId>
|
||||||
|
<version>0.2-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<url>https://github.com/developersu/ns-usbloader/</url>
|
||||||
|
<description>
|
||||||
|
NSP USB loader for TinFoil and GoldLeaf
|
||||||
|
</description>
|
||||||
|
<inceptionYear>2019</inceptionYear>
|
||||||
|
<organization>
|
||||||
|
<name>Dmitry Isaenko</name>
|
||||||
|
<url>https://developersu.blogspot.com/</url>
|
||||||
|
</organization>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>GPLv3</name>
|
||||||
|
<url>LICENSE</url>
|
||||||
|
<distribution>manual</distribution>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>developer.su</id>
|
||||||
|
<name>Dmitry Isaenko</name>
|
||||||
|
<roles>
|
||||||
|
<role>Developer</role>
|
||||||
|
</roles>
|
||||||
|
<timezone>+3</timezone>
|
||||||
|
<url>https://developersu.blogspot.com/</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<issueManagement>
|
||||||
|
<system>GitHub</system>
|
||||||
|
<url>https://github.com/developer_su/${project.artifactId}/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
|
|
23
src/main/java/nsusbloader/AppPreferences.java
Normal file
23
src/main/java/nsusbloader/AppPreferences.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package nsusbloader;
|
||||||
|
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
public class AppPreferences {
|
||||||
|
private static final AppPreferences INSTANCE = new AppPreferences();
|
||||||
|
public static AppPreferences getInstance() { return INSTANCE; }
|
||||||
|
|
||||||
|
private Preferences preferences;
|
||||||
|
|
||||||
|
private AppPreferences(){ preferences = Preferences.userRoot().node("NS-USBloader"); }
|
||||||
|
|
||||||
|
public String getTheme(){
|
||||||
|
String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually
|
||||||
|
if (!theme.matches("(^/res/app_dark.css$)|(^/res/app_light.css$)"))
|
||||||
|
theme = "/res/app_dark.css";
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
public void setTheme(String theme){ preferences.put("THEME", theme); }
|
||||||
|
|
||||||
|
public String getRecent(){ return preferences.get("RECENT", System.getProperty("user.home")); }
|
||||||
|
public void setRecent(String path){ preferences.put("RECENT", path); }
|
||||||
|
}
|
195
src/main/java/nsusbloader/Controllers/NSLMainController.java
Normal file
195
src/main/java/nsusbloader/Controllers/NSLMainController.java
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
package nsusbloader.Controllers;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import nsusbloader.AppPreferences;
|
||||||
|
import nsusbloader.MediatorControl;
|
||||||
|
import nsusbloader.NSLMain;
|
||||||
|
import nsusbloader.UsbCommunications;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class NSLMainController implements Initializable {
|
||||||
|
|
||||||
|
private ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public TextArea logArea; // Accessible from Mediator
|
||||||
|
@FXML
|
||||||
|
private Button selectNspBtn;
|
||||||
|
@FXML
|
||||||
|
private Button uploadStopBtn;
|
||||||
|
private Region btnUpStopImage;
|
||||||
|
@FXML
|
||||||
|
public ProgressBar progressBar; // Accessible from Mediator
|
||||||
|
@FXML
|
||||||
|
private ChoiceBox<String> choiceProtocol;
|
||||||
|
@FXML
|
||||||
|
private Button switchThemeBtn;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Pane specialPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public NSTableViewController tableFilesListController; // Accessible from Mediator
|
||||||
|
|
||||||
|
private Thread usbThread;
|
||||||
|
|
||||||
|
private String previouslyOpenedPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle rb) {
|
||||||
|
this.resourceBundle = rb;
|
||||||
|
logArea.setText(rb.getString("logsGreetingsMessage")+" "+ NSLMain.appVersion+"!\n");
|
||||||
|
if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
|
||||||
|
if (!System.getProperty("user.name").equals("root"))
|
||||||
|
logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
|
||||||
|
|
||||||
|
logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
|
||||||
|
|
||||||
|
MediatorControl.getInstance().setController(this);
|
||||||
|
|
||||||
|
specialPane.getStyleClass().add("special-pane-as-border"); // UI hacks
|
||||||
|
|
||||||
|
uploadStopBtn.setDisable(true);
|
||||||
|
selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
|
||||||
|
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
|
||||||
|
|
||||||
|
selectNspBtn.getStyleClass().add("buttonSelect");
|
||||||
|
|
||||||
|
this.btnUpStopImage = new Region();
|
||||||
|
btnUpStopImage.getStyleClass().add("regionUpload");
|
||||||
|
//uploadStopBtn.getStyleClass().remove("button");
|
||||||
|
uploadStopBtn.getStyleClass().add("buttonUp");
|
||||||
|
uploadStopBtn.setGraphic(btnUpStopImage);
|
||||||
|
|
||||||
|
ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("TinFoil", "GoldLeaf");
|
||||||
|
choiceProtocol.setItems(choiceProtocolList);
|
||||||
|
choiceProtocol.getSelectionModel().select(0); // TODO: shared settings
|
||||||
|
choiceProtocol.setOnAction(e->tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem())); // Add listener to notify tableView controller
|
||||||
|
tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem()); // Notify tableView controller
|
||||||
|
|
||||||
|
this.previouslyOpenedPath = null;
|
||||||
|
|
||||||
|
Region btnSwitchImage = new Region();
|
||||||
|
btnSwitchImage.getStyleClass().add("regionLamp");
|
||||||
|
switchThemeBtn.setGraphic(btnSwitchImage);
|
||||||
|
this.switchThemeBtn.setOnAction(e->switchTheme());
|
||||||
|
|
||||||
|
previouslyOpenedPath = AppPreferences.getInstance().getRecent();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Changes UI theme on the go
|
||||||
|
* */
|
||||||
|
private void switchTheme(){
|
||||||
|
if (switchThemeBtn.getScene().getStylesheets().get(0).equals("/res/app_dark.css")) {
|
||||||
|
switchThemeBtn.getScene().getStylesheets().remove("/res/app_dark.css");
|
||||||
|
switchThemeBtn.getScene().getStylesheets().add("/res/app_light.css");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switchThemeBtn.getScene().getStylesheets().remove("/res/app_light.css");
|
||||||
|
switchThemeBtn.getScene().getStylesheets().add("/res/app_dark.css");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Functionality for selecting NSP button.
|
||||||
|
* Uses setReady and setNotReady to simplify code readability.
|
||||||
|
* */
|
||||||
|
private void selectFilesBtnAction(){
|
||||||
|
List<File> filesList;
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
|
||||||
|
|
||||||
|
File validator = new File(previouslyOpenedPath);
|
||||||
|
if (validator.exists())
|
||||||
|
fileChooser.setInitialDirectory(validator); // TODO: read from prefs
|
||||||
|
else
|
||||||
|
fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: read from prefs
|
||||||
|
|
||||||
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp"));
|
||||||
|
|
||||||
|
filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
|
||||||
|
if (filesList != null && !filesList.isEmpty()) {
|
||||||
|
tableFilesListController.setFiles(filesList);
|
||||||
|
uploadStopBtn.setDisable(false);
|
||||||
|
previouslyOpenedPath = filesList.get(0).getParent();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
tableFilesListController.setFiles(null);
|
||||||
|
uploadStopBtn.setDisable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* It's button listener when no transmission executes
|
||||||
|
* */
|
||||||
|
private void uploadBtnAction(){
|
||||||
|
if (usbThread == null || !usbThread.isAlive()){
|
||||||
|
List<File> nspToUpload;
|
||||||
|
if ((nspToUpload = tableFilesListController.getFiles()) == null) {
|
||||||
|
resourceBundle.getString("logsNoFolderFileSelected");
|
||||||
|
return;
|
||||||
|
}else {
|
||||||
|
logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
|
||||||
|
for (File item: nspToUpload)
|
||||||
|
logArea.appendText(" "+item.getAbsolutePath()+"\n");
|
||||||
|
}
|
||||||
|
UsbCommunications usbCommunications = new UsbCommunications(nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem());
|
||||||
|
usbThread = new Thread(usbCommunications);
|
||||||
|
usbThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* It's button listener when transmission in progress
|
||||||
|
* */
|
||||||
|
private void stopBtnAction(){
|
||||||
|
if (usbThread != null && usbThread.isAlive()){
|
||||||
|
usbThread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
|
||||||
|
* Called from mediator
|
||||||
|
* */
|
||||||
|
public void notifyTransmissionStarted(boolean isTransmissionStarted){
|
||||||
|
if (isTransmissionStarted) {
|
||||||
|
selectNspBtn.setDisable(true);
|
||||||
|
uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
|
||||||
|
|
||||||
|
uploadStopBtn.setText(resourceBundle.getString("btnStop"));
|
||||||
|
|
||||||
|
btnUpStopImage.getStyleClass().remove("regionUpload");
|
||||||
|
btnUpStopImage.getStyleClass().add("regionStop");
|
||||||
|
|
||||||
|
uploadStopBtn.getStyleClass().remove("buttonUp");
|
||||||
|
uploadStopBtn.getStyleClass().add("buttonStop");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectNspBtn.setDisable(false);
|
||||||
|
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
|
||||||
|
|
||||||
|
uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
|
||||||
|
|
||||||
|
btnUpStopImage.getStyleClass().remove("regionStop");
|
||||||
|
btnUpStopImage.getStyleClass().add("regionUpload");
|
||||||
|
|
||||||
|
uploadStopBtn.getStyleClass().remove("buttonStop");
|
||||||
|
uploadStopBtn.getStyleClass().add("buttonUp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Save preferences before exit
|
||||||
|
* */
|
||||||
|
public void exit(){
|
||||||
|
AppPreferences.getInstance().setTheme(switchThemeBtn.getScene().getStylesheets().get(0));
|
||||||
|
AppPreferences.getInstance().setRecent(previouslyOpenedPath);
|
||||||
|
}
|
||||||
|
}
|
54
src/main/java/nsusbloader/Controllers/NSLRowModel.java
Normal file
54
src/main/java/nsusbloader/Controllers/NSLRowModel.java
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package nsusbloader.Controllers;
|
||||||
|
|
||||||
|
import nsusbloader.NSLDataTypes.EFileStatus;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class NSLRowModel {
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
private File nspFile;
|
||||||
|
private String nspFileName;
|
||||||
|
private String nspFileSize;
|
||||||
|
private boolean markForUpload;
|
||||||
|
|
||||||
|
NSLRowModel(File nspFile, boolean checkBoxValue){
|
||||||
|
this.nspFile = nspFile;
|
||||||
|
this.markForUpload = checkBoxValue;
|
||||||
|
this.nspFileName = nspFile.getName();
|
||||||
|
this.nspFileSize = String.format("%.2f", nspFile.length()/1024.0/1024.0);
|
||||||
|
this.status = "";
|
||||||
|
}
|
||||||
|
// Model methods start
|
||||||
|
public String getStatus(){
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
public String getNspFileName(){
|
||||||
|
return nspFileName;
|
||||||
|
}
|
||||||
|
public String getNspFileSize() { return nspFileSize; }
|
||||||
|
public boolean isMarkForUpload() {
|
||||||
|
return markForUpload;
|
||||||
|
}
|
||||||
|
// Model methods end
|
||||||
|
|
||||||
|
public void setMarkForUpload(boolean value){
|
||||||
|
markForUpload = value;
|
||||||
|
}
|
||||||
|
public File getNspFile(){ return nspFile; }
|
||||||
|
public void setStatus(EFileStatus status){ // TODO: Localization
|
||||||
|
switch (status){
|
||||||
|
case UPLOADED:
|
||||||
|
this.status = "Success";
|
||||||
|
markForUpload = false;
|
||||||
|
break;
|
||||||
|
case FAILED:
|
||||||
|
this.status = "Failed";
|
||||||
|
break;
|
||||||
|
case INCORRECT_FILE_FAILED:
|
||||||
|
this.status = "Failed: Incorrect file";
|
||||||
|
markForUpload = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
177
src/main/java/nsusbloader/Controllers/NSTableViewController.java
Normal file
177
src/main/java/nsusbloader/Controllers/NSTableViewController.java
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package nsusbloader.Controllers;
|
||||||
|
|
||||||
|
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.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import nsusbloader.NSLDataTypes.EFileStatus;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class NSTableViewController implements Initializable {
|
||||||
|
@FXML
|
||||||
|
private TableView<NSLRowModel> table;
|
||||||
|
private ObservableList<NSLRowModel> rowsObsLst;
|
||||||
|
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
rowsObsLst = FXCollections.observableArrayList();
|
||||||
|
table.setPlaceholder(new Label());
|
||||||
|
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
|
|
||||||
|
TableColumn<NSLRowModel, String> statusColumn = new TableColumn<>(resourceBundle.getString("tableStatusLbl"));
|
||||||
|
TableColumn<NSLRowModel, String> fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl"));
|
||||||
|
TableColumn<NSLRowModel, String> fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl"));
|
||||||
|
TableColumn<NSLRowModel, Boolean> uploadColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl"));
|
||||||
|
// See https://bugs.openjdk.java.net/browse/JDK-8157687
|
||||||
|
statusColumn.setMinWidth(100.0);
|
||||||
|
statusColumn.setPrefWidth(100.0);
|
||||||
|
statusColumn.setMaxWidth(100.0);
|
||||||
|
statusColumn.setResizable(false);
|
||||||
|
|
||||||
|
fileNameColumn.setMinWidth(25.0);
|
||||||
|
|
||||||
|
fileSizeColumn.setMinWidth(120.0);
|
||||||
|
fileSizeColumn.setPrefWidth(120.0);
|
||||||
|
fileSizeColumn.setMaxWidth(120.0);
|
||||||
|
fileSizeColumn.setResizable(false);
|
||||||
|
|
||||||
|
uploadColumn.setMinWidth(100.0);
|
||||||
|
uploadColumn.setPrefWidth(100.0);
|
||||||
|
uploadColumn.setMaxWidth(100.0);
|
||||||
|
uploadColumn.setResizable(false);
|
||||||
|
|
||||||
|
statusColumn.setCellValueFactory(new PropertyValueFactory<>("status"));
|
||||||
|
fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileName"));
|
||||||
|
fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileSize"));
|
||||||
|
// ><
|
||||||
|
uploadColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<NSLRowModel, Boolean>, ObservableValue<Boolean>>() {
|
||||||
|
@Override
|
||||||
|
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<NSLRowModel, Boolean> paramFeatures) {
|
||||||
|
NSLRowModel model = paramFeatures.getValue();
|
||||||
|
|
||||||
|
SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkForUpload());
|
||||||
|
|
||||||
|
booleanProperty.addListener(new ChangeListener<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {
|
||||||
|
model.setMarkForUpload(newValue);
|
||||||
|
restrictSelection(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return booleanProperty;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadColumn.setCellFactory(new Callback<TableColumn<NSLRowModel, Boolean>, TableCell<NSLRowModel, Boolean>>() {
|
||||||
|
@Override
|
||||||
|
public TableCell<NSLRowModel, Boolean> call(TableColumn<NSLRowModel, Boolean> paramFeatures) {
|
||||||
|
CheckBoxTableCell<NSLRowModel, Boolean> cell = new CheckBoxTableCell<>();
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.setItems(rowsObsLst);
|
||||||
|
table.getColumns().addAll(statusColumn, fileNameColumn, fileSizeColumn, uploadColumn);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* See uploadColumn callback. In case of GoldLeaf we have to restrict selection
|
||||||
|
* */
|
||||||
|
private void restrictSelection(NSLRowModel modelChecked){
|
||||||
|
if (!protocol.equals("TinFoil") && rowsObsLst.size() > 1) { // Tinfoil doesn't need any restrictions. If only one file in list, also useless
|
||||||
|
for (NSLRowModel model: rowsObsLst){
|
||||||
|
if (model != modelChecked)
|
||||||
|
model.setMarkForUpload(false);
|
||||||
|
}
|
||||||
|
table.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add files when user selected them
|
||||||
|
* */
|
||||||
|
public void setFiles(List<File> files){
|
||||||
|
rowsObsLst.clear(); // TODO: consider table refresh
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (protocol.equals("TinFoil")){
|
||||||
|
for (File nspFile: files){
|
||||||
|
rowsObsLst.add(new NSLRowModel(nspFile, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rowsObsLst.clear();
|
||||||
|
for (File nspFile: files){
|
||||||
|
rowsObsLst.add(new NSLRowModel(nspFile, false));
|
||||||
|
}
|
||||||
|
rowsObsLst.get(0).setMarkForUpload(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return files ready for upload. Requested from NSLMainController only
|
||||||
|
* @return null if no files marked for upload
|
||||||
|
* List<File> if there are files
|
||||||
|
* */
|
||||||
|
public List<File> getFiles(){
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
if (rowsObsLst.isEmpty())
|
||||||
|
return null;
|
||||||
|
else {
|
||||||
|
for (NSLRowModel model: rowsObsLst){
|
||||||
|
if (model.isMarkForUpload())
|
||||||
|
files.add(model.getNspFile());
|
||||||
|
}
|
||||||
|
if (!files.isEmpty())
|
||||||
|
return files;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Update files in case something is wrong. Requested from UsbCommunications
|
||||||
|
* */
|
||||||
|
public void setFileStatus(String fileName, EFileStatus status){
|
||||||
|
for (NSLRowModel model: rowsObsLst){
|
||||||
|
if (model.getNspFileName().equals(fileName)){
|
||||||
|
model.setStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.refresh();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called if selected different USB protocol
|
||||||
|
* */
|
||||||
|
public void setNewProtocol(String newProtocol){
|
||||||
|
protocol = newProtocol;
|
||||||
|
if (rowsObsLst.isEmpty())
|
||||||
|
return;
|
||||||
|
if (newProtocol.equals("TinFoil")){
|
||||||
|
for (NSLRowModel model: rowsObsLst)
|
||||||
|
model.setMarkForUpload(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (NSLRowModel model: rowsObsLst)
|
||||||
|
model.setMarkForUpload(false);
|
||||||
|
rowsObsLst.get(0).setMarkForUpload(true);
|
||||||
|
}
|
||||||
|
table.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,25 +1,28 @@
|
||||||
package nsusbloader;
|
package nsusbloader;
|
||||||
|
|
||||||
class MediatorControl {
|
import nsusbloader.Controllers.NSLMainController;
|
||||||
private boolean isTransferActive = false;
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class MediatorControl {
|
||||||
|
private AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure
|
||||||
private NSLMainController applicationController;
|
private NSLMainController applicationController;
|
||||||
|
|
||||||
static MediatorControl getInstance(){
|
public static MediatorControl getInstance(){
|
||||||
return MediatorControlHold.INSTANCE;
|
return MediatorControlHold.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MediatorControlHold {
|
private static class MediatorControlHold {
|
||||||
private static final MediatorControl INSTANCE = new MediatorControl();
|
private static final MediatorControl INSTANCE = new MediatorControl();
|
||||||
}
|
}
|
||||||
void registerController(NSLMainController controller){
|
public void setController(NSLMainController controller){
|
||||||
this.applicationController = controller;
|
this.applicationController = controller;
|
||||||
}
|
}
|
||||||
|
NSLMainController getContoller(){ return this.applicationController; }
|
||||||
|
|
||||||
synchronized void setTransferActive(boolean state) {
|
public synchronized void setTransferActive(boolean state) {
|
||||||
isTransferActive = state;
|
isTransferActive.set(state);
|
||||||
applicationController.notifyTransmissionStarted(state);
|
applicationController.notifyTransmissionStarted(state);
|
||||||
}
|
}
|
||||||
synchronized boolean getTransferActive() {
|
public synchronized boolean getTransferActive() { return this.isTransferActive.get(); }
|
||||||
return this.isTransferActive;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@ package nsusbloader;
|
||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.scene.control.ProgressBar;
|
import javafx.scene.control.ProgressBar;
|
||||||
import javafx.scene.control.TextArea;
|
import javafx.scene.control.TextArea;
|
||||||
|
import nsusbloader.Controllers.NSTableViewController;
|
||||||
|
import nsusbloader.NSLDataTypes.EFileStatus;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
public class MessagesConsumer extends AnimationTimer {
|
public class MessagesConsumer extends AnimationTimer {
|
||||||
|
@ -13,18 +16,24 @@ public class MessagesConsumer extends AnimationTimer {
|
||||||
|
|
||||||
private final BlockingQueue<Double> progressQueue;
|
private final BlockingQueue<Double> progressQueue;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
|
private final HashMap<String, EFileStatus> statusMap;
|
||||||
|
private final NSTableViewController tableViewController;
|
||||||
|
|
||||||
private boolean isInterrupted;
|
private boolean isInterrupted;
|
||||||
|
|
||||||
MessagesConsumer(BlockingQueue<String> msgQueue, TextArea logsArea, BlockingQueue<Double> progressQueue, ProgressBar progressBar){
|
MessagesConsumer(BlockingQueue<String> msgQueue, BlockingQueue<Double> progressQueue, HashMap<String, EFileStatus> statusMap){
|
||||||
this.msgQueue = msgQueue;
|
this.isInterrupted = false;
|
||||||
this.logsArea = logsArea;
|
|
||||||
|
this.msgQueue = msgQueue;
|
||||||
|
this.logsArea = MediatorControl.getInstance().getContoller().logArea;
|
||||||
|
|
||||||
this.progressBar = progressBar;
|
|
||||||
this.progressQueue = progressQueue;
|
this.progressQueue = progressQueue;
|
||||||
|
this.progressBar = MediatorControl.getInstance().getContoller().progressBar;
|
||||||
|
|
||||||
|
this.statusMap = statusMap;
|
||||||
|
this.tableViewController = MediatorControl.getInstance().getContoller().tableFilesListController;
|
||||||
|
|
||||||
progressBar.setProgress(0.0);
|
progressBar.setProgress(0.0);
|
||||||
this.isInterrupted = false;
|
|
||||||
MediatorControl.getInstance().setTransferActive(true);
|
MediatorControl.getInstance().setTransferActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,15 +49,18 @@ public class MessagesConsumer extends AnimationTimer {
|
||||||
if (progressRecieved > 0)
|
if (progressRecieved > 0)
|
||||||
progress.forEach(prg -> progressBar.setProgress(prg));
|
progress.forEach(prg -> progressBar.setProgress(prg));
|
||||||
|
|
||||||
if (isInterrupted) {
|
if (isInterrupted) { // It's safe 'cuz it's could't be interrupted while HashMap populating
|
||||||
MediatorControl.getInstance().setTransferActive(false);
|
MediatorControl.getInstance().setTransferActive(false);
|
||||||
progressBar.setProgress(0.0);
|
progressBar.setProgress(0.0);
|
||||||
|
|
||||||
|
if (statusMap.size() > 0)
|
||||||
|
for (String key : statusMap.keySet())
|
||||||
|
tableViewController.setFileStatus(key, statusMap.get(key));
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
//TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void interrupt(){
|
void interrupt(){
|
||||||
this.isInterrupted = true;
|
this.isInterrupted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
5
src/main/java/nsusbloader/NSLDataTypes/EFileStatus.java
Normal file
5
src/main/java/nsusbloader/NSLDataTypes/EFileStatus.java
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package nsusbloader.NSLDataTypes;
|
||||||
|
|
||||||
|
public enum EFileStatus {
|
||||||
|
UPLOADED, INCORRECT_FILE_FAILED, FAILED
|
||||||
|
}
|
5
src/main/java/nsusbloader/NSLDataTypes/EMsgType.java
Normal file
5
src/main/java/nsusbloader/NSLDataTypes/EMsgType.java
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package nsusbloader.NSLDataTypes;
|
||||||
|
|
||||||
|
public enum EMsgType {
|
||||||
|
PASS, FAIL, INFO, WARNING
|
||||||
|
}
|
|
@ -1,11 +1,3 @@
|
||||||
/**
|
|
||||||
Name: NSL-USBFoil
|
|
||||||
@author Dmitry Isaenko
|
|
||||||
License: GNU GPL v.3
|
|
||||||
@see https://github.com/developersu/
|
|
||||||
@see https://developersu.blogspot.com/
|
|
||||||
2019, Russia
|
|
||||||
*/
|
|
||||||
package nsusbloader;
|
package nsusbloader;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
@ -14,22 +6,24 @@ import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import nsusbloader.Controllers.NSLMainController;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class NSLMain extends Application {
|
public class NSLMain extends Application {
|
||||||
static final String appVersion = "v0.1";
|
public static final String appVersion = "v0.2";
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception{
|
public void start(Stage primaryStage) throws Exception{
|
||||||
|
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
|
||||||
|
|
||||||
ResourceBundle rb;
|
ResourceBundle rb;
|
||||||
if (Locale.getDefault().getISO3Language().equals("rus"))
|
if (Locale.getDefault().getISO3Language().equals("rus"))
|
||||||
rb = ResourceBundle.getBundle("locale", new Locale("ru"));
|
rb = ResourceBundle.getBundle("locale", new Locale("ru"));
|
||||||
else
|
else
|
||||||
rb = ResourceBundle.getBundle("locale", new Locale("en"));
|
rb = ResourceBundle.getBundle("locale", new Locale("en"));
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
|
|
||||||
|
|
||||||
loader.setResources(rb);
|
loader.setResources(rb);
|
||||||
Parent root = loader.load();
|
Parent root = loader.load();
|
||||||
|
|
||||||
|
@ -44,7 +38,9 @@ public class NSLMain extends Application {
|
||||||
primaryStage.setMinWidth(600);
|
primaryStage.setMinWidth(600);
|
||||||
primaryStage.setMinHeight(375);
|
primaryStage.setMinHeight(375);
|
||||||
Scene mainScene = new Scene(root, 800, 400);
|
Scene mainScene = new Scene(root, 800, 400);
|
||||||
mainScene.getStylesheets().add("/res/app.css");
|
|
||||||
|
mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme());
|
||||||
|
|
||||||
primaryStage.setScene(mainScene);
|
primaryStage.setScene(mainScene);
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
|
|
||||||
|
@ -53,6 +49,9 @@ public class NSLMain extends Application {
|
||||||
if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit")))
|
if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit")))
|
||||||
e.consume();
|
e.consume();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
NSLMainController controller = loader.getController();
|
||||||
|
primaryStage.setOnHidden(e-> controller.exit());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
package nsusbloader;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.ProgressBar;
|
|
||||||
import javafx.scene.control.TextArea;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.stage.FileChooser;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
public class NSLMainController implements Initializable {
|
|
||||||
|
|
||||||
private ResourceBundle resourceBundle;
|
|
||||||
|
|
||||||
private List<File> nspToUpload;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextArea logArea;
|
|
||||||
@FXML
|
|
||||||
private Button selectNspBtn;
|
|
||||||
@FXML
|
|
||||||
private Button uploadStopBtn;
|
|
||||||
private Region btnUpStopImage;
|
|
||||||
@FXML
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
|
|
||||||
private Thread usbThread;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL url, ResourceBundle rb) {
|
|
||||||
this.resourceBundle = rb;
|
|
||||||
logArea.setText(rb.getString("logsGreetingsMessage")+" "+NSLMain.appVersion+"!\n");
|
|
||||||
if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
|
|
||||||
if (!System.getProperty("user.name").equals("root"))
|
|
||||||
logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
|
|
||||||
|
|
||||||
logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
|
|
||||||
|
|
||||||
MediatorControl.getInstance().registerController(this);
|
|
||||||
|
|
||||||
uploadStopBtn.setDisable(true);
|
|
||||||
selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
|
|
||||||
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
|
|
||||||
|
|
||||||
this.btnUpStopImage = new Region();
|
|
||||||
btnUpStopImage.getStyleClass().add("regionUpload");
|
|
||||||
//uploadStopBtn.getStyleClass().remove("button");
|
|
||||||
uploadStopBtn.getStyleClass().add("buttonUp");
|
|
||||||
uploadStopBtn.setGraphic(btnUpStopImage);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Functionality for selecting NSP button.
|
|
||||||
* Uses setReady and setNotReady to simplify code readability.
|
|
||||||
* */
|
|
||||||
private void selectFilesBtnAction(){
|
|
||||||
List<File> filesList;
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
|
||||||
fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
|
|
||||||
fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: read from prefs
|
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS ROM", "*.nsp"));
|
|
||||||
|
|
||||||
filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
|
|
||||||
if (filesList != null && !filesList.isEmpty())
|
|
||||||
setReady(filesList);
|
|
||||||
else
|
|
||||||
setNotReady(resourceBundle.getString("logsNoFolderFileSelected"));
|
|
||||||
}
|
|
||||||
private void setReady(List<File> filesList){
|
|
||||||
logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
|
|
||||||
for (File item: filesList)
|
|
||||||
logArea.appendText(" "+item.getAbsolutePath()+"\n");
|
|
||||||
nspToUpload = filesList;
|
|
||||||
uploadStopBtn.setDisable(false);
|
|
||||||
}
|
|
||||||
private void setNotReady(String whyNotReady){
|
|
||||||
logArea.setText(whyNotReady);
|
|
||||||
nspToUpload = null;
|
|
||||||
uploadStopBtn.setDisable(true);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* It's button listener when no transmission executes
|
|
||||||
* */
|
|
||||||
private void uploadBtnAction(){
|
|
||||||
if (usbThread == null || !usbThread.isAlive()){
|
|
||||||
UsbCommunications usbCommunications = new UsbCommunications(logArea, progressBar, nspToUpload); //todo: progress bar
|
|
||||||
usbThread = new Thread(usbCommunications);
|
|
||||||
usbThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* It's button listener when transmission in progress
|
|
||||||
* */
|
|
||||||
private void stopBtnAction(){
|
|
||||||
if (usbThread != null && usbThread.isAlive()){
|
|
||||||
usbThread.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
|
|
||||||
* Called from mediator
|
|
||||||
* */
|
|
||||||
void notifyTransmissionStarted(boolean isTransmissionStarted){
|
|
||||||
if (isTransmissionStarted) {
|
|
||||||
selectNspBtn.setDisable(true);
|
|
||||||
uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
|
|
||||||
|
|
||||||
uploadStopBtn.setText(resourceBundle.getString("btnStop"));
|
|
||||||
|
|
||||||
btnUpStopImage.getStyleClass().remove("regionUpload");
|
|
||||||
btnUpStopImage.getStyleClass().add("regionStop");
|
|
||||||
|
|
||||||
uploadStopBtn.getStyleClass().remove("buttonUp");
|
|
||||||
uploadStopBtn.getStyleClass().add("buttonStop");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectNspBtn.setDisable(false);
|
|
||||||
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
|
|
||||||
|
|
||||||
uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
|
|
||||||
|
|
||||||
btnUpStopImage.getStyleClass().remove("regionStop");
|
|
||||||
btnUpStopImage.getStyleClass().add("regionUpload");
|
|
||||||
|
|
||||||
uploadStopBtn.getStyleClass().remove("buttonStop");
|
|
||||||
uploadStopBtn.getStyleClass().add("buttonUp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
25
src/main/java/nsusbloader/PFS/NCAFile.java
Normal file
25
src/main/java/nsusbloader/PFS/NCAFile.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package nsusbloader.PFS;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class to hold NCA, tik, xml etc. meta-information
|
||||||
|
* */
|
||||||
|
public class NCAFile {
|
||||||
|
//private int ncaNumber;
|
||||||
|
private byte[] ncaFileName;
|
||||||
|
private long ncaOffset;
|
||||||
|
private long ncaSize;
|
||||||
|
|
||||||
|
//public void setNcaNumber(int ncaNumber){ this.ncaNumber = ncaNumber; }
|
||||||
|
public void setNcaFileName(byte[] ncaFileName) { this.ncaFileName = ncaFileName; }
|
||||||
|
public void setNcaOffset(long ncaOffset) { this.ncaOffset = ncaOffset; }
|
||||||
|
public void setNcaSize(long ncaSize) { this.ncaSize = ncaSize; }
|
||||||
|
|
||||||
|
//public int getNcaNumber() {return this.ncaNumber; }
|
||||||
|
public byte[] getNcaFileName() { return ncaFileName; }
|
||||||
|
public byte[] getNcaFileNameLength() { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFileName.length).array(); }
|
||||||
|
public long getNcaOffset() { return ncaOffset; }
|
||||||
|
public long getNcaSize() { return ncaSize; }
|
||||||
|
}
|
243
src/main/java/nsusbloader/PFS/PFSProvider.java
Normal file
243
src/main/java/nsusbloader/PFS/PFSProvider.java
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
package nsusbloader.PFS;
|
||||||
|
|
||||||
|
import nsusbloader.NSLDataTypes.EMsgType;
|
||||||
|
import nsusbloader.ServiceWindow;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in GoldLeaf USB protocol
|
||||||
|
* */
|
||||||
|
public class PFSProvider {
|
||||||
|
private static final byte[] PFS0 = new byte[]{(byte)0x50, (byte)0x46, (byte)0x53, (byte)0x30}; // PFS0, and what did you think?
|
||||||
|
|
||||||
|
private BlockingQueue<String> msgQueue;
|
||||||
|
private ResourceBundle rb;
|
||||||
|
|
||||||
|
private RandomAccessFile randAccessFile;
|
||||||
|
private String nspFileName;
|
||||||
|
private NCAFile[] ncaFiles;
|
||||||
|
private long bodySize;
|
||||||
|
private int ticketID = -1;
|
||||||
|
|
||||||
|
public PFSProvider(File nspFile, BlockingQueue msgQueue){
|
||||||
|
this.msgQueue = msgQueue;
|
||||||
|
try {
|
||||||
|
this.randAccessFile = new RandomAccessFile(nspFile, "r");
|
||||||
|
nspFileName = nspFile.getName();
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException fnfe){
|
||||||
|
printLog("PFS File not founnd: \n "+fnfe.getMessage(), EMsgType.FAIL);
|
||||||
|
nspFileName = null;
|
||||||
|
}
|
||||||
|
if (Locale.getDefault().getISO3Language().equals("rus"))
|
||||||
|
rb = ResourceBundle.getBundle("locale", new Locale("ru"));
|
||||||
|
else
|
||||||
|
rb = ResourceBundle.getBundle("locale", new Locale("en"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean init() {
|
||||||
|
if (nspFileName == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int filesCount;
|
||||||
|
int header;
|
||||||
|
|
||||||
|
printLog("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO);
|
||||||
|
try {
|
||||||
|
byte[] fileStartingBytes = new byte[12];
|
||||||
|
// Read PFS0, files count, header, padding (4 zero bytes)
|
||||||
|
if (randAccessFile.read(fileStartingBytes) == 12)
|
||||||
|
printLog("PFS Read file starting bytes.", EMsgType.PASS);
|
||||||
|
else {
|
||||||
|
printLog("PFS Read file starting bytes.", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check PFS0
|
||||||
|
if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4)))
|
||||||
|
printLog("PFS Read 'PFS0'.", EMsgType.PASS);
|
||||||
|
else {
|
||||||
|
printLog("PFS Read 'PFS0'.", EMsgType.WARNING);
|
||||||
|
if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) {
|
||||||
|
randAccessFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get files count
|
||||||
|
filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
if (filesCount > 0 ) {
|
||||||
|
printLog("PFS Read files count [" + filesCount + "]", EMsgType.PASS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printLog("PFS Read files count", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Get header
|
||||||
|
header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
if (header > 0 )
|
||||||
|
printLog("PFS Read header ["+header+"]", EMsgType.PASS);
|
||||||
|
else {
|
||||||
|
printLog("PFS Read header ", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//*********************************************************************************************
|
||||||
|
// Create NCA set
|
||||||
|
this.ncaFiles = new NCAFile[filesCount];
|
||||||
|
// Collect files from NSP
|
||||||
|
byte[] ncaInfoArr = new byte[24]; // should be unsigned long, but.. java.. u know my pain man
|
||||||
|
|
||||||
|
HashMap<Integer, Long> ncaNameOffsets = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
long nca_offset;
|
||||||
|
long nca_size;
|
||||||
|
long nca_name_offset;
|
||||||
|
|
||||||
|
for (int i=0; i<filesCount; i++){
|
||||||
|
if (randAccessFile.read(ncaInfoArr) == 24) {
|
||||||
|
printLog("PFS Read NCA inside NSP: " + i, EMsgType.PASS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printLog("PFS Read NCA inside NSP: "+i, EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 0, 4)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
nca_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 4, 12)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||||
|
nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||||
|
nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
|
||||||
|
|
||||||
|
printLog(" Padding check", offset == 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
printLog(" NCA offset check "+nca_offset, nca_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
printLog(" NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING);
|
||||||
|
printLog(" NCA name offset check "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
|
||||||
|
NCAFile ncaFile = new NCAFile();
|
||||||
|
ncaFile.setNcaOffset(nca_offset);
|
||||||
|
ncaFile.setNcaSize(nca_size);
|
||||||
|
this.ncaFiles[i] = ncaFile;
|
||||||
|
|
||||||
|
ncaNameOffsets.put(i, nca_name_offset);
|
||||||
|
}
|
||||||
|
// Final offset
|
||||||
|
byte[] bufForInt = new byte[4];
|
||||||
|
if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4])))
|
||||||
|
printLog("PFS Final padding check", EMsgType.PASS);
|
||||||
|
else
|
||||||
|
printLog("PFS Final padding check", EMsgType.WARNING);
|
||||||
|
|
||||||
|
// Calculate position including header for body size offset
|
||||||
|
bodySize = randAccessFile.getFilePointer()+header;
|
||||||
|
//*********************************************************************************************
|
||||||
|
// Collect file names from NCAs
|
||||||
|
printLog("PFS Collecting file names", EMsgType.INFO);
|
||||||
|
List<Byte> ncaFN; // Temporary
|
||||||
|
byte[] b = new byte[1]; // Temporary
|
||||||
|
for (int i=0; i<filesCount; i++){
|
||||||
|
ncaFN = new ArrayList<>();
|
||||||
|
randAccessFile.seek(filesCount*24+16+ncaNameOffsets.get(i)); // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them + 12 bit what were in the beginning
|
||||||
|
while ((randAccessFile.read(b)) != -1){
|
||||||
|
if (b[0] == 0x00)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
ncaFN.add(b[0]);
|
||||||
|
}
|
||||||
|
byte[] exchangeTempArray = new byte[ncaFN.size()];
|
||||||
|
for (int j=0; j < ncaFN.size(); j++)
|
||||||
|
exchangeTempArray[j] = ncaFN.get(j);
|
||||||
|
// Find and store ticket (.tik)
|
||||||
|
if (new String(exchangeTempArray, StandardCharsets.UTF_8).toLowerCase().endsWith(".tik"))
|
||||||
|
this.ticketID = i;
|
||||||
|
this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length));
|
||||||
|
}
|
||||||
|
randAccessFile.close();
|
||||||
|
}
|
||||||
|
catch (IOException ioe){
|
||||||
|
printLog("PFS Failed NSP file analyze for ["+nspFileName+"]\n "+ioe.getMessage(), EMsgType.FAIL);
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
printLog("PFS Finish NSP file analyze for ["+nspFileName+"]", EMsgType.PASS);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return file name as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesNspFileName(){
|
||||||
|
return nspFileName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return file name as String
|
||||||
|
* */
|
||||||
|
public String getStringNspFileName(){
|
||||||
|
return nspFileName;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return file name length as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesNspFileNameLength(){
|
||||||
|
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return NCA count inside of file as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesCountOfNca(){
|
||||||
|
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return NCA count inside of file as int
|
||||||
|
* */
|
||||||
|
public int getIntCountOfNca(){
|
||||||
|
return ncaFiles.length;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return requested-by-number NCA file inside of file
|
||||||
|
* */
|
||||||
|
public NCAFile getNca(int ncaNumber){
|
||||||
|
return ncaFiles[ncaNumber];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return bodySize
|
||||||
|
* */
|
||||||
|
public long getBodySize(){
|
||||||
|
return bodySize;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return special NCA file: ticket
|
||||||
|
* (sugar)
|
||||||
|
* */
|
||||||
|
public int getNcaTicketID(){
|
||||||
|
return ticketID;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This is what will print to textArea of the application.
|
||||||
|
**/
|
||||||
|
private void printLog(String message, EMsgType type){
|
||||||
|
try {
|
||||||
|
switch (type){
|
||||||
|
case PASS:
|
||||||
|
msgQueue.put("[ PASS ] "+message+"\n");
|
||||||
|
break;
|
||||||
|
case FAIL:
|
||||||
|
msgQueue.put("[ FAIL ] "+message+"\n");
|
||||||
|
break;
|
||||||
|
case INFO:
|
||||||
|
msgQueue.put("[ INFO ] "+message+"\n");
|
||||||
|
break;
|
||||||
|
case WARNING:
|
||||||
|
msgQueue.put("[ WARN ] "+message+"\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}catch (InterruptedException ie){
|
||||||
|
ie.printStackTrace(); //TODO: ???
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/main/java/nsusbloader/RainbowHexDump.java
Normal file
31
src/main/java/nsusbloader/RainbowHexDump.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package nsusbloader;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug tool like hexdump <3
|
||||||
|
*/
|
||||||
|
public class RainbowHexDump {
|
||||||
|
private static final String ANSI_RESET = "\u001B[0m";
|
||||||
|
private static final String ANSI_BLACK = "\u001B[30m";
|
||||||
|
private static final String ANSI_RED = "\u001B[31m";
|
||||||
|
private static final String ANSI_GREEN = "\u001B[32m";
|
||||||
|
private static final String ANSI_YELLOW = "\u001B[33m";
|
||||||
|
private static final String ANSI_BLUE = "\u001B[34m";
|
||||||
|
private static final String ANSI_PURPLE = "\u001B[35m";
|
||||||
|
private static final String ANSI_CYAN = "\u001B[36m";
|
||||||
|
private static final String ANSI_WHITE = "\u001B[37m";
|
||||||
|
|
||||||
|
|
||||||
|
public static void hexDumpUTF8(byte[] byteArray){
|
||||||
|
System.out.print(ANSI_BLUE);
|
||||||
|
for (int i=0; i < byteArray.length; i++)
|
||||||
|
System.out.print(String.format("%02d-", i%100));
|
||||||
|
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
|
||||||
|
for (byte b: byteArray)
|
||||||
|
System.out.print(String.format("%02x ", b));
|
||||||
|
System.out.print("\t\t\t"
|
||||||
|
+ new String(byteArray, StandardCharsets.UTF_8)
|
||||||
|
+ "\n");
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ public class ServiceWindow {
|
||||||
* Create window with notification
|
* Create window with notification
|
||||||
* */
|
* */
|
||||||
/* // not used
|
/* // not used
|
||||||
static void getErrorNotification(String title, String body){
|
public static void getErrorNotification(String title, String body){
|
||||||
Alert alertBox = new Alert(Alert.AlertType.ERROR);
|
Alert alertBox = new Alert(Alert.AlertType.ERROR);
|
||||||
alertBox.setTitle(title);
|
alertBox.setTitle(title);
|
||||||
alertBox.setHeaderText(null);
|
alertBox.setHeaderText(null);
|
||||||
|
@ -27,7 +27,7 @@ public class ServiceWindow {
|
||||||
/**
|
/**
|
||||||
* Create notification window with confirm/deny
|
* Create notification window with confirm/deny
|
||||||
* */
|
* */
|
||||||
static boolean getConfirmationWindow(String title, String body){
|
public static boolean getConfirmationWindow(String title, String body){
|
||||||
Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION);
|
Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
alertBox.setTitle(title);
|
alertBox.setTitle(title);
|
||||||
alertBox.setHeaderText(null);
|
alertBox.setHeaderText(null);
|
||||||
|
@ -35,12 +35,9 @@ public class ServiceWindow {
|
||||||
alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
|
alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
|
||||||
alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
|
alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
|
||||||
alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really.
|
alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really.
|
||||||
alertBox.getDialogPane().getStylesheets().add("/res/app.css");
|
alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme());
|
||||||
Optional<ButtonType> result = alertBox.showAndWait();
|
Optional<ButtonType> result = alertBox.showAndWait();
|
||||||
if (result.get() == ButtonType.OK)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
return (result.isPresent() && result.get() == ButtonType.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,23 +2,80 @@
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
<?import javafx.scene.control.ProgressBar?>
|
<?import javafx.scene.control.ProgressBar?>
|
||||||
|
<?import javafx.scene.control.Tab?>
|
||||||
|
<?import javafx.scene.control.TabPane?>
|
||||||
<?import javafx.scene.control.TextArea?>
|
<?import javafx.scene.control.TextArea?>
|
||||||
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.Pane?>
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.SVGPath?>
|
<?import javafx.scene.shape.SVGPath?>
|
||||||
|
|
||||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.NSLMainController">
|
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSLMainController">
|
||||||
<children>
|
<children>
|
||||||
<VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
<VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<children>
|
<children>
|
||||||
<TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS">
|
<TabPane prefHeight="200.0" prefWidth="200.0" side="LEFT" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
||||||
<VBox.margin>
|
<tabs>
|
||||||
<Insets left="5.0" right="5.0" top="5.0" />
|
<Tab closable="false">
|
||||||
</VBox.margin>
|
<content>
|
||||||
</TextArea>
|
<VBox prefHeight="200.0" prefWidth="100.0">
|
||||||
|
<children>
|
||||||
|
<ToolBar>
|
||||||
|
<items>
|
||||||
|
<ChoiceBox fx:id="choiceProtocol" prefWidth="120.0" />
|
||||||
|
<Pane HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="switchThemeBtn" mnemonicParsing="false" />
|
||||||
|
</items>
|
||||||
|
</ToolBar>
|
||||||
|
<GridPane>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" percentWidth="90.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Pane fx:id="specialPane" GridPane.columnIndex="1" />
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="2.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</GridPane>
|
||||||
|
<fx:include fx:id="tableFilesList" source="TableView.fxml" VBox.vgrow="ALWAYS" />
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
</content>
|
||||||
|
<graphic>
|
||||||
|
<SVGPath content="M21,19V17H8V19H21M21,13V11H8V13H21M8,7H21V5H8V7M4,5V7H6V5H4M3,5A1,1 0 0,1 4,4H6A1,1 0 0,1 7,5V7A1,1 0 0,1 6,8H4A1,1 0 0,1 3,7V5M4,11V13H6V11H4M3,11A1,1 0 0,1 4,10H6A1,1 0 0,1 7,11V13A1,1 0 0,1 6,14H4A1,1 0 0,1 3,13V11M4,17V19H6V17H4M3,17A1,1 0 0,1 4,16H6A1,1 0 0,1 7,17V19A1,1 0 0,1 6,20H4A1,1 0 0,1 3,19V17Z" />
|
||||||
|
</graphic>
|
||||||
|
</Tab>
|
||||||
|
<Tab closable="false">
|
||||||
|
<content>
|
||||||
|
<VBox prefHeight="200.0" prefWidth="100.0">
|
||||||
|
<children>
|
||||||
|
<TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="2.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</TextArea>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
</content>
|
||||||
|
<graphic>
|
||||||
|
<SVGPath content="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M7,7H17V5H19V19H5V5H7V7Z" />
|
||||||
|
</graphic>
|
||||||
|
</Tab>
|
||||||
|
</tabs>
|
||||||
|
</TabPane>
|
||||||
<ProgressBar fx:id="progressBar" prefWidth="Infinity" progress="0.0">
|
<ProgressBar fx:id="progressBar" prefWidth="Infinity" progress="0.0">
|
||||||
<VBox.margin>
|
<VBox.margin>
|
||||||
<Insets left="5.0" right="5.0" top="2.0" />
|
<Insets left="5.0" right="5.0" top="2.0" />
|
||||||
|
|
14
src/main/resources/TableView.fxml
Normal file
14
src/main/resources/TableView.fxml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.TableView?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
|
||||||
|
<HBox xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSTableViewController">
|
||||||
|
<children>
|
||||||
|
<TableView fx:id="table" editable="true" HBox.hgrow="ALWAYS" />
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="2.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</HBox>
|
|
@ -12,3 +12,10 @@ logsGreetingsMessage2=--\n\
|
||||||
Source: https://github.com/developersu/ns-usbloader/\n\
|
Source: https://github.com/developersu/ns-usbloader/\n\
|
||||||
Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\
|
Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\
|
||||||
Dmitry Isaenko [developer.su]
|
Dmitry Isaenko [developer.su]
|
||||||
|
windowTitleConfirmWrongPFS0=Incorrect file type
|
||||||
|
windowBodyConfirmWrongPFS0=Selected NSP file has incrrect starting symbols. Most likely it's corrupted.\n\
|
||||||
|
It's better to interrupt proccess now. Continue process anyway?
|
||||||
|
tableStatusLbl=Status
|
||||||
|
tableFileNameLbl=File name
|
||||||
|
tableSizeLbl=Size (~Mb)
|
||||||
|
tableUploadLbl=Upload?
|
||||||
|
|
|
@ -12,4 +12,12 @@ logsGreetingsMessage2=--\n\
|
||||||
\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\
|
\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\
|
||||||
\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\
|
\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\
|
||||||
\u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su]
|
\u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su]
|
||||||
|
windowTitleConfirmWrongPFS0=\u041D\u0435\u043F\u0440\u0438\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0439 \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430
|
||||||
|
windowBodyConfirmWrongPFS0=\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u0444\u0430\u0439\u043B NSP \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0441\u0438\u043C\u0432\u043E\u043B\u044B. \u041E\u043D \u043D\u0430\u0432\u0435\u0440\u043D\u044F\u043A\u0430 \u043F\u043E\u0432\u0440\u0435\u0436\u0434\u0451\u043D.\n\
|
||||||
|
\u041B\u0443\u0447\u0448\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C\u0441\u044F \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441. \u0425\u043E\u0447\u0435\u0448\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0438 \u0441\u043C\u043E\u0442\u0440\u044F \u043D\u0438 \u043D\u0430 \u0447\u0442\u043E?\
|
||||||
|
|
||||||
|
tableUploadLbl=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044C?
|
||||||
|
tableSizeLbl=\u0420\u0430\u0437\u043C\u0435\u0440 (~\u041C\u0431)
|
||||||
|
tableFileNameLbl=\u0418\u043C\u044F \u0444\u0430\u0439\u043B\u0430
|
||||||
|
tableStatusLbl=\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435
|
||||||
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
@font-face {
|
|
||||||
src: url("NotoMono-Regular.ttf");
|
|
||||||
}
|
|
||||||
.root{
|
|
||||||
-fx-background: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button, .buttonUp, .buttonStop{
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
-fx-border-color: #4f4f4f;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
|
|
||||||
}
|
|
||||||
.button:hover, .buttonStop:hover, .buttonUp:hover{
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
-fx-border-color: #a4ffff;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
}
|
|
||||||
.button:focused, .buttonStop:focused, .buttonUp:focused{
|
|
||||||
-fx-background-color: #6a6a6a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:pressed{
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
-fx-border-color: #289de8;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
}
|
|
||||||
.buttonUp:pressed{
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
-fx-border-color: #a2e019;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
}
|
|
||||||
.buttonStop:pressed{
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
-fx-border-color: #fb582c;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-area{
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-control-inner-background: #4f4f4f;
|
|
||||||
-fx-font-family: "Noto Mono";
|
|
||||||
-fx-border-color: #00ffc9;
|
|
||||||
-fx-border-radius: 3;
|
|
||||||
-fx-border-width: 2;
|
|
||||||
-fx-text-fill: #f7fafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-box-border: transparent;
|
|
||||||
}
|
|
||||||
.progress-bar > .track {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
-fx-box-border: transparent;
|
|
||||||
}
|
|
||||||
.progress-bar > .bar {
|
|
||||||
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
|
|
||||||
-fx-background-radius: 2;
|
|
||||||
-fx-background-insets: 1 1 2 1;
|
|
||||||
-fx-padding: 0.23em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-pane {
|
|
||||||
-fx-background-color: #4f4f4f;
|
|
||||||
}
|
|
||||||
.dialog-pane > .button-bar > .container{
|
|
||||||
-fx-background-color: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-pane > .label{
|
|
||||||
-fx-padding: 10 5 10 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.regionUpload{
|
|
||||||
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
|
|
||||||
-fx-background-color: #a2e019;
|
|
||||||
-size: 24;
|
|
||||||
-fx-min-height: -size;
|
|
||||||
-fx-min-width: 20;
|
|
||||||
}
|
|
||||||
.regionStop{
|
|
||||||
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
|
|
||||||
-fx-background-color: #fb582c;
|
|
||||||
-size: 24;
|
|
||||||
-fx-min-height: -size;
|
|
||||||
-fx-min-width: -size;
|
|
||||||
}
|
|
255
src/main/resources/res/app_dark.css
Normal file
255
src/main/resources/res/app_dark.css
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
@font-face {
|
||||||
|
src: url("NotoMono-Regular.ttf");
|
||||||
|
}
|
||||||
|
.root{
|
||||||
|
-fx-background: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button, .buttonUp, .buttonStop, .buttonSelect{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #4f4f4f;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
-fx-effect: none;
|
||||||
|
}
|
||||||
|
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #a4ffff;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
|
||||||
|
-fx-background-color: #6a6a6a;
|
||||||
|
-fx-border-color: #6a6a6a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:pressed, .button:pressed:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #e82382;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.buttonSelect:pressed, .buttonSelect:pressed:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #289de8;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.buttonUp:pressed, .buttonUp:pressed:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #a2e019;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.buttonStop:pressed, .buttonStop:pressed:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #fb582c;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-area{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-control-inner-background: #4f4f4f;
|
||||||
|
-fx-font-family: "Noto Mono";
|
||||||
|
-fx-border-color: #00ffc9;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-box-border: transparent;
|
||||||
|
}
|
||||||
|
.progress-bar > .track {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-box-border: transparent;
|
||||||
|
}
|
||||||
|
.progress-bar > .bar {
|
||||||
|
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
|
||||||
|
-fx-background-radius: 2;
|
||||||
|
-fx-background-insets: 1 1 2 1;
|
||||||
|
-fx-padding: 0.23em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane {
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
}
|
||||||
|
.dialog-pane > .button-bar > .container{
|
||||||
|
-fx-background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane > .label{
|
||||||
|
-fx-padding: 10 5 10 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-bar{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-pane-as-border{
|
||||||
|
-fx-background-color: #f7fafa;
|
||||||
|
-fx-min-height: 1;
|
||||||
|
}
|
||||||
|
// -======================== Choice box =========================-
|
||||||
|
.choice-box {
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #4f4f4f;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-mark-color: #eea11e;
|
||||||
|
-fx-effect: none;
|
||||||
|
}
|
||||||
|
.choice-box > .label {
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box:pressed, .choice-box:pressed:hover{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-border-color: #eea11e;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background color of the whole context menu
|
||||||
|
.choice-box .context-menu {
|
||||||
|
-fx-background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused item background color in the list
|
||||||
|
.choice-box .context-menu .menu-item:focused {
|
||||||
|
-fx-background-color: #eea11e;
|
||||||
|
|
||||||
|
}
|
||||||
|
.choice-box .context-menu .menu-item:focused .label {
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -======================== TAB PANE =========================-
|
||||||
|
.tab-pane .tab SVGPath{
|
||||||
|
-fx-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.tab-pane .tab:selected SVGPath{
|
||||||
|
-fx-fill: #08f3ff;
|
||||||
|
}
|
||||||
|
.tab-pane .tab{
|
||||||
|
-fx-background-color: #424242;
|
||||||
|
-fx-focus-color: transparent;
|
||||||
|
-fx-faint-focus-color: transparent;
|
||||||
|
-fx-border-radius: 0 0 0 0;
|
||||||
|
-fx-border-width: 3 0 0 0;
|
||||||
|
-fx-border-color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane .tab:selected{
|
||||||
|
-fx-background-color: #2d2d2d;
|
||||||
|
-fx-focus-color: transparent;
|
||||||
|
-fx-faint-focus-color: transparent;
|
||||||
|
-fx-border-radius: 0 0 0 0;
|
||||||
|
-fx-border-width: 3 0 0 0;
|
||||||
|
-fx-border-color: #08f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane > .tab-header-area {
|
||||||
|
-fx-background-insets: 0.0;
|
||||||
|
-fx-padding: 5 5 5 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane > .tab-header-area > .tab-header-background
|
||||||
|
{
|
||||||
|
-fx-background-color: #424242;
|
||||||
|
|
||||||
|
}
|
||||||
|
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||||
|
-fx-padding: 10;
|
||||||
|
}
|
||||||
|
// -=========================== TABLE ======================-
|
||||||
|
.table-view {
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
-fx-background-image: url(app_logo.png);
|
||||||
|
-fx-background-position: center;
|
||||||
|
-fx-background-repeat: no-repeat;
|
||||||
|
-fx-border-color: #00ffc9;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
}
|
||||||
|
.table-view .arrow {
|
||||||
|
-fx-mark-color: #08f3ff ;
|
||||||
|
}
|
||||||
|
.table-view .column-header {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-width: 0 1 2 0;
|
||||||
|
-fx-border-color: #6d8484;
|
||||||
|
}
|
||||||
|
.table-view .column-header-background .label{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: #08f3ff;
|
||||||
|
|
||||||
|
}
|
||||||
|
.table-view .column-header-background, .table-view .filler{
|
||||||
|
-fx-background-color: #4f4f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-view .table-cell{
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
|
||||||
|
-fx-background-color: -fx-table-cell-border-color, #424242;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-table-cell-border-color: #6d8484;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
|
||||||
|
-fx-background-color: -fx-table-cell-border-color, #4f4f4f;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-table-cell-border-color: #6d8484;
|
||||||
|
}
|
||||||
|
// -========================== Context menu =====================-
|
||||||
|
.context-menu {
|
||||||
|
-fx-background-color: #2d2d2d;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
.context-menu .menu-item .label {
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
.context-menu .menu-item:focused .label {
|
||||||
|
-fx-text-fill: #f7fafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.regionUpload{
|
||||||
|
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
|
||||||
|
-fx-background-color: #a2e019;
|
||||||
|
-size: 24;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: 20;
|
||||||
|
}
|
||||||
|
.regionStop{
|
||||||
|
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
|
||||||
|
-fx-background-color: #fb582c;
|
||||||
|
-size: 24;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: -size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regionLamp{
|
||||||
|
-fx-shape: "M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V17A1,1 0 0,1 15,18H9A1,1 0 0,1 8,17V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2M9,21V20H15V21A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21M12,4A5,5 0 0,0 7,9C7,11.05 8.23,12.81 10,13.58V16H14V13.58C15.77,12.81 17,11.05 17,9A5,5 0 0,0 12,4Z";
|
||||||
|
-fx-background-color: #f7fafa;
|
||||||
|
-size: 17.5;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: 12.5;
|
||||||
|
}
|
256
src/main/resources/res/app_light.css
Normal file
256
src/main/resources/res/app_light.css
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
@font-face {
|
||||||
|
src: url("NotoMono-Regular.ttf");
|
||||||
|
}
|
||||||
|
.root{
|
||||||
|
-fx-background: #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button, .buttonUp, .buttonStop, .buttonSelect{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #fefefe;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
-fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #00caca;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
|
||||||
|
-fx-background-color: #cccccc;
|
||||||
|
-fx-border-color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:pressed, .button:pressed:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #e82382;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.buttonSelect:pressed, .buttonSelect:pressed:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #289de8;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.buttonUp:pressed, .buttonUp:pressed:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #a2e019;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.buttonStop:pressed, .buttonStop:pressed:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #fb582c;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
// -========================+ TextArea =====================-
|
||||||
|
.text-area{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-control-inner-background: #fefefe;
|
||||||
|
-fx-font-family: "Noto Mono";
|
||||||
|
-fx-border-color: #06b9bb;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-box-border: transparent;
|
||||||
|
}
|
||||||
|
.progress-bar > .track {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-box-border: transparent;
|
||||||
|
}
|
||||||
|
.progress-bar > .bar {
|
||||||
|
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
|
||||||
|
-fx-background-radius: 2;
|
||||||
|
-fx-background-insets: 1 1 2 1;
|
||||||
|
-fx-padding: 0.23em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane {
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
}
|
||||||
|
.dialog-pane > .button-bar > .container{
|
||||||
|
-fx-background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane > .label{
|
||||||
|
-fx-padding: 10 5 10 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-bar{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-pane-as-border{
|
||||||
|
-fx-background-color: #2c2c2c;
|
||||||
|
-fx-min-height: 1;
|
||||||
|
}
|
||||||
|
// -======================== Choice box =========================-
|
||||||
|
.choice-box {
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #fefefe;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-mark-color: #eea11e;
|
||||||
|
-fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
|
||||||
|
}
|
||||||
|
.choice-box > .label {
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box:pressed, .choice-box:pressed:hover{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-border-color: #eea11e;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background color of the whole context menu
|
||||||
|
.choice-box .context-menu {
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused item background color in the list
|
||||||
|
.choice-box .context-menu .menu-item:focused {
|
||||||
|
-fx-background-color: #eea11e;
|
||||||
|
|
||||||
|
}
|
||||||
|
.choice-box .context-menu .menu-item:focused .label {
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -======================== TAB PANE =========================-
|
||||||
|
.tab-pane .tab SVGPath{
|
||||||
|
-fx-fill: #2c2c2c; // OK
|
||||||
|
}
|
||||||
|
.tab-pane .tab:selected SVGPath{
|
||||||
|
-fx-fill: #289de8; // OK
|
||||||
|
}
|
||||||
|
.tab-pane .tab{
|
||||||
|
-fx-background-color: #fefefe; //ok
|
||||||
|
-fx-focus-color: transparent;
|
||||||
|
-fx-faint-focus-color: transparent;
|
||||||
|
-fx-border-radius: 0 0 0 0;
|
||||||
|
-fx-border-width: 3 0 0 0;
|
||||||
|
-fx-border-color: #fefefe; //OK
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane .tab:selected{
|
||||||
|
-fx-background-color: #ebebeb; // OK
|
||||||
|
-fx-focus-color: transparent;
|
||||||
|
-fx-faint-focus-color: transparent;
|
||||||
|
-fx-border-radius: 0 0 0 0;
|
||||||
|
-fx-border-width: 3 0 0 0;
|
||||||
|
-fx-border-color: #289de8; // OK
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane > .tab-header-area {
|
||||||
|
-fx-background-insets: 0.0;
|
||||||
|
-fx-padding: 5 5 5 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane > .tab-header-area > .tab-header-background
|
||||||
|
{
|
||||||
|
-fx-background-color: #fefefe; // OK
|
||||||
|
|
||||||
|
}
|
||||||
|
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||||
|
-fx-padding: 10;
|
||||||
|
}
|
||||||
|
// -=========================== TABLE ======================-
|
||||||
|
.table-view {
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-background-image: url(app_logo.png);
|
||||||
|
-fx-background-position: center;
|
||||||
|
-fx-background-repeat: no-repeat;
|
||||||
|
-fx-border-color: #06b9bb;
|
||||||
|
-fx-border-radius: 3;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
}
|
||||||
|
.table-view .arrow {
|
||||||
|
-fx-mark-color: #2c2c2c ;
|
||||||
|
}
|
||||||
|
.table-view .column-header {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-width: 0 1 2 0;
|
||||||
|
-fx-border-color: #b0b0b0;
|
||||||
|
}
|
||||||
|
.table-view .column-header-background .label{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.table-view .column-header-background, .table-view .filler{
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-view .table-cell{
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
|
||||||
|
-fx-background-color: -fx-table-cell-border-color, #d3fffd;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-table-cell-border-color: #b0b0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
|
||||||
|
-fx-background-color: -fx-table-cell-border-color, #fefefe;
|
||||||
|
-fx-background-insets: 0, 0 0 1 0;
|
||||||
|
-fx-padding: 0.0em; /* 0 */
|
||||||
|
-fx-table-cell-border-color: #b0b0b0;
|
||||||
|
}
|
||||||
|
// -========================== Context menu =====================-
|
||||||
|
.context-menu {
|
||||||
|
-fx-background-color: #fefefe;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
.context-menu .menu-item .label {
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
.context-menu .menu-item:focused .label {
|
||||||
|
-fx-text-fill: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.regionUpload{
|
||||||
|
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
|
||||||
|
-fx-background-color: #a2e019;
|
||||||
|
-size: 24;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regionStop{
|
||||||
|
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
|
||||||
|
-fx-background-color: #fb582c;
|
||||||
|
-size: 24;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: -size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regionLamp {
|
||||||
|
-fx-shape: "M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z";
|
||||||
|
-fx-background-color: #2c2c2c;
|
||||||
|
-size: 17.5;
|
||||||
|
-fx-min-height: -size;
|
||||||
|
-fx-min-width: 12.5;
|
||||||
|
}
|
BIN
src/main/resources/res/app_logo.png
Normal file
BIN
src/main/resources/res/app_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Loading…
Reference in a new issue