drafting GL v1.1.0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Dmitry Isaenko 2025-09-02 01:10:37 +03:00
parent 0a9883ad2b
commit c8efebfcff
7 changed files with 954 additions and 770 deletions

View file

@ -1,7 +1,7 @@
steps:
- name: test-standard
when:
event: [tag, push]
event: [tag, push, manual]
image: maven:3-openjdk-17
commands:
- mvn -B -DskipTests clean package
@ -14,7 +14,7 @@ steps:
- name: make-windows-installer
when:
event: [tag, push]
event: [tag, push, manual]
image: wheatstalk/makensis:3
commands:
- cp target/NS-USBloader.exe misc/windows/NSIS/
@ -32,7 +32,7 @@ steps:
- name: emerge-legacy-artifact
when:
event: [tag, push]
event: [tag, push, manual]
image: maven:3-openjdk-17
commands:
- . ./.make_legacy
@ -44,7 +44,7 @@ steps:
- name: make-legacy-windows-installer
when:
event: [tag, push]
event: [tag, push, manual]
image: wheatstalk/makensis:3
commands:
- cp target/NS-USBloader.exe misc/windows/NSIS/
@ -60,7 +60,7 @@ steps:
- name: emerge-mac-m1-artifact
when:
event: [tag, push]
event: [tag, push, manual]
image: maven:3-openjdk-17
commands:
- . ./.make_m1

View file

@ -67,7 +67,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer
### System requirements
- JDK 11 for macOS and Linux
- JDK 17 for macOS and Linux
- libusb, if you have a Mac with Apple Silicon (install via `brew install libusb`)
### Supported Goldleaf versions
@ -78,7 +78,8 @@ Sometimes I add new posts about this project [on my blog page](https://developer
| v0.6.1 | v0.6 |
| v0.7 - 0.7.3 | v0.7+ |
| v0.8 - 0.9 | v1.0+ |
| v0.10 | v6.0+ |
| v0.10 - 1.0.0 | v6.0+ |
| v1.0.0 | v7.3+ |
where '+' means 'any next NS-USBloader version'.

View file

@ -29,7 +29,7 @@ public class AppPreferences {
private final Preferences preferences;
private final Locale locale;
public static final String[] GOLDLEAF_SUPPORTED_VERSIONS = {"v0.5", "v0.7.x", "v0.8-0.9", "v0.10-1.1.0", "v1.1.1+"};
public static final String[] GOLDLEAF_SUPPORTED_VERSIONS = {"v0.5", "v0.7.x", "v0.8-0.9", "v0.10-1.0.0", "v1.1.0+"};
private static final Font DEFAULT_FONT = Font.getDefault();
private AppPreferences(){

View file

@ -39,7 +39,7 @@ import java.util.concurrent.CompletableFuture;
/**
* GoldLeaf 0.8 processing
*/
class GoldLeaf_010 extends TransferModule {
class GoldLeaf_ex010 extends TransferModule {
private boolean nspFilterForGl;
// CMD
@ -71,11 +71,11 @@ class GoldLeaf_010 extends TransferModule {
private final CancellableRunnable task;
GoldLeaf_010(DeviceHandle handler,
LinkedHashMap<String, File> nspMap,
CancellableRunnable task,
ILogPrinter logPrinter,
boolean nspFilter)
GoldLeaf_ex010(DeviceHandle handler,
LinkedHashMap<String, File> nspMap,
CancellableRunnable task,
ILogPrinter logPrinter,
boolean nspFilter)
{
super(handler, nspMap, task, logPrinter);

View file

@ -24,6 +24,7 @@ import nsusbloader.ModelControllers.Log;
import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EModule;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.com.usb.gl.GoldLeaf_010;
import nsusbloader.com.usb.gl.GoldLeaf_111;
import org.usb4java.*;
@ -67,10 +68,10 @@ public class UsbCommunications extends CancellableRunnable {
case "TinFoil":
module = new TinFoil(handler, nspMap, this, logPrinter);
break;
case "GoldLeafv1.1.1+":
case "GoldLeafv1.1.0+":
module = new GoldLeaf_111(handler, nspMap, this, logPrinter, nspFilterForGl);
break;
case "GoldLeafv0.10-1.1.0":
case "GoldLeafv0.10-1.0.0":
module = new GoldLeaf_010(handler, nspMap, this, logPrinter, nspFilterForGl);
break;
case "GoldLeafv0.8-0.9":

View file

@ -0,0 +1,899 @@
/*
Copyright 2019-2025 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.gl;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import libKonogonka.RainbowDump;
import nsusbloader.MediatorControl;
import nsusbloader.ModelControllers.CancellableRunnable;
import nsusbloader.ModelControllers.ILogPrinter;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.com.helpers.NSSplitReader;
import nsusbloader.com.usb.TransferModule;
import nsusbloader.com.usb.UsbErrorCodes;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static nsusbloader.com.usb.gl.Converters.*;
/**
* GoldLeaf v0.10 processing
*/
public class GoldLeaf_010 extends TransferModule {
private final static int PACKET_SIZE = 4096;
// CMD
public final static byte[] EXCEPTION_CAUGHT = Arrays.copyOf(new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, (byte) 0xF1, (byte) 0xBA}, PACKET_SIZE);
public final static byte[] INVALID_INDEX = Arrays.copyOf(new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, (byte) 0xF2, (byte) 0xBA}, PACKET_SIZE);
public final static byte[] SELECTION_CANCELLED = Arrays.copyOf(new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, (byte) 0xF4, (byte) 0xBA}, PACKET_SIZE);
private final static byte[] CMD_GLCO_SUCCESS_FLAG = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00};
private final static byte[] CMD_GLCO_SUCCESS =
Arrays.copyOf(new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}, PACKET_SIZE);
// System.out.println((356 & 0x1FF) | ((1 + 100) & 0x1FFF) << 9); // 52068 // 0x00 0x00 0xCB 0x64
private final byte[] GL_OBJECT_TYPE_FILE = new byte[]{0x01, 0x00, 0x00, 0x00};
private final byte[] GL_OBJECT_TYPE_DIR = new byte[]{0x02, 0x00, 0x00, 0x00};
private final boolean nspFilter;
private String recentPath;
private String[] recentDirs;
private String[] recentFiles;
private final String[] nspMapKeySetIndexes;
protected String openReadFileNameAndPath;
protected RandomAccessFile randAccessFile;
protected NSSplitReader splitReader;
private final HashMap<String, BufferedOutputStream> writeFilesMap = new HashMap<>();
private long virtDriveSize;
private final HashMap<String, Long> splitFileSize = new HashMap<>();
private final boolean isWindows = System.getProperty("os.name").contains("Windows");
private final String homePath = System.getProperty("user.home");
// For using in CMD_SelectFile with SPEC:/ prefix
protected File selectedFile;
public GoldLeaf_010(DeviceHandle handler,
LinkedHashMap<String, File> nspMap,
CancellableRunnable task,
ILogPrinter logPrinter,
boolean nspFilter) {
super(handler, nspMap, task, logPrinter);
this.nspFilter = nspFilter;
print("=========== GoldLeaf v0.10-1.0.0 ===========\n\t" +
"VIRT:/ equals files added into the application\n\t" +
"HOME:/ equals " + homePath, EMsgType.INFO);
// Let's collect file names to the array (simplifies flow)
nspMapKeySetIndexes = nspMap.keySet().toArray(new String[0]);
// Calculate size of VIRT:/ drive
for (File nspFile : nspMap.values()) {
if (nspFile.isDirectory()) {
var subFiles = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}"));
assert subFiles != null;
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
main_loop:
while (true) { // Till user interrupted process.
int someLength1, someLength2;
var readByte = readGL();
if (readByte == null) // Issue @ readFromUsbGL method
return;
//RainbowDump.hexDumpUTF8(readByte); // DEBUG
System.out.println("\t→ "+ GoldleafCmd.get(readByte[4]));
if (notGLCI(readByte))
continue;
switch (GoldleafCmd.get(readByte[4])) {
case GetDriveCount:
if (getDriveCount())
break main_loop;
break;
case GetDriveInfo:
if (getDriveInfo(arrToIntLE(readByte,8)))
break main_loop;
break;
case GetSpecialPathCount:
if (getSpecialPathCount())
break main_loop;
break;
case GetSpecialPath:
if (getSpecialPath(arrToIntLE(readByte,8)))
break main_loop;
break;
case GetDirectoryCount:
RainbowDump.hexDumpUTF8(readByte); // DEBUG
someLength1 = arrToIntLE(readByte, 8);
if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), true))
break main_loop;
break;
case GetFileCount:
someLength1 = arrToIntLE(readByte, 8);
if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), false))
break main_loop;
break;
case GetDirectory:
someLength1 = arrToIntLE(readByte, 8);
if (getDirectory(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12)))
break main_loop;
break;
case GetFile:
someLength1 = arrToIntLE(readByte, 8);
if (getFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12)))
break main_loop;
break;
case StatPath:
someLength1 = arrToIntLE(readByte, 8);
if (statPath(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case Rename:
someLength1 = arrToIntLE(readByte, 8);
someLength2 = arrToIntLE(readByte, 12+someLength1);
if (rename(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), // 8+4=12
new String(readByte, 12+someLength1+4, someLength2, StandardCharsets.UTF_8)))
break main_loop;
break;
case Delete:
someLength1 = arrToIntLE(readByte, 8);
if (delete(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case Create:
someLength1 = arrToIntLE(readByte, 8);
if (create(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), readByte[8]))
break main_loop;
break;
case ReadFile:
someLength1 = arrToIntLE(readByte, 8);
if (readFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8),
arrToLongLE(readByte, 12+someLength1),
arrToLongLE(readByte, 12+someLength1+8)))
break main_loop;
break;
case WriteFile:
someLength1 = arrToIntLE(readByte, 8);
if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case SelectFile:
if (selectFile())
break main_loop;
break;
case StartFile:
case EndFile:
if (startOrEndFile())
break main_loop;
break;
case CMD_UNKNOWN:
default:
writeGL_FAIL(EXCEPTION_CAUGHT, "GL Unknown command: "+readByte[4]+" [it's a very bad sign]");
}
}
// Close (and flush) all opened streams.
for (var bufferedOutputStream: writeFilesMap.values()){
try{
bufferedOutputStream.close();
} catch (IOException | NullPointerException ignored){}
}
closeOpenedReadFilesGl();
}
private boolean notGLCI(byte[] inputBytes){
return ! "GLCI".equals(new String(inputBytes, 0, 4, StandardCharsets.US_ASCII));
}
/**
* 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 - failed, false - passed
* */
private boolean startOrEndFile(){
return writeGL_PASS("GL Handle 'StartFile' command");
}
/**
* Handle GetDriveCount
* 2 drives declared in current implementation
* @return true - failed, false - passed
*/
private boolean getDriveCount(){
return writeGL_PASS(intToArrLE(2),"GL Handle 'ListDrives' command");
}
/**
* Handle GetDriveInfo
* @return true - failed, false - passed
*/
private boolean getDriveInfo(int driveNo){
if (driveNo < 0 || driveNo > 1)
return writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetDriveInfo' command [no such drive]");
byte[] driveLabel,
driveLabelLen,
driveLetter,
driveLetterLen,
totalFreeSpace;
long totalSizeLong;
if (driveNo == 0){ // 0 == VIRTUAL DRIVE
driveLabel = "Virtual".getBytes(StandardCharsets.UTF_8);
driveLabelLen = intToArrLE(driveLabel.length);
driveLetter = "VIRT".getBytes(StandardCharsets.UTF_8);
driveLetterLen = intToArrLE(driveLetter.length);
totalFreeSpace = new byte[4];
totalSizeLong = virtDriveSize;
}
else { //1 == User home dir
driveLabel = "Home".getBytes(StandardCharsets.UTF_8);
driveLabelLen = intToArrLE(driveLabel.length);
driveLetter = "HOME".getBytes(StandardCharsets.UTF_8);
driveLetterLen = intToArrLE(driveLetter.length);
var userHomeDir = new File(System.getProperty("user.home"));
totalFreeSpace = Arrays.copyOfRange(longToArrLE(userHomeDir.getFreeSpace()), 0, 4);;
totalSizeLong = userHomeDir.getTotalSpace();
}
var totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4);
var command = Arrays.asList(
driveLabelLen,
driveLabel,
driveLetterLen,
driveLetter,
totalFreeSpace,
totalSize);
return writeGL_PASS(command, "GL Handle 'GetDriveInfo' command");
}
/**
* Handle SpecialPathCount
* Let's declare nothing. Write count of special paths
* @return true - failed, false - passed
* */
private boolean getSpecialPathCount(){
return writeGL_PASS(intToArrLE(0), "GL Handle 'SpecialPathCount' command");
}
/**
* Handle SpecialPath
* @return true - failed, false - passed
* */
private boolean getSpecialPath(int specialPathNo){
return writeGL_FAIL(INVALID_INDEX, "GL Handle 'SpecialPath' command [not supported]");
}
/**
* Handle GetDirectoryCount & GetFileCount
* @return true - failed, false - passed
* */
private boolean getDirectoryOrFileCount(String glFileName, boolean isGetDirectoryCount) {
if (glFileName.equals("VIRT:/")) {
return isGetDirectoryCount ?
writeGL_PASS("GL Handle 'GetDirectoryCount' command") :
writeGL_PASS(intToArrLE(nspMap.size()), "GL Handle 'GetFileCount' command Count = " + nspMap.size());
}
else if (glFileName.startsWith("HOME:/")){
var path = decodeGlPath(glFileName);
var pathDir = new File(path);
if (notExistsOrDirectory(pathDir))
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'GetDirectoryOrFileCount' command [doesn't exist or not a folder] "+ pathDir);
this.recentPath = path; // Save recent dir path
var filesOrDirs = isGetDirectoryCount ?
pathDir.list(this::isDirectoryAndNotHidden) :
pathDir.list(this::isFileAndNotHidden);
// If no folders, let's say 0;
if (filesOrDirs == null)
return writeGL_PASS("GL Handle 'GetDirectoryOrFileCount' command");
// Sorting is mandatory 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
return writeGL_PASS(intToArrLE(filesOrDirs.length), "GL Handle 'GetDirectoryOrFileCount' command");
}
else if (glFileName.startsWith("SPEC:/")){
if (isGetDirectoryCount) // If dir request then 0 dirs
return writeGL_PASS("GL Handle 'GetDirectoryCount' command");
else if (selectedFile != null) // Else it's file request, if we have selected then we will report 1.
return writeGL_PASS(intToArrLE(1), "GL Handle 'GetFileCount' command Count = 1");
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] (file) - "+glFileName);
}
// If requested drive is not VIRT and not HOME then reply error
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] "+(isGetDirectoryCount?"(dir) - ":"(file) - ")+glFileName);
}
/**
* Handle GetDirectory
* @return true - failed, false - passed
* */
private boolean getDirectory(String dirName, int subDirNo){
if (dirName.startsWith("HOME:/")) {
dirName = decodeGlPath(dirName);
var command = new ArrayList<byte[]>();
if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){
var dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(dirNameBytes.length));
command.add(dirNameBytes);
}
else {
var pathDir = new File(dirName);
// Make sure it's exists and it's path
if (notExistsOrDirectory(pathDir))
return writeGL_FAIL(INVALID_INDEX, "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(this::isDirectoryAndNotHidden);
// Check that we still don't have any fuckups
if (this.recentDirs != null && this.recentDirs.length > subDirNo){
Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER);
var dirBytesName = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(dirBytesName.length));
command.add(dirBytesName);
}
else
return writeGL_FAIL(INVALID_INDEX,"GL Handle 'GetDirectory' command [doesn't exist or not a folder]");
}
return writeGL_PASS(command, "GL Handle 'GetDirectory' command.");
}
// VIRT:// and any other
return writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetDirectory' command for virtual drive [no folders support]");
}
/**
* Handle GetFile
* @return true - failed, false - passed
* */
private boolean getFile(String glDirName, int subDirNo){
var command = new LinkedList<byte[]>();
if (glDirName.startsWith("HOME:/")) {
var dirName = decodeGlPath(glDirName);
if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){
var fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
}
else {
var pathDir = new File(dirName);
if (notExistsOrDirectory(pathDir))
writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetFile' command [doesn't exist or not a folder]");
this.recentPath = dirName;
this.recentFiles = pathDir.list(this::isFileAndNotHidden);
// Check that we still don't have any fuckups
if (this.recentFiles != null && this.recentFiles.length > subDirNo){
Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER);
var fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
}
else
return writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetFile' command [doesn't exist or not a folder]");
}
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
else if (glDirName.equals("VIRT:/") && (! nspMap.isEmpty())){ // thus nspMapKeySetIndexes also != 0
var fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
else if (glDirName.equals("SPEC:/") && (selectedFile != null)){
var fileNameBytes = selectedFile.getName().getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
// any other cases
return writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetFile' command for virtual drive [no folders support?]");
}
/**
* Handle StatPath
* @return true - failed, false - passed
* */
private boolean statPath(String glFileName){
var command = new ArrayList<byte[]>();
if (glFileName.startsWith("HOME:/")){
var fileDirElement = new File(decodeGlPath(glFileName));
if (fileDirElement.exists()){
if (fileDirElement.isDirectory())
command.add(GL_OBJECT_TYPE_DIR);
else {
command.add(GL_OBJECT_TYPE_FILE);
command.add(longToArrLE(fileDirElement.length()));
}
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
else if (glFileName.startsWith("VIRT:/")) {
var fileName = glFileName.replaceFirst("^.*?:/", "");
if (nspMap.containsKey(fileName)){
command.add(GL_OBJECT_TYPE_FILE); // THIS IS INT
if (nspMap.get(fileName).isDirectory())
command.add(longToArrLE(splitFileSize.get(fileName))); // YES, THIS IS LONG!;
else
command.add(longToArrLE(nspMap.get(fileName).length())); // YES, THIS IS LONG!
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
else if (glFileName.startsWith("SPEC:/")){
var fileName = glFileName.replaceFirst("^.*?:/", "");
if (selectedFile.getName().equals(fileName)){
command.add(GL_OBJECT_TYPE_FILE);
command.add(longToArrLE(selectedFile.length()));
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'StatPath' command [no such path]: "+glFileName);
}
/**
* Handle 'Rename' that is actually 'mv'
* @return true - failed, false - passed
* */
private boolean rename(String glFileName, String glNewFileName){
if (glFileName.startsWith("HOME:/")){
// Prevent GL failures
this.recentPath = null;
this.recentFiles = null;
this.recentDirs = null;
var fileName = decodeGlPath(glFileName);
var newFile = new File(decodeGlPath(glNewFileName));
try {
if (new File(fileName).renameTo(newFile)){
return writeGL_PASS("GL Handle 'Rename' command.");
}
}
catch (SecurityException se){
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Rename' command failed:\n\t" +se.getMessage());
}
}
// For VIRT:/ and others we don't serve requests
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Rename' command is not supported for virtual drive, selected files," +
" if file with such name already exists in folder, read-only directories");
}
/**
* Handle 'Delete'
* @return true - failed, false - passed
* */
private boolean delete(String glFileName) {
if (! glFileName.startsWith("HOME:/"))
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory] "+glFileName);
var file = new File(decodeGlPath(glFileName));
try {
if (file.delete())
return writeGL_PASS("GL Handle 'Rename' command.");
}
catch (SecurityException ignored){} // Ah, leave it
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Create' command [unknown drive/read-only directory]");
}
/**
* Handle 'Create'
* @param type 1 file, 2 folder
* @param glFileName full path including new file name in the end
* @return true - failed, false - passed
* */
private boolean create(String glFileName, byte type) {
if (! glFileName.startsWith("HOME:/")) // For VIRT:/ and others we don't serve requests
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Create' command [not supported for virtual drive/wrong drive/read-only directory]"+glFileName);
var file = new File(decodeGlPath(glFileName));
try {
boolean result = switch (type) {
case 1 -> file.createNewFile();
case 2 -> file.mkdir();
default -> false;
};
if (result)
return writeGL_PASS("GL Handle 'Create' command.");
}
catch (SecurityException | IOException ignored){}
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'Create' command [unknown drive/read-only directory]");
}
/**
* Handle 'ReadFile'
* @param glFileName full path including new file name in the end in format of Goldleaf
* @param offset requested offset
* @param size requested size
* @return true - failed, false - passed
* */
protected boolean readFile(String glFileName, long offset, long size) {
var fileName = glFileName.replaceFirst("^.*?:/", "");
System.out.println(fileName+" readFile "+glFileName+"\t"+offset+"\t"+size+"\n");
if (glFileName.startsWith("VIRT:/")){ // Could have split-file
// Let's find out which file requested
var fNamePath = nspMap.get(fileName).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length
// If we don't have this file opened, let's open it
if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fNamePath))) {
if (openReadFileNameAndPath != null) // (Try to) close what opened
closeRAFandSplitReader();
try{ // And open the rest
var tempFile = nspMap.get(fileName);
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(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
}
}
}
else { // SPEC:/ & HOME:/
String filePath;
if (glFileName.startsWith("SPEC:/")) {
if (! fileName.equals(selectedFile.getName())) {
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\trequested != selected:\n\t"
+ glFileName + "\n\t" + selectedFile);
}
filePath = selectedFile.getAbsolutePath();
}
else {
filePath = decodeGlPath(glFileName); // What requested?
}
// If we don't have this file opened, let's open it
if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(filePath))) {
if (openReadFileNameAndPath != null) // Try close what opened
closeRAF();
try{ // Open what has to be opened
randAccessFile = new RandomAccessFile(filePath, "r");
openReadFileNameAndPath = filePath;
}catch (IOException | NullPointerException ioe){
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
}
}
}
//----------------------- Actual transfer chain ------------------------
try{
var chunk = new byte[(int)size];
int bytesRead;
if (randAccessFile == null){
splitReader.seek(offset);
bytesRead = splitReader.read(chunk); // How many bytes we got?
}
else {
randAccessFile.seek(offset);
bytesRead = randAccessFile.read(chunk); // How many bytes we got?
}
if (bytesRead != (int) size) // Let's check that we read expected size
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command [CMD]" +
"\n At offset: " + offset +
"\n Requested: " + size +
"\n Received: " + bytesRead);
if (writeGL_PASS(longToArrLE(size), "GL Handle 'ReadFile' command [CMD]")) { // Reporting result
System.out.println("SENT 1 -");
return true;
}
System.out.println("SENT 1");
if (writeToUsb(chunk)) { // Bypassing bytes we read total // FIXME: move failure message into method
print("GL Handle 'ReadFile' command", EMsgType.FAIL);
System.out.println("SENT 2 -");
return true;
}
System.out.println("SENT 2");
return false;
}
catch (Exception ioe){
closeOpenedReadFilesGl();
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' transfer chain\n\t"+ioe.getMessage());
}
}
/**
* Handle 'WriteFile'
* @param glFileName full path including new file name in the end
* @return true - failed, false - passed
* */
private boolean writeFile(String glFileName) {
if (glFileName.startsWith("VIRT:/"))
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'WriteFile' command [not supported for virtual drive]");
glFileName = decodeGlPath(glFileName);
// Check if this file being used during this session
if (! writeFilesMap.containsKey(glFileName)){
try{ // If this file exists GL will take care; Otherwise, let's add it
writeFilesMap.put(glFileName,
new BufferedOutputStream(new FileOutputStream(glFileName, true))); // Open what we have to open
} catch (IOException ioe){
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage());
}
}
var transferredData = readGL_file();
if (transferredData == null){
print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL);
return true;
}
try{
writeFilesMap.get(glFileName).write(transferredData, 0, transferredData.length);
}
catch (IOException ioe){
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage());
}
// Report we're good
return writeGL_PASS("GL Handle 'WriteFile' command");
}
/**
* Handle 'SelectFile'
* @return true - failed, false - passed
* */
private boolean selectFile(){
var selectedFile = CompletableFuture.supplyAsync(() -> {
var fChooser = new FileChooser();
fChooser.setTitle(MediatorControl.INSTANCE.getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION
fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a priority.
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(SELECTION_CANCELLED, "GL Handle 'SelectFile' command: Nothing selected");
}
var selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_8);
var command = Arrays.asList(
intToArrLE(selectedFileNameBytes.length),
selectedFileNameBytes);
if (writeGL_PASS(command, "GL Handle 'SelectFile' command")) {
this.selectedFile = null;
return true;
}
this.selectedFile = selectedFile;
return false;
}
/*--------------------------------*/
/* GL HELPERS */
/*--------------------------------*/
/**
* Convert path received from GL to host-default structure
*/
protected String decodeGlPath(String glPath){
if (isWindows)
glPath = glPath.replace('/', '\\');
return homePath + glPath.substring(5); // e.g. HOME:/some/file/
}
private boolean isFileAndNotHidden(File parent, String child){
var entry = new File(parent, child);
return (! entry.isDirectory()) && (nspFilter ?
child.toLowerCase().endsWith(".nsp") :
! entry.isHidden());
}
private boolean isDirectoryAndNotHidden(File parent, String child){
var dir = new File(parent, child);
return (dir.isDirectory() && ! dir.isHidden());
}
private boolean notExistsOrDirectory(File pathDir){
return (! pathDir.exists() ) || (! pathDir.isDirectory());
}
/**
* Close files opened for read/write
*/
protected void closeOpenedReadFilesGl(){
if (openReadFileNameAndPath != null){
closeRAFandSplitReader();
openReadFileNameAndPath = null;
randAccessFile = null;
splitReader = null;
}
}
protected void closeRAFandSplitReader(){
closeRAF();
try{
splitReader.close();
}
catch (IOException ioe_){
print("Unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
catch (Exception ignored){}
}
protected void closeRAF(){
try{
randAccessFile.close();
}
catch (IOException ioe_){
print("Unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
catch (Exception ignored){}
}
/*----------------------------------------------------*/
/* GL READ/WRITE USB SPECIFIC */
/*----------------------------------------------------*/
private byte[] readGL(){
var readBuffer = ByteBuffer.allocateDirect(PACKET_SIZE);
var readBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled()) {
int result = LibUsb.bulkTransfer(handlerNS, IN_EP, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
switch (result) {
case LibUsb.SUCCESS:
var receivedBytes = new byte[readBufTransferred.get()];
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:
print("GL Data transfer issue [read]\n Returned: " +
LibUsb.errorName(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return null;
}
}
print("GL Execution interrupted", EMsgType.INFO);
return null;
}
private byte[] readGL_file(){
var readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask..
var readBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled() ) {
int result = LibUsb.bulkTransfer(handlerNS, IN_EP, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
switch (result) {
case LibUsb.SUCCESS:
var receivedBytes = new byte[readBufTransferred.get()];
readBuffer.get(receivedBytes);
return receivedBytes;
case LibUsb.ERROR_TIMEOUT:
continue;
default:
print("GL Data transfer issue [read]\n Returned: " +
LibUsb.errorName(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return null;
}
}
print("GL Execution interrupted", EMsgType.INFO);
return null;
}
/**
* Write new command
* */
protected boolean writeGL_PASS(String onFailureText){
if (writeToUsb(CMD_GLCO_SUCCESS)){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
protected boolean writeGL_PASS(byte[] message, String onFailureText){
var result = writeToUsb(ByteBuffer.allocate(PACKET_SIZE)
.put(CMD_GLCO_SUCCESS_FLAG)
.put(message)
.array());
if(result){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
protected boolean writeGL_PASS(List<byte[]> messages, String onFailureText){
var writeBuffer = ByteBuffer.allocate(PACKET_SIZE)
.put(CMD_GLCO_SUCCESS_FLAG);
messages.forEach(writeBuffer::put);
if (writeToUsb(writeBuffer.array())){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
protected boolean writeGL_FAIL(byte[] failurePacket, String failureMessage){
if (writeToUsb(failurePacket)){
print(failureMessage, EMsgType.WARNING);
return true;
}
print(failureMessage, 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){
//RainbowHexDump.hexDumpUTF16LE(message); // DEBUG
var writeBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled()) {
int result = LibUsb.bulkTransfer(handlerNS,
OUT_EP,
ByteBuffer.allocateDirect(message.length).put(message), // order -> BIG_ENDIAN; Don't writeBuffer.rewind();
writeBufTransferred,
1000); // TIMEOUT. 0 stands for infinite. Endpoint OUT = 0x01
switch (result){
case LibUsb.SUCCESS:
if (writeBufTransferred.get() == message.length)
return false;
else {
print("GL Data transfer issue [write]\n Requested: " +
message.length +
"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
return true;
}
case LibUsb.ERROR_TIMEOUT:
continue;
default:
print("GL Data transfer issue [write]\n Returned: " +
LibUsb.errorName(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return true;
}
}
print("GL Execution interrupted", EMsgType.INFO);
return true;
}
}

View file

@ -18,535 +18,36 @@
*/
package nsusbloader.com.usb.gl;
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 nsusbloader.com.usb.TransferModule;
import nsusbloader.com.usb.UsbErrorCodes;
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;
import static nsusbloader.com.usb.gl.Converters.*;
import static nsusbloader.com.usb.gl.Converters.longToArrLE;
/**
* GoldLeaf 1.1.1 processing
*/
public class GoldLeaf_111 extends TransferModule {
// CMD
private final static byte[] CMD_GLCO_FAILURE =
Arrays.copyOf(new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, (byte) 0xAD, (byte) 0xDE}, 4096); // used @ writeToUsb_GLCMD
private final static byte[] CMD_GLCO_SUCCESS =
new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}; // used @ writeToUsb_GLCMD
// System.out.println((356 & 0x1FF) | ((1 + 100) & 0x1FFF) << 9); // 52068 // 0x00 0x00 0xCB 0x64
private final byte[] GL_OBJECT_TYPE_FILE = new byte[]{0x01, 0x00, 0x00, 0x00};
private final byte[] GL_OBJECT_TYPE_DIR = new byte[]{0x02, 0x00, 0x00, 0x00};
private final boolean nspFilter;
private String recentPath;
private String[] recentDirs;
private String[] recentFiles;
private final String[] nspMapKeySetIndexes;
private String openReadFileNameAndPath;
private RandomAccessFile randAccessFile;
private NSSplitReader splitReader;
private final HashMap<String, BufferedOutputStream> writeFilesMap = new HashMap<>();
private long virtDriveSize;
private final HashMap<String, Long> splitFileSize = new HashMap<>();
private final boolean isWindows = System.getProperty("os.name").contains("Windows");
private final String homePath = System.getProperty("user.home") + File.separator;
// For using in CMD_SelectFile with SPEC:/ prefix
private File selectedFile;
public GoldLeaf_111(DeviceHandle handler,
LinkedHashMap<String, File> nspMap,
public class GoldLeaf_111 extends GoldLeaf_010{
public GoldLeaf_111(DeviceHandle handler, LinkedHashMap<String, File> nspMap,
CancellableRunnable task,
ILogPrinter logPrinter,
boolean nspFilter) {
super(handler, nspMap, task, logPrinter);
this.nspFilter = nspFilter;
print("=========== GoldLeaf v1.1.1 ===========\n\t" +
"VIRT:/ equals files added into the application\n\t" +
"HOME:/ equals " +homePath, EMsgType.INFO);
// Let's collect file names to the array (simplifies flow)
nspMapKeySetIndexes = nspMap.keySet().toArray(new String[0]);
// Calculate size of VIRT:/ drive
for (File nspFile : nspMap.values()) {
if (nspFile.isDirectory()) {
var subFiles = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}"));
assert subFiles != null;
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
main_loop:
while (true) { // Till user interrupted process.
int someLength1, someLength2;
var readByte = readGL();
if (readByte == null) // Issue @ readFromUsbGL method
return;
//RainbowDump.hexDumpUTF8(readByte); // DEBUG
//System.out.println("\t→ "+ GoldleafCmd.get(readByte[4]));
if (notGLCI(readByte))
continue;
switch (GoldleafCmd.get(readByte[4])) {
case GetDriveCount:
if (getDriveCount())
break main_loop;
break;
case GetDriveInfo:
if (getDriveInfo(arrToIntLE(readByte,8)))
break main_loop;
break;
case GetSpecialPathCount:
if (getSpecialPathCount())
break main_loop;
break;
case GetSpecialPath:
if (getSpecialPath(arrToIntLE(readByte,8)))
break main_loop;
break;
case GetDirectoryCount:
someLength1 = arrToIntLE(readByte, 8);
if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), true))
break main_loop;
break;
case GetFileCount:
someLength1 = arrToIntLE(readByte, 8);
if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), false))
break main_loop;
break;
case GetDirectory:
someLength1 = arrToIntLE(readByte, 8);
if (getDirectory(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12)))
break main_loop;
break;
case GetFile:
someLength1 = arrToIntLE(readByte, 8);
if (getFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12)))
break main_loop;
break;
case StatPath:
someLength1 = arrToIntLE(readByte, 8);
if (statPath(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case Rename:
someLength1 = arrToIntLE(readByte, 8);
someLength2 = arrToIntLE(readByte, 12+someLength1);
if (rename(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), // 8+4=12
new String(readByte, 12+someLength1+4, someLength2, StandardCharsets.UTF_8)))
break main_loop;
break;
case Delete:
someLength1 = arrToIntLE(readByte, 8);
if (delete(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case Create:
someLength1 = arrToIntLE(readByte, 8);
if (create(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), readByte[8]))
break main_loop;
break;
case ReadFile:
someLength1 = arrToIntLE(readByte, 8);
if (readFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8),
arrToLongLE(readByte, 12+someLength1),
arrToLongLE(readByte, 12+someLength1+8)))
break main_loop;
break;
case WriteFile:
someLength1 = arrToIntLE(readByte, 8);
if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8)))
break main_loop;
break;
case SelectFile:
if (selectFile())
break main_loop;
break;
case StartFile:
case EndFile:
if (startOrEndFile())
break main_loop;
break;
case CMD_UNKNOWN:
default:
writeGL_FAIL("GL Unknown command: "+readByte[4]+" [it's a very bad sign]");
}
}
// Close (and flush) all opened streams.
for (var bufferedOutputStream: writeFilesMap.values()){
try{
bufferedOutputStream.close();
} catch (IOException | NullPointerException ignored){}
}
closeOpenedReadFilesGl();
}
private boolean notGLCI(byte[] inputBytes){
return ! "GLCI".equals(new String(inputBytes, 0, 4, StandardCharsets.US_ASCII));
super(handler, nspMap, task, logPrinter, nspFilter);
}
/**
* 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 - failed, false - passed
* */
private boolean startOrEndFile(){
return writeGL_PASS("GL Handle 'StartFile' command");
}
/**
* Handle GetDriveCount
* 2 drives declared in current implementation
* @return true - failed, false - passed
*/
private boolean getDriveCount(){
return writeGL_PASS(intToArrLE(2),"GL Handle 'ListDrives' command");
}
/**
* Handle GetDriveInfo
* @return true - failed, false - passed
*/
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;
long totalSizeLong;
if (driveNo == 0){ // 0 == VIRTUAL DRIVE
driveLabel = "Virtual".getBytes(StandardCharsets.UTF_8);
driveLabelLen = intToArrLE(driveLabel.length);
driveLetter = "VIRT".getBytes(StandardCharsets.UTF_8);
driveLetterLen = intToArrLE(driveLetter.length);
totalFreeSpace = new byte[4];
totalSizeLong = virtDriveSize;
}
else { //1 == User home dir
driveLabel = "Home".getBytes(StandardCharsets.UTF_8);
driveLabelLen = intToArrLE(driveLabel.length);
driveLetter = "HOME".getBytes(StandardCharsets.UTF_8);
driveLetterLen = intToArrLE(driveLetter.length);
var userHomeDir = new File(System.getProperty("user.home"));
totalFreeSpace = Arrays.copyOfRange(longToArrLE(userHomeDir.getFreeSpace()), 0, 4);;
totalSizeLong = userHomeDir.getTotalSpace();
}
var totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4);
var command = Arrays.asList(
driveLabelLen,
driveLabel,
driveLetterLen,
driveLetter,
totalFreeSpace,
totalSize);
return writeGL_PASS(command, "GL Handle 'GetDriveInfo' command");
}
/**
* Handle SpecialPathCount
* Let's declare nothing. Write count of special paths
* @return true - failed, false - passed
* */
private boolean getSpecialPathCount(){
return writeGL_PASS(intToArrLE(0), "GL Handle 'SpecialPathCount' command");
}
/**
* Handle SpecialPath
* @return true - failed, false - passed
* */
private boolean getSpecialPath(int specialPathNo){
return writeGL_FAIL("GL Handle 'SpecialPath' command [not supported]");
}
/**
* Handle GetDirectoryCount & GetFileCount
* @return true - failed, false - passed
* */
private boolean getDirectoryOrFileCount(String glFileName, boolean isGetDirectoryCount) {
if (glFileName.equals("VIRT:/")) {
return isGetDirectoryCount ?
writeGL_PASS("GL Handle 'GetDirectoryCount' command") :
writeGL_PASS(intToArrLE(nspMap.size()), "GL Handle 'GetFileCount' command Count = " + nspMap.size());
}
else if (glFileName.startsWith("HOME:/")){
var path = updateHomePath(glFileName);
var pathDir = new File(path);
if (notExistsOrDirectory(pathDir))
return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [doesn't exist or not a folder] "+ pathDir);
this.recentPath = path; // Save recent dir path
var filesOrDirs = isGetDirectoryCount ?
pathDir.list(this::isDirectoryAndNotHidden) :
pathDir.list(this::isFileAndNotHidden);
// If no folders, let's say 0;
if (filesOrDirs == null)
return writeGL_PASS("GL Handle 'GetDirectoryOrFileCount' command");
// Sorting is mandatory 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
return writeGL_PASS(intToArrLE(filesOrDirs.length), "GL Handle 'GetDirectoryOrFileCount' command");
}
else if (glFileName.startsWith("SPEC:/")){
if (isGetDirectoryCount) // If dir request then 0 dirs
return writeGL_PASS("GL Handle 'GetDirectoryCount' command");
else if (selectedFile != null) // Else it's file request, if we have selected then we will report 1.
return writeGL_PASS(intToArrLE(1), "GL Handle 'GetFileCount' command Count = 1");
return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request] (file) - "+glFileName);
}
// 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) - ")+glFileName);
}
/**
* Handle GetDirectory
* @return true - failed, false - passed
* */
private boolean getDirectory(String dirName, int subDirNo){
if (dirName.startsWith("HOME:/")) {
dirName = updateHomePath(dirName);
var command = new ArrayList<byte[]>();
if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){
var dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(dirNameBytes.length));
command.add(dirNameBytes);
}
else {
var pathDir = new File(dirName);
// Make sure it's exists and it's path
if (notExistsOrDirectory(pathDir))
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(this::isDirectoryAndNotHidden);
// Check that we still don't have any fuckups
if (this.recentDirs != null && this.recentDirs.length > subDirNo){
Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER);
var dirBytesName = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(dirBytesName.length));
command.add(dirBytesName);
}
else
return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]");
}
return writeGL_PASS(command, "GL Handle 'GetDirectory' command.");
}
// VIRT:// and any other
return writeGL_FAIL("GL Handle 'GetDirectory' command for virtual drive [no folders support]");
}
/**
* Handle GetFile
* @return true - failed, false - passed
* */
private boolean getFile(String glDirName, int subDirNo){
var command = new LinkedList<byte[]>();
if (glDirName.startsWith("HOME:/")) {
var dirName = updateHomePath(glDirName);
if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){
var fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
}
else {
var pathDir = new File(dirName);
if (notExistsOrDirectory(pathDir))
writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]");
this.recentPath = dirName;
this.recentFiles = pathDir.list(this::isFileAndNotHidden);
// Check that we still don't have any fuckups
if (this.recentFiles != null && this.recentFiles.length > subDirNo){
Arrays.sort(recentFiles, String.CASE_INSENSITIVE_ORDER);
var fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
}
else
return writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]");
}
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
else if (glDirName.equals("VIRT:/") && (! nspMap.isEmpty())){ // thus nspMapKeySetIndexes also != 0
var fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
else if (glDirName.equals("SPEC:/") && (selectedFile != null)){
var fileNameBytes = selectedFile.getName().getBytes(StandardCharsets.UTF_8);
command.add(intToArrLE(fileNameBytes.length));
command.add(fileNameBytes);
return writeGL_PASS(command, "GL Handle 'GetFile' command.");
}
// any other cases
return writeGL_FAIL("GL Handle 'GetFile' command for virtual drive [no folders support?]");
}
/**
* Handle StatPath
* @return true - failed, false - passed
* */
private boolean statPath(String glFileName){
var command = new ArrayList<byte[]>();
if (glFileName.startsWith("HOME:/")){
var fileDirElement = new File(updateHomePath(glFileName));
if (fileDirElement.exists()){
if (fileDirElement.isDirectory())
command.add(GL_OBJECT_TYPE_DIR);
else {
command.add(GL_OBJECT_TYPE_FILE);
command.add(longToArrLE(fileDirElement.length()));
}
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
else if (glFileName.startsWith("VIRT:/")) {
var fileName = glFileName.replaceFirst("^.*?:/", "");
if (nspMap.containsKey(fileName)){
command.add(GL_OBJECT_TYPE_FILE); // THIS IS INT
if (nspMap.get(fileName).isDirectory())
command.add(longToArrLE(splitFileSize.get(fileName))); // YES, THIS IS LONG!;
else
command.add(longToArrLE(nspMap.get(fileName).length())); // YES, THIS IS LONG!
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
else if (glFileName.startsWith("SPEC:/")){
var fileName = glFileName.replaceFirst("^.*?:/", "");
if (selectedFile.getName().equals(fileName)){
command.add(GL_OBJECT_TYPE_FILE);
command.add(longToArrLE(selectedFile.length()));
return writeGL_PASS(command, "GL Handle 'StatPath' command for "+glFileName);
}
}
return writeGL_FAIL("GL Handle 'StatPath' command [no such path]: "+glFileName);
}
/**
* Handle 'Rename' that is actually 'mv'
* @return true - failed, false - passed
* */
private boolean rename(String glFileName, String glNewFileName){
if (glFileName.startsWith("HOME:/")){
// Prevent GL failures
this.recentPath = null;
this.recentFiles = null;
this.recentDirs = null;
var fileName = updateHomePath(glFileName);
var newFile = new File(updateHomePath(glNewFileName));
try {
if (new File(fileName).renameTo(newFile)){
return writeGL_PASS("GL Handle 'Rename' command.");
}
}
catch (SecurityException se){
return writeGL_FAIL("GL Handle 'Rename' command failed:\n\t" +se.getMessage());
}
}
// For VIRT:/ and others we don't serve requests
return writeGL_FAIL("GL Handle 'Rename' command is not supported for virtual drive, selected files," +
" if file with such name already exists in folder, read-only directories");
}
/**
* Handle 'Delete'
* @return true - failed, false - passed
* */
private boolean delete(String glFileName) {
if (! glFileName.startsWith("HOME:/"))
return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory] "+glFileName);
var file = new File(updateHomePath(glFileName));
try {
if (file.delete())
return writeGL_PASS("GL Handle 'Rename' command.");
}
catch (SecurityException ignored){} // Ah, leave it
return writeGL_FAIL("GL Handle 'Create' command [unknown drive/read-only directory]");
}
/**
* Handle 'Create'
* @param type 1 file, 2 folder
* @param glFileName full path including new file name in the end
* @return true - failed, false - passed
* */
private boolean create(String glFileName, byte type) {
if (! glFileName.startsWith("HOME:/")) // For VIRT:/ and others we don't serve requests
return writeGL_FAIL("GL Handle 'Create' command [not supported for virtual drive/wrong drive/read-only directory]"+glFileName);
var file = new File(updateHomePath(glFileName));
try {
boolean result = switch (type) {
case 1 -> file.createNewFile();
case 2 -> file.mkdir();
default -> false;
};
if (result)
return writeGL_PASS("GL Handle 'Create' command.");
}
catch (SecurityException | IOException ignored){}
return writeGL_FAIL("GL Handle 'Create' command [unknown drive/read-only directory]");
}
/**
* Handle 'ReadFile'
* @param glFileName full path including new file name in the end in format of Goldleaf
* @param offset requested offset
* @param size requested size
* @return true - failed, false - passed
* */
private boolean readFile(String glFileName, long offset, long size) {
//System.out.println("readFile "+glFileName+"\t"+offset+"\t"+size+"\n");
@Override
protected boolean readFile(String glFileName, long offset, long size) {
var fileName = glFileName.replaceFirst("^.*?:/", "");
System.out.println(fileName+" readFile "+glFileName+"\t"+offset+"\t"+size+"\n");
if (glFileName.startsWith("VIRT:/")){ // Could have split-file
// Let's find out which file requested
var fNamePath = nspMap.get(fileName).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length
@ -567,7 +68,7 @@ public class GoldLeaf_111 extends TransferModule {
openReadFileNameAndPath = fNamePath;
}
catch (IOException | NullPointerException ioe){
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
}
}
}
@ -575,14 +76,14 @@ public class GoldLeaf_111 extends TransferModule {
String filePath;
if (glFileName.startsWith("SPEC:/")) {
if (! fileName.equals(selectedFile.getName()))
return writeGL_FAIL("GL Handle 'ReadFile' command\n\trequested != selected:\n\t"
+glFileName+"\n\t"+selectedFile);
if (! fileName.equals(selectedFile.getName())) {
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\trequested != selected:\n\t"
+ glFileName + "\n\t" + selectedFile);
}
filePath = selectedFile.getAbsolutePath();
}
else {
filePath = updateHomePath(glFileName); // What requested?
filePath = decodeGlPath(glFileName); // What requested?
}
// If we don't have this file opened, let's open it
if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(filePath))) {
@ -592,7 +93,7 @@ public class GoldLeaf_111 extends TransferModule {
randAccessFile = new RandomAccessFile(filePath, "r");
openReadFileNameAndPath = filePath;
}catch (IOException | NullPointerException ioe){
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
}
}
}
@ -611,273 +112,55 @@ public class GoldLeaf_111 extends TransferModule {
}
if (bytesRead != (int) size) // Let's check that we read expected size
return writeGL_FAIL("GL Handle 'ReadFile' command [CMD]" +
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' command [CMD]" +
"\n At offset: " + offset +
"\n Requested: " + size +
"\n Received: " + bytesRead);
if (writeGL_PASS(longToArrLE(size), "GL Handle 'ReadFile' command [CMD]")) // Reporting result
return true;
if (writeToUsb(chunk)) { // Bypassing bytes we read total // FIXME: move failure message into method
print("GL Handle 'ReadFile' command", EMsgType.FAIL);
if (writeGL_PASS(longToArrLE(size), "GL Handle 'ReadFile' command [CMD]")) { // Reporting result
System.out.println("+SENT 1 -");
return true;
}
System.out.println("+SENT 1");
if (writeToUsbV100(chunk)) { // Bypassing bytes we read total // FIXME: move failure message into method
print("GL Handle 'ReadFile' command", EMsgType.FAIL);
System.out.println("+SENT 2 -");
return true;
}
System.out.println("+SENT 2");
return false;
}
catch (Exception ioe){
closeOpenedReadFilesGl();
return writeGL_FAIL("GL Handle 'ReadFile' transfer chain\n\t"+ioe.getMessage());
return writeGL_FAIL(EXCEPTION_CAUGHT, "GL Handle 'ReadFile' transfer chain\n\t"+ioe.getMessage());
}
}
/**
* Handle 'WriteFile'
* @param fileName full path including new file name in the end
* @return true - failed, false - passed
* */
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 this file being used during this session
if (! writeFilesMap.containsKey(fileName)){
try{ // If this file exists GL will take care; Otherwise, let's add it
writeFilesMap.put(fileName,
new BufferedOutputStream(new FileOutputStream(fileName, true))); // Open what we have to open
} catch (IOException ioe){
return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage());
}
}
var transferredData = readGL_file();
if (transferredData == null){
print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL);
return true;
}
try{
writeFilesMap.get(fileName).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
return writeGL_PASS("GL Handle 'WriteFile' command");
}
/**
* Handle 'SelectFile'
* @return true - failed, false - passed
* */
private boolean selectFile(){
var selectedFile = CompletableFuture.supplyAsync(() -> {
var fChooser = new FileChooser();
fChooser.setTitle(MediatorControl.INSTANCE.getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION
fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a priority.
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");
}
var selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_8);
var command = Arrays.asList(
intToArrLE(selectedFileNameBytes.length),
selectedFileNameBytes);
if (writeGL_PASS(command, "GL Handle 'SelectFile' command")) {
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); // Better not using .replaceAll() since it will consider \ as special directive
return glPath;
}
private boolean isFileAndNotHidden(File parent, String child){
var entry = new File(parent, child);
return (! entry.isDirectory()) && (nspFilter ?
child.toLowerCase().endsWith(".nsp") :
! entry.isHidden());
}
private boolean isDirectoryAndNotHidden(File parent, String child){
var dir = new File(parent, child);
return (dir.isDirectory() && ! dir.isHidden());
}
private boolean notExistsOrDirectory(File pathDir){
return (! pathDir.exists() ) || (! pathDir.isDirectory());
}
/**
* Close files opened for read/write
*/
private void closeOpenedReadFilesGl(){
if (openReadFileNameAndPath != null){
closeRAFandSplitReader();
openReadFileNameAndPath = null;
randAccessFile = null;
splitReader = null;
}
}
private void closeRAFandSplitReader(){
closeRAF();
try{
splitReader.close();
}
catch (IOException ioe_){
print("Unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
catch (Exception ignored){}
}
private void closeRAF(){
try{
randAccessFile.close();
}
catch (IOException ioe_){
print("Unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING);
}
catch (Exception ignored){}
}
/*----------------------------------------------------*/
/* GL READ/WRITE USB SPECIFIC */
/*----------------------------------------------------*/
private byte[] readGL(){
var readBuffer = ByteBuffer.allocateDirect(4096);
var readBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled()) {
int result = LibUsb.bulkTransfer(handlerNS, IN_EP, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
switch (result) {
case LibUsb.SUCCESS:
var receivedBytes = new byte[readBufTransferred.get()];
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:
print("GL Data transfer issue [read]\n Returned: " +
UsbErrorCodes.getErrCode(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return null;
}
}
print("GL Execution interrupted", EMsgType.INFO);
return null;
}
private byte[] readGL_file(){
var readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask..
var readBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled() ) {
int result = LibUsb.bulkTransfer(handlerNS, IN_EP, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
switch (result) {
case LibUsb.SUCCESS:
var receivedBytes = new byte[readBufTransferred.get()];
readBuffer.get(receivedBytes);
return receivedBytes;
case LibUsb.ERROR_TIMEOUT:
continue;
default:
print("GL Data transfer issue [read]\n Returned: " +
UsbErrorCodes.getErrCode(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return null;
}
}
print("GL Execution interrupted", EMsgType.INFO);
return null;
}
/**
* Write new command
* */
private boolean writeGL_PASS(String onFailureText){
if (writeToUsb(Arrays.copyOf(CMD_GLCO_SUCCESS, 4096))){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
private boolean writeGL_PASS(byte[] message, String onFailureText){
var result = writeToUsb(ByteBuffer.allocate(4096)
.put(CMD_GLCO_SUCCESS)
.put(message)
.array());
if(result){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
private boolean writeGL_PASS(List<byte[]> messages, String onFailureText){
var writeBuffer = ByteBuffer.allocate(4096)
.put(CMD_GLCO_SUCCESS);
messages.forEach(writeBuffer::put);
if (writeToUsb(writeBuffer.array())){
print(onFailureText, EMsgType.FAIL);
return true;
}
return false;
}
private boolean writeGL_FAIL(String failureMessage){
if (writeToUsb(CMD_GLCO_FAILURE)){
print(failureMessage, EMsgType.WARNING);
return true;
}
print(failureMessage, 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){
//RainbowHexDump.hexDumpUTF16LE(message); // DEBUG
private boolean writeToUsbV100(byte[] message) {
var writeBufTransferred = IntBuffer.allocate(1);
while (! task.isCancelled()) {
int result = LibUsb.bulkTransfer(handlerNS,
OUT_EP,
ByteBuffer.allocateDirect(message.length).put(message), // order -> BIG_ENDIAN; Don't writeBuffer.rewind();
ByteBuffer.allocateDirect(message.length)
.order(ByteOrder.LITTLE_ENDIAN)
.put(message),
writeBufTransferred,
1000); // TIMEOUT. 0 stands for infinite. Endpoint OUT = 0x01
1000);
switch (result){
case LibUsb.SUCCESS:
if (writeBufTransferred.get() == message.length)
return false;
else {
print("GL Data transfer issue [write]\n Requested: " +
message.length +
"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
return true;
}
print("GL Data transfer issue [write]\n Requested: " +
message.length +
"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
return true;
case LibUsb.ERROR_TIMEOUT:
print("GL Data transfer issue [write]", EMsgType.WARNING);
continue;
default:
print("GL Data transfer issue [write]\n Returned: " +
UsbErrorCodes.getErrCode(result) +
LibUsb.errorName(result) +
"\n GL Execution stopped", EMsgType.FAIL);
return true;
}