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 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
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								pom.xml
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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;
 | 
			
		||||
 | 
			
		||||
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(); }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(){
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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;
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
     * */
 | 
			
		||||
    /* // 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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");
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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\
 | 
			
		||||
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?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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