diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index ceb9542..e4ae860 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -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+"}; + public static final String[] GOLDLEAF_SUPPORTED_VERSIONS = {"v0.5", "v0.7.x", "v0.8-0.9", "v0.10-1.1.0", "v1.1.1+"}; private static final Font DEFAULT_FONT = Font.getDefault(); private AppPreferences(){ diff --git a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java index 0109258..03ad1c4 100644 --- a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java +++ b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java @@ -352,8 +352,8 @@ public class BinToAsmPrinter { int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + offset) & 0xfffff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (" + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (" + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), conditionalJumpLocation, (conditionalJumpLocation + 0x100)); } @@ -362,8 +362,8 @@ public class BinToAsmPrinter { int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + offset) & 0xfffff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CBNZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (" + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CBNZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (" + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), conditionalJumpLocation, (conditionalJumpLocation + 0x100)); } @@ -372,8 +372,8 @@ public class BinToAsmPrinter { int conditionalJumpLocationPatch = ((instructionExpression & 0x3ffffff) * 4 + offset) & 0xfffff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " B " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " B " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); } @@ -382,8 +382,8 @@ public class BinToAsmPrinter { int conditionalJumpLocationPatch = ((instructionExpression & 0x3ffffff) * 4 + offset) & 0xfffff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " BL " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " BL " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); } @@ -392,15 +392,15 @@ public class BinToAsmPrinter { int sfHw = (instructionExpression >> 22 & 1); return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " MOV " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " MOV " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, (sfHw == 0) ? "w" : "x", (instructionExpression & 0b11111), imm16); } private static String printNOPSimplified(int instructionExpression, int offset){ return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " NOP " + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression); + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " NOP " + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression); } private static String printTBZSimplified(int instructionExpression, int offset){ @@ -410,8 +410,8 @@ public class BinToAsmPrinter { int label = offset + (instructionExpression >> 5 & 0x3fff) * 4; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " TBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ", " + ANSI_PURPLE + "%x" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " TBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ", " + ANSI_PURPLE + "%x" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, (xwSelector == 0) ? "w" : "x", Rt, imm, label); } @@ -419,15 +419,15 @@ public class BinToAsmPrinter { int conditionalJumpLocation = ((instructionExpression >> 4 & 0b1111111111111111111) * 4 + offset) & 0xfffff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " B.%s " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " B.%s " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, getBConditionalMarker(instructionExpression & 0xf), conditionalJumpLocation, (conditionalJumpLocation + 0x100)); } private static String printImTooLazy(String name, int instructionExpression, int offset){ return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " "+name+" . . . \n"+ ANSI_RESET, - offset, Integer.reverseBytes(instructionExpression), instructionExpression); + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " "+name+" . . . \n"+ ANSI_RESET, + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression); } private static String printSUBSimplified(int instructionExpression, int offset){ @@ -437,8 +437,8 @@ public class BinToAsmPrinter { int imm12 = instructionExpression >> 10 & 0xFFF; // unsigned only return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " SUB (imm) " + ANSI_GREEN + "%s%d, " + ANSI_BLUE + "%s%d, #0x%x" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " SUB (imm) " + ANSI_GREEN + "%s%d, " + ANSI_BLUE + "%s%d, #0x%x" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, wx, Rt, wx, Rn, imm12); } @@ -448,8 +448,8 @@ public class BinToAsmPrinter { int Rd = instructionExpression & 0x1F; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " MOV (reg) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "%s%d" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " MOV (reg) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "%s%d" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, sfHw, Rm, sfHw, Rd); } @@ -458,8 +458,8 @@ public class BinToAsmPrinter { int imm = instructionExpression >> 10 & 0xFFF; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMN " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMN " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, (instructionExpression >> 31 == 0) ? "w" : "x", Rn, imm); } @@ -471,8 +471,8 @@ public class BinToAsmPrinter { return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDR(imm) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "[%s%d, #0x%x]" + ANSI_RESET + " (note: unsigned offset)\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDR(imm) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "[%s%d, #0x%x]" + ANSI_RESET + " (note: unsigned offset)\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, wx, Rt, wx, Rn, imm12); } private static String printLRDBImmUnsignSimplified(int instructionExpression, int offset){ @@ -482,8 +482,8 @@ public class BinToAsmPrinter { int imm12 = (instructionExpression >> 10 & 0xFFF) * 8; // unsigned only return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDRB(imm) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "[%s%d, #0x%x]" + ANSI_RESET + " (note: unsigned offset)\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDRB(imm) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "[%s%d, #0x%x]" + ANSI_RESET + " (note: unsigned offset)\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, wx, Rt, wx, Rn, imm12); } @@ -494,9 +494,9 @@ public class BinToAsmPrinter { int LSL = (instructionExpression >> 22 & 0b1) == 1 ? 12 : 0; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMP " + ANSI_GREEN + sf + "%d," + + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMP " + ANSI_GREEN + sf + "%d," + ANSI_BLUE + "0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ") " + ANSI_PURPLE + "LSL #%d" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, Rn, conditionalJumpLocation, (conditionalJumpLocation + 0x100), LSL); @@ -527,9 +527,9 @@ public class BinToAsmPrinter { } return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMP (sr) " + ANSI_GREEN + sf + "%d," + + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " CMP (sr) " + ANSI_GREEN + sf + "%d," + ANSI_BLUE + sf + "%d " + ANSI_BLUE + LSLStr + ANSI_PURPLE + " %d" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, Rn, Rm, imm6); } @@ -544,23 +544,23 @@ public class BinToAsmPrinter { imm = instructionExpression >> 10 & 0x1fff; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " AND " + ANSI_GREEN + sf + "%d, " + ANSI_BLUE + + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " AND " + ANSI_GREEN + sf + "%d, " + ANSI_BLUE + sf + "%d" + ANSI_PURPLE + " # ??? 0b%s " + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, Rn, Rd, Converter.intToBinaryString(imm)); + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, Rn, Rd, Converter.intToBinaryString(imm)); } private static String printRetSimplified(int instructionExpression, int offset){ int Xn = (instructionExpression >> 5) & 0x1F; return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " RET " + ANSI_GREEN + " X%d" + ANSI_RESET + "\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression, Xn == 0 ? 30 : Xn); + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " RET " + ANSI_GREEN + " X%d" + ANSI_RESET + "\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression, Xn == 0 ? 30 : Xn); } private static String printUnknownSimplified(int instructionExpression, int offset){ return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ??? 0b"+ANSI_RESET+ Converter.intToBinaryString(instructionExpression) +"\n", - offset, Integer.reverseBytes(instructionExpression), instructionExpression); + "%06x 7100%06x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ??? 0b"+ANSI_RESET+ Converter.intToBinaryString(instructionExpression) +"\n", + offset+0x100, offset,Integer.reverseBytes(instructionExpression), instructionExpression); } private static String intAsBinString(int number) { diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java index cd2b528..121bd2f 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -148,7 +148,7 @@ public class FsPatch { private void findAllOffsets() throws Exception{ this.wizard = new HeuristicFsWizard(_textSection); String errorsAndNotes = wizard.getErrorsAndNotes(); - if (errorsAndNotes.length() > 0) + if (! errorsAndNotes.isEmpty()) logPrinter.print(errorsAndNotes, EMsgType.WARNING); } private void mkDirs(){ diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_111.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_111.java new file mode 100644 index 0000000..37ae87b --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_111.java @@ -0,0 +1,1110 @@ +/* + 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 . +*/ +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.apache.logging.log4j.core.util.IOUtils; +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_111 extends TransferModule { + private final 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 final String[] nspMapKeySetIndexes; + + private String openReadFileNameAndPath; + private RandomAccessFile randAccessFile; + private NSSplitReader splitReader; + + private final HashMap writeFilesMap = new HashMap<>(); + private long virtDriveSize; + private final HashMap 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; + + private final CancellableRunnable task; + + private enum GL_CMD { + CMD_GetDriveCount((byte) 1), + CMD_GetDriveInfo((byte) 2), + CMD_StatPath((byte) 3), + CMD_GetFileCount((byte) 4), + CMD_GetFile((byte) 5), + CMD_GetDirectoryCount((byte) 6), + CMD_GetDirectory((byte) 7), + CMD_StartFile((byte) 8), + CMD_ReadFile((byte) 9), + CMD_WriteFile((byte) 10), + CMD_EndFile((byte) 11), + CMD_Create((byte) 12), + CMD_Delete((byte) 13), + CMD_Rename((byte) 14), + CMD_GetSpecialPathCount((byte) 15), + CMD_GetSpecialPath((byte) 16), + CMD_SelectFile((byte) 17), + CMD_UNKNOWN((byte) 255); + + private final byte id; + + GL_CMD(byte id) { + this.id = id; + } + + public static GL_CMD get(byte id) { + for(GL_CMD cmd : values()) { + if(cmd.id == id) + return cmd; + } + return CMD_UNKNOWN; + } + } + + GoldLeaf_111(DeviceHandle handler, + LinkedHashMap nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter) { + super(handler, nspMap, task, logPrinter); + + this.task = task; + this.nspFilterForGl = nspFilter; + + print("=========== GoldLeaf v1.1.1 ===========\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 + int i = 0; + nspMapKeySetIndexes = new String[nspMap.size()]; + for (String fileName : nspMap.keySet()) + nspMapKeySetIndexes[i++] = fileName; + + // Calculate size of VIRT:/ drive + for (File nspFile : nspMap.values()){ + if (nspFile.isDirectory()) { + var subFiles = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}")); + long size = 0; + assert subFiles != null; + 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; + + byte[] readByte = readGL(); + + if (readByte == null) // Issue @ readFromUsbGL method + return; + + //RainbowHexDump.hexDumpUTF16LE(readByte); // DEBUG + //System.out.println("CHOICE: "+readByte[4]); // DEBUG + // Arrays.equals(Arrays.copyOfRange(readByte, 0,4), CMD_GLCI) + + if (notGLCI(readByte)) + continue; + + switch (GL_CMD.get(readByte[4])) { + 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); + if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), true)) + break main_loop; + break; + case CMD_GetFileCount: + someLength1 = arrToIntLE(readByte, 8); + if (getDirectoryOrFileCount(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), false)) + break main_loop; + break; + case CMD_GetDirectory: + someLength1 = arrToIntLE(readByte, 8); + if (getDirectory(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12))) + break main_loop; + break; + case CMD_GetFile: + someLength1 = arrToIntLE(readByte, 8); + if (getFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength1+12))) + break main_loop; + break; + case CMD_StatPath: + someLength1 = arrToIntLE(readByte, 8); + if (statPath(new String(readByte, 12, someLength1, StandardCharsets.UTF_8))) + break main_loop; + break; + case CMD_Rename: + someLength1 = arrToIntLE(readByte, 12); + someLength2 = arrToIntLE(readByte, 16+someLength1); + if (rename(new String(readByte, 16, someLength1, StandardCharsets.UTF_8), + new String(readByte, 16+someLength1+4, someLength2, StandardCharsets.UTF_8))) + break main_loop; + break; + case CMD_Delete: + someLength1 = arrToIntLE(readByte, 12); + if (delete(new String(readByte, 16, someLength1, StandardCharsets.UTF_8))) + break main_loop; + break; + case CMD_Create: + someLength1 = arrToIntLE(readByte, 12); + if (create(new String(readByte, 16, someLength1, StandardCharsets.UTF_8), readByte[8])) + break main_loop; + break; + case CMD_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 CMD_WriteFile: + someLength1 = arrToIntLE(readByte, 8); + //if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8), arrToLongLE(readByte, 12+someLength1))) + if (writeFile(new String(readByte, 12, someLength1, StandardCharsets.UTF_8))) + 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.isEmpty()){ + for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ + try{ + fBufOutStream.close(); + } catch (IOException | NullPointerException ignored){} + } + } + closeOpenedReadFilesGl(); + } + private boolean notGLCI(byte[] inputBytes){ + return ! "GLCI".equals(new String(inputBytes, 0, 4, StandardCharsets.US_ASCII)); + } + + /** + * 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 - failed, false - passed + * */ + private boolean startOrEndFile(){ + if (writeGL_PASS()){ + print("GL Handle 'StartFile' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * Handle GetDriveCount + * @return true - failed, false - passed + */ + private boolean getDriveCount(){ + // Let's declare 2 drives + byte[] drivesCnt = intToArrLE(2); //2 + // Write count of drives + if (writeGL_PASS(drivesCnt)) { + print("GL Handle 'ListDrives' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * 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, + totalSize; + long totalSizeLong; + + // 0 == VIRTUAL DRIVE + if (driveNo == 0){ + driveLabel = "Virtual".getBytes(StandardCharsets.UTF_8); + driveLabelLen = intToArrLE(driveLabel.length); + driveLetter = "VIRT".getBytes(StandardCharsets.UTF_8); // TODO: Consider moving to class field declaration + driveLetterLen = intToArrLE(driveLetter.length);// since GL 0.7 + totalFreeSpace = new byte[4]; + totalSizeLong = virtDriveSize; + } + else { //1 == User home dir + driveLabel = "Home".getBytes(StandardCharsets.UTF_8); + driveLabelLen = intToArrLE(driveLabel.length);// since GL 0.7 + driveLetter = "HOME".getBytes(StandardCharsets.UTF_8); + driveLetterLen = intToArrLE(driveLetter.length);// since GL 0.7 + File userHomeDir = new File(System.getProperty("user.home")); + long totalFreeSpaceLong = userHomeDir.getFreeSpace(); + totalFreeSpace = Arrays.copyOfRange(longToArrLE(totalFreeSpaceLong), 0, 4);; + totalSizeLong = userHomeDir.getTotalSpace(); + } + totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); + + List command = new LinkedList<>(); + command.add(driveLabelLen); + command.add(driveLabel); + command.add(driveLetterLen); + command.add(driveLetter); + command.add(totalFreeSpace); + command.add(totalSize); + + if (writeGL_PASS(command)) { + print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); + return true; + } + + return false; + } + /** + * Handle SpecialPathCount + * @return true - failed, false - passed + * */ + private boolean getSpecialPathCount(){ + // Let's declare nothing =) + byte[] specialPathCnt = intToArrLE(0); + // Write count of special paths + if (writeGL_PASS(specialPathCnt)) { + print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * 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 path, boolean isGetDirectoryCount) { + if (path.equals("VIRT:/")) { + if (isGetDirectoryCount){ + if (writeGL_PASS()) { + print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + return true; + } + } + else { + if (writeGL_PASS(intToArrLE(nspMap.size()))) { + 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()) { + print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + return true; + } + return false; + } + // 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 + if (writeGL_PASS(intToArrLE(filesOrDirs.length))) { + 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()) { + 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))) { + 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 - failed, false - passed + * */ + private boolean getDirectory(String dirName, int subDirNo){ + if (dirName.startsWith("HOME:/")) { + dirName = updateHomePath(dirName); + + List command = new LinkedList<>(); + + if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){ + byte[] dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8); + + command.add(intToArrLE(dirNameBytes.length)); + 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_8); + command.add(intToArrLE(dirBytesName.length)); + command.add(dirBytesName); + } + else + return writeGL_FAIL("GL Handle 'GetDirectory' command [doesn't exist or not a folder]"); + } + + if (writeGL_PASS(command)) { + 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 - failed, false - passed + * */ + private boolean getFile(String dirName, int subDirNo){ + List command = new LinkedList<>(); + + if (dirName.startsWith("HOME:/")) { + dirName = updateHomePath(dirName); + + if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ + byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8); + + command.add(intToArrLE(fileNameBytes.length)); //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_8); + command.add(intToArrLE(fileNameBytes.length)); //Since GL 0.7 + command.add(fileNameBytes); + } + else + return writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); + } + + if (writeGL_PASS(command)) { + print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + else if (dirName.equals("VIRT:/")){ + if (! nspMap.isEmpty()){ // therefore nspMapKeySetIndexes also != 0 + var fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_8); + command.add(intToArrLE(fileNameBytes.length)); + command.add(fileNameBytes); + if (writeGL_PASS(command)) { + 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_8); + command.add(intToArrLE(fileNameBytes.length)); + command.add(fileNameBytes); + if (writeGL_PASS(command)) { + 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 - failed, false - passed + * */ + private boolean statPath(String filePath){ + var command = new ArrayList(); + + if (filePath.startsWith("HOME:/")){ + filePath = updateHomePath(filePath); + + var 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)) { + 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)) { + 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)) { + 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 - failed, false - passed + * */ + 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()) { + 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 - failed, false - passed + * */ + private boolean delete(String fileName) { + if (fileName.startsWith("HOME:/")) { + fileName = updateHomePath(fileName); + + File fileToDel = new File(fileName); + try { + if (fileToDel.delete()){ + if (writeGL_PASS()) { + 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 - failed, false - passed + * */ + 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()) { + print("GL Handle 'Create' command.", EMsgType.FAIL); + return true; + } + //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 - failed, false - passed + * */ + 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))) { + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + return true; + } + // Let's bypass bytes we read total + if (writeToUsb(chunk)) { + 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 many 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))) { + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + return true; + } + // Let's bypass bytes we read total + if (writeToUsb(chunk)) { + print("GL Handle 'ReadFile' command", EMsgType.FAIL); + return true; + } + return false; + } + } + catch (Exception ioe){ + try{ + randAccessFile.close(); + } + catch (NullPointerException ignored){} + catch (IOException ioe_){ + print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); + } + try{ + splitReader.close(); + } + catch (NullPointerException ignored){} + catch (IOException ioe_){ + 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 - failed, false - passed + * */ + //@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.isEmpty() || (! 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){ + 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()) { + print("GL Handle 'WriteFile' command", EMsgType.FAIL); + return true; + } + return false; + } + + /** + * Handle 'SelectFile' + * @return true - failed, false - passed + * */ + private boolean selectFile(){ + File selectedFile = CompletableFuture.supplyAsync(() -> { + FileChooser 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"); + } + + List command = new LinkedList<>(); + byte[] selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_8); + command.add(intToArrLE(selectedFileNameBytes.length)); + command.add(selectedFileNameBytes); + if (writeGL_PASS(command)) { + 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){ + String updatedGlPath; + if (isWindows) + updatedGlPath = glPath.replaceAll("/", "\\\\"); + updatedGlPath = homePath + glPath.substring(6); // Do not use replaceAll since it will consider \ as special directive + return updatedGlPath; + } + /** + * Convert INT (Little endian) value to bytes-array representation + * */ + private byte[] intToArrLE(int value){ + return ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN) + .putInt(value) + .array(); + } + /** + * Convert LONG (Little endian) value to bytes-array representation + * */ + private byte[] longToArrLE(long value){ + return ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN) + .putLong(value) + .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(){ + var readBuffer = ByteBuffer.allocateDirect(4096); + var readBufTransferred = IntBuffer.allocate(1); + + while (! task.isCancelled()) { + int 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: + 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, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + switch (result) { + case LibUsb.SUCCESS: + byte[] 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. Shitty implementation. + * */ + private boolean writeGL_PASS(byte[] message){ + return writeToUsb( + ByteBuffer.allocate(4096) + .put(CMD_GLCO_SUCCESS) + .put(message) + .array()); + } + private boolean writeGL_PASS(){ + return writeToUsb(Arrays.copyOf(CMD_GLCO_SUCCESS, 4096)); + } + private boolean writeGL_PASS(List messages){ + var writeBuffer = ByteBuffer.allocate(4096) + .put(CMD_GLCO_SUCCESS); + messages.forEach(writeBuffer::put); + return writeToUsb(writeBuffer.array()); + } + + private boolean writeGL_FAIL(String reportToUImsg){ + if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ + print(reportToUImsg, EMsgType.WARNING); + return true; + } + 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){ + //System.out.println(">"); + //RainbowHexDump.hexDumpUTF16LE(message); // DEBUG + var writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; + writeBuffer.put(message); // Don't do writeBuffer.rewind(); + var writeBufTransferred = IntBuffer.allocate(1); + + while (! task.isCancelled()) { + int 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 { + 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: " + + UsbErrorCodes.getErrCode(result) + + "\n GL Execution stopped", EMsgType.FAIL); + return true; + } + } + print("GL Execution interrupted", EMsgType.INFO); + return true; + } +} diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index 76063d4..e426d0c 100644 --- a/src/main/java/nsusbloader/com/usb/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2025 Dmitry Isaenko This file is part of NS-USBloader. @@ -66,7 +66,10 @@ public class UsbCommunications extends CancellableRunnable { case "TinFoil": module = new TinFoil(handler, nspMap, this, logPrinter); break; - case "GoldLeafv0.10+": + case "GoldLeafv1.1.1+": + module = new GoldLeaf_111(handler, nspMap, this, logPrinter, nspFilterForGl); + break; + case "GoldLeafv0.10-1.1.0": module = new GoldLeaf_010(handler, nspMap, this, logPrinter, nspFilterForGl); break; case "GoldLeafv0.8-0.9": diff --git a/src/test/java/integration/EsIntegrationTest.java b/src/test/java/integration/EsIntegrationTest.java index 8fb868c..dc13b06 100644 --- a/src/test/java/integration/EsIntegrationTest.java +++ b/src/test/java/integration/EsIntegrationTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.*; import java.io.File; import java.util.Arrays; -@Disabled +//@Disabled public class EsIntegrationTest { static String pathToFirmware; static String pathToFirmwares; @@ -22,18 +22,17 @@ public class EsIntegrationTest { pathToKeysFile = environment.getProdkeysLocation(); saveTo = environment.getSaveToLocation() + File.separator + "ES_LPR"; pathToFirmwares = environment.getFirmwaresLocation(); - pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 17.0.0"; + pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 19.0.1"; } @DisplayName("ES Integration validation - everything") @Test void makeEss() throws Exception{ - File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> { - return s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*"); - //return s.matches("^Firmware 10.0.1.*"); - }); + File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> + s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*")); assert fwDirs != null; Arrays.sort(fwDirs); + Arrays.stream(fwDirs).forEach(System.out::println); for (File dir : fwDirs){ EsPatchMaker esPatchMaker = new EsPatchMaker(dir.getAbsolutePath(), pathToKeysFile, saveTo); Thread workThread = new Thread(esPatchMaker); @@ -42,6 +41,7 @@ public class EsIntegrationTest { } } + @Disabled @DisplayName("ES Integration validation - one particular firmware") @Test void makeEs() throws Exception{ diff --git a/src/test/java/integration/FsIntegrationTest.java b/src/test/java/integration/FsIntegrationTest.java index f56544c..a68530d 100644 --- a/src/test/java/integration/FsIntegrationTest.java +++ b/src/test/java/integration/FsIntegrationTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; import java.io.File; import java.util.Arrays; -@Disabled +//@Disabled public class FsIntegrationTest { static String pathToFirmware; static String pathToFirmwares; @@ -25,19 +25,17 @@ public class FsIntegrationTest { pathToKeysFile = environment.getProdkeysLocation(); saveTo = environment.getSaveToLocation() + File.separator + "FS_LPR"; pathToFirmwares = environment.getFirmwaresLocation(); - pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 17.0.0"; + pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 15.0.0"; } @DisplayName("FS Integration validation - everything") @Test void makeFss() throws Exception{ - File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> { - return (s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*") && ! s.endsWith(".zip")); - //return s.matches("^Firmware 10.0.1.*"); - }); + File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> + s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*") && ! s.endsWith(".zip")); assert fwDirs != null; Arrays.sort(fwDirs); - + Arrays.stream(fwDirs).forEach(System.out::println); for (File dir : fwDirs){ System.out.println("\n\t\t\t"+dir.getName()); FsPatchMaker fsPatchMaker = new FsPatchMaker(dir.getAbsolutePath(), pathToKeysFile, saveTo); @@ -47,6 +45,7 @@ public class FsIntegrationTest { } } + @Disabled @DisplayName("FS Integration validation - one particular firmware") @Test void makeFs() throws Exception{ diff --git a/src/test/java/integration/LoaderIntegrationTest.java b/src/test/java/integration/LoaderIntegrationTest.java index 25a448a..aa9956a 100644 --- a/src/test/java/integration/LoaderIntegrationTest.java +++ b/src/test/java/integration/LoaderIntegrationTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; import java.io.File; -@Disabled +//@Disabled public class LoaderIntegrationTest { static String pathToAtmo; static String saveTo;