ns-usbloader/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java

1137 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.stage.FileChooser;
import nsusbloader.MediatorControl;
import nsusbloader.ModelControllers.CancellableRunnable;
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 final boolean isWindows;
private final String homePath;
// For using in CMD_SelectFile with SPEC:/ prefix
private File selectedFile;
private final CancellableRunnable task;
GoldLeaf_08(DeviceHandle handler,
LinkedHashMap<String, File> nspMap,
CancellableRunnable task,
ILogPrinter logPrinter,
boolean nspFilter)
{
super(handler, nspMap, task, logPrinter);
this.task = task;
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]");
}
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){ // Nothing selected
this.selectedFile = null;
return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected");
}
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;
}
/*----------------------------------------------------*/
/* 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 = 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;
}
}