Merge pull request #1 from developersu/development

Development
This commit is contained in:
Dmitry Isaenko 2019-02-18 03:16:16 +03:00 committed by GitHub
commit 3565cd0fc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2020 additions and 587 deletions

View file

@ -1,12 +1,14 @@
# 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.
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
@ -32,7 +34,7 @@ Install JRE/JDK 8 or higher (openJDK is good. Oracle's one is also good). JavaFX
See 'Linux' section.
Set 'Security & Privacy' if needed.
Set 'Security & Privacy' settings if needed.
### Windows:
@ -56,7 +58,22 @@ Set 'Security & Privacy' if needed.
## Known bugs
* 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:
- [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
- [ ] 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
View file

@ -5,12 +5,50 @@
<modelVersion>4.0.0</modelVersion>
<groupId>loper</groupId>
<artifactId>NS-USBloader</artifactId>
<version>0.1-SNAPSHOT</version>
<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>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/developer_su/${project.artifactId}/issues</url>
</issueManagement>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>

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

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

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

View 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();
}
}

View file

@ -1,25 +1,28 @@
package nsusbloader;
class MediatorControl {
private boolean isTransferActive = false;
import nsusbloader.Controllers.NSLMainController;
import java.util.concurrent.atomic.AtomicBoolean;
public class MediatorControl {
private AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure
private NSLMainController applicationController;
static MediatorControl getInstance(){
public static MediatorControl getInstance(){
return MediatorControlHold.INSTANCE;
}
private static class MediatorControlHold {
private static final MediatorControl INSTANCE = new MediatorControl();
}
void registerController(NSLMainController controller){
public void setController(NSLMainController controller){
this.applicationController = controller;
}
NSLMainController getContoller(){ return this.applicationController; }
synchronized void setTransferActive(boolean state) {
isTransferActive = state;
public synchronized void setTransferActive(boolean state) {
isTransferActive.set(state);
applicationController.notifyTransmissionStarted(state);
}
synchronized boolean getTransferActive() {
return this.isTransferActive;
}
public synchronized boolean getTransferActive() { return this.isTransferActive.get(); }
}

View file

@ -3,8 +3,11 @@ package nsusbloader;
import javafx.animation.AnimationTimer;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import nsusbloader.Controllers.NSTableViewController;
import nsusbloader.NSLDataTypes.EFileStatus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
public class MessagesConsumer extends AnimationTimer {
@ -13,18 +16,24 @@ public class MessagesConsumer extends AnimationTimer {
private final BlockingQueue<Double> progressQueue;
private final ProgressBar progressBar;
private final HashMap<String, EFileStatus> statusMap;
private final NSTableViewController tableViewController;
private boolean isInterrupted;
MessagesConsumer(BlockingQueue<String> msgQueue, TextArea logsArea, BlockingQueue<Double> progressQueue, ProgressBar progressBar){
this.msgQueue = msgQueue;
this.logsArea = logsArea;
MessagesConsumer(BlockingQueue<String> msgQueue, BlockingQueue<Double> progressQueue, HashMap<String, EFileStatus> statusMap){
this.isInterrupted = false;
this.msgQueue = msgQueue;
this.logsArea = MediatorControl.getInstance().getContoller().logArea;
this.progressBar = progressBar;
this.progressQueue = progressQueue;
this.progressBar = MediatorControl.getInstance().getContoller().progressBar;
this.statusMap = statusMap;
this.tableViewController = MediatorControl.getInstance().getContoller().tableFilesListController;
progressBar.setProgress(0.0);
this.isInterrupted = false;
MediatorControl.getInstance().setTransferActive(true);
}
@ -40,12 +49,15 @@ public class MessagesConsumer extends AnimationTimer {
if (progressRecieved > 0)
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);
progressBar.setProgress(0.0);
if (statusMap.size() > 0)
for (String key : statusMap.keySet())
tableViewController.setFileStatus(key, statusMap.get(key));
this.stop();
}
//TODO
}
void interrupt(){

View file

@ -0,0 +1,5 @@
package nsusbloader.NSLDataTypes;
public enum EFileStatus {
UPLOADED, INCORRECT_FILE_FAILED, FAILED
}

View file

@ -0,0 +1,5 @@
package nsusbloader.NSLDataTypes;
public enum EMsgType {
PASS, FAIL, INFO, WARNING
}

View file

@ -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;
import javafx.application.Application;
@ -14,22 +6,24 @@ import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import nsusbloader.Controllers.NSLMainController;
import java.util.Locale;
import java.util.ResourceBundle;
public class NSLMain extends Application {
static final String appVersion = "v0.1";
public static final String appVersion = "v0.2";
@Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
ResourceBundle rb;
if (Locale.getDefault().getISO3Language().equals("rus"))
rb = ResourceBundle.getBundle("locale", new Locale("ru"));
else
rb = ResourceBundle.getBundle("locale", new Locale("en"));
FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
loader.setResources(rb);
Parent root = loader.load();
@ -44,7 +38,9 @@ public class NSLMain extends Application {
primaryStage.setMinWidth(600);
primaryStage.setMinHeight(375);
Scene mainScene = new Scene(root, 800, 400);
mainScene.getStylesheets().add("/res/app.css");
mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme());
primaryStage.setScene(mainScene);
primaryStage.show();
@ -53,6 +49,9 @@ public class NSLMain extends Application {
if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit")))
e.consume();
});
NSLMainController controller = loader.getController();
primaryStage.setOnHidden(e-> controller.exit());
}
public static void main(String[] args) {

View file

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

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

View 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: ???
}
}
}

View 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");
}
}

View file

@ -11,7 +11,7 @@ public class ServiceWindow {
* Create window with notification
* */
/* // not used
static void getErrorNotification(String title, String body){
public static void getErrorNotification(String title, String body){
Alert alertBox = new Alert(Alert.AlertType.ERROR);
alertBox.setTitle(title);
alertBox.setHeaderText(null);
@ -27,7 +27,7 @@ public class ServiceWindow {
/**
* 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);
alertBox.setTitle(title);
alertBox.setHeaderText(null);
@ -35,12 +35,9 @@ public class ServiceWindow {
alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
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();
if (result.get() == ButtonType.OK)
return true;
else
return false;
return (result.isPresent() && result.get() == ButtonType.OK);
}
}

View file

@ -1,8 +1,9 @@
package nsusbloader;
import javafx.concurrent.Task;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.PFS.PFSProvider;
import org.usb4java.*;
import java.io.*;
@ -17,18 +18,22 @@ import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class UsbCommunications extends Task<Void> {
public class UsbCommunications extends Task<Void> {
private final int DEFAULT_INTERFACE = 0;
private BlockingQueue<String> msgQueue;
private BlockingQueue<Double> progressQueue;
private enum MsgType {PASS, FAIL, INFO, WARNING}
private HashMap<String, EFileStatus> statusMap; // BlockingQueue for literally one object. TODO: read more books ; replace to hashMap
private EFileStatus status = EFileStatus.FAILED;
private MessagesConsumer msgConsumer;
private HashMap<String, File> nspMap;
private Context contextNS;
private DeviceHandle handlerNS;
private String protocol;
/*
Ok, here is a story. We will pass to NS only file names, not full path. => see nspMap where 'key' is a file name.
File name itself should not be greater then 512 bytes, but in real world it's limited by OS to something like 256 bytes.
@ -40,13 +45,15 @@ class UsbCommunications extends Task<Void> {
Since this application let user an ability (theoretically) to choose same files in different folders, the latest selected file will be added to the list and handled correctly.
I have no idea why he/she will make a decision to do that. Just in case, we're good in this point.
*/
UsbCommunications(TextArea logArea, ProgressBar progressBar, List<File> nspList){
public UsbCommunications(List<File> nspList, String protocol){
this.protocol = protocol;
this.nspMap = new HashMap<>();
for (File f: nspList)
nspMap.put(f.getName(), f);
this.msgQueue = new LinkedBlockingQueue<>();
this.progressQueue = new LinkedBlockingQueue<>();
this.msgConsumer = new MessagesConsumer(this.msgQueue, logArea, this.progressQueue, progressBar);
this.statusMap = new HashMap<>();
this.msgConsumer = new MessagesConsumer(this.msgQueue, this.progressQueue, this.statusMap);
}
@Override
@ -54,28 +61,28 @@ class UsbCommunications extends Task<Void> {
this.msgConsumer.start();
int result = -9999;
printLog("\tStart chain", MsgType.INFO);
printLog("\tStart chain", EMsgType.INFO);
// Creating Context required by libusb. Optional. TODO: Consider removing.
contextNS = new Context();
result = LibUsb.init(contextNS);
if (result != LibUsb.SUCCESS) {
printLog("libusb initialization\n Returned: "+result, MsgType.FAIL);
printLog("libusb initialization\n Returned: "+result, EMsgType.FAIL);
close();
return null;
}
else
printLog("libusb initialization", MsgType.PASS);
printLog("libusb initialization", EMsgType.PASS);
// Searching for NS in devices: obtain list of all devices
DeviceList deviceList = new DeviceList();
result = LibUsb.getDeviceList(contextNS, deviceList);
if (result < 0) {
printLog("Get device list\n Returned: "+result, MsgType.FAIL);
printLog("Get device list\n Returned: "+result, EMsgType.FAIL);
close();
return null;
}
else {
printLog("Get device list", MsgType.PASS);
printLog("Get device list", EMsgType.PASS);
}
// Searching for NS in devices: looking for NS
DeviceDescriptor descriptor;
@ -84,14 +91,14 @@ class UsbCommunications extends Task<Void> {
descriptor = new DeviceDescriptor(); // mmm.. leave it as is.
result = LibUsb.getDeviceDescriptor(device, descriptor);
if (result != LibUsb.SUCCESS){
printLog("Read file descriptors for USB devices\n Returned: "+result, MsgType.FAIL);
printLog("Read file descriptors for USB devices\n Returned: "+result, EMsgType.FAIL);
LibUsb.freeDeviceList(deviceList, true);
close();
return null;
}
if ((descriptor.idVendor() == 0x057E) && descriptor.idProduct() == 0x3000){
deviceNS = device;
printLog("Read file descriptors for USB devices", MsgType.PASS);
printLog("Read file descriptors for USB devices", EMsgType.PASS);
break;
}
}
@ -134,10 +141,10 @@ class UsbCommunications extends Task<Void> {
////////////////////////////////////////// DEBUG INFORMATION END /////////////////////////////////////////////
if (deviceNS != null){
printLog("NS in connected USB devices found", MsgType.PASS);
printLog("NS in connected USB devices found", EMsgType.PASS);
}
else {
printLog("NS in connected USB devices not found\n Returned: "+result, MsgType.FAIL);
printLog("NS in connected USB devices not found", EMsgType.FAIL);
close();
return null;
}
@ -147,25 +154,25 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS) {
switch (result){
case LibUsb.ERROR_ACCESS:
printLog("Open NS USB device\n Returned: ERROR_ACCESS", MsgType.FAIL);
printLog("Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!",MsgType.INFO);
printLog("Open NS USB device\n Returned: ERROR_ACCESS", EMsgType.FAIL);
printLog("Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!", EMsgType.INFO);
break;
case LibUsb.ERROR_NO_MEM:
printLog("Open NS USB device\n Returned: ERROR_NO_MEM", MsgType.FAIL);
printLog("Open NS USB device\n Returned: ERROR_NO_MEM", EMsgType.FAIL);
break;
case LibUsb.ERROR_NO_DEVICE:
printLog("Open NS USB device\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Open NS USB device\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
default:
printLog("Open NS USB device\n Returned:" + result, MsgType.FAIL);
printLog("Open NS USB device\n Returned:" + result, EMsgType.FAIL);
}
close();
return null;
}
else
printLog("Open NS USB device", MsgType.PASS);
printLog("Open NS USB device", EMsgType.PASS);
printLog("Free device list", MsgType.INFO);
printLog("Free device list", EMsgType.INFO);
LibUsb.freeDeviceList(deviceList, true);
// DO some stuff to connected NS
@ -174,105 +181,131 @@ class UsbCommunications extends Task<Void> {
if (canDetach){
int usedByKernel = LibUsb.kernelDriverActive(handlerNS, DEFAULT_INTERFACE);
if (usedByKernel == LibUsb.SUCCESS){
printLog("Can proceed with libusb driver", MsgType.PASS); // we're good
printLog("Can proceed with libusb driver", EMsgType.PASS); // we're good
}
else {
switch (usedByKernel){
case 1: // used by kernel
result = LibUsb.detachKernelDriver(handlerNS, DEFAULT_INTERFACE);
printLog("Detach kernel required", MsgType.INFO);
printLog("Detach kernel required", EMsgType.INFO);
if (result != 0) {
switch (result){
case LibUsb.ERROR_NOT_FOUND:
printLog("Detach kernel\n Returned: ERROR_NOT_FOUND", MsgType.FAIL);
printLog("Detach kernel\n Returned: ERROR_NOT_FOUND", EMsgType.FAIL);
break;
case LibUsb.ERROR_INVALID_PARAM:
printLog("Detach kernel\n Returned: ERROR_INVALID_PARAM", MsgType.FAIL);
printLog("Detach kernel\n Returned: ERROR_INVALID_PARAM", EMsgType.FAIL);
break;
case LibUsb.ERROR_NO_DEVICE:
printLog("Detach kernel\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Detach kernel\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
case LibUsb.ERROR_NOT_SUPPORTED: // Should never appear only if libusb buggy
printLog("Detach kernel\n Returned: ERROR_NOT_SUPPORTED", MsgType.FAIL);
printLog("Detach kernel\n Returned: ERROR_NOT_SUPPORTED", EMsgType.FAIL);
break;
default:
printLog("Detach kernel\n Returned: " + result, MsgType.FAIL);
printLog("Detach kernel\n Returned: " + result, EMsgType.FAIL);
break;
}
close();
return null;
}
else {
printLog("Detach kernel", MsgType.PASS);
printLog("Detach kernel", EMsgType.PASS);
break;
}
case LibUsb.ERROR_NO_DEVICE:
printLog("Can't proceed with libusb driver\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Can't proceed with libusb driver\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
case LibUsb.ERROR_NOT_SUPPORTED:
printLog("Can't proceed with libusb driver\n Returned: ERROR_NOT_SUPPORTED", MsgType.FAIL);
printLog("Can't proceed with libusb driver\n Returned: ERROR_NOT_SUPPORTED", EMsgType.FAIL);
break;
default:
printLog("Can't proceed with libusb driver\n Returned: "+result, MsgType.FAIL);
printLog("Can't proceed with libusb driver\n Returned: "+result, EMsgType.FAIL);
}
}
}
else
printLog("libusb doesn't supports function 'CAP_SUPPORTS_DETACH_KERNEL_DRIVER'. Proceeding.", MsgType.WARNING);
printLog("libusb doesn't supports function 'CAP_SUPPORTS_DETACH_KERNEL_DRIVER'. Proceeding.", EMsgType.WARNING);
// Set configuration (soft reset if needed)
result = LibUsb.setConfiguration(handlerNS, 1); // 1 - configuration all we need
if (result != LibUsb.SUCCESS){
switch (result){
case LibUsb.ERROR_NOT_FOUND:
printLog("Set active configuration to device\n Returned: ERROR_NOT_FOUND", MsgType.FAIL);
printLog("Set active configuration to device\n Returned: ERROR_NOT_FOUND", EMsgType.FAIL);
break;
case LibUsb.ERROR_BUSY:
printLog("Set active configuration to device\n Returned: ERROR_BUSY", MsgType.FAIL);
printLog("Set active configuration to device\n Returned: ERROR_BUSY", EMsgType.FAIL);
break;
case LibUsb.ERROR_NO_DEVICE:
printLog("Set active configuration to device\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Set active configuration to device\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
case LibUsb.ERROR_INVALID_PARAM:
printLog("Set active configuration to device\n Returned: ERROR_INVALID_PARAM", MsgType.FAIL);
printLog("Set active configuration to device\n Returned: ERROR_INVALID_PARAM", EMsgType.FAIL);
break;
default:
printLog("Set active configuration to device\n Returned: "+result, MsgType.FAIL);
printLog("Set active configuration to device\n Returned: "+result, EMsgType.FAIL);
break;
}
close();
return null;
}
else {
printLog("Set active configuration to device.", MsgType.PASS);
printLog("Set active configuration to device.", EMsgType.PASS);
}
// Claim interface
result = LibUsb.claimInterface(handlerNS, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) {
printLog("Claim interface\n Returned: "+result, MsgType.FAIL);
printLog("Claim interface\n Returned: "+result, EMsgType.FAIL);
close();
return null;
}
else
printLog("Claim interface", MsgType.PASS);
printLog("Claim interface", EMsgType.PASS);
//--------------------------------------------------------------------------------------------------------------
if (protocol.equals("TinFoil")) {
new TinFoil();
} else {
new GoldLeaf();
}
close();
printLog("\tEnd chain", EMsgType.INFO);
return null;
}
/**
* Tinfoil processing
* */
private class TinFoil{
TinFoil(){
if (!sendListOfNSP())
return;
if (proceedCommands()) // REPORT SUCCESS
status = EFileStatus.UPLOADED; // Don't change status that is already set to FAILED
}
/**
* Send what NSP will be transferred
* */
private boolean sendListOfNSP(){
// Send list of NSP files:
// Proceed "TUL0"
if (!writeToUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30}
printLog("Send list of files: handshake", MsgType.FAIL);
close();
return null;
printLog("TF Send list of files: handshake", EMsgType.FAIL);
return false;
}
else
printLog("Send list of files: handshake", MsgType.PASS);
printLog("TF Send list of files: handshake", EMsgType.PASS);
//Collect file names
StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder
for(String nspFileName: nspMap.keySet())
nspListNamesBuilder.append(nspFileName+'\n'); // And here we come with java string default encoding (UTF-16)
for(String nspFileName: nspMap.keySet()) {
nspListNamesBuilder.append(nspFileName); // And here we come with java string default encoding (UTF-16)
nspListNamesBuilder.append('\n');
}
byte[] nspListNames = nspListNamesBuilder.toString().getBytes(StandardCharsets.UTF_8);
ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); // integer = 4 bytes; BTW Java is stored in big-endian format
@ -281,62 +314,32 @@ class UsbCommunications extends Task<Void> {
//byteBuffer.reset();
// Sending NSP list
printLog("TF Send list of files", EMsgType.INFO);
if (!writeToUsb(nspListSize)) { // size of the list we're going to transfer goes...
printLog("Send list of files: send length.", MsgType.FAIL);
close();
return null;
} else
printLog("Send list of files: send length.", MsgType.PASS);
printLog(" [send list length]", EMsgType.FAIL);
return false;
}
printLog(" [send list length]", EMsgType.PASS);
if (!writeToUsb(new byte[8])) { // 8 zero bytes goes...
printLog("Send list of files: send padding.", MsgType.FAIL);
close();
return null;
printLog(" [send padding]", EMsgType.FAIL);
return false;
}
else
printLog("Send list of files: send padding.", MsgType.PASS);
printLog(" [send padding]", EMsgType.PASS);
if (!writeToUsb(nspListNames)) { // list of the names goes...
printLog("Send list of files: send list itself.", MsgType.FAIL);
close();
return null;
printLog(" [send list itself]", EMsgType.FAIL);
return false;
}
else
printLog("Send list of files: send list itself.", MsgType.PASS);
printLog(" [send list itself]", EMsgType.PASS);
proceedCommands();
close();
printLog("\tEnd chain", MsgType.INFO);
return null;
}
/**
* Correct exit
* */
private void close(){
// close handler in the end
if (handlerNS != null) {
// Try to release interface
int result = LibUsb.releaseInterface(handlerNS, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS)
printLog("Release interface\n Returned: "+result+" (sometimes it's not an issue)", MsgType.WARNING);
else
printLog("Release interface", MsgType.PASS);
LibUsb.close(handlerNS);
printLog("Requested handler close", MsgType.INFO);
}
// close context in the end
if (contextNS != null) {
LibUsb.exit(contextNS);
printLog("Requested context close", MsgType.INFO);
}
msgConsumer.interrupt();
return true;
}
/**
* After we sent commands to NS, this chain starts
* */
private void proceedCommands(){
printLog("Awaiting for NS commands.", MsgType.INFO);
private boolean proceedCommands(){
printLog("TF Awaiting for NS commands.", EMsgType.INFO);
/* byte[] magic = new byte[4];
ByteBuffer bb = StandardCharsets.UTF_8.encode("TUC0").rewind().get(magic);
@ -347,10 +350,10 @@ class UsbCommunications extends Task<Void> {
while (true){
if (Thread.currentThread().isInterrupted()) // Check if user interrupted process.
return;
return false;
receivedArray = readFromUsb();
if (receivedArray == null)
return; // catches exception
return false; // catches exception
if (!Arrays.equals(Arrays.copyOfRange(receivedArray, 0,4), magic)) // Bytes from 0 to 3 should contain 'magic' TUC0, so must be verified like this
continue;
@ -358,17 +361,17 @@ class UsbCommunications extends Task<Void> {
// 8th to 12th(explicits) bytes in returned data stands for command ID as unsigned integer (Little-endian). Actually, we have to compare arrays here, but in real world it can't be greater then 0/1/2, thus:
// BTW also protocol specifies 4th byte to be 0x00 kinda indicating that that this command is valid. But, as you may see, never happens other situation when it's not = 0.
if (receivedArray[8] == 0x00){ //0x00 - exit
printLog("Received EXIT command. Terminating.", MsgType.PASS);
return; // All interaction with USB device should be ended (expected);
printLog("TF Received EXIT command. Terminating.", EMsgType.PASS);
return true; // All interaction with USB device should be ended (expected);
}
else if ((receivedArray[8] == 0x01) || (receivedArray[8] == 0x02)){ //0x01 - file range; 0x02 unknown bug on backend side (dirty hack).
printLog("Received FILE_RANGE command. Proceeding: [0x0"+receivedArray[8]+"]", MsgType.PASS);
printLog("TF Received FILE_RANGE command. Proceeding: [0x0"+receivedArray[8]+"]", EMsgType.PASS);
/*// We can get in this pocket a length of file name (+32). Why +32? I dunno man.. Do we need this? Definitely not. This app can live without it.
long receivedSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 12,20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
logsArea.appendText("[V] Received FILE_RANGE command. Size: "+Long.toUnsignedString(receivedSize)+"\n"); // this shit returns string that will be chosen next '+32'. And, BTW, can't be greater then 512
*/
if (!fileRangeCmd()) {
return; // catches exception
return false; // catches exception
}
}
}
@ -402,25 +405,21 @@ class UsbCommunications extends Task<Void> {
return false;
String receivedRequestedNSP = new String(receivedArray, StandardCharsets.UTF_8);
printLog("Reply to requested file: "+receivedRequestedNSP
printLog("TF Reply to requested file: "+receivedRequestedNSP
+"\n Range Size: "+receivedRangeSize
+"\n Range Offset: "+receivedRangeOffset, MsgType.INFO);
+"\n Range Offset: "+receivedRangeOffset, EMsgType.INFO);
// Sending response header
if (!sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply.
return false;
// Read file starting:
// from Range Offset (receivedRangeOffset)
// to Range Size (receivedRangeSize) like end: receivedRangeOffset+receivedRangeSize
try {
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor?
byte[] bufferCurrent ;//= new byte[1048576]; // eq. Allocate 1mb
int bufferLength;
if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){
printLog("Requested skip is out of File size. Nothing to transmit.", MsgType.FAIL);
printLog("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL);
return false;
}
@ -433,7 +432,7 @@ class UsbCommunications extends Task<Void> {
return true;
if ((currentOffset + readPice) >= receivedRangeSize )
readPice = Math.toIntExact(receivedRangeSize - currentOffset);
//System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // TODO: NOTE: -----------------------DEBUG-----------------
//System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // TODO: NOTE: DEBUG
// updating progress bar (if a lot of data requested) START BLOCK
if (isProgessBarInitiated){
try {
@ -444,7 +443,7 @@ class UsbCommunications extends Task<Void> {
else
progressQueue.put((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0);
}catch (InterruptedException ie){
getException().printStackTrace();
getException().printStackTrace(); // TODO: Do something with this
}
}
else {
@ -460,25 +459,29 @@ class UsbCommunications extends Task<Void> {
if (bufferLength != -1){
//write to USB
if (!writeToUsb(bufferCurrent)) {
printLog("Failure during NSP transmission.", MsgType.FAIL);
printLog("TF Failure during NSP transmission.", EMsgType.FAIL);
return false;
}
currentOffset += readPice;
}
else {
printLog("Unexpected reading of stream ended.", MsgType.WARNING);
printLog("TF Reading of stream suddenly ended.", EMsgType.WARNING);
return false;
}
}
bufferedInStream.close();
} catch (FileNotFoundException fnfe){
printLog("FileNotFoundException:\n"+fnfe.getMessage(), MsgType.FAIL);
printLog("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL);
fnfe.printStackTrace();
return false;
} catch (IOException ioe){
printLog("IOException:\n"+ioe.getMessage(), MsgType.FAIL);
printLog("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace();
return false;
} catch (ArithmeticException ae){
printLog("ArithmeticException (can't cast end offset minus current to 'integer'):\n"+ae.getMessage(), MsgType.FAIL);
printLog("TF ArithmeticException (can't cast end offset minus current to 'integer'):\n "+ae.getMessage(), EMsgType.FAIL);
ae.printStackTrace();
return false;
}
@ -490,29 +493,283 @@ class UsbCommunications extends Task<Void> {
* false if failed
* */
private boolean sendResponse(byte[] rangeSize){ // This method as separate function itself for application needed as a cookie in the middle of desert.
printLog("Sending response", MsgType.INFO);
printLog("TF Sending response", EMsgType.INFO);
if (!writeToUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0'
(byte) 0x01, // CMD_TYPE_RESPONSE = 1
(byte) 0x00, (byte) 0x00, (byte) 0x00, // kinda padding. Guys, didn't you want to use integer value for CMD semantic?
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00} ) // Send integer value of '1' in Little-endian format.
){
printLog("[1/3]", MsgType.FAIL);
printLog(" [1/3]", EMsgType.FAIL);
return false;
}
printLog("[1/3]", MsgType.PASS);
printLog(" [1/3]", EMsgType.PASS);
if(!writeToUsb(rangeSize)) { // Send EXACTLY what has been received
printLog("[2/3]", MsgType.FAIL);
printLog(" [2/3]", EMsgType.FAIL);
return false;
}
printLog("[2/3]", MsgType.PASS);
printLog(" [2/3]", EMsgType.PASS);
if(!writeToUsb(new byte[12])) { // kinda another one padding
printLog("[3/3]", MsgType.FAIL);
printLog(" [3/3]", EMsgType.FAIL);
return false;
}
printLog("[3/3]", MsgType.PASS);
printLog(" [3/3]", EMsgType.PASS);
return true;
}
}
/**
* GoldLeaf processing
* */
private class GoldLeaf{
// CMD G L U C ID 0 0 0
private final byte[] CMD_ConnectionRequest = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x00, 0x00, 0x00, 0x00}; // Write-only command
private final byte[] CMD_NSPName = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x02, 0x00, 0x00, 0x00}; // Write-only command
private final byte[] CMD_NSPData = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x04, 0x00, 0x00, 0x00}; // Write-only command
private final byte[] CMD_ConnectionResponse = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x01, 0x00, 0x00, 0x00};
private final byte[] CMD_Start = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x03, 0x00, 0x00, 0x00};
private final byte[] CMD_NSPContent = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x05, 0x00, 0x00, 0x00};
private final byte[] CMD_NSPTicket = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x06, 0x00, 0x00, 0x00};
private final byte[] CMD_Finish = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x07, 0x00, 0x00, 0x00};
GoldLeaf(){
printLog("===========================================================================", EMsgType.INFO);
PFSProvider pfsElement = new PFSProvider(nspMap.get(nspMap.keySet().toArray()[0]), msgQueue);
if (!pfsElement.init()) {
printLog("GL File provided have incorrect structure and won't be uploaded", EMsgType.FAIL);
status = EFileStatus.INCORRECT_FILE_FAILED;
return;
}
printLog("GL File structure validated and it will be uploaded", EMsgType.PASS);
if (initGoldLeafProtocol(pfsElement))
status = EFileStatus.UPLOADED;
// else - no change status that is already set to FAILED
}
private boolean initGoldLeafProtocol(PFSProvider pfsElement){
// Go parse commands
byte[] readByte;
// Go connect to GoldLeaf
if (writeToUsb(CMD_ConnectionRequest))
printLog("GL Initiating GoldLeaf connection", EMsgType.PASS);
else {
printLog("GL Initiating GoldLeaf connection", EMsgType.FAIL);
return false;
}
while (true) {
readByte = readFromUsb();
if (readByte == null)
return false;
if (Arrays.equals(readByte, CMD_ConnectionResponse)) {
if (!handleConnectionResponse(pfsElement))
return false;
else
continue;
}
if (Arrays.equals(readByte, CMD_Start)) {
if (!handleStart(pfsElement))
return false;
else
continue;
}
if (Arrays.equals(readByte, CMD_NSPContent)) {
if (!handleNSPContent(pfsElement, true))
return false;
else
continue;
}
if (Arrays.equals(readByte, CMD_NSPTicket)) {
if (!handleNSPContent(pfsElement, false))
return false;
else
continue;
}
if (Arrays.equals(readByte, CMD_Finish)) {
printLog("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS);
break;
}
}
return true;
}
/**
* ConnectionResponse command handler
* */
private boolean handleConnectionResponse(PFSProvider pfsElement){
printLog("GL 'ConnectionResonse' command:", EMsgType.INFO);
if (!writeToUsb(CMD_NSPName)) {
printLog(" [1/3]", EMsgType.FAIL);
return false;
}
printLog(" [1/3]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesNspFileNameLength())) {
printLog(" [2/3]", EMsgType.FAIL);
return false;
}
printLog(" [2/3]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesNspFileName())) {
printLog(" [3/3]", EMsgType.FAIL);
return false;
}
printLog(" [3/3]", EMsgType.PASS);
return true;
}
/**
* Start command handler
* */
private boolean handleStart(PFSProvider pfsElement){
printLog("GL Handle 'Start' command:", EMsgType.INFO);
if (!writeToUsb(CMD_NSPData)) {
printLog(" [Send command]", EMsgType.FAIL);
return false;
}
printLog(" [Send command]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesCountOfNca())) {
printLog(" [Send length]", EMsgType.FAIL);
return false;
}
printLog(" [Send length]", EMsgType.PASS);
int ncaCount = pfsElement.getIntCountOfNca();
printLog(" [Send information for "+ncaCount+" files]", EMsgType.INFO);
for (int i = 0; i < ncaCount; i++){
if (!writeToUsb(pfsElement.getNca(i).getNcaFileNameLength())) {
printLog(" [1/4] File #"+i, EMsgType.FAIL);
return false;
}
printLog(" [1/4] File #"+i, EMsgType.PASS);
if (!writeToUsb(pfsElement.getNca(i).getNcaFileName())) {
printLog(" [2/4] File #"+i, EMsgType.FAIL);
return false;
}
printLog(" [2/4] File #"+i, EMsgType.PASS);
if (!writeToUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) { // offset. real.
printLog(" [2/4] File #"+i, EMsgType.FAIL);
return false;
}
printLog(" [3/4] File #"+i, EMsgType.PASS);
if (!writeToUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) { // size
printLog(" [4/4] File #"+i, EMsgType.FAIL);
return false;
}
printLog(" [4/4] File #"+i, EMsgType.PASS);
}
return true;
}
/**
* NSPContent command handler
* isItRawRequest - if True, just ask NS what's needed
* - if False, send ticket
* */
private boolean handleNSPContent(PFSProvider pfsElement, boolean isItRawRequest){
int requestedNcaID;
boolean isProgessBarInitiated = false;
if (isItRawRequest) {
printLog("GL Handle 'Content' command", EMsgType.INFO);
byte[] readByte = readFromUsb();
if (readByte == null || readByte.length != 4) {
printLog(" [Read requested ID]", EMsgType.FAIL);
return false;
}
requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt();
printLog(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS);
}
else {
requestedNcaID = pfsElement.getNcaTicketID();
printLog("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO);
}
long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize();
long realNcaSize = pfsElement.getNca(requestedNcaID).getNcaSize();
long readFrom = 0;
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
byte[] readBuf;
File nspFile = nspMap.get(pfsElement.getStringNspFileName()); // wuuuut ( >< )
try{
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspFile)); // TODO: refactor?
if (bufferedInStream.skip(realNcaOffset) != realNcaOffset)
return false;
while (readFrom < realNcaSize){
if (Thread.currentThread().isInterrupted()) // Check if user interrupted process.
return false;
if (realNcaSize - readFrom < readPice)
readPice = Math.toIntExact(realNcaSize - readFrom); // it's safe, I guarantee
readBuf = new byte[readPice];
if (bufferedInStream.read(readBuf) != readPice)
return false;
if (!writeToUsb(readBuf))
return false;
//-----------------------------------------/
if (isProgessBarInitiated){
try {
if (readFrom+readPice == realNcaSize){
progressQueue.put(1.0);
isProgessBarInitiated = false;
}
else
progressQueue.put((readFrom+readPice)/(realNcaSize/100.0) / 100.0);
}catch (InterruptedException ie){
getException().printStackTrace(); // TODO: Do something with this
}
}
else {
if ((readPice == 8388608) && (readFrom == 0))
isProgessBarInitiated = true;
}
//-----------------------------------------/
readFrom += readPice;
}
bufferedInStream.close();
}
catch (IOException ioe){
printLog(" Failed to read NCA ID "+requestedNcaID+". IO Exception:\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace();
return false;
}
return true;
}
}
//------------------------------------------------------------------------------------------------------------------
/**
* Correct exit
* */
private void close(){
// close handler in the end
if (handlerNS != null) {
// Try to release interface
int result = LibUsb.releaseInterface(handlerNS, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS)
printLog("Release interface\n Returned: "+result+" (sometimes it's not an issue)", EMsgType.WARNING);
else
printLog("Release interface", EMsgType.PASS);
LibUsb.close(handlerNS);
printLog("Requested handler close", EMsgType.INFO);
}
// close context in the end
if (contextNS != null) {
LibUsb.exit(contextNS);
printLog("Requested context close", EMsgType.INFO);
}
// Report status
for (String fileName: nspMap.keySet())
statusMap.put(fileName, status);
msgConsumer.interrupt();
}
/**
* Sending any byte array to USB device
* @return 'true' if no issues
@ -528,25 +785,25 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS){
switch (result){
case LibUsb.ERROR_TIMEOUT:
printLog("Data transfer (write) issue\n Returned: ERROR_TIMEOUT", MsgType.FAIL);
printLog("Data transfer (write) issue\n Returned: ERROR_TIMEOUT", EMsgType.FAIL);
break;
case LibUsb.ERROR_PIPE: //WUT?? I dunno man looks overkill in here..
printLog("Data transfer (write) issue\n Returned: ERROR_PIPE", MsgType.FAIL);
printLog("Data transfer (write) issue\n Returned: ERROR_PIPE", EMsgType.FAIL);
break;
case LibUsb.ERROR_OVERFLOW:
printLog("Data transfer (write) issue\n Returned: ERROR_OVERFLOW", MsgType.FAIL);
printLog("Data transfer (write) issue\n Returned: ERROR_OVERFLOW", EMsgType.FAIL);
break;
case LibUsb.ERROR_NO_DEVICE:
printLog("Data transfer (write) issue\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Data transfer (write) issue\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
default:
printLog("Data transfer (write) issue\n Returned: "+result, MsgType.FAIL);
printLog("Data transfer (write) issue\n Returned: "+result, EMsgType.FAIL);
}
printLog("Execution stopped", MsgType.FAIL);
printLog("Execution stopped", EMsgType.FAIL);
return false;
}else {
if (writeBufTransferred.get() != message.length){
printLog("Data transfer (write) issue\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), MsgType.FAIL);
printLog("Data transfer (write) issue\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
return false;
}
else {
@ -570,21 +827,24 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS){
switch (result){
case LibUsb.ERROR_TIMEOUT:
printLog("Data transfer (read) issue\n Returned: ERROR_TIMEOUT", MsgType.FAIL);
printLog("Data transfer (read) issue\n Returned: ERROR_TIMEOUT", EMsgType.FAIL);
break;
case LibUsb.ERROR_PIPE: //WUT?? I dunno man looks overkill in here..
printLog("Data transfer (read) issue\n Returned: ERROR_PIPE", MsgType.FAIL);
printLog("Data transfer (read) issue\n Returned: ERROR_PIPE", EMsgType.FAIL);
break;
case LibUsb.ERROR_OVERFLOW:
printLog("Data transfer (read) issue\n Returned: ERROR_OVERFLOW", MsgType.FAIL);
printLog("Data transfer (read) issue\n Returned: ERROR_OVERFLOW", EMsgType.FAIL);
break;
case LibUsb.ERROR_NO_DEVICE:
printLog("Data transfer (read) issue\n Returned: ERROR_NO_DEVICE", MsgType.FAIL);
printLog("Data transfer (read) issue\n Returned: ERROR_NO_DEVICE", EMsgType.FAIL);
break;
case LibUsb.ERROR_IO:
printLog("Data transfer (read) issue\n Returned: ERROR_IO", EMsgType.FAIL);
break;
default:
printLog("Data transfer (read) issue\n Returned: "+result, MsgType.FAIL);
printLog("Data transfer (read) issue\n Returned: "+result, EMsgType.FAIL);
}
printLog("Execution stopped", MsgType.FAIL);
printLog("Execution stopped", EMsgType.FAIL);
return null;
} else {
int trans = readBufTransferred.get();
@ -596,10 +856,11 @@ class UsbCommunications extends Task<Void> {
return receivedBytes;
}
}
/**
* This is what will print to textArea of the application.
* */
private void printLog(String message, MsgType type){
private void printLog(String message, EMsgType type){
try {
switch (type){
case PASS:
@ -620,21 +881,5 @@ class UsbCommunications extends Task<Void> {
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
/**
* Debug tool like hexdump <3
*/
/*
private void hexDumpUTF8(byte[] byteArray){
for (int i=0; i < byteArray.length; i++)
System.out.print(String.format("%02d-", i%10));
System.out.println("\t[[COLUMNS LEN = "+byteArray.length+"]]");
for (byte b: byteArray)
System.out.print(String.format("%02x ", b));
System.out.print("\t\t\t"
+ new String(byteArray, StandardCharsets.UTF_8)
+ "\n");
}
*/
}

View file

@ -2,23 +2,80 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?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>
<VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<TabPane prefHeight="200.0" prefWidth="200.0" side="LEFT" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
<tabs>
<Tab closable="false">
<content>
<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 left="5.0" right="5.0" top="5.0" />
<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">
<VBox.margin>
<Insets left="5.0" right="5.0" top="2.0" />

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

View file

@ -12,3 +12,10 @@ logsGreetingsMessage2=--\n\
Source: https://github.com/developersu/ns-usbloader/\n\
Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\
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?

View file

@ -12,4 +12,12 @@ logsGreetingsMessage2=--\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\
\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

View file

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB