ns-usbloader/src/main/java/nsusbloader/COM/USB/GoldLeaf_08.java
Dmitry Isaenko 8da07a37ba Refactor save-settings chain on exit (see Controllers package)
Remove concept of 'OldGoldleaf version'. Now all supported version of GoldLeaf listed in Settings tab and one of them has to be always selected.
Add GoldLeaf CLI support
Remove unnecessary tails of 'extras' from CLI Tinfoil/Awoo NET mode.
2020-07-07 02:20:57 +03:00

1131 lines
48 KiB
Java

/*
Copyright 2019-2020 Dmitry Isaenko
This file is part of NS-USBloader.
NS-USBloader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NS-USBloader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>.
*/
package nsusbloader.COM.USB;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.stage.FileChooser;
import nsusbloader.COM.ICommunications;
import nsusbloader.MediatorControl;
import nsusbloader.ModelControllers.ILogPrinter;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
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 0.8 processing
*/
class GoldLeaf_08 extends TransferModule {
private boolean nspFilterForGl;
// 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, 0x00, 0x00, (byte) 0xAD, (byte) 0xDE}; // used @ writeToUsb_GLCMD TODO: TEST
// 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 NSSplitReader splitReader;
private HashMap<String, BufferedOutputStream> writeFilesMap;
private long virtDriveSize;
private HashMap<String, Long> splitFileSize;
private boolean isWindows;
private String homePath;
// For using in CMD_SelectFile with SPEC:/ prefix
private File selectedFile;
GoldLeaf_08(DeviceHandle handler, LinkedHashMap<String, File> nspMap, ICommunications task, ILogPrinter logPrinter, boolean nspFilter){
super(handler, nspMap, task, logPrinter);
final byte CMD_GetDriveCount = 1;
final byte CMD_GetDriveInfo = 2;
final byte CMD_StatPath = 3;
final byte CMD_GetFileCount = 4;
final byte CMD_GetFile = 5;
final byte CMD_GetDirectoryCount = 6;
final byte CMD_GetDirectory = 7;
final byte CMD_StartFile = 8; // 1 -open read RAF; 2 open write RAF; 3 open write RAF and seek to EOF (???).
final byte CMD_ReadFile = 9;
final byte CMD_WriteFile = 10;
final byte CMD_EndFile = 11; // 1 - closed read RAF; 2 close write RAF.
final byte CMD_Create = 12;
final byte CMD_Delete = 13;
final byte CMD_Rename = 14;
final byte CMD_GetSpecialPathCount = 15;
final byte CMD_GetSpecialPath = 16;
final byte CMD_SelectFile = 17;
final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49};
this.nspFilterForGl = nspFilter;
logPrinter.print("============= GoldLeaf v0.8 =============\n\t" +
"VIRT:/ equals files added into the application\n\t" +
"HOME:/ 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;
splitFileSize = new HashMap<>();
// Calculate size of VIRT:/ drive
for (File nspFile : nspMap.values()){
if (nspFile.isDirectory()) {
File[] subFiles = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}"));
long size = 0;
for (File subFile : subFiles) // Validated by parent class
size += subFile.length();
virtDriveSize += size;
splitFileSize.put(nspFile.getName(), size);
}
else
virtDriveSize += nspFile.length();
}
// 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)))
if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_16LE)))
break main_loop;
break;
case CMD_SelectFile:
if (selectFile())
break main_loop;
break;
case CMD_StartFile:
case CMD_EndFile:
if (startOrEndFile())
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 | NullPointerException 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 | NullPointerException ignored){}
try{
splitReader.close();
}
catch (IOException | NullPointerException ignored){}
openReadFileNameAndPath = null;
randAccessFile = null;
splitReader = null;
}
}
/**
* Handle StartFile & EndFile
* NOTE: It's something internal for GL and used somehow by GL-PC-app, so just ignore this, at least for v0.8.
* @return true if failed
* false if everything is ok
* */
private boolean startOrEndFile(){
if (writeGL_PASS()){
logPrinter.print("GL Handle 'StartFile' command", EMsgType.FAIL);
return true;
}
return false;
}
/**
* 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 == 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];
totalSizeLong = virtDriveSize;
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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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
if (nspMap.get(filePath).isDirectory()) {
command.add(longToArrLE(splitFileSize.get(filePath))); // YES, THIS IS LONG!;
}
else
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+"\t"+offset+"\t"+size+"\n");
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 (Exception ignored){}
try{
splitReader.close();
}catch (Exception ignored){}
}
// Open what has to be opened
try{
File tempFile = nspMap.get(fileName.substring(6));
if (tempFile.isDirectory()) {
randAccessFile = null;
splitReader = new NSSplitReader(tempFile, 0);
}
else {
splitReader = null;
randAccessFile = new RandomAccessFile(tempFile, "r");
}
openReadFileNameAndPath = fNamePath;
}
catch (IOException | NullPointerException 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 | NullPointerException ignored){}
}
// Open what has to be opened
try{
randAccessFile = new RandomAccessFile(fileName, "r");
openReadFileNameAndPath = fileName;
}catch (IOException | NullPointerException ioe){
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
}
}
}
//----------------------- Actual transfer chain ------------------------
try{
if (randAccessFile == null){
splitReader.seek(offset);
byte[] chunk = new byte[(int)size]; // WTF MAN?
// Let's find out how much bytes we got
int bytesRead = splitReader.read(chunk);
// Let's check that we read expected size
if (bytesRead != (int)size)
return writeGL_FAIL("GL Handle 'ReadFile' command [CMD]" +
"\n At offset: " + offset +
"\n Requested: " + size +
"\n Received: " + 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;
}
else {
randAccessFile.seek(offset);
byte[] chunk = new byte[(int)size]; // yes, I know, but nothing to do here.
// 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 (Exception ioe){
try{
randAccessFile.close();
}
catch (NullPointerException ignored){}
catch (IOException ioe_){
logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
try{
splitReader.close();
}
catch (NullPointerException ignored){}
catch (IOException ioe_){
logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
openReadFileNameAndPath = null;
randAccessFile = null;
splitReader = 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
*
* @return true if failed
* false if everything is ok
* */
//@param size requested size
//private boolean writeFile(String fileName, long size) {
private boolean writeFile(String fileName) {
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<byte[]> 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 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) +
"\n 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) +
"\n 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<byte[]> 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) +
"\n GL Execution stopped", EMsgType.FAIL);
return true;
}
}
logPrinter.print("GL Execution interrupted", EMsgType.INFO);
return true;
}
}