From 6f1700fbab958b40383cf69e630edc603dded737 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Wed, 25 Sep 2019 05:43:47 +0300 Subject: [PATCH] v0.8 Fix for interrupt process issue; Refactoring. --- pom.xml | 6 +- .../ModelControllers/LogPrinter.java | 7 +- .../nsusbloader/NET/NETCommunications.java | 13 +- src/main/java/nsusbloader/NSLMain.java | 2 +- src/main/java/nsusbloader/USB/GoldLeaf.java | 1115 ++++++++++++ .../java/nsusbloader/USB/ITransferModule.java | 7 + src/main/java/nsusbloader/USB/TinFoil.java | 321 ++++ .../nsusbloader/USB/UsbCommunications.java | 1516 +---------------- src/main/java/nsusbloader/USB/UsbConnect.java | 183 ++ 9 files changed, 1655 insertions(+), 1515 deletions(-) create mode 100644 src/main/java/nsusbloader/USB/GoldLeaf.java create mode 100644 src/main/java/nsusbloader/USB/ITransferModule.java create mode 100644 src/main/java/nsusbloader/USB/TinFoil.java create mode 100644 src/main/java/nsusbloader/USB/UsbConnect.java diff --git a/pom.xml b/pom.xml index 72f9d15..8f21b07 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ NS-USBloader ns-usbloader - 0.7-SNAPSHOT + 0.8-SNAPSHOT https://github.com/developersu/ns-usbloader/ @@ -218,11 +218,11 @@ 1.8 - 0.7.0.0 + 0.8.0.0 ${project.version} TinFoil and GoldLeaf installer for your NS GNU General Public License v3, 2019 ${organization.name}. Russia/LPR. - 0.7.0.0 + 0.8.0.0 ${project.version} ${organization.name} ${project.name} diff --git a/src/main/java/nsusbloader/ModelControllers/LogPrinter.java b/src/main/java/nsusbloader/ModelControllers/LogPrinter.java index 8a6edb7..4d0c34d 100644 --- a/src/main/java/nsusbloader/ModelControllers/LogPrinter.java +++ b/src/main/java/nsusbloader/ModelControllers/LogPrinter.java @@ -49,8 +49,11 @@ public class LogPrinter { /** * Update progress for progress bar * */ - public void updateProgress(Double value) throws InterruptedException{ - progressQueue.put(value); + public void updateProgress(Double value) { + try { + progressQueue.put(value); + } + catch (InterruptedException ignored){} // TODO: Do something with this } /** * When we're done - update status diff --git a/src/main/java/nsusbloader/NET/NETCommunications.java b/src/main/java/nsusbloader/NET/NETCommunications.java index 0a711c9..0a42575 100644 --- a/src/main/java/nsusbloader/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/NET/NETCommunications.java @@ -374,23 +374,14 @@ public class NETCommunications extends Task { // todo: thows IOException? } currSockOS.write(byteBuf); //-----------------------------------------/ - try { - logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0); - }catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } + logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0); //-----------------------------------------/ currentOffset += readPice; } currSockOS.flush(); // TODO: check if this really needed. bis.close(); //-----------------------------------------/ - try{ - logPrinter.updateProgress(1.0); - } - catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } + logPrinter.updateProgress(1.0); //-----------------------------------------/ } catch (IOException ioe){ diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 4eb4c00..2866ee2 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -12,7 +12,7 @@ import java.util.Locale; import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v0.7"; + public static final String appVersion = "v0.8"; @Override public void start(Stage primaryStage) throws Exception{ FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml")); diff --git a/src/main/java/nsusbloader/USB/GoldLeaf.java b/src/main/java/nsusbloader/USB/GoldLeaf.java new file mode 100644 index 0000000..1e1f46a --- /dev/null +++ b/src/main/java/nsusbloader/USB/GoldLeaf.java @@ -0,0 +1,1115 @@ +package nsusbloader.USB; + +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.stage.FileChooser; +import nsusbloader.MediatorControl; +import nsusbloader.ModelControllers.LogPrinter; +import nsusbloader.NSLDataTypes.EFileStatus; +import nsusbloader.NSLDataTypes.EMsgType; +import org.usb4java.DeviceHandle; +import org.usb4java.LibUsb; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +/** + * GoldLeaf processing + */ +class GoldLeaf implements ITransferModule { + private LogPrinter logPrinter; + private DeviceHandle handlerNS; + private LinkedHashMap nspMap; + private boolean nspFilterForGl; + private Task task; + + // CMD + private final byte[] CMD_GLCO_SUCCESS = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}; // used @ writeToUsb_GLCMD + private final byte[] CMD_GLCO_FAILURE = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x64, (byte) 0xcb, 0x00, 0x00}; // used @ writeToUsb_GLCMD + + // System.out.println((356 & 0x1FF) | ((1 + 100) & 0x1FFF) << 9); // 52068 // 0x00 0x00 0xCB 0x64 + private final byte[] GL_OBJ_TYPE_FILE = new byte[]{0x01, 0x00, 0x00, 0x00}; + private final byte[] GL_OBJ_TYPE_DIR = new byte[]{0x02, 0x00, 0x00, 0x00}; + + private String recentPath = null; + private String[] recentDirs = null; + private String[] recentFiles = null; + + private String[] nspMapKeySetIndexes; + + private String openReadFileNameAndPath; + private RandomAccessFile randAccessFile; + + private HashMap writeFilesMap; + + private boolean isWindows; + private String homePath; + // For using in CMD_SelectFile with SPEC:/ prefix + private File selectedFile; + + GoldLeaf(DeviceHandle handler, LinkedHashMap nspMap, Task task, LogPrinter logPrinter, boolean nspFilter){ + final byte CMD_GetDriveCount = 0x00; + final byte CMD_GetDriveInfo = 0x01; + final byte CMD_StatPath = 0x02; // proxy done [proxy: in case if folder contains ENG+RUS+UKR file names works incorrect] + final byte CMD_GetFileCount = 0x03; + final byte CMD_GetFile = 0x04; // proxy done + final byte CMD_GetDirectoryCount = 0x05; + final byte CMD_GetDirectory = 0x06; // proxy done + final byte CMD_ReadFile = 0x07; // no way to do poxy + final byte CMD_WriteFile = 0x08; // add predictable behavior + final byte CMD_Create = 0x09; + final byte CMD_Delete = 0x0a;//10 + final byte CMD_Rename = 0x0b;//11 + final byte CMD_GetSpecialPathCount = 0x0c;//12 // Special folders count; simplified usage @ NS-UL + final byte CMD_GetSpecialPath = 0x0d;//13 // Information about special folders; simplified usage @ NS-UL + final byte CMD_SelectFile = 0x0e;//14 + final byte CMD_Max = 0x0f;//15 // not used @ NS-UL & GT + + final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; + + this.handlerNS = handler; + this.nspMap = nspMap; + this.logPrinter = logPrinter; + this.nspFilterForGl = nspFilter; + this.task = task; + + logPrinter.print("============= GoldLeaf =============\n\tVIRT:/ equals files added into the application\n\tHOME:/ equals " + +System.getProperty("user.home"), EMsgType.INFO); + + // Let's collect file names to the array to simplify our life + writeFilesMap = new HashMap<>(); + int i = 0; + nspMapKeySetIndexes = new String[nspMap.size()]; + for (String fileName : nspMap.keySet()) + nspMapKeySetIndexes[i++] = fileName; + + isWindows = System.getProperty("os.name").contains("Windows"); + + homePath = System.getProperty("user.home")+File.separator; + + // Go parse commands + byte[] readByte; + int someLength1, + someLength2; + main_loop: + while (true) { // Till user interrupted process. + readByte = readGL(); + + if (readByte == null) // Issue @ readFromUsbGL method + return; + + //RainbowHexDump.hexDumpUTF16LE(readByte); // DEBUG + //System.out.println("CHOICE: "+readByte[4]); // DEBUG + + if (Arrays.equals(Arrays.copyOfRange(readByte, 0,4), CMD_GLCI)) { + switch (readByte[4]) { + case CMD_GetDriveCount: + if (getDriveCount()) + break main_loop; + break; + case CMD_GetDriveInfo: + if (getDriveInfo(arrToIntLE(readByte,8))) + break main_loop; + break; + case CMD_GetSpecialPathCount: + if (getSpecialPathCount()) + break main_loop; + break; + case CMD_GetSpecialPath: + if (getSpecialPath(arrToIntLE(readByte,8))) + break main_loop; + break; + case CMD_GetDirectoryCount: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), true)) + break main_loop; + break; + case CMD_GetFileCount: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), false)) + break main_loop; + break; + case CMD_GetDirectory: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (getDirectory(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), arrToIntLE(readByte, someLength1+12))) + break main_loop; + break; + case CMD_GetFile: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (getFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), arrToIntLE(readByte, someLength1+12))) + break main_loop; + break; + case CMD_StatPath: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (statPath(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE))) + break main_loop; + break; + case CMD_Rename: + someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 + someLength2 = arrToIntLE(readByte, 16+someLength1) * 2; // Since GL 0.7 + if (rename(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE), + new String(readByte, 16+someLength1+4, someLength2, StandardCharsets.UTF_16LE))) + break main_loop; + break; + case CMD_Delete: + someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 + if (delete(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE))) + break main_loop; + break; + case CMD_Create: + someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 + if (create(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE), readByte[8])) + break main_loop; + break; + case CMD_ReadFile: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (readFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), + arrToLongLE(readByte, 12+someLength1), + arrToLongLE(readByte, 12+someLength1+8))) + break main_loop; + break; + case CMD_WriteFile: + someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 + if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), + arrToLongLE(readByte, 12+someLength1))) + break main_loop; + break; + case CMD_SelectFile: + if (selectFile()) + break main_loop; + break; + default: + writeGL_FAIL("GL Unknown command: "+readByte[4]+" [it's a very bad sign]"); + } + } + } + // Close (and flush) all opened streams. + if (writeFilesMap.size() != 0){ + for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ + try{ + fBufOutStream.close(); + }catch (IOException ignored){} + } + } + closeOpenedReadFilesGl(); + } + + /** + * Close files opened for read/write + */ + private void closeOpenedReadFilesGl(){ + if (openReadFileNameAndPath != null){ // Perfect time to close our opened files + try{ + randAccessFile.close(); + } + catch (IOException ignored){} + openReadFileNameAndPath = null; + randAccessFile = null; + } + } + /** + * Handle GetDriveCount + * @return true if failed + * false if everything is ok + */ + private boolean getDriveCount(){ + // Let's declare 2 drives + byte[] drivesCnt = intToArrLE(2); //2 + // Write count of drives + if (writeGL_PASS(drivesCnt)) { + logPrinter.print("GL Handle 'ListDrives' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * Handle GetDriveInfo + * @return true if failed + * false if everything is ok + */ + private boolean getDriveInfo(int driveNo){ + if (driveNo < 0 || driveNo > 1){ + return writeGL_FAIL("GL Handle 'GetDriveInfo' command [no such drive]"); + } + + byte[] driveLabel, + driveLabelLen, + driveLetter, + driveLetterLen, + totalFreeSpace, + totalSize; + long totalSizeLong = 0; + + // 0 == VIRTUAL DRIVE + if (driveNo == 0){ + driveLabel = "Virtual".getBytes(StandardCharsets.UTF_16LE); + driveLabelLen = intToArrLE(driveLabel.length / 2); // since GL 0.7 + driveLetter = "VIRT".getBytes(StandardCharsets.UTF_16LE); // TODO: Consider moving to class field declaration + driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7 + totalFreeSpace = new byte[4]; + for (File nspFile : nspMap.values()){ + totalSizeLong += nspFile.length(); + } + totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! + } + else { //1 == User home dir + driveLabel = "Home".getBytes(StandardCharsets.UTF_16LE); + driveLabelLen = intToArrLE(driveLabel.length / 2);// since GL 0.7 + driveLetter = "HOME".getBytes(StandardCharsets.UTF_16LE); + driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7 + File userHomeDir = new File(System.getProperty("user.home")); + long totalFreeSpaceLong = userHomeDir.getFreeSpace(); + totalFreeSpace = Arrays.copyOfRange(longToArrLE(totalFreeSpaceLong), 0, 4); // Dirty hack; now for GL!; + totalSizeLong = userHomeDir.getTotalSpace(); + totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! + + //System.out.println("totalSize: "+totalSizeLong+"totalFreeSpace: "+totalFreeSpaceLong); + } + + List command = new LinkedList<>(); + command.add(driveLabelLen); + command.add(driveLabel); + command.add(driveLetterLen); + command.add(driveLetter); + command.add(totalFreeSpace); + command.add(totalSize); + + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); + return true; + } + + return false; + } + /** + * Handle SpecialPathCount + * @return true if failed + * false if everything is ok + * */ + private boolean getSpecialPathCount(){ + // Let's declare nothing =) + byte[] specialPathCnt = intToArrLE(0); + // Write count of special paths + if (writeGL_PASS(specialPathCnt)) { + logPrinter.print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * Handle SpecialPath + * @return true if failed + * false if everything is ok + * */ + private boolean getSpecialPath(int specialPathNo){ + return writeGL_FAIL("GL Handle 'SpecialPath' command [not supported]"); + } + /** + * Handle GetDirectoryCount & GetFileCount + * @return true if failed + * false if everything is ok + * */ + private boolean getDirectoryOrFileCount(String path, boolean isGetDirectoryCount) { + if (path.equals("VIRT:/")) { + if (isGetDirectoryCount){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + return true; + } + } + else { + if (writeGL_PASS(intToArrLE(nspMap.size()))) { + logPrinter.print("GL Handle 'GetFileCount' command Count = "+nspMap.size(), EMsgType.FAIL); + return true; + } + } + } + else if (path.startsWith("HOME:/")){ + // Let's make it normal path + path = updateHomePath(path); + // Open it + File pathDir = new File(path); + + // Make sure it's exists and it's path + if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) + return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [doesn't exist or not a folder]"); + // Save recent dir path + this.recentPath = path; + String[] filesOrDirs; + // Now collecting every folder or file inside + if (isGetDirectoryCount){ + filesOrDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (dir.isDirectory() && ! dir.isHidden()); + }); + } + else { + if (nspFilterForGl){ + filesOrDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && name.toLowerCase().endsWith(".nsp")); + }); + } + else { + filesOrDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && (! dir.isHidden())); + }); + } + } + // If somehow there are no folders, let's say 0; + if (filesOrDirs == null){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + return true; + } + return false; + } + // Sorting is mandatory TODO: NOTE: Proxy tail + Arrays.sort(filesOrDirs, String.CASE_INSENSITIVE_ORDER); + + if (isGetDirectoryCount) + this.recentDirs = filesOrDirs; + else + this.recentFiles = filesOrDirs; + // Otherwise, let's tell how may folders are in there + if (writeGL_PASS(intToArrLE(filesOrDirs.length))) { + logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + return true; + } + } + else if (path.startsWith("SPEC:/")){ + if (isGetDirectoryCount){ // If dir request then 0 dirs + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + return true; + } + } + else if (selectedFile != null){ // Else it's file request, if we have selected then we will report 1. + if (writeGL_PASS(intToArrLE(1))) { + logPrinter.print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); + return true; + } + } + else + return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] (file) - "+path); + } + else { // If requested drive is not VIRT and not HOME then reply error + return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] "+(isGetDirectoryCount?"(dir) - ":"(file) - ")+path); + } + return false; + } + /** + * Handle GetDirectory + * @return true if failed + * false if everything is ok + * */ + private boolean getDirectory(String dirName, int subDirNo){ + if (dirName.startsWith("HOME:/")) { + dirName = updateHomePath(dirName); + + List command = new LinkedList<>(); + + if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){ + byte[] dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_16LE); + + command.add(intToArrLE(dirNameBytes.length / 2)); // Since GL 0.7 + command.add(dirNameBytes); + } + else { + File pathDir = new File(dirName); + // Make sure it's exists and it's path + if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) + return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]"); + this.recentPath = dirName; + // Now collecting every folder or file inside + this.recentDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (dir.isDirectory() && ! dir.isHidden()); // TODO: FIX FOR WIN ? + }); + // Check that we still don't have any fuckups + if (this.recentDirs != null && this.recentDirs.length > subDirNo){ + Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER); + byte[] dirBytesName = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(dirBytesName.length / 2)); // Since GL 0.7 + command.add(dirBytesName); + } + else + return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]"); + } + //if (proxyForGL) // TODO: NOTE: PROXY TAILS + // return proxyGetDirFile(true); + + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); + return true; + } + return false; + } + // VIRT:// and any other + return writeGL_FAIL("GL Handle 'GetDirectory' command for virtual drive [no folders support]"); + } + /** + * Handle GetFile + * @return true if failed + * false if everything is ok + * */ + private boolean getFile(String dirName, int subDirNo){ + List command = new LinkedList<>(); + + if (dirName.startsWith("HOME:/")) { + dirName = updateHomePath(dirName); + + if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ + byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_16LE); + + command.add(intToArrLE(fileNameBytes.length / 2)); //Since GL 0.7 + command.add(fileNameBytes); + } + else { + File pathDir = new File(dirName); + // Make sure it's exists and it's path + if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) + writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); + this.recentPath = dirName; + // Now collecting every folder or file inside + if (nspFilterForGl){ + this.recentFiles = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && name.toLowerCase().endsWith(".nsp")); // TODO: FIX FOR WIN ? MOVE TO PROD + }); + } + else { + this.recentFiles = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && (! dir.isHidden())); // TODO: FIX FOR WIN + }); + } + // Check that we still don't have any fuckups + if (this.recentFiles != null && this.recentFiles.length > subDirNo){ + Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER); // TODO: NOTE: array sorting is an overhead for using poxy loops + byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(fileNameBytes.length / 2)); //Since GL 0.7 + command.add(fileNameBytes); + } + else + return writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); + } + //if (proxyForGL) // TODO: NOTE: PROXY TAILS + // return proxyGetDirFile(false); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + else if (dirName.equals("VIRT:/")){ + if (nspMap.size() != 0){ // therefore nspMapKeySetIndexes also != 0 + byte[] fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 + command.add(fileNameBytes); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + else if (dirName.equals("SPEC:/")){ + if (selectedFile != null){ + byte[] fileNameBytes = selectedFile.getName().getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 + command.add(fileNameBytes); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + // any other cases + return writeGL_FAIL("GL Handle 'GetFile' command for virtual drive [no folders support?]"); + } + /** + * Handle StatPath + * @return true if failed + * false if everything is ok + * */ + private boolean statPath(String filePath){ + List command = new LinkedList<>(); + + if (filePath.startsWith("HOME:/")){ + filePath = updateHomePath(filePath); + //if (proxyForGL) // TODO:NOTE PROXY TAILS + // return proxyStatPath(filePath); // dirty name + + File fileDirElement = new File(filePath); + if (fileDirElement.exists()){ + if (fileDirElement.isDirectory()) + command.add(GL_OBJ_TYPE_DIR); + else { + command.add(GL_OBJ_TYPE_FILE); + command.add(longToArrLE(fileDirElement.length())); + } + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + else if (filePath.startsWith("VIRT:/")) { + filePath = filePath.replaceFirst("VIRT:/", ""); + if (nspMap.containsKey(filePath)){ + command.add(GL_OBJ_TYPE_FILE); // THIS IS INT + command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG! + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + else if (filePath.startsWith("SPEC:/")){ + System.out.println(filePath); + filePath = filePath.replaceFirst("SPEC:/",""); + if (selectedFile.getName().equals(filePath)){ + command.add(GL_OBJ_TYPE_FILE); + command.add(longToArrLE(selectedFile.length())); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + return writeGL_FAIL("GL Handle 'StatPath' command [no such folder] - "+filePath); + } + /** + * Handle 'Rename' that is actually 'mv' + * @return true if failed + * false if everything is ok + * */ + private boolean rename(String fileName, String newFileName){ + if (fileName.startsWith("HOME:/")){ + // This shit takes too much time to explain, but such behaviour won't let GL to fail + this.recentPath = null; + this.recentFiles = null; + this.recentDirs = null; + fileName = updateHomePath(fileName); + newFileName = updateHomePath(newFileName); + + File currentFile = new File(fileName); + File newFile = new File(newFileName); + if (! newFile.exists()){ // Else, report error + try { + if (currentFile.renameTo(newFile)){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + catch (SecurityException ignored){} // Ah, leave it + } + } + // For VIRT:/ and others we don't serve requests + return writeGL_FAIL("GL Handle 'Rename' command [not supported for virtual drive/wrong drive/file with such name already exists/read-only directory]"); + } + /** + * Handle 'Delete' + * @return true if failed + * false if everything is ok + * */ + private boolean delete(String fileName) { + if (fileName.startsWith("HOME:/")) { + fileName = updateHomePath(fileName); + + File fileToDel = new File(fileName); + try { + if (fileToDel.delete()){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + catch (SecurityException ignored){} // Ah, leave it + } + // For VIRT:/ and others we don't serve requests + return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory]"); + } + /** + * Handle 'Create' + * @param type 1 for file + * 2 for folder + * @param fileName full path including new file name in the end + * @return true if failed + * false if everything is ok + * */ + private boolean create(String fileName, byte type) { + if (fileName.startsWith("HOME:/")) { + fileName = updateHomePath(fileName); + File fileToCreate = new File(fileName); + boolean result = false; + if (type == 1){ + try { + result = fileToCreate.createNewFile(); + } + catch (SecurityException | IOException ignored){} + } + else if (type == 2){ + try { + result = fileToCreate.mkdir(); + } + catch (SecurityException ignored){} + } + if (result){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Create' command.", EMsgType.FAIL); + return true; + } + //logPrinter.print("GL Handle 'Create' command.", EMsgType.PASS); + return false; + } + } + // For VIRT:/ and others we don't serve requests + return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory]"); + } + + /** + * Handle 'ReadFile' + * @param fileName full path including new file name in the end + * @param offset requested offset + * @param size requested size + * @return true if failed + * false if everything is ok + * */ + private boolean readFile(String fileName, long offset, long size) { + //System.out.println("readFile "+fileName+" "+offset+" "+size); + if (fileName.startsWith("VIRT:/")){ + // Let's find out which file requested + String fNamePath = nspMap.get(fileName.substring(6)).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length + // If we don't have this file opened, let's open it + if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fNamePath))) { + // Try close what opened + if (openReadFileNameAndPath != null){ + try{ + randAccessFile.close(); + }catch (IOException ignored){} + } + // Open what has to be opened + try{ + randAccessFile = new RandomAccessFile(nspMap.get(fileName.substring(6)), "r"); + openReadFileNameAndPath = fNamePath; + } + catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); + } + } + } + else { + // Let's find out which file requested + fileName = updateHomePath(fileName); + // If we don't have this file opened, let's open it + if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fileName))) { + // Try close what opened + if (openReadFileNameAndPath != null){ + try{ + randAccessFile.close(); + }catch (IOException ignored){} + } + // Open what has to be opened + try{ + randAccessFile = new RandomAccessFile(fileName, "r"); + openReadFileNameAndPath = fileName; + }catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); + } + } + } + //----------------------- Actual transfer chain ------------------------ + try{ + randAccessFile.seek(offset); + byte[] chunk = new byte[(int)size]; // WTF MAN? + // Let's find out how much bytes we got + int bytesRead = randAccessFile.read(chunk); + // Let's check that we read expected size + if (bytesRead != (int)size) + return writeGL_FAIL("GL Handle 'ReadFile' command [CMD] Requested = "+size+" Read from file = "+bytesRead); + // Let's tell as a command about our result. + if (writeGL_PASS(longToArrLE(size))) { + logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + return true; + } + // Let's bypass bytes we read total + if (writeToUsb(chunk)) { + logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); + return true; + } + return false; + } + catch (IOException ioe){ + try{ + randAccessFile.close(); + } + catch (IOException ioee){ + logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioee.getMessage(), EMsgType.WARNING); + } + openReadFileNameAndPath = null; + randAccessFile = null; + return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); + } + } + /** + * Handle 'WriteFile' + * @param fileName full path including new file name in the end + * @param size requested size + * @return true if failed + * false if everything is ok + * */ + private boolean writeFile(String fileName, long size) { + //System.out.println("writeFile "+fileName+" "+size); + if (fileName.startsWith("VIRT:/")){ + return writeGL_FAIL("GL Handle 'WriteFile' command [not supported for virtual drive]"); + } + else { + fileName = updateHomePath(fileName); + // Check if we didn't see this (or any) file during this session + if (writeFilesMap.size() == 0 || (! writeFilesMap.containsKey(fileName))){ + // Open what we have to open + File writeFile = new File(fileName); + // If this file exists GL will take care + // Otherwise, let's add it + try{ + BufferedOutputStream writeFileBufOutStream = new BufferedOutputStream(new FileOutputStream(writeFile, true)); + writeFilesMap.put(fileName, writeFileBufOutStream); + } catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage()); + } + } + // Now we have stream + BufferedOutputStream myStream = writeFilesMap.get(fileName); + + byte[] transferredData; + + if ((transferredData = readGL_file()) == null){ + logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); + return true; + } + try{ + myStream.write(transferredData, 0, transferredData.length); + } + catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage()); + } + // Report we're good + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); + return true; + } + return false; + } + } + + /** + * Handle 'SelectFile' + * @return true if failed + * false if everything is ok + * */ + private boolean selectFile(){ + File selectedFile = CompletableFuture.supplyAsync(() -> { + FileChooser fChooser = new FileChooser(); + fChooser.setTitle(MediatorControl.getInstance().getContoller().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a prio. + fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); + return fChooser.showOpenDialog(null); // Leave as is for now. + }, Platform::runLater).join(); + + if (selectedFile != null){ + List command = new LinkedList<>(); + byte[] selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 + command.add(selectedFileNameBytes); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); + this.selectedFile = null; + return true; + } + this.selectedFile = selectedFile; + return false; + } + // Nothing selected; Report failure. + this.selectedFile = null; + return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected"); + } + + @Override + public EFileStatus getStatus() { + return EFileStatus.UNKNOWN; + } + /*----------------------------------------------------*/ + /* GL HELPERS */ + /*----------------------------------------------------*/ + /** + * Convert path received from GL to normal + */ + private String updateHomePath(String glPath){ + if (isWindows) + glPath = glPath.replaceAll("/", "\\\\"); + glPath = homePath+glPath.substring(6); // Do not use replaceAll since it will consider \ as special directive + return glPath; + } + /** + * Convert INT (Little endian) value to bytes-array representation + * */ + private byte[] intToArrLE(int value){ + ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.putInt(value); + return byteBuffer.array(); + } + /** + * Convert LONG (Little endian) value to bytes-array representation + * */ + private byte[] longToArrLE(long value){ + ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.putLong(value); + return byteBuffer.array(); + } + /** + * Convert bytes-array to INT value (Little endian) + * */ + private int arrToIntLE(byte[] byteArrayWithInt, int intStartPosition){ + return ByteBuffer.wrap(byteArrayWithInt).order(ByteOrder.LITTLE_ENDIAN).getInt(intStartPosition); + } + /** + * Convert bytes-array to LONG value (Little endian) + * */ + private long arrToLongLE(byte[] byteArrayWithLong, int intStartPosition){ + return ByteBuffer.wrap(byteArrayWithLong).order(ByteOrder.LITTLE_ENDIAN).getLong(intStartPosition); + } + + //------------------------------------------------------------------------------------------------------------------ + + /*----------------------------------------------------*/ + /* GL READ/WRITE USB SPECIFIC */ + /*----------------------------------------------------*/ + + private byte[] readGL(){ + ByteBuffer readBuffer; + readBuffer = ByteBuffer.allocateDirect(4096); // GL really? + + IntBuffer readBufTransferred = IntBuffer.allocate(1); + + int result; + + while (! task.isCancelled()) { + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + switch (result) { + case LibUsb.SUCCESS: + int trans = readBufTransferred.get(); + byte[] receivedBytes = new byte[trans]; + readBuffer.get(receivedBytes); + return receivedBytes; + case LibUsb.ERROR_TIMEOUT: + closeOpenedReadFilesGl(); // Could be a problem if GL glitches and slow down process. Or if user has extra-slow SD card. TODO: refactor + continue; + default: + logPrinter.print("GL Data transfer issue [read]\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("GL Execution stopped", EMsgType.FAIL); + return null; + } + } + logPrinter.print("GL Execution interrupted", EMsgType.INFO); + return null; + } + private byte[] readGL_file(){ + ByteBuffer readBuffer; + readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. + + IntBuffer readBufTransferred = IntBuffer.allocate(1); + + int result; + + while (! task.isCancelled()) { + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + switch (result) { + case LibUsb.SUCCESS: + int trans = readBufTransferred.get(); + byte[] receivedBytes = new byte[trans]; + readBuffer.get(receivedBytes); + return receivedBytes; + case LibUsb.ERROR_TIMEOUT: + continue; + default: + logPrinter.print("GL Data transfer issue [read]\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("GL Execution stopped", EMsgType.FAIL); + return null; + } + } + logPrinter.print("GL Execution interrupted", EMsgType.INFO); + return null; + } + /** + * Write new command. Shitty implementation. + * */ + private boolean writeGL_PASS(byte[] message){ + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(message); + return writeToUsb(writeBuffer.array()); + } + private boolean writeGL_PASS(){ + return writeToUsb(Arrays.copyOf(CMD_GLCO_SUCCESS, 4096)); + } + private boolean writeGL_PASS(List messages){ + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + writeBuffer.put(CMD_GLCO_SUCCESS); + for (byte[] arr : messages) + writeBuffer.put(arr); + return writeToUsb(writeBuffer.array()); + } + + private boolean writeGL_FAIL(String reportToUImsg){ + if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ + logPrinter.print(reportToUImsg, EMsgType.WARNING); + return true; + } + logPrinter.print(reportToUImsg, EMsgType.FAIL); + return false; + } + /** + * Sending any byte array to USB device + * @return 'false' if no issues + * 'true' if errors happened + * */ + private boolean writeToUsb(byte[] message){ + ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; + writeBuffer.put(message); // Don't do writeBuffer.rewind(); + IntBuffer writeBufTransferred = IntBuffer.allocate(1); + int result; + + while (! task.isCancelled()) { + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01 + + switch (result){ + case LibUsb.SUCCESS: + if (writeBufTransferred.get() == message.length) + return false; + else { + logPrinter.print("GL Data transfer issue [write]\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); + return true; + } + case LibUsb.ERROR_TIMEOUT: + continue; + default: + logPrinter.print("GL Data transfer issue [write]\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("GL Execution stopped", EMsgType.FAIL); + return true; + } + } + logPrinter.print("GL Execution interrupted", EMsgType.INFO); + return true; + } + + /*----------------------------------------------------*/ + /* GL EXPERIMENTAL PART */ + /* (left for better times) */ + /*----------------------------------------------------*/ + /* + private boolean proxyStatPath(String path) { + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + List fileBytesSize = new LinkedList<>(); + if ((recentDirs.length == 0) && (recentFiles.length == 0)) { + return writeGL_FAIL("proxyStatPath"); + } + if (recentDirs.length > 0){ + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(GL_OBJ_TYPE_DIR); + byte[] resultingDir = writeBuffer.array(); + writeToUsb(resultingDir); + for (int i = 1; i < recentDirs.length; i++) { + readGL(); + writeToUsb(resultingDir); + } + } + if (recentFiles.length > 0){ + path = path.replaceAll(recentDirs[0]+"$", ""); // Remove the name from path + for (String fileName : recentFiles){ + File f = new File(path+fileName); + fileBytesSize.add(longToArrLE(f.length())); + } + writeBuffer.clear(); + for (int i = 0; i < recentFiles.length; i++){ + readGL(); + writeBuffer.clear(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(GL_OBJ_TYPE_FILE); + writeBuffer.put(fileBytesSize.get(i)); + writeToUsb(writeBuffer.array()); + } + } + return false; + } + + private boolean proxyGetDirFile(boolean forDirs){ + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + List dirBytesNameSize = new LinkedList<>(); + List dirBytesName = new LinkedList<>(); + if (forDirs) { + if (recentDirs.length <= 0) + return writeGL_FAIL("proxyGetDirFile"); + for (String dirName : recentDirs) { + byte[] name = dirName.getBytes(StandardCharsets.UTF_16LE); + dirBytesNameSize.add(intToArrLE(name.length)); + dirBytesName.add(name); + } + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(0)); + writeBuffer.put(dirBytesName.get(0)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + for (int i = 1; i < recentDirs.length; i++){ + readGL(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(i)); + writeBuffer.put(dirBytesName.get(i)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + } + } + else { + if (recentDirs.length <= 0) + return writeGL_FAIL("proxyGetDirFile"); + for (String dirName : recentFiles){ + byte[] name = dirName.getBytes(StandardCharsets.UTF_16LE); + dirBytesNameSize.add(intToArrLE(name.length)); + dirBytesName.add(name); + } + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(0)); + writeBuffer.put(dirBytesName.get(0)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + for (int i = 1; i < recentFiles.length; i++){ + readGL(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(i)); + writeBuffer.put(dirBytesName.get(i)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + } + } + return false; + } + */ +} diff --git a/src/main/java/nsusbloader/USB/ITransferModule.java b/src/main/java/nsusbloader/USB/ITransferModule.java new file mode 100644 index 0000000..2b053a7 --- /dev/null +++ b/src/main/java/nsusbloader/USB/ITransferModule.java @@ -0,0 +1,7 @@ +package nsusbloader.USB; + +import nsusbloader.NSLDataTypes.EFileStatus; + +public interface ITransferModule { + EFileStatus getStatus(); +} diff --git a/src/main/java/nsusbloader/USB/TinFoil.java b/src/main/java/nsusbloader/USB/TinFoil.java new file mode 100644 index 0000000..4a73da9 --- /dev/null +++ b/src/main/java/nsusbloader/USB/TinFoil.java @@ -0,0 +1,321 @@ +package nsusbloader.USB; + +import javafx.concurrent.Task; +import nsusbloader.ModelControllers.LogPrinter; +import nsusbloader.NSLDataTypes.EFileStatus; +import nsusbloader.NSLDataTypes.EMsgType; +import org.usb4java.DeviceHandle; +import org.usb4java.LibUsb; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.LinkedHashMap; + +/** + * Tinfoil processing + * */ +class TinFoil implements ITransferModule { + private LogPrinter logPrinter; + private DeviceHandle handlerNS; + private LinkedHashMap nspMap; + private EFileStatus status = EFileStatus.FAILED; + private Task task; + + TinFoil(DeviceHandle handler, LinkedHashMap nspMap, Task task, LogPrinter logPrinter){ + this.handlerNS = handler; + this.nspMap = nspMap; + this.task = task; + this.logPrinter = logPrinter; + + logPrinter.print("============= TinFoil =============", EMsgType.INFO); + + if (!sendListOfNSP()) + return; + + if (proceedCommands()) // REPORT SUCCESS + status = EFileStatus.UPLOADED; // Don't change status that is already set to FAILED + } + /** + * Send what NSP will be transferred + * */ + private boolean sendListOfNSP(){ + // Send list of NSP files: + // Proceed "TUL0" + if (writeUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} + logPrinter.print("TF Send list of files: handshake", EMsgType.FAIL); + return false; + } + else + logPrinter.print("TF Send list of files: handshake", EMsgType.PASS); + //Collect file names + StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder + for(String nspFileName: nspMap.keySet()) { + nspListNamesBuilder.append(nspFileName); // And here we come with java string default encoding (UTF-16) + nspListNamesBuilder.append('\n'); + } + + byte[] nspListNames = nspListNamesBuilder.toString().getBytes(StandardCharsets.UTF_8); + ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); // integer = 4 bytes; BTW Java is stored in big-endian format + byteBuffer.putInt(nspListNames.length); // This way we obtain length in int converted to byte array in correct Big-endian order. Trust me. + byte[] nspListSize = byteBuffer.array(); // TODO: rewind? not sure.. + //byteBuffer.reset(); + + // Sending NSP list + logPrinter.print("TF Send list of files", EMsgType.INFO); + if (writeUsb(nspListSize)) { // size of the list we're going to transfer goes... + logPrinter.print(" [send list length]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [send list length]", EMsgType.PASS); + + if (writeUsb(new byte[8])) { // 8 zero bytes goes... + logPrinter.print(" [send padding]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [send padding]", EMsgType.PASS); + + if (writeUsb(nspListNames)) { // list of the names goes... + logPrinter.print(" [send list itself]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [send list itself]", EMsgType.PASS); + + return true; + } + /** + * After we sent commands to NS, this chain starts + * */ + private boolean proceedCommands(){ + logPrinter.print("TF Awaiting for NS commands.", EMsgType.INFO); + + /* byte[] magic = new byte[4]; + ByteBuffer bb = StandardCharsets.UTF_8.encode("TUC0").rewind().get(magic); // Let's rephrase this 'string' + */ + final byte[] magic = new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30}; // eq. 'TUC0' @ UTF-8 (actually ASCII lol, u know what I mean) + + byte[] receivedArray; + + while (true){ // Check if user interrupted process. + + receivedArray = readUsb(); + + if (receivedArray == null) // catches error + return false; + + 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; + + // 8th to 12th(explicits) bytes in returned data stands for command ID as unsigned integer (Little-endian). Actually, we have to compare arrays here, but in real world it can't be greater then 0/1/2, thus: + // BTW also protocol specifies 4th byte to be 0x00 kinda indicating that that this command is valid. But, as you may see, never happens other situation when it's not = 0. + if (receivedArray[8] == 0x00){ //0x00 - exit + logPrinter.print("TF Received 'EXIT' command. Terminating.", EMsgType.PASS); + return true; // All interaction with USB device should be ended (expected); + } + else if ((receivedArray[8] == 0x01) || (receivedArray[8] == 0x02)){ //0x01 - file range; 0x02 unknown bug on backend side (dirty hack). + logPrinter.print("TF Received 'FILE RANGE' command. Proceeding: [0x0"+receivedArray[8]+"]", EMsgType.PASS); + /*// We can get in this pocket a length of file name (+32). Why +32? I dunno man.. Do we need this? Definitely not. This app can live without it. + long receivedSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 12,20)).order(ByteOrder.LITTLE_ENDIAN).getLong(); + logsArea.appendText("[V] Received FILE_RANGE command. Size: "+Long.toUnsignedString(receivedSize)+"\n"); // this shit returns string that will be chosen next '+32'. And, BTW, can't be greater then 512 + */ + if (! fileRangeCmd()) + return false; // catches exception + } + } + } + /** + * This is what returns requested file (files) + * Executes multiple times + * @return 'true' if everything is ok + * 'false' is error/exception occurs + * */ + private boolean fileRangeCmd(){ + byte[] receivedArray; + // Here we take information of what other side wants + receivedArray = readUsb(); + if (receivedArray == null) + return false; + + // range_offset of the requested file. In the begining it will be 0x10. + long receivedRangeSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 0,8)).order(ByteOrder.LITTLE_ENDIAN).getLong(); // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb + byte[] receivedRangeSizeRAW = Arrays.copyOfRange(receivedArray, 0,8); // used (only) when we use sendResponse(). It's just simply. + long receivedRangeOffset = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 8,16)).order(ByteOrder.LITTLE_ENDIAN).getLong(); // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb + /* Below, it's REAL NSP file name length that we sent before among others (WITHOUT +32 byes). It can't be greater then... see what is written in the beginning of this code. + We don't need this since in next pocket we'll get name itself UTF-8 encoded. Could be used to double-checks or something like that. + long receivedNspNameLen = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 16,24)).order(ByteOrder.LITTLE_ENDIAN).getLong(); */ + + // Requesting UTF-8 file name required: + receivedArray = readUsb(); + if (receivedArray == null) + return false; + + String receivedRequestedNSP = new String(receivedArray, StandardCharsets.UTF_8); + logPrinter.print("TF Reply to requested file: "+receivedRequestedNSP + +"\n Range Size: "+receivedRangeSize + +"\n Range Offset: "+receivedRangeOffset, EMsgType.INFO); + + // Sending response header + if (!sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply. + return false; + + try { + + BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor? + byte[] bufferCurrent ;//= new byte[1048576]; // eq. Allocate 1mb + + if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){ + logPrinter.print("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL); + return false; + } + + long currentOffset = 0; + // 'End Offset' equal to receivedRangeSize. + int readPice = 8388608; // = 8Mb + + while (currentOffset < receivedRangeSize){ + if ((currentOffset + readPice) >= receivedRangeSize ) + readPice = Math.toIntExact(receivedRangeSize - currentOffset); + //System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // NOTE: DEBUG + // updating progress bar (if a lot of data requested) START BLOCK + //-----------------------------------------/ + logPrinter.updateProgress((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0); + //-----------------------------------------/ + bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring. + + if (bufferedInStream.read(bufferCurrent) != readPice) { // changed since @ v0.3.2 + logPrinter.print("TF Reading of stream suddenly ended.", EMsgType.WARNING); + return false; + } + //write to USB + if (writeUsb(bufferCurrent)) { + logPrinter.print("TF Failure during NSP transmission.", EMsgType.FAIL); + return false; + } + currentOffset += readPice; + } + bufferedInStream.close(); + //-----------------------------------------/ + logPrinter.updateProgress(1.0); + //-----------------------------------------/ + } catch (FileNotFoundException fnfe){ + logPrinter.print("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL); + fnfe.printStackTrace(); + return false; + } catch (IOException ioe){ + logPrinter.print("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL); + ioe.printStackTrace(); + return false; + } catch (ArithmeticException ae){ + logPrinter.print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):\n "+ae.getMessage(), EMsgType.FAIL); + ae.printStackTrace(); + return false; + } + + return true; + } + /** + * Send response header. + * @return true if everything OK + * 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. + logPrinter.print("TF Sending response", EMsgType.INFO); + if (writeUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0' + (byte) 0x01, // CMD_TYPE_RESPONSE = 1 + (byte) 0x00, (byte) 0x00, (byte) 0x00, // kinda padding. Guys, didn't you want to use integer value for CMD semantic? + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00} ) // Send integer value of '1' in Little-endian format. + ){ + logPrinter.print(" [1/3]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [1/3]", EMsgType.PASS); + if(writeUsb(rangeSize)) { // Send EXACTLY what has been received + logPrinter.print(" [2/3]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [2/3]", EMsgType.PASS); + if(writeUsb(new byte[12])) { // kinda another one padding + logPrinter.print(" [3/3]", EMsgType.FAIL); + return false; + } + logPrinter.print(" [3/3]", EMsgType.PASS); + return true; + } + + /** + * Sending any byte array to USB device + * @return 'false' if no issues + * 'true' if errors happened + * */ + private boolean writeUsb(byte[] message){ + ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; + writeBuffer.put(message); // Don't do writeBuffer.rewind(); + IntBuffer writeBufTransferred = IntBuffer.allocate(1); + int result; + + while (! task.isCancelled()) { + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01 + + switch (result){ + case LibUsb.SUCCESS: + if (writeBufTransferred.get() == message.length) + return false; + else { + logPrinter.print("TF Data transfer issue [write]\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); + return true; + } + case LibUsb.ERROR_TIMEOUT: + continue; + default: + logPrinter.print("TF Data transfer issue [write]\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("TF Execution stopped", EMsgType.FAIL); + return true; + } + } + logPrinter.print("TF Execution interrupted", EMsgType.INFO); + return true; + } + /** + * Reading what USB device responded. + * @return byte array if data read successful + * 'null' if read failed + * */ + private byte[] readUsb(){ + ByteBuffer readBuffer = ByteBuffer.allocateDirect(512); + // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. + IntBuffer readBufTransferred = IntBuffer.allocate(1); + + int result; + while (! task.isCancelled()) { + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + switch (result) { + case LibUsb.SUCCESS: + int trans = readBufTransferred.get(); + byte[] receivedBytes = new byte[trans]; + readBuffer.get(receivedBytes); + return receivedBytes; + case LibUsb.ERROR_TIMEOUT: + continue; + default: + logPrinter.print("TF Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("TF Execution stopped", EMsgType.FAIL); + return null; + } + } + logPrinter.print("TF Execution interrupted", EMsgType.INFO); + return null; + } + + /** + * Status getter + * @return status + */ + @Override + public EFileStatus getStatus() { + return status; + } +} diff --git a/src/main/java/nsusbloader/USB/UsbCommunications.java b/src/main/java/nsusbloader/USB/UsbCommunications.java index a8033cc..97df6a6 100644 --- a/src/main/java/nsusbloader/USB/UsbCommunications.java +++ b/src/main/java/nsusbloader/USB/UsbCommunications.java @@ -1,41 +1,22 @@ package nsusbloader.USB; -import javafx.application.Platform; import javafx.concurrent.Task; -import javafx.stage.FileChooser; -import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.RainbowHexDump; import org.usb4java.*; import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.IntBuffer; -import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.CompletableFuture; // TODO: add filter option to show only NSP files public class UsbCommunications extends Task { - private final int DEFAULT_INTERFACE = 0; private LogPrinter logPrinter; - private EFileStatus status = EFileStatus.FAILED; - private LinkedHashMap nspMap; - - private Context contextNS; - private DeviceHandle handlerNS; - private String protocol; - private boolean nspFilterForGl; - private boolean proxyForGL = false; - /* 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. @@ -58,1499 +39,38 @@ public class UsbCommunications extends Task { @Override protected Void call() { - int result = -9999; - logPrinter.print("\tStart chain", EMsgType.INFO); - // Creating Context required by libusb. Optional. TODO: Consider removing. - contextNS = new Context(); - result = LibUsb.init(contextNS); - if (result != LibUsb.SUCCESS) { - logPrinter.print("libusb initialization\n Returned: "+result, EMsgType.FAIL); - close(); + + UsbConnect usbConnect = new UsbConnect(logPrinter); + + if (! usbConnect.isConnected()){ + close(EFileStatus.FAILED); return null; } + + DeviceHandle handler = usbConnect.getHandlerNS(); + + ITransferModule module; + + if (protocol.equals("TinFoil")) + module = new TinFoil(handler, nspMap, this, logPrinter); else - logPrinter.print("libusb initialization", EMsgType.PASS); + module = new GoldLeaf(handler, nspMap, this, logPrinter, nspFilterForGl); - // Searching for NS in devices: obtain list of all devices - DeviceList deviceList = new DeviceList(); - result = LibUsb.getDeviceList(contextNS, deviceList); - if (result < 0) { - logPrinter.print("Get device list\n Returned: "+result, EMsgType.FAIL); - close(); - return null; - } - else { - logPrinter.print("Get device list", EMsgType.PASS); - } - // Searching for NS in devices: looking for NS - DeviceDescriptor descriptor; - Device deviceNS = null; - for (Device device: deviceList){ - descriptor = new DeviceDescriptor(); // mmm.. leave it as is. - result = LibUsb.getDeviceDescriptor(device, descriptor); - if (result != LibUsb.SUCCESS){ - logPrinter.print("Read file descriptors for USB devices\n Returned: "+result, EMsgType.FAIL); - LibUsb.freeDeviceList(deviceList, true); - close(); - return null; - } - if ((descriptor.idVendor() == 0x057E) && descriptor.idProduct() == 0x3000){ - deviceNS = device; - logPrinter.print("Read file descriptors for USB devices", EMsgType.PASS); - break; - } - } - // Free device list. - if (deviceNS != null){ - logPrinter.print("NS in connected USB devices found", EMsgType.PASS); - } - else { - logPrinter.print("NS in connected USB devices not found", EMsgType.FAIL); - close(); - return null; - } - // Handle NS device - handlerNS = new DeviceHandle(); - result = LibUsb.open(deviceNS, handlerNS); - if (result != LibUsb.SUCCESS) { - logPrinter.print("Open NS USB device\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - if (result == LibUsb.ERROR_ACCESS) - logPrinter.print("Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!\n\n" + - "Steps to set 'udev' rules:\n" + - "root # vim /etc/udev/rules.d/99-NS.rules\n" + - "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"057e\", ATTRS{idProduct}==\"3000\", GROUP=\"plugdev\"\n" + - "root # udevadm control --reload-rules && udevadm trigger\n", EMsgType.INFO); - // Let's make a bit dirty workaround since such shit happened - if (contextNS != null) { - LibUsb.exit(contextNS); - logPrinter.print("Requested context close", EMsgType.INFO); - } + usbConnect.close(); - // Report status and close - logPrinter.update(nspMap, status); - logPrinter.print("\tEnd chain", EMsgType.INFO); - logPrinter.close(); - return null; - } - else - logPrinter.print("Open NS USB device", EMsgType.PASS); + close(module.getStatus()); - logPrinter.print("Free device list", EMsgType.INFO); - LibUsb.freeDeviceList(deviceList, true); - - // DO some stuff to connected NS - // Check if this device uses kernel driver and detach if possible: - boolean canDetach = LibUsb.hasCapability(LibUsb.CAP_SUPPORTS_DETACH_KERNEL_DRIVER); // if cant, it's windows ot old lib - if (canDetach){ - int usedByKernel = LibUsb.kernelDriverActive(handlerNS, DEFAULT_INTERFACE); - if (usedByKernel == LibUsb.SUCCESS){ - logPrinter.print("Can proceed with libusb driver", EMsgType.PASS); // we're good - } - else if (usedByKernel == 1) { // used by kernel - result = LibUsb.detachKernelDriver(handlerNS, DEFAULT_INTERFACE); - logPrinter.print("Detach kernel required", EMsgType.INFO); - if (result != 0) { - logPrinter.print("Detach kernel\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - close(); - return null; - } else - logPrinter.print("Detach kernel", EMsgType.PASS); - } - else - logPrinter.print("Can't proceed with libusb driver\n Returned: "+UsbErrorCodes.getErrCode(usedByKernel), EMsgType.FAIL); - } - else - logPrinter.print("libusb doesn't support function 'CAP_SUPPORTS_DETACH_KERNEL_DRIVER'. It's normal. Proceeding.", EMsgType.WARNING); - /* - // Reset device - result = LibUsb.resetDevice(handlerNS); - if (result == 0) - logPrinter.print("Reset device", EMsgType.PASS); - else { - logPrinter.print("Reset device returned: " + result, EMsgType.FAIL); - updateAndClose(); - return null; - } - */ - // Set configuration (soft reset if needed) - result = LibUsb.setConfiguration(handlerNS, 1); // 1 - configuration all we need - if (result != LibUsb.SUCCESS){ - logPrinter.print("Set active configuration to device\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - close(); - return null; - } - else { - logPrinter.print("Set active configuration to device.", EMsgType.PASS); - } - - // Claim interface - result = LibUsb.claimInterface(handlerNS, DEFAULT_INTERFACE); - if (result != LibUsb.SUCCESS) { - logPrinter.print("Claim interface\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - close(); - return null; - } - else - logPrinter.print("Claim interface", EMsgType.PASS); - - //-------------------------------------------------------------------------------------------------------------- - if (protocol.equals("TinFoil")) { - new TinFoil(); - } else { - new GoldLeaf(); - } - - close(); return null; } - /** - * Tinfoil processing - * */ - private class TinFoil{ - TinFoil(){ - logPrinter.print("============= TinFoil =============", EMsgType.INFO); - - if (!sendListOfNSP()) - return; - - if (proceedCommands()) // REPORT SUCCESS - status = EFileStatus.UPLOADED; // Don't change status that is already set to FAILED - } - /** - * Send what NSP will be transferred - * */ - private boolean sendListOfNSP(){ - // Send list of NSP files: - // Proceed "TUL0" - if (writeToUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} - logPrinter.print("TF Send list of files: handshake", EMsgType.FAIL); - return false; - } - else - logPrinter.print("TF Send list of files: handshake", EMsgType.PASS); - //Collect file names - StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder - for(String nspFileName: nspMap.keySet()) { - nspListNamesBuilder.append(nspFileName); // And here we come with java string default encoding (UTF-16) - nspListNamesBuilder.append('\n'); - } - - byte[] nspListNames = nspListNamesBuilder.toString().getBytes(StandardCharsets.UTF_8); - ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); // integer = 4 bytes; BTW Java is stored in big-endian format - byteBuffer.putInt(nspListNames.length); // This way we obtain length in int converted to byte array in correct Big-endian order. Trust me. - byte[] nspListSize = byteBuffer.array(); // TODO: rewind? not sure.. - //byteBuffer.reset(); - - // Sending NSP list - logPrinter.print("TF Send list of files", EMsgType.INFO); - if (writeToUsb(nspListSize)) { // size of the list we're going to transfer goes... - logPrinter.print(" [send list length]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [send list length]", EMsgType.PASS); - - if (writeToUsb(new byte[8])) { // 8 zero bytes goes... - logPrinter.print(" [send padding]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [send padding]", EMsgType.PASS); - - if (writeToUsb(nspListNames)) { // list of the names goes... - logPrinter.print(" [send list itself]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [send list itself]", EMsgType.PASS); - - return true; - } - /** - * After we sent commands to NS, this chain starts - * */ - private boolean proceedCommands(){ - logPrinter.print("TF Awaiting for NS commands.", EMsgType.INFO); - - /* byte[] magic = new byte[4]; - ByteBuffer bb = StandardCharsets.UTF_8.encode("TUC0").rewind().get(magic); - // Let's rephrase this 'string' */ - final byte[] magic = new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30}; // eq. 'TUC0' @ UTF-8 (actually ASCII lol, u know what I mean) - - byte[] receivedArray; - - while (true){ - if (isCancelled()) // Check if user interrupted process. - return false; - receivedArray = readFromUsb(); - if (receivedArray == null) - return false; // catches exception - - if (!Arrays.equals(Arrays.copyOfRange(receivedArray, 0,4), magic)) // Bytes from 0 to 3 should contain 'magic' TUC0, so must be verified like this - continue; - - // 8th to 12th(explicits) bytes in returned data stands for command ID as unsigned integer (Little-endian). Actually, we have to compare arrays here, but in real world it can't be greater then 0/1/2, thus: - // BTW also protocol specifies 4th byte to be 0x00 kinda indicating that that this command is valid. But, as you may see, never happens other situation when it's not = 0. - if (receivedArray[8] == 0x00){ //0x00 - exit - logPrinter.print("TF Received EXIT command. Terminating.", EMsgType.PASS); - return true; // All interaction with USB device should be ended (expected); - } - else if ((receivedArray[8] == 0x01) || (receivedArray[8] == 0x02)){ //0x01 - file range; 0x02 unknown bug on backend side (dirty hack). - logPrinter.print("TF Received FILE_RANGE command. Proceeding: [0x0"+receivedArray[8]+"]", EMsgType.PASS); - /*// We can get in this pocket a length of file name (+32). Why +32? I dunno man.. Do we need this? Definitely not. This app can live without it. - long receivedSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 12,20)).order(ByteOrder.LITTLE_ENDIAN).getLong(); - logsArea.appendText("[V] Received FILE_RANGE command. Size: "+Long.toUnsignedString(receivedSize)+"\n"); // this shit returns string that will be chosen next '+32'. And, BTW, can't be greater then 512 - */ - if (!fileRangeCmd()) { - return false; // catches exception - } - } - } - } - /** - * This is what returns requested file (files) - * Executes multiple times - * @return 'true' if everything is ok - * 'false' is error/exception occurs - * */ - private boolean fileRangeCmd(){ - boolean isProgessBarInitiated = false; - - byte[] receivedArray; - // Here we take information of what other side wants - receivedArray = readFromUsb(); - if (receivedArray == null) - return false; - - // range_offset of the requested file. In the begining it will be 0x10. - long receivedRangeSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 0,8)).order(ByteOrder.LITTLE_ENDIAN).getLong(); // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb - byte[] receivedRangeSizeRAW = Arrays.copyOfRange(receivedArray, 0,8); // used (only) when we use sendResponse(). It's just simply. - long receivedRangeOffset = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 8,16)).order(ByteOrder.LITTLE_ENDIAN).getLong(); // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb - /* Below, it's REAL NSP file name length that we sent before among others (WITHOUT +32 byes). It can't be greater then... see what is written in the beginning of this code. - We don't need this since in next pocket we'll get name itself UTF-8 encoded. Could be used to double-checks or something like that. - long receivedNspNameLen = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 16,24)).order(ByteOrder.LITTLE_ENDIAN).getLong(); */ - - // Requesting UTF-8 file name required: - receivedArray = readFromUsb(); - if (receivedArray == null) - return false; - - String receivedRequestedNSP = new String(receivedArray, StandardCharsets.UTF_8); - logPrinter.print("TF Reply to requested file: "+receivedRequestedNSP - +"\n Range Size: "+receivedRangeSize - +"\n Range Offset: "+receivedRangeOffset, EMsgType.INFO); - - // Sending response header - if (!sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply. - return false; - - try { - - BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor? - byte[] bufferCurrent ;//= new byte[1048576]; // eq. Allocate 1mb - - if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){ - logPrinter.print("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL); - return false; - } - - long currentOffset = 0; - // 'End Offset' equal to receivedRangeSize. - int readPice = 8388608; // = 8Mb - - while (currentOffset < receivedRangeSize){ - if (isCancelled()) // Check if user interrupted process. - return true; - if ((currentOffset + readPice) >= receivedRangeSize ) - readPice = Math.toIntExact(receivedRangeSize - currentOffset); - //System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // TODO: NOTE: DEBUG - // updating progress bar (if a lot of data requested) START BLOCK - //-----------------------------------------/ - try { - logPrinter.updateProgress((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0); - }catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } - //-----------------------------------------/ - bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring. - - if (bufferedInStream.read(bufferCurrent) != readPice) { // changed since @ v0.3.2 - logPrinter.print("TF Reading of stream suddenly ended.", EMsgType.WARNING); - return false; - } - //write to USB - if (writeToUsb(bufferCurrent)) { - logPrinter.print("TF Failure during NSP transmission.", EMsgType.FAIL); - return false; - } - currentOffset += readPice; - } - bufferedInStream.close(); - //-----------------------------------------/ - try{ - logPrinter.updateProgress(1.0); - } - catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } - //-----------------------------------------/ - } catch (FileNotFoundException fnfe){ - logPrinter.print("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL); - fnfe.printStackTrace(); - return false; - } catch (IOException ioe){ - logPrinter.print("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL); - ioe.printStackTrace(); - return false; - } catch (ArithmeticException ae){ - logPrinter.print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):\n "+ae.getMessage(), EMsgType.FAIL); - ae.printStackTrace(); - return false; - } - - return true; - } - /** - * Send response header. - * @return true if everything OK - * 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. - logPrinter.print("TF Sending response", EMsgType.INFO); - if (writeToUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0' - (byte) 0x01, // CMD_TYPE_RESPONSE = 1 - (byte) 0x00, (byte) 0x00, (byte) 0x00, // kinda padding. Guys, didn't you want to use integer value for CMD semantic? - (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00} ) // Send integer value of '1' in Little-endian format. - ){ - logPrinter.print(" [1/3]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [1/3]", EMsgType.PASS); - if(writeToUsb(rangeSize)) { // Send EXACTLY what has been received - logPrinter.print(" [2/3]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [2/3]", EMsgType.PASS); - if(writeToUsb(new byte[12])) { // kinda another one padding - logPrinter.print(" [3/3]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [3/3]", EMsgType.PASS); - return true; - } - - } /** - * GoldLeaf processing - * */ - private class GoldLeaf{ - // CMD - private final byte[] CMD_GLCO_SUCCESS = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}; // used @ writeToUsb_GLCMD - private final byte[] CMD_GLCO_FAILURE = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x64, (byte) 0xcb, 0x00, 0x00}; // used @ writeToUsb_GLCMD - - // System.out.println((356 & 0x1FF) | ((1 + 100) & 0x1FFF) << 9); // 52068 // 0x00 0x00 0xCB 0x64 - private final byte[] GL_OBJ_TYPE_FILE = new byte[]{0x01, 0x00, 0x00, 0x00}; - private final byte[] GL_OBJ_TYPE_DIR = new byte[]{0x02, 0x00, 0x00, 0x00}; - - private String recentPath = null; - private String[] recentDirs = null; - private String[] recentFiles = null; - - private String[] nspMapKeySetIndexes; - - private String openReadFileNameAndPath; - private RandomAccessFile randAccessFile; - - private HashMap writeFilesMap; - - private boolean isWindows; - private String homePath; - // For using in CMD_SelectFile with SPEC:/ prefix - private File selectedFile; - - GoldLeaf(){ - final byte CMD_GetDriveCount = 0x00; - final byte CMD_GetDriveInfo = 0x01; - final byte CMD_StatPath = 0x02; // TODO: proxy done [proxy: in case if folder contains ENG+RUS+UKR file names works incorrect] - final byte CMD_GetFileCount = 0x03; - final byte CMD_GetFile = 0x04; // TODO: proxy done - final byte CMD_GetDirectoryCount = 0x05; - final byte CMD_GetDirectory = 0x06; // TODO: proxy done - final byte CMD_ReadFile = 0x07; // TODO: no way to do poxy - final byte CMD_WriteFile = 0x08; // TODO: add predictable behavior - final byte CMD_Create = 0x09; - final byte CMD_Delete = 0x0a;//10 - final byte CMD_Rename = 0x0b;//11 - final byte CMD_GetSpecialPathCount = 0x0c;//12 // Special folders count; simplified usage @ NS-UL - final byte CMD_GetSpecialPath = 0x0d;//13 // Information about special folders; simplified usage @ NS-UL - final byte CMD_SelectFile = 0x0e;//14 - final byte CMD_Max = 0x0f;//15 // not used @ NS-UL & GT - - final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; - - logPrinter.print("============= GoldLeaf =============\n\tVIRT:/ equals files added into the application\n\tHOME:/ equals " - +System.getProperty("user.home"), EMsgType.INFO); - // Let's collect file names to the array to simplify our life - writeFilesMap = new HashMap<>(); - int i = 0; - nspMapKeySetIndexes = new String[nspMap.size()]; - for (String fileName : nspMap.keySet()) - nspMapKeySetIndexes[i++] = fileName; - - status = EFileStatus.UNKNOWN; - - isWindows = System.getProperty("os.name").contains("Windows"); - - homePath = System.getProperty("user.home")+File.separator; - - // Go parse commands - byte[] readByte; - int someLength1, - someLength2; - while (! isCancelled()) { // Till user interrupted process. - readByte = readGL(); - - if (readByte == null) // Issue @ readFromUsbGL method - return; - else if(readByte.length < 4096) { // Just timeout of waiting for reply; continue loop - closeOpenedReadFilesGl(); - continue; - } - - //RainbowHexDump.hexDumpUTF16LE(readByte); // DEBUG - //System.out.println("CHOICE: "+readByte[4]); // DEBUG - - if (Arrays.equals(Arrays.copyOfRange(readByte, 0,4), CMD_GLCI)) { - switch (readByte[4]) { - case CMD_GetDriveCount: - if (getDriveCount()) - return; - break; - case CMD_GetDriveInfo: - if (getDriveInfo(arrToIntLE(readByte,8))) - return; - break; - case CMD_GetSpecialPathCount: - if (getSpecialPathCount()) - return; - break; - case CMD_GetSpecialPath: - if (getSpecialPath(arrToIntLE(readByte,8))) - return; - break; - case CMD_GetDirectoryCount: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), true)) - return; - break; - case CMD_GetFileCount: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), false)) - return; - break; - case CMD_GetDirectory: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (getDirectory(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), arrToIntLE(readByte, someLength1+12))) - return; - break; - case CMD_GetFile: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (getFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), arrToIntLE(readByte, someLength1+12))) - return; - break; - case CMD_StatPath: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (statPath(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE))) - return; - break; - case CMD_Rename: - someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 - someLength2 = arrToIntLE(readByte, 16+someLength1) * 2; // Since GL 0.7 - if (rename(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE), - new String(readByte, 16+someLength1+4, someLength2, StandardCharsets.UTF_16LE))) - return; - break; - case CMD_Delete: - someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 - if (delete(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE))) - return; - break; - case CMD_Create: - someLength1 = arrToIntLE(readByte, 12) * 2; // Since GL 0.7 - if (create(new String(readByte, 16, someLength1, StandardCharsets.UTF_16LE), readByte[8])) - return; - break; - case CMD_ReadFile: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (readFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), - arrToLongLE(readByte, 12+someLength1), - arrToLongLE(readByte, 12+someLength1+8))) - return; - break; - case CMD_WriteFile: - someLength1 = arrToIntLE(readByte, 8) * 2; // Since GL 0.7 - if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE), - arrToLongLE(readByte, 12+someLength1))) - return; - break; - case CMD_SelectFile: - if (selectFile()) - return; - break; - default: - writeGL_FAIL("GL Unknown command: "+readByte[4]+" [it's a very bad sign]"); - } - } - } - // Close (and flush) all opened streams. - if (writeFilesMap.size() != 0){ - for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ - try{ - fBufOutStream.close(); - }catch (IOException ignored){} - } - } - closeOpenedReadFilesGl(); - } - - /** - * Close files opened for read/write - */ - private void closeOpenedReadFilesGl(){ - if (openReadFileNameAndPath != null){ // Perfect time to close our opened files - try{ - randAccessFile.close(); - } - catch (IOException ignored){} - openReadFileNameAndPath = null; - randAccessFile = null; - } - } - /** - * Handle GetDriveCount - * @return true if failed - * false if everything is ok - */ - private boolean getDriveCount(){ - // Let's declare 2 drives - byte[] drivesCnt = intToArrLE(2); //2 - // Write count of drives - if (writeGL_PASS(drivesCnt)) { - logPrinter.print("GL Handle 'ListDrives' command", EMsgType.FAIL); - return true; - } - return false; - } - /** - * Handle GetDriveInfo - * @return true if failed - * false if everything is ok - */ - private boolean getDriveInfo(int driveNo){ - if (driveNo < 0 || driveNo > 1){ - return writeGL_FAIL("GL Handle 'GetDriveInfo' command [no such drive]"); - } - - byte[] driveLabel, - driveLabelLen, - driveLetter, - driveLetterLen, - totalFreeSpace, - totalSize; - long totalSizeLong = 0; - - // 0 == VIRTUAL DRIVE - if (driveNo == 0){ - driveLabel = "Virtual".getBytes(StandardCharsets.UTF_16LE); - driveLabelLen = intToArrLE(driveLabel.length / 2); // since GL 0.7 - driveLetter = "VIRT".getBytes(StandardCharsets.UTF_16LE); // TODO: Consider moving to class field declaration - driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7 - totalFreeSpace = new byte[4]; - for (File nspFile : nspMap.values()){ - totalSizeLong += nspFile.length(); - } - totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! - } - else { //1 == User home dir - driveLabel = "Home".getBytes(StandardCharsets.UTF_16LE); - driveLabelLen = intToArrLE(driveLabel.length / 2);// since GL 0.7 - driveLetter = "HOME".getBytes(StandardCharsets.UTF_16LE); - driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7 - File userHomeDir = new File(System.getProperty("user.home")); - long totalFreeSpaceLong = userHomeDir.getFreeSpace(); - totalFreeSpace = Arrays.copyOfRange(longToArrLE(totalFreeSpaceLong), 0, 4); // Dirty hack; now for GL!; - totalSizeLong = userHomeDir.getTotalSpace(); - totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! - - //System.out.println("totalSize: "+totalSizeLong+"totalFreeSpace: "+totalFreeSpaceLong); - } - - List command = new LinkedList<>(); - command.add(driveLabelLen); - command.add(driveLabel); - command.add(driveLetterLen); - command.add(driveLetter); - command.add(totalFreeSpace); - command.add(totalSize); - - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); - return true; - } - - return false; - } - /** - * Handle SpecialPathCount - * @return true if failed - * false if everything is ok - * */ - private boolean getSpecialPathCount(){ - // Let's declare nothing =) - byte[] specialPathCnt = intToArrLE(0); - // Write count of special paths - if (writeGL_PASS(specialPathCnt)) { - logPrinter.print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); - return true; - } - return false; - } - /** - * Handle SpecialPath - * @return true if failed - * false if everything is ok - * */ - private boolean getSpecialPath(int specialPathNo){ - return writeGL_FAIL("GL Handle 'SpecialPath' command [not supported]"); - } - /** - * Handle GetDirectoryCount & GetFileCount - * @return true if failed - * false if everything is ok - * */ - private boolean getDirectoryOrFileCount(String path, boolean isGetDirectoryCount) { - if (path.equals("VIRT:/")) { - if (isGetDirectoryCount){ - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); - return true; - } - } - else { - if (writeGL_PASS(intToArrLE(nspMap.size()))) { - logPrinter.print("GL Handle 'GetFileCount' command Count = "+nspMap.size(), EMsgType.FAIL); - return true; - } - } - } - else if (path.startsWith("HOME:/")){ - // Let's make it normal path - path = updateHomePath(path); - // Open it - File pathDir = new File(path); - - // Make sure it's exists and it's path - if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) - return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [doesn't exist or not a folder]"); - // Save recent dir path - this.recentPath = path; - String[] filesOrDirs; - // Now collecting every folder or file inside - if (isGetDirectoryCount){ - filesOrDirs = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (dir.isDirectory() && ! dir.isHidden()); - }); - } - else { - if (nspFilterForGl){ - filesOrDirs = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (! dir.isDirectory() && name.toLowerCase().endsWith(".nsp")); - }); - } - else { - filesOrDirs = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (! dir.isDirectory() && (! dir.isHidden())); - }); - } - } - // If somehow there are no folders, let's say 0; - if (filesOrDirs == null){ - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); - return true; - } - return false; - } - // Sorting is mandatory TODO: NOTE: Proxy tail - Arrays.sort(filesOrDirs, String.CASE_INSENSITIVE_ORDER); - - if (isGetDirectoryCount) - this.recentDirs = filesOrDirs; - else - this.recentFiles = filesOrDirs; - // Otherwise, let's tell how may folders are in there - if (writeGL_PASS(intToArrLE(filesOrDirs.length))) { - logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); - return true; - } - } - else if (path.startsWith("SPEC:/")){ - if (isGetDirectoryCount){ // If dir request then 0 dirs - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); - return true; - } - } - else if (selectedFile != null){ // Else it's file request, if we have selected then we will report 1. - if (writeGL_PASS(intToArrLE(1))) { - logPrinter.print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); - return true; - } - } - else - return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] (file) - "+path); - } - else { // If requested drive is not VIRT and not HOME then reply error - return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] "+(isGetDirectoryCount?"(dir) - ":"(file) - ")+path); - } - return false; - } - /** - * Handle GetDirectory - * @return true if failed - * false if everything is ok - * */ - private boolean getDirectory(String dirName, int subDirNo){ - if (dirName.startsWith("HOME:/")) { - dirName = updateHomePath(dirName); - - List command = new LinkedList<>(); - - if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){ - byte[] dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_16LE); - - command.add(intToArrLE(dirNameBytes.length / 2)); // Since GL 0.7 - command.add(dirNameBytes); - } - else { - File pathDir = new File(dirName); - // Make sure it's exists and it's path - if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) - return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]"); - this.recentPath = dirName; - // Now collecting every folder or file inside - this.recentDirs = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (dir.isDirectory() && ! dir.isHidden()); // TODO: FIX FOR WIN ? - }); - // Check that we still don't have any fuckups - if (this.recentDirs != null && this.recentDirs.length > subDirNo){ - Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER); - byte[] dirBytesName = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(dirBytesName.length / 2)); // Since GL 0.7 - command.add(dirBytesName); - } - else - return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]"); - } - //if (proxyForGL) // TODO: NOTE: PROXY TAILS - // return proxyGetDirFile(true); - - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); - return true; - } - return false; - } - // VIRT:// and any other - return writeGL_FAIL("GL Handle 'GetDirectory' command for virtual drive [no folders support]"); - } - /** - * Handle GetFile - * @return true if failed - * false if everything is ok - * */ - private boolean getFile(String dirName, int subDirNo){ - List command = new LinkedList<>(); - - if (dirName.startsWith("HOME:/")) { - dirName = updateHomePath(dirName); - - if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ - byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_16LE); - - command.add(intToArrLE(fileNameBytes.length / 2)); //Since GL 0.7 - command.add(fileNameBytes); - } - else { - File pathDir = new File(dirName); - // Make sure it's exists and it's path - if ((! pathDir.exists() ) || (! pathDir.isDirectory()) ) - writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); - this.recentPath = dirName; - // Now collecting every folder or file inside - if (nspFilterForGl){ - this.recentFiles = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (! dir.isDirectory() && name.toLowerCase().endsWith(".nsp")); // TODO: FIX FOR WIN ? MOVE TO PROD - }); - } - else { - this.recentFiles = pathDir.list((current, name) -> { - File dir = new File(current, name); - return (! dir.isDirectory() && (! dir.isHidden())); // TODO: FIX FOR WIN - }); - } - // Check that we still don't have any fuckups - if (this.recentFiles != null && this.recentFiles.length > subDirNo){ - Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER); // TODO: NOTE: array sorting is an overhead for using poxy loops - byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(fileNameBytes.length / 2)); //Since GL 0.7 - command.add(fileNameBytes); - } - else - return writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); - } - //if (proxyForGL) // TODO: NOTE: PROXY TAILS - // return proxyGetDirFile(false); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); - return true; - } - return false; - } - else if (dirName.equals("VIRT:/")){ - if (nspMap.size() != 0){ // therefore nspMapKeySetIndexes also != 0 - byte[] fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 - command.add(fileNameBytes); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - else if (dirName.equals("SPEC:/")){ - if (selectedFile != null){ - byte[] fileNameBytes = selectedFile.getName().getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 - command.add(fileNameBytes); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - // any other cases - return writeGL_FAIL("GL Handle 'GetFile' command for virtual drive [no folders support?]"); - } - /** - * Handle StatPath - * @return true if failed - * false if everything is ok - * */ - private boolean statPath(String filePath){ - List command = new LinkedList<>(); - - if (filePath.startsWith("HOME:/")){ - filePath = updateHomePath(filePath); - //if (proxyForGL) // TODO:NOTE PROXY TAILS - // return proxyStatPath(filePath); // dirty name - - File fileDirElement = new File(filePath); - if (fileDirElement.exists()){ - if (fileDirElement.isDirectory()) - command.add(GL_OBJ_TYPE_DIR); - else { - command.add(GL_OBJ_TYPE_FILE); - command.add(longToArrLE(fileDirElement.length())); - } - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - else if (filePath.startsWith("VIRT:/")) { - filePath = filePath.replaceFirst("VIRT:/", ""); - if (nspMap.containsKey(filePath)){ - command.add(GL_OBJ_TYPE_FILE); // THIS IS INT - command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG! - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - else if (filePath.startsWith("SPEC:/")){ - System.out.println(filePath); - filePath = filePath.replaceFirst("SPEC:/",""); - if (selectedFile.getName().equals(filePath)){ - command.add(GL_OBJ_TYPE_FILE); - command.add(longToArrLE(selectedFile.length())); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - return writeGL_FAIL("GL Handle 'StatPath' command [no such folder] - "+filePath); - } - /** - * Handle 'Rename' that is actually 'mv' - * @return true if failed - * false if everything is ok - * */ - private boolean rename(String fileName, String newFileName){ - if (fileName.startsWith("HOME:/")){ - // This shit takes too much time to explain, but such behaviour won't let GL to fail - this.recentPath = null; - this.recentFiles = null; - this.recentDirs = null; - fileName = updateHomePath(fileName); - newFileName = updateHomePath(newFileName); - - File currentFile = new File(fileName); - File newFile = new File(newFileName); - if (! newFile.exists()){ // Else, report error - try { - if (currentFile.renameTo(newFile)){ - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - catch (SecurityException ignored){} // Ah, leave it - } - } - // For VIRT:/ and others we don't serve requests - return writeGL_FAIL("GL Handle 'Rename' command [not supported for virtual drive/wrong drive/file with such name already exists/read-only directory]"); - } - /** - * Handle 'Delete' - * @return true if failed - * false if everything is ok - * */ - private boolean delete(String fileName) { - if (fileName.startsWith("HOME:/")) { - fileName = updateHomePath(fileName); - - File fileToDel = new File(fileName); - try { - if (fileToDel.delete()){ - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - catch (SecurityException ignored){} // Ah, leave it - } - // For VIRT:/ and others we don't serve requests - return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory]"); - } - /** - * Handle 'Create' - * @param type 1 for file - * 2 for folder - * @param fileName full path including new file name in the end - * @return true if failed - * false if everything is ok - * */ - private boolean create(String fileName, byte type) { - if (fileName.startsWith("HOME:/")) { - fileName = updateHomePath(fileName); - File fileToCreate = new File(fileName); - boolean result = false; - if (type == 1){ - try { - result = fileToCreate.createNewFile(); - } - catch (SecurityException | IOException ignored){} - } - else if (type == 2){ - try { - result = fileToCreate.mkdir(); - } - catch (SecurityException ignored){} - } - if (result){ - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Create' command.", EMsgType.FAIL); - return true; - } - //logPrinter.print("GL Handle 'Create' command.", EMsgType.PASS); - return false; - } - } - // For VIRT:/ and others we don't serve requests - return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory]"); - } - - /** - * Handle 'ReadFile' - * @param fileName full path including new file name in the end - * @param offset requested offset - * @param size requested size - * @return true if failed - * false if everything is ok - * */ - private boolean readFile(String fileName, long offset, long size) { - //System.out.println("readFile "+fileName+" "+offset+" "+size); - if (fileName.startsWith("VIRT:/")){ - // Let's find out which file requested - String fNamePath = nspMap.get(fileName.substring(6)).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length - // If we don't have this file opened, let's open it - if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fNamePath))) { - // Try close what opened - if (openReadFileNameAndPath != null){ - try{ - randAccessFile.close(); - }catch (IOException ignored){} - } - // Open what has to be opened - try{ - randAccessFile = new RandomAccessFile(nspMap.get(fileName.substring(6)), "r"); - openReadFileNameAndPath = fNamePath; - } - catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); - } - } - } - else { - // Let's find out which file requested - fileName = updateHomePath(fileName); - // If we don't have this file opened, let's open it - if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fileName))) { - // Try close what opened - if (openReadFileNameAndPath != null){ - try{ - randAccessFile.close(); - }catch (IOException ignored){} - } - // Open what has to be opened - try{ - randAccessFile = new RandomAccessFile(fileName, "r"); - openReadFileNameAndPath = fileName; - }catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); - } - } - } - //----------------------- Actual transfer chain ------------------------ - try{ - randAccessFile.seek(offset); - byte[] chunk = new byte[(int)size]; // WTF MAN? - // Let's find out how much bytes we got - int bytesRead = randAccessFile.read(chunk); - // Let's check that we read expected size - if (bytesRead != (int)size) - return writeGL_FAIL("GL Handle 'ReadFile' command [CMD] Requested = "+size+" Read from file = "+bytesRead); - // Let's tell as a command about our result. - if (writeGL_PASS(longToArrLE(size))) { - logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); - return true; - } - // Let's bypass bytes we read total - if (writeToUsb(chunk)) { - logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); - return true; - } - return false; - } - catch (IOException ioe){ - try{ - randAccessFile.close(); - } - catch (IOException ioee){ - logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioee.getMessage(), EMsgType.WARNING); - } - openReadFileNameAndPath = null; - randAccessFile = null; - return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); - } - } - /** - * Handle 'WriteFile' - * @param fileName full path including new file name in the end - * @param size requested size - * @return true if failed - * false if everything is ok - * */ - private boolean writeFile(String fileName, long size) { - //System.out.println("writeFile "+fileName+" "+size); - if (fileName.startsWith("VIRT:/")){ - return writeGL_FAIL("GL Handle 'WriteFile' command [not supported for virtual drive]"); - } - else { - fileName = updateHomePath(fileName); - // Check if we didn't see this (or any) file during this session - if (writeFilesMap.size() == 0 || (! writeFilesMap.containsKey(fileName))){ - // Open what we have to open - File writeFile = new File(fileName); - // If this file exists GL will take care - // Otherwise, let's add it - try{ - BufferedOutputStream writeFileBufOutStream = new BufferedOutputStream(new FileOutputStream(writeFile, true)); - writeFilesMap.put(fileName, writeFileBufOutStream); - } catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage()); - } - } - // Now we have stream - BufferedOutputStream myStream = writeFilesMap.get(fileName); - - byte[] transferredData; - - if ((transferredData = readGL_file()) == null){ - logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); - return true; - } - try{ - myStream.write(transferredData, 0, transferredData.length); - } - catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage()); - } - // Report we're good - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); - return true; - } - return false; - } - } - - /** - * Handle 'SelectFile' - * @return true if failed - * false if everything is ok - * */ - private boolean selectFile(){ - File selectedFile = CompletableFuture.supplyAsync(() -> { - FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getContoller().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION - fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a prio. - fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); - return fChooser.showOpenDialog(null); // Leave as is for now. - }, Platform::runLater).join(); - - if (selectedFile != null){ - List command = new LinkedList<>(); - byte[] selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 - command.add(selectedFileNameBytes); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); - this.selectedFile = null; - return true; - } - this.selectedFile = selectedFile; - return false; - } - // Nothing selected; Report failure. - this.selectedFile = null; - return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected"); - } - - /*----------------------------------------------------*/ - /* GL READ/WRITE USB SPECIFIC */ - /*----------------------------------------------------*/ - - /** - * Write new command. Shitty implementation. - * */ - private boolean writeGL_PASS(byte[] message){ - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(message); - return writeToUsb(writeBuffer.array()); - } - private boolean writeGL_PASS(){ - return writeToUsb(Arrays.copyOf(CMD_GLCO_SUCCESS, 4096)); - } - private boolean writeGL_PASS(List messages){ - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - writeBuffer.put(CMD_GLCO_SUCCESS); - for (byte[] arr : messages) - writeBuffer.put(arr); - return writeToUsb(writeBuffer.array()); - } - - private boolean writeGL_FAIL(String reportToUImsg){ - if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ - logPrinter.print(reportToUImsg, EMsgType.WARNING); - return true; - } - logPrinter.print(reportToUImsg, EMsgType.FAIL); - return false; - } - private byte[] readGL(){ - ByteBuffer readBuffer = ByteBuffer.allocateDirect(4096); // GL really? - // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. - IntBuffer readBufTransferred = IntBuffer.allocate(1); - - int result; - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 - - if (result != LibUsb.SUCCESS && result != LibUsb.ERROR_TIMEOUT){ - logPrinter.print("GL Data transfer (read) issue\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("Execution stopped", EMsgType.FAIL); - return null; - } - else { - int trans = readBufTransferred.get(); - byte[] receivedBytes = new byte[trans]; - readBuffer.get(receivedBytes); - return receivedBytes; - } - } - private byte[] readGL_file(){ - ByteBuffer readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. - // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. - IntBuffer readBufTransferred = IntBuffer.allocate(1); // Works for 8mb - - int result; - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 5000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 TODO: Fix or leave as is - - if (result != LibUsb.SUCCESS && result != LibUsb.ERROR_TIMEOUT){ - logPrinter.print("GL Data transfer (read) issue\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("Execution stopped", EMsgType.FAIL); - return null; - } - else { - int trans = readBufTransferred.get(); - byte[] receivedBytes = new byte[trans]; - readBuffer.get(receivedBytes); - return receivedBytes; - } - } - /*----------------------------------------------------*/ - /* GL HELPERS */ - /*----------------------------------------------------*/ - /** - * Convert path received from GL to normal - */ - private String updateHomePath(String glPath){ - if (isWindows) - glPath = glPath.replaceAll("/", "\\\\"); - glPath = homePath+glPath.substring(6); // Do not use replaceAll since it will consider \ as special directive - return glPath; - } - /** - * Convert INT (Little endian) value to bytes-array representation - * */ - private byte[] intToArrLE(int value){ - ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.putInt(value); - return byteBuffer.array(); - } - /** - * Convert LONG (Little endian) value to bytes-array representation - * */ - private byte[] longToArrLE(long value){ - ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.putLong(value); - return byteBuffer.array(); - } - /** - * Convert bytes-array to INT value (Little endian) - * */ - private int arrToIntLE(byte[] byteArrayWithInt, int intStartPosition){ - return ByteBuffer.wrap(byteArrayWithInt).order(ByteOrder.LITTLE_ENDIAN).getInt(intStartPosition); - } - /** - * Convert bytes-array to LONG value (Little endian) - * */ - private long arrToLongLE(byte[] byteArrayWithLong, int intStartPosition){ - return ByteBuffer.wrap(byteArrayWithLong).order(ByteOrder.LITTLE_ENDIAN).getLong(intStartPosition); - } - - /*----------------------------------------------------*/ - /* GL EXPERIMENTAL PART */ - /* (left for better times) */ - /*----------------------------------------------------*/ - /* - private boolean proxyStatPath(String path) { - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - List fileBytesSize = new LinkedList<>(); - if ((recentDirs.length == 0) && (recentFiles.length == 0)) { - return writeGL_FAIL("proxyStatPath"); - } - if (recentDirs.length > 0){ - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(GL_OBJ_TYPE_DIR); - byte[] resultingDir = writeBuffer.array(); - writeToUsb(resultingDir); - for (int i = 1; i < recentDirs.length; i++) { - readGL(); - writeToUsb(resultingDir); - } - } - if (recentFiles.length > 0){ - path = path.replaceAll(recentDirs[0]+"$", ""); // Remove the name from path - for (String fileName : recentFiles){ - File f = new File(path+fileName); - fileBytesSize.add(longToArrLE(f.length())); - } - writeBuffer.clear(); - for (int i = 0; i < recentFiles.length; i++){ - readGL(); - writeBuffer.clear(); - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(GL_OBJ_TYPE_FILE); - writeBuffer.put(fileBytesSize.get(i)); - writeToUsb(writeBuffer.array()); - } - } - return false; - } - - private boolean proxyGetDirFile(boolean forDirs){ - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - List dirBytesNameSize = new LinkedList<>(); - List dirBytesName = new LinkedList<>(); - if (forDirs) { - if (recentDirs.length <= 0) - return writeGL_FAIL("proxyGetDirFile"); - for (String dirName : recentDirs) { - byte[] name = dirName.getBytes(StandardCharsets.UTF_16LE); - dirBytesNameSize.add(intToArrLE(name.length)); - dirBytesName.add(name); - } - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(dirBytesNameSize.get(0)); - writeBuffer.put(dirBytesName.get(0)); - writeToUsb(writeBuffer.array()); - writeBuffer.clear(); - for (int i = 1; i < recentDirs.length; i++){ - readGL(); - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(dirBytesNameSize.get(i)); - writeBuffer.put(dirBytesName.get(i)); - writeToUsb(writeBuffer.array()); - writeBuffer.clear(); - } - } - else { - if (recentDirs.length <= 0) - return writeGL_FAIL("proxyGetDirFile"); - for (String dirName : recentFiles){ - byte[] name = dirName.getBytes(StandardCharsets.UTF_16LE); - dirBytesNameSize.add(intToArrLE(name.length)); - dirBytesName.add(name); - } - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(dirBytesNameSize.get(0)); - writeBuffer.put(dirBytesName.get(0)); - writeToUsb(writeBuffer.array()); - writeBuffer.clear(); - for (int i = 1; i < recentFiles.length; i++){ - readGL(); - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(dirBytesNameSize.get(i)); - writeBuffer.put(dirBytesName.get(i)); - writeToUsb(writeBuffer.array()); - writeBuffer.clear(); - } - } - return false; - } - */ - } - //------------------------------------------------------------------------------------------------------------------ - /** - * 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) - logPrinter.print("Release interface\n Returned: "+result+" (sometimes it's not an issue)", EMsgType.WARNING); - else - logPrinter.print("Release interface", EMsgType.PASS); - - LibUsb.close(handlerNS); - logPrinter.print("Requested handler close", EMsgType.INFO); - } - // Close context in the end - if (contextNS != null) { - LibUsb.exit(contextNS); - logPrinter.print("Requested context close", EMsgType.INFO); - } - - // Report status and close + * Report status and close + */ + private void close(EFileStatus status){ logPrinter.update(nspMap, status); logPrinter.print("\tEnd chain", EMsgType.INFO); logPrinter.close(); } - /** - * Sending any byte array to USB device - * @return 'false' if no issues - * 'true' if errors happened - * */ - private boolean writeToUsb(byte[] message){ - ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; - writeBuffer.put(message);// DONT EVEN THINK OF USING writeBuffer.rewind(); // well.. - //RainbowHexDump.hexDumpUTF16LE(message); - //System.out.println("-------------------"); - IntBuffer writeBufTransferred = IntBuffer.allocate(1); - int result; - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 0); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01 - if (result != LibUsb.SUCCESS){ - logPrinter.print("Data transfer (write) issue\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("Execution stopped", EMsgType.FAIL); - return true; - } - else { - if (writeBufTransferred.get() != message.length){ - logPrinter.print("Data transfer (write) issue\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); - return true; - } - else { - return false; - } - } - } - /** - * Reading what USB device responded. - * @return byte array if data read successful - * 'null' if read failed - * */ - private byte[] readFromUsb(){ - ByteBuffer readBuffer = ByteBuffer.allocateDirect(512); - // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. - IntBuffer readBufTransferred = IntBuffer.allocate(1); - int result; - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 0); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 - - if (result != LibUsb.SUCCESS){ - logPrinter.print("Data transfer (read) issue\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("Execution stopped", EMsgType.FAIL); - return null; - } else { - int trans = readBufTransferred.get(); - byte[] receivedBytes = new byte[trans]; - readBuffer.get(receivedBytes); - /* DEBUG START----------------------------------------------------------------------------------------------* - hexDumpUTF8(receivedBytes); - // DEBUG END----------------------------------------------------------------------------------------------*/ - return receivedBytes; - } - } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/USB/UsbConnect.java b/src/main/java/nsusbloader/USB/UsbConnect.java new file mode 100644 index 0000000..2e95cf0 --- /dev/null +++ b/src/main/java/nsusbloader/USB/UsbConnect.java @@ -0,0 +1,183 @@ +package nsusbloader.USB; + +import nsusbloader.ModelControllers.LogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import org.usb4java.*; + +class UsbConnect { + private final int DEFAULT_INTERFACE = 0; + + private Context contextNS; + private DeviceHandle handlerNS; + + private LogPrinter logPrinter; + + private boolean connected; + + UsbConnect(LogPrinter logPrinter){ + this.logPrinter = logPrinter; + this.connected = false; + + int result; + + // Creating Context required by libusb. Optional. TODO: Consider removing. + contextNS = new Context(); + result = LibUsb.init(contextNS); + if (result != LibUsb.SUCCESS) { + logPrinter.print("libusb initialization\n Returned: "+result, EMsgType.FAIL); + close(); + return; + } + else + logPrinter.print("libusb initialization", EMsgType.PASS); + + // Searching for NS in devices: obtain list of all devices + DeviceList deviceList = new DeviceList(); + result = LibUsb.getDeviceList(contextNS, deviceList); + if (result < 0) { + logPrinter.print("Get device list\n Returned: "+result, EMsgType.FAIL); + close(); + return; + } + else + logPrinter.print("Get device list", EMsgType.PASS); + // Searching for NS in devices: looking for NS + DeviceDescriptor descriptor; + Device deviceNS = null; + for (Device device: deviceList){ + descriptor = new DeviceDescriptor(); // mmm.. leave it as is. + result = LibUsb.getDeviceDescriptor(device, descriptor); + if (result != LibUsb.SUCCESS){ + logPrinter.print("Read file descriptors for USB devices\n Returned: "+result, EMsgType.FAIL); + LibUsb.freeDeviceList(deviceList, true); + close(); + return; + } + if ((descriptor.idVendor() == 0x057E) && descriptor.idProduct() == 0x3000){ + deviceNS = device; + logPrinter.print("Read file descriptors for USB devices", EMsgType.PASS); + break; + } + } + // Free device list. + if (deviceNS != null){ + logPrinter.print("NS in connected USB devices found", EMsgType.PASS); + } + else { + logPrinter.print("NS in connected USB devices not found", EMsgType.FAIL); + close(); + return; + } + // Handle NS device + handlerNS = new DeviceHandle(); + result = LibUsb.open(deviceNS, handlerNS); + if (result != LibUsb.SUCCESS) { + logPrinter.print("Open NS USB device\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + if (result == LibUsb.ERROR_ACCESS) + logPrinter.print("Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!\n\n" + + "Steps to set 'udev' rules:\n" + + "root # vim /etc/udev/rules.d/99-NS.rules\n" + + "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"057e\", ATTRS{idProduct}==\"3000\", GROUP=\"plugdev\"\n" + + "root # udevadm control --reload-rules && udevadm trigger\n", EMsgType.INFO); + // Let's make a bit dirty workaround since such shit happened + logPrinter.print("Requested context close", EMsgType.INFO); + LibUsb.exit(contextNS); + return; // And close + } + else + logPrinter.print("Open NS USB device", EMsgType.PASS); + + logPrinter.print("Free device list", EMsgType.INFO); + LibUsb.freeDeviceList(deviceList, true); + + // DO some stuff to connected NS + // Check if this device uses kernel driver and detach if possible: + boolean canDetach = LibUsb.hasCapability(LibUsb.CAP_SUPPORTS_DETACH_KERNEL_DRIVER); // if cant, it's windows ot old lib + if (canDetach){ + int usedByKernel = LibUsb.kernelDriverActive(handlerNS, DEFAULT_INTERFACE); + if (usedByKernel == LibUsb.SUCCESS) + logPrinter.print("Can proceed with libusb driver", EMsgType.PASS); // we're good + else if (usedByKernel == 1) { // used by kernel + result = LibUsb.detachKernelDriver(handlerNS, DEFAULT_INTERFACE); + logPrinter.print("Detach kernel required", EMsgType.INFO); + if (result != 0) { + logPrinter.print("Detach kernel\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + close(); + return; + } + else + logPrinter.print("Detach kernel", EMsgType.PASS); + } + else + logPrinter.print("Can't proceed with libusb driver\n Returned: "+UsbErrorCodes.getErrCode(usedByKernel), EMsgType.FAIL); + } + else + logPrinter.print("libusb doesn't support function 'CAP_SUPPORTS_DETACH_KERNEL_DRIVER'. It's normal. Proceeding.", EMsgType.WARNING); + /* + // Reset device + result = LibUsb.resetDevice(handlerNS); + if (result == 0) + logPrinter.print("Reset device", EMsgType.PASS); + else { + logPrinter.print("Reset device returned: " + result, EMsgType.FAIL); + updateAndClose(); + return; + } + */ + // Set configuration (soft reset if needed) + result = LibUsb.setConfiguration(handlerNS, 1); // 1 - configuration all we need + if (result != LibUsb.SUCCESS){ + logPrinter.print("Set active configuration to device\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + close(); + return; + } + else + logPrinter.print("Set active configuration to device.", EMsgType.PASS); + + // Claim interface + result = LibUsb.claimInterface(handlerNS, DEFAULT_INTERFACE); + if (result != LibUsb.SUCCESS) { + logPrinter.print("Claim interface\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + close(); + return; + } + else + logPrinter.print("Claim interface", EMsgType.PASS); + + this.connected = true; + } + + /** + * Get USB status + * @return status of connection + */ + boolean isConnected() { return connected; } + /** + * Getter for handler + * @return DeviceHandle of NS + */ + DeviceHandle getHandlerNS(){ return handlerNS; } + /** + * Correct exit + * */ + 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) + logPrinter.print("Release interface\n Returned: "+result+" (sometimes it's not an issue)", EMsgType.WARNING); + else + logPrinter.print("Release interface", EMsgType.PASS); + + LibUsb.close(handlerNS); + logPrinter.print("Requested handler close", EMsgType.INFO); + } + // Close context in the end + if (contextNS != null) { + LibUsb.exit(contextNS); + logPrinter.print("Requested context close", EMsgType.INFO); + } + } +}