v0.2-development intermediate results. Almost ready.

This commit is contained in:
Dmitry Isaenko 2019-02-17 18:38:07 +03:00
parent ad23eb0c82
commit c4d0959cf3
14 changed files with 405 additions and 293 deletions

View file

@ -58,6 +58,10 @@ 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 by [Konstanin Kelemen](https://github.com/konstantin-kelemen). Appreciate assistance of [Vitaliy Natarov](https://github.com/SebastianUA).
- [x] Windows support - [x] Windows support

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.2-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

@ -1,12 +1,12 @@
package nsusbloader.Controllers; package nsusbloader.Controllers;
import nsusbloader.NSLDataTypes.FileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File; import java.io.File;
public class NSLRowModel { public class NSLRowModel {
private String status; // 0 = unknown, 1 = uploaded, 2 = bad file private String status;
private File nspFile; private File nspFile;
private String nspFileName; private String nspFileName;
private String nspFileSize; private String nspFileSize;
@ -35,24 +35,20 @@ public class NSLRowModel {
public void setMarkForUpload(boolean value){ public void setMarkForUpload(boolean value){
markForUpload = value; markForUpload = value;
} }
public File getNspFile(){ return nspFile; }
public void setStatus(FileStatus status){ // TODO: Localization public void setStatus(EFileStatus status){ // TODO: Localization
switch (status){ switch (status){
case FAILED:
this.status = "Upload failed";
break;
case UPLOADED: case UPLOADED:
this.status = "Uploaded"; this.status = "Uploaded";
markForUpload = false; markForUpload = false;
break; break;
case INCORRECT: case FAILED:
this.status = "Upload failed";
break;
case INCORRECT_FILE_FAILED:
this.status = "File incorrect"; this.status = "File incorrect";
markForUpload = false; markForUpload = false;
break; break;
} }
}
public File getNspFile(){
return nspFile;
} }
} }

View file

@ -14,7 +14,7 @@ import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback; import javafx.util.Callback;
import nsusbloader.NSLDataTypes.FileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
@ -91,7 +91,8 @@ public class NSTableViewController implements Initializable {
for (NSLRowModel model: rowsObsLst){ for (NSLRowModel model: rowsObsLst){
if (model != modelChecked) if (model != modelChecked)
model.setMarkForUpload(false); model.setMarkForUpload(false);
}table.refresh(); }
table.refresh();
} }
} }
/** /**
@ -136,9 +137,9 @@ public class NSTableViewController implements Initializable {
} }
} }
/** /**
* Update files in case something is wrong. Requested from UsbCommunications _OR_ PFS * Update files in case something is wrong. Requested from UsbCommunications
* */ * */
public void setFileStatus(String fileName, FileStatus status){ public void setFileStatus(String fileName, EFileStatus status){
for (NSLRowModel model: rowsObsLst){ for (NSLRowModel model: rowsObsLst){
if (model.getNspFileName().equals(fileName)){ if (model.getNspFileName().equals(fileName)){
model.setStatus(status); model.setStatus(status);

View file

@ -1,7 +1,9 @@
package nsusbloader; package nsusbloader;
import java.util.concurrent.atomic.AtomicBoolean;
class MediatorControl { class MediatorControl {
private boolean isTransferActive = false; private AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure
private NSLMainController applicationController; private NSLMainController applicationController;
static MediatorControl getInstance(){ static MediatorControl getInstance(){
@ -11,15 +13,14 @@ class MediatorControl {
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){ void setController(NSLMainController controller){
this.applicationController = controller; this.applicationController = controller;
} }
NSLMainController getContoller(){ return this.applicationController; }
synchronized void setTransferActive(boolean state) { synchronized void setTransferActive(boolean state) {
isTransferActive = state; isTransferActive.set(state);
applicationController.notifyTransmissionStarted(state); applicationController.notifyTransmissionStarted(state);
} }
synchronized boolean getTransferActive() { 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);
} }
@ -43,6 +52,10 @@ public class MessagesConsumer extends AnimationTimer {
if (isInterrupted) { if (isInterrupted) {
MediatorControl.getInstance().setTransferActive(false); MediatorControl.getInstance().setTransferActive(false);
progressBar.setProgress(0.0); progressBar.setProgress(0.0);
if (statusMap.size() > 0) // It's safe 'cuz it's could't be interrupted while HashMap populating
for (String key : statusMap.keySet())
tableViewController.setFileStatus(key, statusMap.get(key));
this.stop(); this.stop();
} }
//TODO //TODO

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: NS-USBloader
@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;

View file

@ -12,7 +12,6 @@ import nsusbloader.Controllers.NSTableViewController;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@ -21,14 +20,14 @@ public class NSLMainController implements Initializable {
private ResourceBundle resourceBundle; private ResourceBundle resourceBundle;
@FXML @FXML
private TextArea logArea; public TextArea logArea; // Accessible from Mediator
@FXML @FXML
private Button selectNspBtn; private Button selectNspBtn;
@FXML @FXML
private Button uploadStopBtn; private Button uploadStopBtn;
private Region btnUpStopImage; private Region btnUpStopImage;
@FXML @FXML
private ProgressBar progressBar; public ProgressBar progressBar; // Accessible from Mediator
@FXML @FXML
private ChoiceBox<String> choiceProtocol; private ChoiceBox<String> choiceProtocol;
@FXML @FXML
@ -38,7 +37,7 @@ public class NSLMainController implements Initializable {
private Pane specialPane; private Pane specialPane;
@FXML @FXML
private NSTableViewController tableFilesListController; public NSTableViewController tableFilesListController; // Accessible from Mediator
private Thread usbThread; private Thread usbThread;
@ -54,7 +53,7 @@ public class NSLMainController implements Initializable {
logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n"); logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
MediatorControl.getInstance().registerController(this); MediatorControl.getInstance().setController(this);
specialPane.getStyleClass().add("special-pane-as-border"); // UI hacks specialPane.getStyleClass().add("special-pane-as-border"); // UI hacks
@ -62,6 +61,8 @@ public class NSLMainController implements Initializable {
selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); }); selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
uploadStopBtn.setOnAction(e->{ uploadBtnAction(); }); uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
selectNspBtn.getStyleClass().add("buttonSelect");
this.btnUpStopImage = new Region(); this.btnUpStopImage = new Region();
btnUpStopImage.getStyleClass().add("regionUpload"); btnUpStopImage.getStyleClass().add("regionUpload");
//uploadStopBtn.getStyleClass().remove("button"); //uploadStopBtn.getStyleClass().remove("button");
@ -80,7 +81,6 @@ public class NSLMainController implements Initializable {
btnSwitchImage.getStyleClass().add("regionLamp"); btnSwitchImage.getStyleClass().add("regionLamp");
switchThemeBtn.setGraphic(btnSwitchImage); switchThemeBtn.setGraphic(btnSwitchImage);
this.switchThemeBtn.setOnAction(e->switchTheme()); this.switchThemeBtn.setOnAction(e->switchTheme());
} }
/** /**
* Changes UI theme on the go * Changes UI theme on the go
@ -139,7 +139,7 @@ public class NSLMainController implements Initializable {
for (File item: nspToUpload) for (File item: nspToUpload)
logArea.appendText(" "+item.getAbsolutePath()+"\n"); logArea.appendText(" "+item.getAbsolutePath()+"\n");
} }
UsbCommunications usbCommunications = new UsbCommunications(logArea, progressBar, nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem()); UsbCommunications usbCommunications = new UsbCommunications(nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem());
usbThread = new Thread(usbCommunications); usbThread = new Thread(usbCommunications);
usbThread.start(); usbThread.start();
} }

View file

@ -1,6 +1,6 @@
package nsusbloader.PFS; package nsusbloader.PFS;
import nsusbloader.NSLDataTypes.MsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.ServiceWindow; import nsusbloader.ServiceWindow;
import java.io.*; import java.io.*;
@ -10,8 +10,6 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import static nsusbloader.RainbowHexDump.hexDumpUTF8;
/** /**
* Used in GoldLeaf USB protocol * Used in GoldLeaf USB protocol
* */ * */
@ -34,7 +32,7 @@ public class PFSProvider {
nspFileName = nspFile.getName(); nspFileName = nspFile.getName();
} }
catch (FileNotFoundException fnfe){ catch (FileNotFoundException fnfe){
printLog("File not founnd: \n "+fnfe.getMessage(), MsgType.FAIL); printLog("PFS File not founnd: \n "+fnfe.getMessage(), EMsgType.FAIL);
nspFileName = null; nspFileName = null;
} }
if (Locale.getDefault().getISO3Language().equals("rus")) if (Locale.getDefault().getISO3Language().equals("rus"))
@ -50,22 +48,22 @@ public class PFSProvider {
int filesCount; int filesCount;
int header; int header;
printLog("Start NSP file analyze for ["+nspFileName+"]", MsgType.INFO); printLog("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO);
try { try {
byte[] fileStartingBytes = new byte[12]; byte[] fileStartingBytes = new byte[12];
// Read PFS0, files count, header, padding (4 zero bytes) // Read PFS0, files count, header, padding (4 zero bytes)
if (randAccessFile.read(fileStartingBytes) == 12) if (randAccessFile.read(fileStartingBytes) == 12)
printLog("Read file starting bytes", MsgType.PASS); printLog("PFS Read file starting bytes.", EMsgType.PASS);
else { else {
printLog("Read file starting bytes", MsgType.FAIL); printLog("PFS Read file starting bytes.", EMsgType.FAIL);
randAccessFile.close(); randAccessFile.close();
return false; return false;
} }
// Check PFS0 // Check PFS0
if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4))) if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4)))
printLog("Read PFS0", MsgType.PASS); printLog("PFS Read 'PFS0'.", EMsgType.PASS);
else { else {
printLog("Read PFS0", MsgType.WARNING); printLog("PFS Read 'PFS0'.", EMsgType.WARNING);
if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) { if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) {
randAccessFile.close(); randAccessFile.close();
return false; return false;
@ -74,19 +72,19 @@ public class PFSProvider {
// Get files count // Get files count
filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt(); filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (filesCount > 0 ) { if (filesCount > 0 ) {
printLog("Read files count [" + filesCount + "]", MsgType.PASS); printLog("PFS Read files count [" + filesCount + "]", EMsgType.PASS);
} }
else { else {
printLog("Read files count", MsgType.FAIL); printLog("PFS Read files count", EMsgType.FAIL);
randAccessFile.close(); randAccessFile.close();
return false; return false;
} }
// Get header // Get header
header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt(); header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (header > 0 ) if (header > 0 )
printLog("Read header ["+header+"]", MsgType.PASS); printLog("PFS Read header ["+header+"]", EMsgType.PASS);
else { else {
printLog("Read header ", MsgType.FAIL); printLog("PFS Read header ", EMsgType.FAIL);
randAccessFile.close(); randAccessFile.close();
return false; return false;
} }
@ -105,10 +103,10 @@ public class PFSProvider {
for (int i=0; i<filesCount; i++){ for (int i=0; i<filesCount; i++){
if (randAccessFile.read(ncaInfoArr) == 24) { if (randAccessFile.read(ncaInfoArr) == 24) {
printLog("Read NCA inside NSP: " + i, MsgType.PASS); printLog("PFS Read NCA inside NSP: " + i, EMsgType.PASS);
} }
else { else {
printLog("Read NCA inside NSP: "+i, MsgType.FAIL); printLog("PFS Read NCA inside NSP: "+i, EMsgType.FAIL);
randAccessFile.close(); randAccessFile.close();
return false; return false;
} }
@ -117,23 +115,10 @@ public class PFSProvider {
nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).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. nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
if (offset == 0) // TODO: add consitancy of class checker or reuse with ternary operator printLog(" Padding check", offset == 0?EMsgType.PASS:EMsgType.WARNING);
printLog(" Padding check", MsgType.PASS); printLog(" NCA offset check "+nca_offset, nca_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
else printLog(" NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING);
printLog(" Padding check", MsgType.WARNING); printLog(" NCA name offset check "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
if (nca_offset >= 0)
printLog(" NCA offset check "+nca_offset, MsgType.PASS);
else
printLog(" NCA offset check "+nca_offset, MsgType.WARNING);
if (nca_size >= 0)
printLog(" NCA size check: "+nca_size, MsgType.PASS);
else
printLog(" NCA size check "+nca_size, MsgType.WARNING);
if (nca_name_offset >= 0)
printLog(" NCA name offset check "+nca_name_offset, MsgType.PASS);
else
printLog(" NCA name offset check "+nca_name_offset, MsgType.WARNING);
NCAFile ncaFile = new NCAFile(); NCAFile ncaFile = new NCAFile();
ncaFile.setNcaOffset(nca_offset); ncaFile.setNcaOffset(nca_offset);
@ -145,15 +130,15 @@ public class PFSProvider {
// Final offset // Final offset
byte[] bufForInt = new byte[4]; byte[] bufForInt = new byte[4];
if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4]))) if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4])))
printLog("Final padding check", MsgType.PASS); printLog("PFS Final padding check", EMsgType.PASS);
else else
printLog("Final padding check", MsgType.WARNING); printLog("PFS Final padding check", EMsgType.WARNING);
// Calculate position including header for body size offset // Calculate position including header for body size offset
bodySize = randAccessFile.getFilePointer()+header; bodySize = randAccessFile.getFilePointer()+header;
//********************************************************************************************* //*********************************************************************************************
// Collect file names from NCAs // Collect file names from NCAs
printLog("Collecting file names", MsgType.INFO); printLog("PFS Collecting file names", EMsgType.INFO);
List<Byte> ncaFN; // Temporary List<Byte> ncaFN; // Temporary
byte[] b = new byte[1]; // Temporary byte[] b = new byte[1]; // Temporary
for (int i=0; i<filesCount; i++){ for (int i=0; i<filesCount; i++){
@ -176,9 +161,10 @@ public class PFSProvider {
randAccessFile.close(); randAccessFile.close();
} }
catch (IOException ioe){ catch (IOException ioe){
ioe.printStackTrace(); //TODO: INFORM printLog("PFS Failed NSP file analyze for ["+nspFileName+"]\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace();
} }
printLog("Finish NSP file analyze for ["+nspFileName+"]", MsgType.PASS); printLog("PFS Finish NSP file analyze for ["+nspFileName+"]", EMsgType.PASS);
return true; return true;
} }
@ -234,7 +220,7 @@ public class PFSProvider {
/** /**
* 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:
@ -249,12 +235,9 @@ public class PFSProvider {
case WARNING: case WARNING:
msgQueue.put("[ WARN ] "+message+"\n"); msgQueue.put("[ WARN ] "+message+"\n");
break; break;
default:
msgQueue.put(message);
} }
}catch (InterruptedException ie){ }catch (InterruptedException ie){
ie.printStackTrace(); //TODO: INFORM ie.printStackTrace(); //TODO: ???
} }
} }
} }

View file

@ -1,9 +1,8 @@
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.NSLDataTypes.MsgType;
import nsusbloader.PFS.PFSProvider; import nsusbloader.PFS.PFSProvider;
import org.usb4java.*; import org.usb4java.*;
@ -13,20 +12,19 @@ import java.nio.ByteOrder;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import static nsusbloader.RainbowHexDump.hexDumpUTF8;
class UsbCommunications extends Task<Void> { 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 HashMap<String, EFileStatus> statusMap; // BlockingQueue for literally one object. TODO: read more books ; replace to hashMap
private MessagesConsumer msgConsumer; private MessagesConsumer msgConsumer;
private HashMap<String, File> nspMap; private HashMap<String, File> nspMap;
@ -46,14 +44,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, String protocol){ UsbCommunications(List<File> nspList, String protocol){
this.protocol = 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
@ -61,28 +60,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;
@ -91,14 +90,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;
} }
} }
@ -141,10 +140,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;
} }
@ -154,25 +153,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
@ -181,89 +180,89 @@ 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")) { if (protocol.equals("TinFoil")) {
@ -273,17 +272,31 @@ class UsbCommunications extends Task<Void> {
} }
close(); close();
printLog("\tEnd chain", MsgType.INFO); printLog("\tEnd chain", EMsgType.INFO);
return null; return null;
} }
/**
* Report transfer status
* */
private void reportTransferStatus(EFileStatus status){
for (String fileName: nspMap.keySet())
statusMap.put(fileName, status);
}
/** /**
* Tinfoil processing * Tinfoil processing
* */ * */
private class TinFoil{ private class TinFoil{
TinFoil(){ TinFoil(){
if (!sendListOfNSP())
if (!sendListOfNSP()) {
reportTransferStatus(EFileStatus.FAILED);
return; return;
proceedCommands(); }
if (proceedCommands()) // REPORT SUCCESS
reportTransferStatus(EFileStatus.UPLOADED);
else // REPORT FAILURE
reportTransferStatus(EFileStatus.FAILED);
} }
/** /**
* Send what NSP will be transferred * Send what NSP will be transferred
@ -292,16 +305,17 @@ class UsbCommunications extends Task<Void> {
// 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 false;
} }
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
@ -310,33 +324,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 false;
} else }
printLog("Send list of files: send length.", MsgType.PASS); printLog(" [send list length]", EMsgType.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 false;
} }
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 false;
} }
else printLog(" [send list itself]", EMsgType.PASS);
printLog("Send list of files: send list itself.", MsgType.PASS);
return true; return true;
} }
/** /**
* 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 +360,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 +371,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 +415,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 +442,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 {
@ -460,26 +469,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(); 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;
} }
@ -491,32 +503,32 @@ 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;
} }
} }
/** /**
* Tinfoil processing * GoldLeaf processing
* */ * */
private class GoldLeaf{ private class GoldLeaf{
// CMD G L U C ID 0 0 0 // CMD G L U C ID 0 0 0
@ -531,112 +543,133 @@ class UsbCommunications extends Task<Void> {
private final byte[] CMD_Finish = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x07, 0x00, 0x00, 0x00}; private final byte[] CMD_Finish = new byte[]{0x47, 0x4c, 0x55, 0x43, 0x07, 0x00, 0x00, 0x00};
GoldLeaf(){ GoldLeaf(){
List<PFSProvider> pfsList = new ArrayList<>(); printLog("===========================================================================", EMsgType.INFO);
PFSProvider pfsElement = new PFSProvider(nspMap.get(nspMap.keySet().toArray()[0]), msgQueue);
StringBuilder allValidFiles = new StringBuilder(); if (!pfsElement.init()) {
StringBuilder nonValidFiles = new StringBuilder(); printLog("GL File provided have incorrect structure and won't be uploaded", EMsgType.FAIL);
// Prepare data reportTransferStatus(EFileStatus.INCORRECT_FILE_FAILED);
for (File nspFile : nspMap.values()) {
PFSProvider pfsp = new PFSProvider(nspFile, msgQueue);
if (pfsp.init()) {
pfsList.add(pfsp);
allValidFiles.append(nspFile.getName());
allValidFiles.append("\n");
}
else {
nonValidFiles.append(nspFile.getName());
nonValidFiles.append("\n");
}
}
if (pfsList.size() == 0){
printLog("All files provided have incorrect structure and won't be uploaded", MsgType.FAIL);
return; return;
} }
printLog("===========================================================================", MsgType.INFO); printLog("GL File structure validated and it will be uploaded", EMsgType.PASS);
printLog("Verified files prepared for upload: \n "+allValidFiles, MsgType.PASS);
if (!nonValidFiles.toString().isEmpty())
printLog("Files with incorrect structure that won't be uploaded: \n"+nonValidFiles, MsgType.INFO);
//--------------------------------------------------------------------------------------------------------------
if (initGoldLeafProtocol(pfsElement))
reportTransferStatus(EFileStatus.UPLOADED);
else
reportTransferStatus(EFileStatus.FAILED);
}
private boolean initGoldLeafProtocol(PFSProvider pfsElement){
// Go parse commands // Go parse commands
byte[] readByte; byte[] readByte;
for(PFSProvider pfsElement: pfsList) {
// Go connect to GoldLeaf // Go connect to GoldLeaf
if (writeToUsb(CMD_ConnectionRequest)) if (writeToUsb(CMD_ConnectionRequest))
printLog("Initiating GoldLeaf connection" + nonValidFiles, MsgType.PASS); printLog("GL Initiating GoldLeaf connection", EMsgType.PASS);
else { else {
printLog("Initiating GoldLeaf connection" + nonValidFiles, MsgType.FAIL); printLog("GL Initiating GoldLeaf connection", EMsgType.FAIL);
return; return false;
} }
int a = 0; // TODO:DEBUG
while (true) { while (true) {
System.out.println("In loop. Iter: "+a); // TODO:DEBUG
readByte = readFromUsb(); readByte = readFromUsb();
if (readByte == null) if (readByte == null)
return; return false;
hexDumpUTF8(readByte); // TODO:DEBUG
if (Arrays.equals(readByte, CMD_ConnectionResponse)) { if (Arrays.equals(readByte, CMD_ConnectionResponse)) {
if (!handleConnectionResponse(pfsElement)) if (!handleConnectionResponse(pfsElement))
return; return false;
else else
continue; continue;
} }
if (Arrays.equals(readByte, CMD_Start)) { if (Arrays.equals(readByte, CMD_Start)) {
if (!handleStart(pfsElement)) if (!handleStart(pfsElement))
return; return false;
else else
continue; continue;
} }
if (Arrays.equals(readByte, CMD_NSPContent)) { if (Arrays.equals(readByte, CMD_NSPContent)) {
if (!handleNSPContent(pfsElement, true)) if (!handleNSPContent(pfsElement, true))
return; return false;
else else
continue; continue;
} }
if (Arrays.equals(readByte, CMD_NSPTicket)) { if (Arrays.equals(readByte, CMD_NSPTicket)) {
if (!handleNSPContent(pfsElement, false)) if (!handleNSPContent(pfsElement, false))
return; return false;
else else
continue; continue;
} }
if (Arrays.equals(readByte, CMD_Finish)) { if (Arrays.equals(readByte, CMD_Finish)) {
printLog("Closing GoldLeaf connection: Transfer successful", MsgType.PASS); printLog("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS);
break; // TODO: GO TO NEXT NSP break;
}
} }
} }
return true;
} }
/** /**
* ConnectionResponse command handler * ConnectionResponse command handler
* */ * */
private boolean handleConnectionResponse(PFSProvider pfsElement){ private boolean handleConnectionResponse(PFSProvider pfsElement){
if (!writeToUsb(CMD_NSPName)) printLog("GL 'ConnectionResonse' command:", EMsgType.INFO);
if (!writeToUsb(CMD_NSPName)) {
printLog(" [1/3]", EMsgType.FAIL);
return false; return false;
if (!writeToUsb(pfsElement.getBytesNspFileNameLength())) }
printLog(" [1/3]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesNspFileNameLength())) {
printLog(" [2/3]", EMsgType.FAIL);
return false; return false;
if (!writeToUsb(pfsElement.getBytesNspFileName())) }
printLog(" [2/3]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesNspFileName())) {
printLog(" [3/3]", EMsgType.FAIL);
return false; return false;
}
printLog(" [3/3]", EMsgType.PASS);
return true; return true;
} }
/** /**
* Start command handler * Start command handler
* */ * */
private boolean handleStart(PFSProvider pfsElement){ private boolean handleStart(PFSProvider pfsElement){
if (!writeToUsb(CMD_NSPData)) printLog("GL Handle 'Start' command:", EMsgType.INFO);
if (!writeToUsb(CMD_NSPData)) {
printLog(" [Send command]", EMsgType.FAIL);
return false; return false;
if (!writeToUsb(pfsElement.getBytesCountOfNca())) }
printLog(" [Send command]", EMsgType.PASS);
if (!writeToUsb(pfsElement.getBytesCountOfNca())) {
printLog(" [Send length]", EMsgType.FAIL);
return false; return false;
for (int i = 0; i < pfsElement.getIntCountOfNca(); i++){ }
if (!writeToUsb(pfsElement.getNca(i).getNcaFileNameLength())) 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; return false;
if (!writeToUsb(pfsElement.getNca(i).getNcaFileName())) }
printLog(" [1/4] File #"+i, EMsgType.PASS);
if (!writeToUsb(pfsElement.getNca(i).getNcaFileName())) {
printLog(" [2/4] File #"+i, EMsgType.FAIL);
return false; return false;
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.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; return false;
if (!writeToUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) // size }
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; return false;
} }
printLog(" [4/4] File #"+i, EMsgType.PASS);
}
return true; return true;
} }
/** /**
@ -648,13 +681,18 @@ class UsbCommunications extends Task<Void> {
int requestedNcaID; int requestedNcaID;
boolean isProgessBarInitiated = false; boolean isProgessBarInitiated = false;
if (isItRawRequest) { if (isItRawRequest) {
printLog("GL Handle 'Content' command", EMsgType.INFO);
byte[] readByte = readFromUsb(); byte[] readByte = readFromUsb();
if (readByte == null || readByte.length != 4) if (readByte == null || readByte.length != 4) {
printLog(" [Read requested ID]", EMsgType.FAIL);
return false; return false;
}
requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt(); requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt();
printLog(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS);
} }
else { else {
requestedNcaID = pfsElement.getNcaTicketID(); requestedNcaID = pfsElement.getNcaTicketID();
printLog("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO);
} }
long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize(); long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize();
@ -683,7 +721,7 @@ class UsbCommunications extends Task<Void> {
if (!writeToUsb(readBuf)) if (!writeToUsb(readBuf))
return false; return false;
/***********************************/ //-----------------------------------------/
if (isProgessBarInitiated){ if (isProgessBarInitiated){
try { try {
if (readFrom+readPice == realNcaSize){ if (readFrom+readPice == realNcaSize){
@ -700,12 +738,13 @@ class UsbCommunications extends Task<Void> {
if ((readPice == 8388608) && (readFrom == 0)) if ((readPice == 8388608) && (readFrom == 0))
isProgessBarInitiated = true; isProgessBarInitiated = true;
} }
/***********************************/ //-----------------------------------------/
readFrom += readPice; readFrom += readPice;
} }
bufferedInStream.close(); bufferedInStream.close();
} }
catch (IOException ioe){ catch (IOException ioe){
printLog(" Failed to read NCA ID "+requestedNcaID+". IO Exception:\n "+ioe.getMessage(), EMsgType.FAIL);
ioe.printStackTrace(); ioe.printStackTrace();
return false; return false;
} }
@ -723,17 +762,17 @@ class UsbCommunications extends Task<Void> {
int result = LibUsb.releaseInterface(handlerNS, DEFAULT_INTERFACE); int result = LibUsb.releaseInterface(handlerNS, DEFAULT_INTERFACE);
if (result != LibUsb.SUCCESS) if (result != LibUsb.SUCCESS)
printLog("Release interface\n Returned: "+result+" (sometimes it's not an issue)", MsgType.WARNING); printLog("Release interface\n Returned: "+result+" (sometimes it's not an issue)", EMsgType.WARNING);
else else
printLog("Release interface", MsgType.PASS); printLog("Release interface", EMsgType.PASS);
LibUsb.close(handlerNS); LibUsb.close(handlerNS);
printLog("Requested handler close", MsgType.INFO); printLog("Requested handler close", EMsgType.INFO);
} }
// close context in the end // close context in the end
if (contextNS != null) { if (contextNS != null) {
LibUsb.exit(contextNS); LibUsb.exit(contextNS);
printLog("Requested context close", MsgType.INFO); printLog("Requested context close", EMsgType.INFO);
} }
msgConsumer.interrupt(); msgConsumer.interrupt();
} }
@ -752,25 +791,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 {
@ -794,24 +833,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; break;
case LibUsb.ERROR_IO: case LibUsb.ERROR_IO:
printLog("Data transfer (read) issue\n Returned: ERROR_IO", MsgType.FAIL); 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();
@ -827,7 +866,7 @@ class UsbCommunications extends Task<Void> {
/** /**
* 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:
@ -848,6 +887,5 @@ class UsbCommunications extends Task<Void> {
}catch (InterruptedException ie){ }catch (InterruptedException ie){
ie.printStackTrace(); ie.printStackTrace();
} }
} }
} }

View file

@ -5,7 +5,7 @@
-fx-background: #2d2d2d; -fx-background: #2d2d2d;
} }
.button, .buttonUp, .buttonStop{ .button, .buttonUp, .buttonStop, .buttonSelect{
-fx-background-color: #4f4f4f; -fx-background-color: #4f4f4f;
-fx-border-color: #4f4f4f; -fx-border-color: #4f4f4f;
-fx-border-radius: 3; -fx-border-radius: 3;
@ -13,19 +13,26 @@
-fx-text-fill: #f7fafa; -fx-text-fill: #f7fafa;
-fx-effect: none; -fx-effect: none;
} }
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .choice-box:focused:hover{ .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-background-color: #4f4f4f;
-fx-border-color: #a4ffff; -fx-border-color: #a4ffff;
-fx-border-radius: 3; -fx-border-radius: 3;
-fx-border-width: 2; -fx-border-width: 2;
-fx-text-fill: #f7fafa; -fx-text-fill: #f7fafa;
} }
.button:focused, .buttonStop:focused, .buttonUp:focused, .choice-box:focused{ .button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
-fx-background-color: #6a6a6a; -fx-background-color: #6a6a6a;
-fx-border-color: #6a6a6a; -fx-border-color: #6a6a6a;
} }
.button:pressed, .button:pressed:hover{ .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-background-color: #4f4f4f;
-fx-border-color: #289de8; -fx-border-color: #289de8;
-fx-border-radius: 3; -fx-border-radius: 3;
@ -196,7 +203,18 @@
-fx-padding: 0.0em; /* 0 */ -fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #f7fafa; -fx-table-cell-border-color: #f7fafa;
} }
// -========================== 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;
}

View file

@ -5,7 +5,7 @@
-fx-background: #ebebeb; -fx-background: #ebebeb;
} }
.button, .buttonUp, .buttonStop{ .button, .buttonUp, .buttonStop, .buttonSelect{
-fx-background-color: #fefefe; -fx-background-color: #fefefe;
-fx-border-color: #fefefe; -fx-border-color: #fefefe;
-fx-border-radius: 3; -fx-border-radius: 3;
@ -14,19 +14,26 @@
-fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0); -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, .choice-box:focused:hover{ .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-background-color: #fefefe;
-fx-border-color: #00caca; -fx-border-color: #00caca;
-fx-border-radius: 3; -fx-border-radius: 3;
-fx-border-width: 2; -fx-border-width: 2;
-fx-text-fill: #2c2c2c; -fx-text-fill: #2c2c2c;
} }
.button:focused, .buttonStop:focused, .buttonUp:focused, .choice-box:focused{ .button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
-fx-background-color: #cccccc; -fx-background-color: #cccccc;
-fx-border-color: #cccccc; -fx-border-color: #cccccc;
} }
.button:pressed, .button:pressed:hover{ .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-background-color: #fefefe;
-fx-border-color: #289de8; -fx-border-color: #289de8;
-fx-border-radius: 3; -fx-border-radius: 3;
@ -197,7 +204,18 @@
-fx-padding: 0.0em; /* 0 */ -fx-padding: 0.0em; /* 0 */
-fx-table-cell-border-color: #2c2c2c; -fx-table-cell-border-color: #2c2c2c;
} }
// -========================== 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;
}