Merge pull request #1 from developersu/development

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

View file

@ -1,12 +1,14 @@
# NS-USBloader # NS-USBloader
NS-USBloader is a PC-side tinfoil NSP USB uploader. Replacement for default usb_install_pc.py NS-USBloader is a PC-side TinFoil/GoldLeaf NSP USB uploader. Replacement for default usb_install_pc.py and GoldTree.
With GUI and cookies. With GUI and cookies.
Read more: https://developersu.blogspot.com/2019/02/ns-usbloader-en.html Read more: https://developersu.blogspot.com/2019/02/ns-usbloader-en.html
Here is the version of 'not perfect byt anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR). Here is the version of 'not perfect but anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR).
Ok, I'm almost sure that this version has bugs. I don't remember where I downloaded it. But it works for me somehow..
Let's rephrase, if you have working version of TinFoil DO NOT use this one.
## License ## License
@ -32,7 +34,7 @@ Install JRE/JDK 8 or higher (openJDK is good. Oracle's one is also good). JavaFX
See 'Linux' section. See 'Linux' section.
Set 'Security & Privacy' if needed. Set 'Security & Privacy' settings if needed.
### Windows: ### Windows:
@ -56,7 +58,22 @@ Set 'Security & Privacy' if needed.
## Known bugs ## Known bugs
* Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't selected anything on NS). * Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't selected anything on NS).
## NOTES
Table 'Status' = 'Uploaded' does not means that file installed. It means that it has been sent to NS without any issues! That's what this app about.
Handling successful/failed installation is a purpose of the other side application (TinFoil/GoldLeaf). (And they don't provide any feedback interfaces so I can't detect success/failure.)
## TODO: ## TODO:
- [x] macOS QA by [Konstanin Kelemen](https://github.com/konstantin-kelemen). Appreciate assistance of [Vitaliy Natarov](https://github.com/SebastianUA). - [x] macOS QA v0.1
- [ ] macOS QA v0.2 (partly)
- [x] Windows support - [x] Windows support
- [ ] code refactoring - [ ] code refactoring (almost. todo: printLog() )
- [x] GoldLeaf support
- [ ] XCI support
- [ ] File order sort (non-critical)
## Thanks
Appreciate assistance and support of both Vitaliy and Konstantin. Without you all this magic would not have happened.
[Konstanin Kelemen](https://github.com/konstantin-kelemen)
[Vitaliy Natarov](https://github.com/SebastianUA)

42
pom.xml
View file

@ -5,12 +5,50 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>loper</groupId> <groupId>loper</groupId>
<artifactId>NS-USBloader</artifactId>
<version>0.1-SNAPSHOT</version>
<name>NS-USBloader</name> <name>NS-USBloader</name>
<artifactId>ns-usbloader</artifactId>
<version>0.2-SNAPSHOT</version>
<url>https://github.com/developersu/ns-usbloader/</url>
<description>
NSP USB loader for TinFoil and GoldLeaf
</description>
<inceptionYear>2019</inceptionYear>
<organization>
<name>Dmitry Isaenko</name>
<url>https://developersu.blogspot.com/</url>
</organization>
<licenses>
<license>
<name>GPLv3</name>
<url>LICENSE</url>
<distribution>manual</distribution>
</license>
</licenses>
<developers>
<developer>
<id>developer.su</id>
<name>Dmitry Isaenko</name>
<roles>
<role>Developer</role>
</roles>
<timezone>+3</timezone>
<url>https://developersu.blogspot.com/</url>
</developer>
</developers>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/developer_su/${project.artifactId}/issues</url>
</issueManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>

View file

@ -0,0 +1,23 @@
package nsusbloader;
import java.util.prefs.Preferences;
public class AppPreferences {
private static final AppPreferences INSTANCE = new AppPreferences();
public static AppPreferences getInstance() { return INSTANCE; }
private Preferences preferences;
private AppPreferences(){ preferences = Preferences.userRoot().node("NS-USBloader"); }
public String getTheme(){
String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually
if (!theme.matches("(^/res/app_dark.css$)|(^/res/app_light.css$)"))
theme = "/res/app_dark.css";
return theme;
}
public void setTheme(String theme){ preferences.put("THEME", theme); }
public String getRecent(){ return preferences.get("RECENT", System.getProperty("user.home")); }
public void setRecent(String path){ preferences.put("RECENT", path); }
}

View file

@ -0,0 +1,195 @@
package nsusbloader.Controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.stage.FileChooser;
import nsusbloader.AppPreferences;
import nsusbloader.MediatorControl;
import nsusbloader.NSLMain;
import nsusbloader.UsbCommunications;
import java.io.File;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
public class NSLMainController implements Initializable {
private ResourceBundle resourceBundle;
@FXML
public TextArea logArea; // Accessible from Mediator
@FXML
private Button selectNspBtn;
@FXML
private Button uploadStopBtn;
private Region btnUpStopImage;
@FXML
public ProgressBar progressBar; // Accessible from Mediator
@FXML
private ChoiceBox<String> choiceProtocol;
@FXML
private Button switchThemeBtn;
@FXML
private Pane specialPane;
@FXML
public NSTableViewController tableFilesListController; // Accessible from Mediator
private Thread usbThread;
private String previouslyOpenedPath;
@Override
public void initialize(URL url, ResourceBundle rb) {
this.resourceBundle = rb;
logArea.setText(rb.getString("logsGreetingsMessage")+" "+ NSLMain.appVersion+"!\n");
if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
if (!System.getProperty("user.name").equals("root"))
logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
MediatorControl.getInstance().setController(this);
specialPane.getStyleClass().add("special-pane-as-border"); // UI hacks
uploadStopBtn.setDisable(true);
selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
selectNspBtn.getStyleClass().add("buttonSelect");
this.btnUpStopImage = new Region();
btnUpStopImage.getStyleClass().add("regionUpload");
//uploadStopBtn.getStyleClass().remove("button");
uploadStopBtn.getStyleClass().add("buttonUp");
uploadStopBtn.setGraphic(btnUpStopImage);
ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("TinFoil", "GoldLeaf");
choiceProtocol.setItems(choiceProtocolList);
choiceProtocol.getSelectionModel().select(0); // TODO: shared settings
choiceProtocol.setOnAction(e->tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem())); // Add listener to notify tableView controller
tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem()); // Notify tableView controller
this.previouslyOpenedPath = null;
Region btnSwitchImage = new Region();
btnSwitchImage.getStyleClass().add("regionLamp");
switchThemeBtn.setGraphic(btnSwitchImage);
this.switchThemeBtn.setOnAction(e->switchTheme());
previouslyOpenedPath = AppPreferences.getInstance().getRecent();
}
/**
* Changes UI theme on the go
* */
private void switchTheme(){
if (switchThemeBtn.getScene().getStylesheets().get(0).equals("/res/app_dark.css")) {
switchThemeBtn.getScene().getStylesheets().remove("/res/app_dark.css");
switchThemeBtn.getScene().getStylesheets().add("/res/app_light.css");
}
else {
switchThemeBtn.getScene().getStylesheets().remove("/res/app_light.css");
switchThemeBtn.getScene().getStylesheets().add("/res/app_dark.css");
}
}
/**
* Functionality for selecting NSP button.
* Uses setReady and setNotReady to simplify code readability.
* */
private void selectFilesBtnAction(){
List<File> filesList;
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
File validator = new File(previouslyOpenedPath);
if (validator.exists())
fileChooser.setInitialDirectory(validator); // TODO: read from prefs
else
fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: read from prefs
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp"));
filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
if (filesList != null && !filesList.isEmpty()) {
tableFilesListController.setFiles(filesList);
uploadStopBtn.setDisable(false);
previouslyOpenedPath = filesList.get(0).getParent();
}
else{
tableFilesListController.setFiles(null);
uploadStopBtn.setDisable(true);
}
}
/**
* It's button listener when no transmission executes
* */
private void uploadBtnAction(){
if (usbThread == null || !usbThread.isAlive()){
List<File> nspToUpload;
if ((nspToUpload = tableFilesListController.getFiles()) == null) {
resourceBundle.getString("logsNoFolderFileSelected");
return;
}else {
logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
for (File item: nspToUpload)
logArea.appendText(" "+item.getAbsolutePath()+"\n");
}
UsbCommunications usbCommunications = new UsbCommunications(nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem());
usbThread = new Thread(usbCommunications);
usbThread.start();
}
}
/**
* It's button listener when transmission in progress
* */
private void stopBtnAction(){
if (usbThread != null && usbThread.isAlive()){
usbThread.interrupt();
}
}
/**
* This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
* Called from mediator
* */
public void notifyTransmissionStarted(boolean isTransmissionStarted){
if (isTransmissionStarted) {
selectNspBtn.setDisable(true);
uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
uploadStopBtn.setText(resourceBundle.getString("btnStop"));
btnUpStopImage.getStyleClass().remove("regionUpload");
btnUpStopImage.getStyleClass().add("regionStop");
uploadStopBtn.getStyleClass().remove("buttonUp");
uploadStopBtn.getStyleClass().add("buttonStop");
}
else {
selectNspBtn.setDisable(false);
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
btnUpStopImage.getStyleClass().remove("regionStop");
btnUpStopImage.getStyleClass().add("regionUpload");
uploadStopBtn.getStyleClass().remove("buttonStop");
uploadStopBtn.getStyleClass().add("buttonUp");
}
}
/**
* Save preferences before exit
* */
public void exit(){
AppPreferences.getInstance().setTheme(switchThemeBtn.getScene().getStylesheets().get(0));
AppPreferences.getInstance().setRecent(previouslyOpenedPath);
}
}

View file

@ -0,0 +1,54 @@
package nsusbloader.Controllers;
import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File;
public class NSLRowModel {
private String status;
private File nspFile;
private String nspFileName;
private String nspFileSize;
private boolean markForUpload;
NSLRowModel(File nspFile, boolean checkBoxValue){
this.nspFile = nspFile;
this.markForUpload = checkBoxValue;
this.nspFileName = nspFile.getName();
this.nspFileSize = String.format("%.2f", nspFile.length()/1024.0/1024.0);
this.status = "";
}
// Model methods start
public String getStatus(){
return status;
}
public String getNspFileName(){
return nspFileName;
}
public String getNspFileSize() { return nspFileSize; }
public boolean isMarkForUpload() {
return markForUpload;
}
// Model methods end
public void setMarkForUpload(boolean value){
markForUpload = value;
}
public File getNspFile(){ return nspFile; }
public void setStatus(EFileStatus status){ // TODO: Localization
switch (status){
case UPLOADED:
this.status = "Success";
markForUpload = false;
break;
case FAILED:
this.status = "Failed";
break;
case INCORRECT_FILE_FAILED:
this.status = "Failed: Incorrect file";
markForUpload = false;
break;
}
}
}

View file

@ -0,0 +1,177 @@
package nsusbloader.Controllers;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;
import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
public class NSTableViewController implements Initializable {
@FXML
private TableView<NSLRowModel> table;
private ObservableList<NSLRowModel> rowsObsLst;
private String protocol;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
rowsObsLst = FXCollections.observableArrayList();
table.setPlaceholder(new Label());
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<NSLRowModel, String> statusColumn = new TableColumn<>(resourceBundle.getString("tableStatusLbl"));
TableColumn<NSLRowModel, String> fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl"));
TableColumn<NSLRowModel, String> fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl"));
TableColumn<NSLRowModel, Boolean> uploadColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl"));
// See https://bugs.openjdk.java.net/browse/JDK-8157687
statusColumn.setMinWidth(100.0);
statusColumn.setPrefWidth(100.0);
statusColumn.setMaxWidth(100.0);
statusColumn.setResizable(false);
fileNameColumn.setMinWidth(25.0);
fileSizeColumn.setMinWidth(120.0);
fileSizeColumn.setPrefWidth(120.0);
fileSizeColumn.setMaxWidth(120.0);
fileSizeColumn.setResizable(false);
uploadColumn.setMinWidth(100.0);
uploadColumn.setPrefWidth(100.0);
uploadColumn.setMaxWidth(100.0);
uploadColumn.setResizable(false);
statusColumn.setCellValueFactory(new PropertyValueFactory<>("status"));
fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileName"));
fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileSize"));
// ><
uploadColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<NSLRowModel, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<NSLRowModel, Boolean> paramFeatures) {
NSLRowModel model = paramFeatures.getValue();
SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkForUpload());
booleanProperty.addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {
model.setMarkForUpload(newValue);
restrictSelection(model);
}
});
return booleanProperty;
}
});
uploadColumn.setCellFactory(new Callback<TableColumn<NSLRowModel, Boolean>, TableCell<NSLRowModel, Boolean>>() {
@Override
public TableCell<NSLRowModel, Boolean> call(TableColumn<NSLRowModel, Boolean> paramFeatures) {
CheckBoxTableCell<NSLRowModel, Boolean> cell = new CheckBoxTableCell<>();
return cell;
}
});
table.setItems(rowsObsLst);
table.getColumns().addAll(statusColumn, fileNameColumn, fileSizeColumn, uploadColumn);
}
/**
* See uploadColumn callback. In case of GoldLeaf we have to restrict selection
* */
private void restrictSelection(NSLRowModel modelChecked){
if (!protocol.equals("TinFoil") && rowsObsLst.size() > 1) { // Tinfoil doesn't need any restrictions. If only one file in list, also useless
for (NSLRowModel model: rowsObsLst){
if (model != modelChecked)
model.setMarkForUpload(false);
}
table.refresh();
}
}
/**
* Add files when user selected them
* */
public void setFiles(List<File> files){
rowsObsLst.clear(); // TODO: consider table refresh
if (files == null) {
return;
}
if (protocol.equals("TinFoil")){
for (File nspFile: files){
rowsObsLst.add(new NSLRowModel(nspFile, true));
}
}
else {
rowsObsLst.clear();
for (File nspFile: files){
rowsObsLst.add(new NSLRowModel(nspFile, false));
}
rowsObsLst.get(0).setMarkForUpload(true);
}
}
/**
* Return files ready for upload. Requested from NSLMainController only
* @return null if no files marked for upload
* List<File> if there are files
* */
public List<File> getFiles(){
List<File> files = new ArrayList<>();
if (rowsObsLst.isEmpty())
return null;
else {
for (NSLRowModel model: rowsObsLst){
if (model.isMarkForUpload())
files.add(model.getNspFile());
}
if (!files.isEmpty())
return files;
else
return null;
}
}
/**
* Update files in case something is wrong. Requested from UsbCommunications
* */
public void setFileStatus(String fileName, EFileStatus status){
for (NSLRowModel model: rowsObsLst){
if (model.getNspFileName().equals(fileName)){
model.setStatus(status);
}
}
table.refresh();
}
/**
* Called if selected different USB protocol
* */
public void setNewProtocol(String newProtocol){
protocol = newProtocol;
if (rowsObsLst.isEmpty())
return;
if (newProtocol.equals("TinFoil")){
for (NSLRowModel model: rowsObsLst)
model.setMarkForUpload(true);
}
else {
for (NSLRowModel model: rowsObsLst)
model.setMarkForUpload(false);
rowsObsLst.get(0).setMarkForUpload(true);
}
table.refresh();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,133 +0,0 @@
package nsusbloader;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Region;
import javafx.stage.FileChooser;
import java.io.File;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
public class NSLMainController implements Initializable {
private ResourceBundle resourceBundle;
private List<File> nspToUpload;
@FXML
private TextArea logArea;
@FXML
private Button selectNspBtn;
@FXML
private Button uploadStopBtn;
private Region btnUpStopImage;
@FXML
private ProgressBar progressBar;
private Thread usbThread;
@Override
public void initialize(URL url, ResourceBundle rb) {
this.resourceBundle = rb;
logArea.setText(rb.getString("logsGreetingsMessage")+" "+NSLMain.appVersion+"!\n");
if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
if (!System.getProperty("user.name").equals("root"))
logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
MediatorControl.getInstance().registerController(this);
uploadStopBtn.setDisable(true);
selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
this.btnUpStopImage = new Region();
btnUpStopImage.getStyleClass().add("regionUpload");
//uploadStopBtn.getStyleClass().remove("button");
uploadStopBtn.getStyleClass().add("buttonUp");
uploadStopBtn.setGraphic(btnUpStopImage);
}
/**
* Functionality for selecting NSP button.
* Uses setReady and setNotReady to simplify code readability.
* */
private void selectFilesBtnAction(){
List<File> filesList;
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: read from prefs
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS ROM", "*.nsp"));
filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
if (filesList != null && !filesList.isEmpty())
setReady(filesList);
else
setNotReady(resourceBundle.getString("logsNoFolderFileSelected"));
}
private void setReady(List<File> filesList){
logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
for (File item: filesList)
logArea.appendText(" "+item.getAbsolutePath()+"\n");
nspToUpload = filesList;
uploadStopBtn.setDisable(false);
}
private void setNotReady(String whyNotReady){
logArea.setText(whyNotReady);
nspToUpload = null;
uploadStopBtn.setDisable(true);
}
/**
* It's button listener when no transmission executes
* */
private void uploadBtnAction(){
if (usbThread == null || !usbThread.isAlive()){
UsbCommunications usbCommunications = new UsbCommunications(logArea, progressBar, nspToUpload); //todo: progress bar
usbThread = new Thread(usbCommunications);
usbThread.start();
}
}
/**
* It's button listener when transmission in progress
* */
private void stopBtnAction(){
if (usbThread != null && usbThread.isAlive()){
usbThread.interrupt();
}
}
/**
* This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
* Called from mediator
* */
void notifyTransmissionStarted(boolean isTransmissionStarted){
if (isTransmissionStarted) {
selectNspBtn.setDisable(true);
uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
uploadStopBtn.setText(resourceBundle.getString("btnStop"));
btnUpStopImage.getStyleClass().remove("regionUpload");
btnUpStopImage.getStyleClass().add("regionStop");
uploadStopBtn.getStyleClass().remove("buttonUp");
uploadStopBtn.getStyleClass().add("buttonStop");
}
else {
selectNspBtn.setDisable(false);
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
btnUpStopImage.getStyleClass().remove("regionStop");
btnUpStopImage.getStyleClass().add("regionUpload");
uploadStopBtn.getStyleClass().remove("buttonStop");
uploadStopBtn.getStyleClass().add("buttonUp");
}
}
}

View file

@ -0,0 +1,25 @@
package nsusbloader.PFS;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Data class to hold NCA, tik, xml etc. meta-information
* */
public class NCAFile {
//private int ncaNumber;
private byte[] ncaFileName;
private long ncaOffset;
private long ncaSize;
//public void setNcaNumber(int ncaNumber){ this.ncaNumber = ncaNumber; }
public void setNcaFileName(byte[] ncaFileName) { this.ncaFileName = ncaFileName; }
public void setNcaOffset(long ncaOffset) { this.ncaOffset = ncaOffset; }
public void setNcaSize(long ncaSize) { this.ncaSize = ncaSize; }
//public int getNcaNumber() {return this.ncaNumber; }
public byte[] getNcaFileName() { return ncaFileName; }
public byte[] getNcaFileNameLength() { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFileName.length).array(); }
public long getNcaOffset() { return ncaOffset; }
public long getNcaSize() { return ncaSize; }
}

View file

@ -0,0 +1,243 @@
package nsusbloader.PFS;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.ServiceWindow;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.BlockingQueue;
/**
* Used in GoldLeaf USB protocol
* */
public class PFSProvider {
private static final byte[] PFS0 = new byte[]{(byte)0x50, (byte)0x46, (byte)0x53, (byte)0x30}; // PFS0, and what did you think?
private BlockingQueue<String> msgQueue;
private ResourceBundle rb;
private RandomAccessFile randAccessFile;
private String nspFileName;
private NCAFile[] ncaFiles;
private long bodySize;
private int ticketID = -1;
public PFSProvider(File nspFile, BlockingQueue msgQueue){
this.msgQueue = msgQueue;
try {
this.randAccessFile = new RandomAccessFile(nspFile, "r");
nspFileName = nspFile.getName();
}
catch (FileNotFoundException fnfe){
printLog("PFS File not founnd: \n "+fnfe.getMessage(), EMsgType.FAIL);
nspFileName = null;
}
if (Locale.getDefault().getISO3Language().equals("rus"))
rb = ResourceBundle.getBundle("locale", new Locale("ru"));
else
rb = ResourceBundle.getBundle("locale", new Locale("en"));
}
public boolean init() {
if (nspFileName == null)
return false;
int filesCount;
int header;
printLog("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO);
try {
byte[] fileStartingBytes = new byte[12];
// Read PFS0, files count, header, padding (4 zero bytes)
if (randAccessFile.read(fileStartingBytes) == 12)
printLog("PFS Read file starting bytes.", EMsgType.PASS);
else {
printLog("PFS Read file starting bytes.", EMsgType.FAIL);
randAccessFile.close();
return false;
}
// Check PFS0
if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4)))
printLog("PFS Read 'PFS0'.", EMsgType.PASS);
else {
printLog("PFS Read 'PFS0'.", EMsgType.WARNING);
if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) {
randAccessFile.close();
return false;
}
}
// Get files count
filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (filesCount > 0 ) {
printLog("PFS Read files count [" + filesCount + "]", EMsgType.PASS);
}
else {
printLog("PFS Read files count", EMsgType.FAIL);
randAccessFile.close();
return false;
}
// Get header
header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (header > 0 )
printLog("PFS Read header ["+header+"]", EMsgType.PASS);
else {
printLog("PFS Read header ", EMsgType.FAIL);
randAccessFile.close();
return false;
}
//*********************************************************************************************
// Create NCA set
this.ncaFiles = new NCAFile[filesCount];
// Collect files from NSP
byte[] ncaInfoArr = new byte[24]; // should be unsigned long, but.. java.. u know my pain man
HashMap<Integer, Long> ncaNameOffsets = new LinkedHashMap<>();
int offset;
long nca_offset;
long nca_size;
long nca_name_offset;
for (int i=0; i<filesCount; i++){
if (randAccessFile.read(ncaInfoArr) == 24) {
printLog("PFS Read NCA inside NSP: " + i, EMsgType.PASS);
}
else {
printLog("PFS Read NCA inside NSP: "+i, EMsgType.FAIL);
randAccessFile.close();
return false;
}
offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 0, 4)).order(ByteOrder.LITTLE_ENDIAN).getInt();
nca_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 4, 12)).order(ByteOrder.LITTLE_ENDIAN).getLong();
nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
printLog(" Padding check", offset == 0?EMsgType.PASS:EMsgType.WARNING);
printLog(" NCA offset check "+nca_offset, nca_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
printLog(" NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING);
printLog(" NCA name offset check "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
NCAFile ncaFile = new NCAFile();
ncaFile.setNcaOffset(nca_offset);
ncaFile.setNcaSize(nca_size);
this.ncaFiles[i] = ncaFile;
ncaNameOffsets.put(i, nca_name_offset);
}
// Final offset
byte[] bufForInt = new byte[4];
if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4])))
printLog("PFS Final padding check", EMsgType.PASS);
else
printLog("PFS Final padding check", EMsgType.WARNING);
// Calculate position including header for body size offset
bodySize = randAccessFile.getFilePointer()+header;
//*********************************************************************************************
// Collect file names from NCAs
printLog("PFS Collecting file names", EMsgType.INFO);
List<Byte> ncaFN; // Temporary
byte[] b = new byte[1]; // Temporary
for (int i=0; i<filesCount; i++){
ncaFN = new ArrayList<>();
randAccessFile.seek(filesCount*24+16+ncaNameOffsets.get(i)); // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them + 12 bit what were in the beginning
while ((randAccessFile.read(b)) != -1){
if (b[0] == 0x00)
break;
else
ncaFN.add(b[0]);
}
byte[] exchangeTempArray = new byte[ncaFN.size()];
for (int j=0; j < ncaFN.size(); j++)
exchangeTempArray[j] = ncaFN.get(j);
// Find and store ticket (.tik)
if (new String(exchangeTempArray, StandardCharsets.UTF_8).toLowerCase().endsWith(".tik"))
this.ticketID = i;
this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length));
}
randAccessFile.close();
}
catch (IOException ioe){
printLog("PFS Failed NSP file analyze for ["+nspFileName+"]\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace();
}
printLog("PFS Finish NSP file analyze for ["+nspFileName+"]", EMsgType.PASS);
return true;
}
/**
* Return file name as byte array
* */
public byte[] getBytesNspFileName(){
return nspFileName.getBytes(StandardCharsets.UTF_8);
}
/**
* Return file name as String
* */
public String getStringNspFileName(){
return nspFileName;
}
/**
* Return file name length as byte array
* */
public byte[] getBytesNspFileNameLength(){
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array();
}
/**
* Return NCA count inside of file as byte array
* */
public byte[] getBytesCountOfNca(){
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array();
}
/**
* Return NCA count inside of file as int
* */
public int getIntCountOfNca(){
return ncaFiles.length;
}
/**
* Return requested-by-number NCA file inside of file
* */
public NCAFile getNca(int ncaNumber){
return ncaFiles[ncaNumber];
}
/**
* Return bodySize
* */
public long getBodySize(){
return bodySize;
}
/**
* Return special NCA file: ticket
* (sugar)
* */
public int getNcaTicketID(){
return ticketID;
}
/**
* This is what will print to textArea of the application.
**/
private void printLog(String message, EMsgType type){
try {
switch (type){
case PASS:
msgQueue.put("[ PASS ] "+message+"\n");
break;
case FAIL:
msgQueue.put("[ FAIL ] "+message+"\n");
break;
case INFO:
msgQueue.put("[ INFO ] "+message+"\n");
break;
case WARNING:
msgQueue.put("[ WARN ] "+message+"\n");
break;
}
}catch (InterruptedException ie){
ie.printStackTrace(); //TODO: ???
}
}
}

View file

@ -0,0 +1,31 @@
package nsusbloader;
import java.nio.charset.StandardCharsets;
/**
* Debug tool like hexdump <3
*/
public class RainbowHexDump {
private static final String ANSI_RESET = "\u001B[0m";
private static final String ANSI_BLACK = "\u001B[30m";
private static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_GREEN = "\u001B[32m";
private static final String ANSI_YELLOW = "\u001B[33m";
private static final String ANSI_BLUE = "\u001B[34m";
private static final String ANSI_PURPLE = "\u001B[35m";
private static final String ANSI_CYAN = "\u001B[36m";
private static final String ANSI_WHITE = "\u001B[37m";
public static void hexDumpUTF8(byte[] byteArray){
System.out.print(ANSI_BLUE);
for (int i=0; i < byteArray.length; i++)
System.out.print(String.format("%02d-", i%100));
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
for (byte b: byteArray)
System.out.print(String.format("%02x ", b));
System.out.print("\t\t\t"
+ new String(byteArray, StandardCharsets.UTF_8)
+ "\n");
}
}

View file

@ -11,7 +11,7 @@ public class ServiceWindow {
* Create window with notification * Create window with notification
* */ * */
/* // not used /* // not used
static void getErrorNotification(String title, String body){ public static void getErrorNotification(String title, String body){
Alert alertBox = new Alert(Alert.AlertType.ERROR); Alert alertBox = new Alert(Alert.AlertType.ERROR);
alertBox.setTitle(title); alertBox.setTitle(title);
alertBox.setHeaderText(null); alertBox.setHeaderText(null);
@ -27,7 +27,7 @@ public class ServiceWindow {
/** /**
* Create notification window with confirm/deny * Create notification window with confirm/deny
* */ * */
static boolean getConfirmationWindow(String title, String body){ public static boolean getConfirmationWindow(String title, String body){
Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION); Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION);
alertBox.setTitle(title); alertBox.setTitle(title);
alertBox.setHeaderText(null); alertBox.setHeaderText(null);
@ -35,12 +35,9 @@ public class ServiceWindow {
alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE); alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really. alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really.
alertBox.getDialogPane().getStylesheets().add("/res/app.css"); alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme());
Optional<ButtonType> result = alertBox.showAndWait(); Optional<ButtonType> result = alertBox.showAndWait();
if (result.get() == ButtonType.OK)
return true;
else
return false;
return (result.isPresent() && result.get() == ButtonType.OK);
} }
} }

View file

@ -1,8 +1,9 @@
package nsusbloader; package nsusbloader;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.scene.control.ProgressBar; import nsusbloader.NSLDataTypes.EFileStatus;
import javafx.scene.control.TextArea; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.PFS.PFSProvider;
import org.usb4java.*; import org.usb4java.*;
import java.io.*; import java.io.*;
@ -17,18 +18,22 @@ import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
class UsbCommunications extends Task<Void> { public class UsbCommunications extends Task<Void> {
private final int DEFAULT_INTERFACE = 0; private final int DEFAULT_INTERFACE = 0;
private BlockingQueue<String> msgQueue; private BlockingQueue<String> msgQueue;
private BlockingQueue<Double> progressQueue; 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 MessagesConsumer msgConsumer;
private HashMap<String, File> nspMap; private HashMap<String, File> nspMap;
private Context contextNS; private Context contextNS;
private DeviceHandle handlerNS; 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. 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. 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. 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. 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<>(); this.nspMap = new HashMap<>();
for (File f: nspList) for (File f: nspList)
nspMap.put(f.getName(), f); nspMap.put(f.getName(), f);
this.msgQueue = new LinkedBlockingQueue<>(); this.msgQueue = new LinkedBlockingQueue<>();
this.progressQueue = 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 @Override
@ -54,28 +61,28 @@ class UsbCommunications extends Task<Void> {
this.msgConsumer.start(); this.msgConsumer.start();
int result = -9999; int result = -9999;
printLog("\tStart chain", MsgType.INFO); printLog("\tStart chain", EMsgType.INFO);
// Creating Context required by libusb. Optional. TODO: Consider removing. // Creating Context required by libusb. Optional. TODO: Consider removing.
contextNS = new Context(); contextNS = new Context();
result = LibUsb.init(contextNS); result = LibUsb.init(contextNS);
if (result != LibUsb.SUCCESS) { if (result != LibUsb.SUCCESS) {
printLog("libusb initialization\n Returned: "+result, MsgType.FAIL); printLog("libusb initialization\n Returned: "+result, EMsgType.FAIL);
close(); close();
return null; return null;
} }
else else
printLog("libusb initialization", MsgType.PASS); printLog("libusb initialization", EMsgType.PASS);
// Searching for NS in devices: obtain list of all devices // Searching for NS in devices: obtain list of all devices
DeviceList deviceList = new DeviceList(); DeviceList deviceList = new DeviceList();
result = LibUsb.getDeviceList(contextNS, deviceList); result = LibUsb.getDeviceList(contextNS, deviceList);
if (result < 0) { if (result < 0) {
printLog("Get device list\n Returned: "+result, MsgType.FAIL); printLog("Get device list\n Returned: "+result, EMsgType.FAIL);
close(); close();
return null; return null;
} }
else { else {
printLog("Get device list", MsgType.PASS); printLog("Get device list", EMsgType.PASS);
} }
// Searching for NS in devices: looking for NS // Searching for NS in devices: looking for NS
DeviceDescriptor descriptor; DeviceDescriptor descriptor;
@ -84,14 +91,14 @@ class UsbCommunications extends Task<Void> {
descriptor = new DeviceDescriptor(); // mmm.. leave it as is. descriptor = new DeviceDescriptor(); // mmm.. leave it as is.
result = LibUsb.getDeviceDescriptor(device, descriptor); result = LibUsb.getDeviceDescriptor(device, descriptor);
if (result != LibUsb.SUCCESS){ 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); LibUsb.freeDeviceList(deviceList, true);
close(); close();
return null; return null;
} }
if ((descriptor.idVendor() == 0x057E) && descriptor.idProduct() == 0x3000){ if ((descriptor.idVendor() == 0x057E) && descriptor.idProduct() == 0x3000){
deviceNS = device; deviceNS = device;
printLog("Read file descriptors for USB devices", MsgType.PASS); printLog("Read file descriptors for USB devices", EMsgType.PASS);
break; break;
} }
} }
@ -134,10 +141,10 @@ class UsbCommunications extends Task<Void> {
////////////////////////////////////////// DEBUG INFORMATION END ///////////////////////////////////////////// ////////////////////////////////////////// DEBUG INFORMATION END /////////////////////////////////////////////
if (deviceNS != null){ if (deviceNS != null){
printLog("NS in connected USB devices found", MsgType.PASS); printLog("NS in connected USB devices found", EMsgType.PASS);
} }
else { 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(); close();
return null; return null;
} }
@ -147,25 +154,25 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS) { if (result != LibUsb.SUCCESS) {
switch (result){ switch (result){
case LibUsb.ERROR_ACCESS: case LibUsb.ERROR_ACCESS:
printLog("Open NS USB device\n Returned: ERROR_ACCESS", MsgType.FAIL); 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)!",MsgType.INFO); printLog("Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!", EMsgType.INFO);
break; break;
case LibUsb.ERROR_NO_MEM: 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; break;
case LibUsb.ERROR_NO_DEVICE: 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; break;
default: default:
printLog("Open NS USB device\n Returned:" + result, MsgType.FAIL); printLog("Open NS USB device\n Returned:" + result, EMsgType.FAIL);
} }
close(); close();
return null; return null;
} }
else 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); LibUsb.freeDeviceList(deviceList, true);
// DO some stuff to connected NS // DO some stuff to connected NS
@ -174,105 +181,131 @@ class UsbCommunications extends Task<Void> {
if (canDetach){ if (canDetach){
int usedByKernel = LibUsb.kernelDriverActive(handlerNS, DEFAULT_INTERFACE); int usedByKernel = LibUsb.kernelDriverActive(handlerNS, DEFAULT_INTERFACE);
if (usedByKernel == LibUsb.SUCCESS){ 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 { else {
switch (usedByKernel){ switch (usedByKernel){
case 1: // used by kernel case 1: // used by kernel
result = LibUsb.detachKernelDriver(handlerNS, DEFAULT_INTERFACE); result = LibUsb.detachKernelDriver(handlerNS, DEFAULT_INTERFACE);
printLog("Detach kernel required", MsgType.INFO); printLog("Detach kernel required", EMsgType.INFO);
if (result != 0) { if (result != 0) {
switch (result){ switch (result){
case LibUsb.ERROR_NOT_FOUND: 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; break;
case LibUsb.ERROR_INVALID_PARAM: 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; break;
case LibUsb.ERROR_NO_DEVICE: 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; break;
case LibUsb.ERROR_NOT_SUPPORTED: // Should never appear only if libusb buggy 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; break;
default: default:
printLog("Detach kernel\n Returned: " + result, MsgType.FAIL); printLog("Detach kernel\n Returned: " + result, EMsgType.FAIL);
break; break;
} }
close(); close();
return null; return null;
} }
else { else {
printLog("Detach kernel", MsgType.PASS); printLog("Detach kernel", EMsgType.PASS);
break; break;
} }
case LibUsb.ERROR_NO_DEVICE: 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; break;
case LibUsb.ERROR_NOT_SUPPORTED: 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; break;
default: 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 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) // Set configuration (soft reset if needed)
result = LibUsb.setConfiguration(handlerNS, 1); // 1 - configuration all we need result = LibUsb.setConfiguration(handlerNS, 1); // 1 - configuration all we need
if (result != LibUsb.SUCCESS){ if (result != LibUsb.SUCCESS){
switch (result){ switch (result){
case LibUsb.ERROR_NOT_FOUND: 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; break;
case LibUsb.ERROR_BUSY: 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; break;
case LibUsb.ERROR_NO_DEVICE: 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; break;
case LibUsb.ERROR_INVALID_PARAM: 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; break;
default: default:
printLog("Set active configuration to device\n Returned: "+result, MsgType.FAIL); printLog("Set active configuration to device\n Returned: "+result, EMsgType.FAIL);
break; break;
} }
close(); close();
return null; return null;
} }
else { else {
printLog("Set active configuration to device.", MsgType.PASS); printLog("Set active configuration to device.", EMsgType.PASS);
} }
// Claim interface // Claim interface
result = LibUsb.claimInterface(handlerNS, DEFAULT_INTERFACE); result = LibUsb.claimInterface(handlerNS, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) { if (result != LibUsb.SUCCESS) {
printLog("Claim interface\n Returned: "+result, MsgType.FAIL); printLog("Claim interface\n Returned: "+result, EMsgType.FAIL);
close(); close();
return null; return null;
} }
else 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: // Send list of NSP files:
// Proceed "TUL0" // Proceed "TUL0"
if (!writeToUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} 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); printLog("TF Send list of files: handshake", EMsgType.FAIL);
close(); return false;
return null;
} }
else else
printLog("Send list of files: handshake", MsgType.PASS); printLog("TF Send list of files: handshake", EMsgType.PASS);
//Collect file names //Collect file names
StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder
for(String nspFileName: nspMap.keySet()) for(String nspFileName: nspMap.keySet()) {
nspListNamesBuilder.append(nspFileName+'\n'); // And here we come with java string default encoding (UTF-16) 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); 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 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(); //byteBuffer.reset();
// Sending NSP list // Sending NSP list
printLog("TF Send list of files", EMsgType.INFO);
if (!writeToUsb(nspListSize)) { // size of the list we're going to transfer goes... if (!writeToUsb(nspListSize)) { // size of the list we're going to transfer goes...
printLog("Send list of files: send length.", MsgType.FAIL); printLog(" [send list length]", EMsgType.FAIL);
close(); return false;
return null; }
} else printLog(" [send list length]", EMsgType.PASS);
printLog("Send list of files: send length.", MsgType.PASS);
if (!writeToUsb(new byte[8])) { // 8 zero bytes goes... if (!writeToUsb(new byte[8])) { // 8 zero bytes goes...
printLog("Send list of files: send padding.", MsgType.FAIL); printLog(" [send padding]", EMsgType.FAIL);
close(); return false;
return null;
} }
else printLog(" [send padding]", EMsgType.PASS);
printLog("Send list of files: send padding.", MsgType.PASS);
if (!writeToUsb(nspListNames)) { // list of the names goes... if (!writeToUsb(nspListNames)) { // list of the names goes...
printLog("Send list of files: send list itself.", MsgType.FAIL); printLog(" [send list itself]", EMsgType.FAIL);
close(); return false;
return null;
} }
else printLog(" [send list itself]", EMsgType.PASS);
printLog("Send list of files: send list itself.", MsgType.PASS);
proceedCommands(); return true;
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();
} }
/** /**
* After we sent commands to NS, this chain starts * After we sent commands to NS, this chain starts
* */ * */
private void proceedCommands(){ private boolean proceedCommands(){
printLog("Awaiting for NS commands.", MsgType.INFO); printLog("TF Awaiting for NS commands.", EMsgType.INFO);
/* byte[] magic = new byte[4]; /* byte[] magic = new byte[4];
ByteBuffer bb = StandardCharsets.UTF_8.encode("TUC0").rewind().get(magic); ByteBuffer bb = StandardCharsets.UTF_8.encode("TUC0").rewind().get(magic);
@ -347,10 +350,10 @@ class UsbCommunications extends Task<Void> {
while (true){ while (true){
if (Thread.currentThread().isInterrupted()) // Check if user interrupted process. if (Thread.currentThread().isInterrupted()) // Check if user interrupted process.
return; return false;
receivedArray = readFromUsb(); receivedArray = readFromUsb();
if (receivedArray == null) 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 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; 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: // 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. // 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 if (receivedArray[8] == 0x00){ //0x00 - exit
printLog("Received EXIT command. Terminating.", MsgType.PASS); printLog("TF Received EXIT command. Terminating.", EMsgType.PASS);
return; // All interaction with USB device should be ended (expected); 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). 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. /*// 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(); 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 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()) { if (!fileRangeCmd()) {
return; // catches exception return false; // catches exception
} }
} }
} }
@ -402,25 +405,21 @@ class UsbCommunications extends Task<Void> {
return false; return false;
String receivedRequestedNSP = new String(receivedArray, StandardCharsets.UTF_8); 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 Size: "+receivedRangeSize
+"\n Range Offset: "+receivedRangeOffset, MsgType.INFO); +"\n Range Offset: "+receivedRangeOffset, EMsgType.INFO);
// Sending response header // Sending response header
if (!sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply. if (!sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply.
return false; return false;
// Read file starting:
// from Range Offset (receivedRangeOffset)
// to Range Size (receivedRangeSize) like end: receivedRangeOffset+receivedRangeSize
try { try {
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor? BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor?
byte[] bufferCurrent ;//= new byte[1048576]; // eq. Allocate 1mb byte[] bufferCurrent ;//= new byte[1048576]; // eq. Allocate 1mb
int bufferLength; int bufferLength;
if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){ 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; return false;
} }
@ -433,7 +432,7 @@ class UsbCommunications extends Task<Void> {
return true; return true;
if ((currentOffset + readPice) >= receivedRangeSize ) if ((currentOffset + readPice) >= receivedRangeSize )
readPice = Math.toIntExact(receivedRangeSize - currentOffset); 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 // updating progress bar (if a lot of data requested) START BLOCK
if (isProgessBarInitiated){ if (isProgessBarInitiated){
try { try {
@ -444,7 +443,7 @@ class UsbCommunications extends Task<Void> {
else else
progressQueue.put((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0); progressQueue.put((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0);
}catch (InterruptedException ie){ }catch (InterruptedException ie){
getException().printStackTrace(); getException().printStackTrace(); // TODO: Do something with this
} }
} }
else { else {
@ -460,25 +459,29 @@ class UsbCommunications extends Task<Void> {
if (bufferLength != -1){ if (bufferLength != -1){
//write to USB //write to USB
if (!writeToUsb(bufferCurrent)) { if (!writeToUsb(bufferCurrent)) {
printLog("Failure during NSP transmission.", MsgType.FAIL); printLog("TF Failure during NSP transmission.", EMsgType.FAIL);
return false; return false;
} }
currentOffset += readPice; currentOffset += readPice;
} }
else { else {
printLog("Unexpected reading of stream ended.", MsgType.WARNING); printLog("TF Reading of stream suddenly ended.", EMsgType.WARNING);
return false; return false;
} }
} }
bufferedInStream.close();
} catch (FileNotFoundException fnfe){ } catch (FileNotFoundException fnfe){
printLog("FileNotFoundException:\n"+fnfe.getMessage(), MsgType.FAIL); printLog("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL);
fnfe.printStackTrace();
return false; return false;
} catch (IOException ioe){ } catch (IOException ioe){
printLog("IOException:\n"+ioe.getMessage(), MsgType.FAIL); printLog("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace();
return false; return false;
} catch (ArithmeticException ae){ } 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; return false;
} }
@ -490,29 +493,283 @@ class UsbCommunications extends Task<Void> {
* false if failed * 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. 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' if (!writeToUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0'
(byte) 0x01, // CMD_TYPE_RESPONSE = 1 (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) 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. (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; return false;
} }
printLog("[1/3]", MsgType.PASS); printLog(" [1/3]", EMsgType.PASS);
if(!writeToUsb(rangeSize)) { // Send EXACTLY what has been received if(!writeToUsb(rangeSize)) { // Send EXACTLY what has been received
printLog("[2/3]", MsgType.FAIL); printLog(" [2/3]", EMsgType.FAIL);
return false; return false;
} }
printLog("[2/3]", MsgType.PASS); printLog(" [2/3]", EMsgType.PASS);
if(!writeToUsb(new byte[12])) { // kinda another one padding if(!writeToUsb(new byte[12])) { // kinda another one padding
printLog("[3/3]", MsgType.FAIL); printLog(" [3/3]", EMsgType.FAIL);
return false; return false;
} }
printLog("[3/3]", MsgType.PASS); printLog(" [3/3]", EMsgType.PASS);
return true; 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 * Sending any byte array to USB device
* @return 'true' if no issues * @return 'true' if no issues
@ -528,25 +785,25 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS){ if (result != LibUsb.SUCCESS){
switch (result){ switch (result){
case LibUsb.ERROR_TIMEOUT: 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; break;
case LibUsb.ERROR_PIPE: //WUT?? I dunno man looks overkill in here.. 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; break;
case LibUsb.ERROR_OVERFLOW: 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; break;
case LibUsb.ERROR_NO_DEVICE: 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; break;
default: 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; return false;
}else { }else {
if (writeBufTransferred.get() != message.length){ 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; return false;
} }
else { else {
@ -570,21 +827,24 @@ class UsbCommunications extends Task<Void> {
if (result != LibUsb.SUCCESS){ if (result != LibUsb.SUCCESS){
switch (result){ switch (result){
case LibUsb.ERROR_TIMEOUT: 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; break;
case LibUsb.ERROR_PIPE: //WUT?? I dunno man looks overkill in here.. 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; break;
case LibUsb.ERROR_OVERFLOW: 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; break;
case LibUsb.ERROR_NO_DEVICE: 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; break;
default: 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; return null;
} else { } else {
int trans = readBufTransferred.get(); int trans = readBufTransferred.get();
@ -596,10 +856,11 @@ class UsbCommunications extends Task<Void> {
return receivedBytes; return receivedBytes;
} }
} }
/** /**
* This is what will print to textArea of the application. * 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 { try {
switch (type){ switch (type){
case PASS: case PASS:
@ -620,21 +881,5 @@ class UsbCommunications extends Task<Void> {
}catch (InterruptedException ie){ }catch (InterruptedException ie){
ie.printStackTrace(); ie.printStackTrace();
} }
} }
/**
* Debug tool like hexdump <3
*/
/*
private void hexDumpUTF8(byte[] byteArray){
for (int i=0; i < byteArray.length; i++)
System.out.print(String.format("%02d-", i%10));
System.out.println("\t[[COLUMNS LEN = "+byteArray.length+"]]");
for (byte b: byteArray)
System.out.print(String.format("%02x ", b));
System.out.print("\t\t\t"
+ new String(byteArray, StandardCharsets.UTF_8)
+ "\n");
}
*/
} }

View file

@ -2,23 +2,80 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.ProgressBar?> <?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TextArea?> <?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.SVGPath?> <?import javafx.scene.shape.SVGPath?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.NSLMainController"> <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSLMainController">
<children> <children>
<VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<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> <children>
<TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS"> <TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS">
<VBox.margin> <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> </VBox.margin>
</TextArea> </TextArea>
</children>
</VBox>
</content>
<graphic>
<SVGPath content="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M7,7H17V5H19V19H5V5H7V7Z" />
</graphic>
</Tab>
</tabs>
</TabPane>
<ProgressBar fx:id="progressBar" prefWidth="Infinity" progress="0.0"> <ProgressBar fx:id="progressBar" prefWidth="Infinity" progress="0.0">
<VBox.margin> <VBox.margin>
<Insets left="5.0" right="5.0" top="2.0" /> <Insets left="5.0" right="5.0" top="2.0" />

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<HBox xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSTableViewController">
<children>
<TableView fx:id="table" editable="true" HBox.hgrow="ALWAYS" />
</children>
<padding>
<Insets bottom="2.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>

View file

@ -12,3 +12,10 @@ logsGreetingsMessage2=--\n\
Source: https://github.com/developersu/ns-usbloader/\n\ Source: https://github.com/developersu/ns-usbloader/\n\
Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\
Dmitry Isaenko [developer.su] Dmitry Isaenko [developer.su]
windowTitleConfirmWrongPFS0=Incorrect file type
windowBodyConfirmWrongPFS0=Selected NSP file has incrrect starting symbols. Most likely it's corrupted.\n\
It's better to interrupt proccess now. Continue process anyway?
tableStatusLbl=Status
tableFileNameLbl=File name
tableSizeLbl=Size (~Mb)
tableUploadLbl=Upload?

View file

@ -12,4 +12,12 @@ logsGreetingsMessage2=--\n\
\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\ \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\
\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\ \u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\
\u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su] \u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su]
windowTitleConfirmWrongPFS0=\u041D\u0435\u043F\u0440\u0438\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0439 \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430
windowBodyConfirmWrongPFS0=\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u0444\u0430\u0439\u043B NSP \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0441\u0438\u043C\u0432\u043E\u043B\u044B. \u041E\u043D \u043D\u0430\u0432\u0435\u0440\u043D\u044F\u043A\u0430 \u043F\u043E\u0432\u0440\u0435\u0436\u0434\u0451\u043D.\n\
\u041B\u0443\u0447\u0448\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C\u0441\u044F \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441. \u0425\u043E\u0447\u0435\u0448\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0438 \u0441\u043C\u043E\u0442\u0440\u044F \u043D\u0438 \u043D\u0430 \u0447\u0442\u043E?\
tableUploadLbl=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044C?
tableSizeLbl=\u0420\u0430\u0437\u043C\u0435\u0440 (~\u041C\u0431)
tableFileNameLbl=\u0418\u043C\u044F \u0444\u0430\u0439\u043B\u0430
tableStatusLbl=\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435

View file

@ -1,100 +0,0 @@
@font-face {
src: url("NotoMono-Regular.ttf");
}
.root{
-fx-background: #2d2d2d;
}
.button, .buttonUp, .buttonStop{
-fx-background-color: #4f4f4f;
-fx-border-color: #4f4f4f;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.button:hover, .buttonStop:hover, .buttonUp:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #a4ffff;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.button:focused, .buttonStop:focused, .buttonUp:focused{
-fx-background-color: #6a6a6a;
}
.button:pressed{
-fx-background-color: #4f4f4f;
-fx-border-color: #289de8;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.buttonUp:pressed{
-fx-background-color: #4f4f4f;
-fx-border-color: #a2e019;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.buttonStop:pressed{
-fx-background-color: #4f4f4f;
-fx-border-color: #fb582c;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.text-area{
-fx-background-color: transparent;
-fx-control-inner-background: #4f4f4f;
-fx-font-family: "Noto Mono";
-fx-border-color: #00ffc9;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.progress-bar {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .track {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .bar {
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
-fx-background-radius: 2;
-fx-background-insets: 1 1 2 1;
-fx-padding: 0.23em;
}
.dialog-pane {
-fx-background-color: #4f4f4f;
}
.dialog-pane > .button-bar > .container{
-fx-background-color: #2d2d2d;
}
.dialog-pane > .label{
-fx-padding: 10 5 10 5;
}
.regionUpload{
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
-fx-background-color: #a2e019;
-size: 24;
-fx-min-height: -size;
-fx-min-width: 20;
}
.regionStop{
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
-fx-background-color: #fb582c;
-size: 24;
-fx-min-height: -size;
-fx-min-width: -size;
}

View file

@ -0,0 +1,255 @@
@font-face {
src: url("NotoMono-Regular.ttf");
}
.root{
-fx-background: #2d2d2d;
}
.button, .buttonUp, .buttonStop, .buttonSelect{
-fx-background-color: #4f4f4f;
-fx-border-color: #4f4f4f;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
-fx-effect: none;
}
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #a4ffff;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
-fx-background-color: #6a6a6a;
-fx-border-color: #6a6a6a;
}
.button:pressed, .button:pressed:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #e82382;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.buttonSelect:pressed, .buttonSelect:pressed:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #289de8;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.buttonUp:pressed, .buttonUp:pressed:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #a2e019;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.buttonStop:pressed, .buttonStop:pressed:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #fb582c;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.text-area{
-fx-background-color: transparent;
-fx-control-inner-background: #4f4f4f;
-fx-font-family: "Noto Mono";
-fx-border-color: #00ffc9;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
.progress-bar {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .track {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .bar {
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
-fx-background-radius: 2;
-fx-background-insets: 1 1 2 1;
-fx-padding: 0.23em;
}
.dialog-pane {
-fx-background-color: #4f4f4f;
}
.dialog-pane > .button-bar > .container{
-fx-background-color: #2d2d2d;
}
.dialog-pane > .label{
-fx-padding: 10 5 10 5;
}
.tool-bar{
-fx-background-color: transparent;
}
.special-pane-as-border{
-fx-background-color: #f7fafa;
-fx-min-height: 1;
}
// -======================== Choice box =========================-
.choice-box {
-fx-background-color: #4f4f4f;
-fx-border-color: #4f4f4f;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-mark-color: #eea11e;
-fx-effect: none;
}
.choice-box > .label {
-fx-text-fill: #f7fafa;
}
.choice-box:pressed, .choice-box:pressed:hover{
-fx-background-color: #4f4f4f;
-fx-border-color: #eea11e;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #f7fafa;
}
// Background color of the whole context menu
.choice-box .context-menu {
-fx-background-color: #2d2d2d;
}
// Focused item background color in the list
.choice-box .context-menu .menu-item:focused {
-fx-background-color: #eea11e;
}
.choice-box .context-menu .menu-item:focused .label {
-fx-text-fill: #2c2c2c;
}
// -======================== TAB PANE =========================-
.tab-pane .tab SVGPath{
-fx-fill: #f7fafa;
}
.tab-pane .tab:selected SVGPath{
-fx-fill: #08f3ff;
}
.tab-pane .tab{
-fx-background-color: #424242;
-fx-focus-color: transparent;
-fx-faint-focus-color: transparent;
-fx-border-radius: 0 0 0 0;
-fx-border-width: 3 0 0 0;
-fx-border-color: #424242;
}
.tab-pane .tab:selected{
-fx-background-color: #2d2d2d;
-fx-focus-color: transparent;
-fx-faint-focus-color: transparent;
-fx-border-radius: 0 0 0 0;
-fx-border-width: 3 0 0 0;
-fx-border-color: #08f3ff;
}
.tab-pane > .tab-header-area {
-fx-background-insets: 0.0;
-fx-padding: 5 5 5 5;
}
.tab-pane > .tab-header-area > .tab-header-background
{
-fx-background-color: #424242;
}
.tab-pane > .tab-header-area > .headers-region > .tab {
-fx-padding: 10;
}
// -=========================== TABLE ======================-
.table-view {
-fx-background-color: #4f4f4f;
-fx-background-image: url(app_logo.png);
-fx-background-position: center;
-fx-background-repeat: no-repeat;
-fx-border-color: #00ffc9;
-fx-border-radius: 3;
-fx-border-width: 2;
}
.table-view .arrow {
-fx-mark-color: #08f3ff ;
}
.table-view .column-header {
-fx-background-color: transparent;
-fx-border-width: 0 1 2 0;
-fx-border-color: #6d8484;
}
.table-view .column-header-background .label{
-fx-background-color: transparent;
-fx-text-fill: #08f3ff;
}
.table-view .column-header-background, .table-view .filler{
-fx-background-color: #4f4f4f;
}
.table-view .table-cell{
-fx-text-fill: #f7fafa;
}
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
-fx-background-color: -fx-table-cell-border-color, #424242;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #6d8484;
}
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
-fx-background-color: -fx-table-cell-border-color, #4f4f4f;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #6d8484;
}
// -========================== Context menu =====================-
.context-menu {
-fx-background-color: #2d2d2d;
-fx-text-fill: white;
-fx-cursor: hand;
}
.context-menu .menu-item .label {
-fx-text-fill: #f7fafa;
}
.context-menu .menu-item:focused .label {
-fx-text-fill: #f7fafa;
}
.regionUpload{
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
-fx-background-color: #a2e019;
-size: 24;
-fx-min-height: -size;
-fx-min-width: 20;
}
.regionStop{
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
-fx-background-color: #fb582c;
-size: 24;
-fx-min-height: -size;
-fx-min-width: -size;
}
.regionLamp{
-fx-shape: "M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V17A1,1 0 0,1 15,18H9A1,1 0 0,1 8,17V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2M9,21V20H15V21A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21M12,4A5,5 0 0,0 7,9C7,11.05 8.23,12.81 10,13.58V16H14V13.58C15.77,12.81 17,11.05 17,9A5,5 0 0,0 12,4Z";
-fx-background-color: #f7fafa;
-size: 17.5;
-fx-min-height: -size;
-fx-min-width: 12.5;
}

View file

@ -0,0 +1,256 @@
@font-face {
src: url("NotoMono-Regular.ttf");
}
.root{
-fx-background: #ebebeb;
}
.button, .buttonUp, .buttonStop, .buttonSelect{
-fx-background-color: #fefefe;
-fx-border-color: #fefefe;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
-fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
}
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
-fx-background-color: #fefefe;
-fx-border-color: #00caca;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
-fx-background-color: #cccccc;
-fx-border-color: #cccccc;
}
.button:pressed, .button:pressed:hover{
-fx-background-color: #fefefe;
-fx-border-color: #e82382;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
.buttonSelect:pressed, .buttonSelect:pressed:hover{
-fx-background-color: #fefefe;
-fx-border-color: #289de8;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
.buttonUp:pressed, .buttonUp:pressed:hover{
-fx-background-color: #fefefe;
-fx-border-color: #a2e019;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
.buttonStop:pressed, .buttonStop:pressed:hover{
-fx-background-color: #fefefe;
-fx-border-color: #fb582c;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
// -========================+ TextArea =====================-
.text-area{
-fx-background-color: transparent;
-fx-control-inner-background: #fefefe;
-fx-font-family: "Noto Mono";
-fx-border-color: #06b9bb;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
.progress-bar {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .track {
-fx-background-color: transparent;
-fx-box-border: transparent;
}
.progress-bar > .bar {
-fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
-fx-background-radius: 2;
-fx-background-insets: 1 1 2 1;
-fx-padding: 0.23em;
}
.dialog-pane {
-fx-background-color: #fefefe;
}
.dialog-pane > .button-bar > .container{
-fx-background-color: #2d2d2d;
}
.dialog-pane > .label{
-fx-padding: 10 5 10 5;
}
.tool-bar{
-fx-background-color: transparent;
}
.special-pane-as-border{
-fx-background-color: #2c2c2c;
-fx-min-height: 1;
}
// -======================== Choice box =========================-
.choice-box {
-fx-background-color: #fefefe;
-fx-border-color: #fefefe;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-mark-color: #eea11e;
-fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
}
.choice-box > .label {
-fx-text-fill: #2c2c2c;
}
.choice-box:pressed, .choice-box:pressed:hover{
-fx-background-color: #fefefe;
-fx-border-color: #eea11e;
-fx-border-radius: 3;
-fx-border-width: 2;
-fx-text-fill: #2c2c2c;
}
// Background color of the whole context menu
.choice-box .context-menu {
-fx-background-color: #fefefe;
}
// Focused item background color in the list
.choice-box .context-menu .menu-item:focused {
-fx-background-color: #eea11e;
}
.choice-box .context-menu .menu-item:focused .label {
-fx-text-fill: #2c2c2c;
}
// -======================== TAB PANE =========================-
.tab-pane .tab SVGPath{
-fx-fill: #2c2c2c; // OK
}
.tab-pane .tab:selected SVGPath{
-fx-fill: #289de8; // OK
}
.tab-pane .tab{
-fx-background-color: #fefefe; //ok
-fx-focus-color: transparent;
-fx-faint-focus-color: transparent;
-fx-border-radius: 0 0 0 0;
-fx-border-width: 3 0 0 0;
-fx-border-color: #fefefe; //OK
}
.tab-pane .tab:selected{
-fx-background-color: #ebebeb; // OK
-fx-focus-color: transparent;
-fx-faint-focus-color: transparent;
-fx-border-radius: 0 0 0 0;
-fx-border-width: 3 0 0 0;
-fx-border-color: #289de8; // OK
}
.tab-pane > .tab-header-area {
-fx-background-insets: 0.0;
-fx-padding: 5 5 5 5;
}
.tab-pane > .tab-header-area > .tab-header-background
{
-fx-background-color: #fefefe; // OK
}
.tab-pane > .tab-header-area > .headers-region > .tab {
-fx-padding: 10;
}
// -=========================== TABLE ======================-
.table-view {
-fx-background-color: #fefefe;
-fx-background-image: url(app_logo.png);
-fx-background-position: center;
-fx-background-repeat: no-repeat;
-fx-border-color: #06b9bb;
-fx-border-radius: 3;
-fx-border-width: 2;
}
.table-view .arrow {
-fx-mark-color: #2c2c2c ;
}
.table-view .column-header {
-fx-background-color: transparent;
-fx-border-width: 0 1 2 0;
-fx-border-color: #b0b0b0;
}
.table-view .column-header-background .label{
-fx-background-color: transparent;
-fx-text-fill: #2c2c2c;
}
.table-view .column-header-background, .table-view .filler{
-fx-background-color: #fefefe;
}
.table-view .table-cell{
-fx-text-fill: #2c2c2c;
}
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
-fx-background-color: -fx-table-cell-border-color, #d3fffd;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #b0b0b0;
}
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
-fx-background-color: -fx-table-cell-border-color, #fefefe;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #b0b0b0;
}
// -========================== Context menu =====================-
.context-menu {
-fx-background-color: #fefefe;
-fx-text-fill: white;
-fx-cursor: hand;
}
.context-menu .menu-item .label {
-fx-text-fill: #2c2c2c;
}
.context-menu .menu-item:focused .label {
-fx-text-fill: #2c2c2c;
}
.regionUpload{
-fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
-fx-background-color: #a2e019;
-size: 24;
-fx-min-height: -size;
-fx-min-width: 20;
}
.regionStop{
-fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
-fx-background-color: #fb582c;
-size: 24;
-fx-min-height: -size;
-fx-min-width: -size;
}
.regionLamp {
-fx-shape: "M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z";
-fx-background-color: #2c2c2c;
-size: 17.5;
-fx-min-height: -size;
-fx-min-width: 12.5;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB