diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a06f84e..f48f330 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,6 +13,7 @@ on: branches: [ "workflow" ] pull_request: branches: [ "workflow" ] + workflow_dispatch: jobs: build: diff --git a/.woodpecker/woodpecker.yml b/.woodpecker/woodpecker.yml index 11eea07..5d4730f 100644 --- a/.woodpecker/woodpecker.yml +++ b/.woodpecker/woodpecker.yml @@ -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 diff --git a/README.md b/README.md index 91f8aa2..ed626c9 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,14 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Japanese by [kuragehime](https://github.com/kuragehimekurara1) * Ryukyuan languages by [kuragehime](https://github.com/kuragehimekurara1) * Turkish language by [Erimsaholut](https://github.com/Erimsaholut) +* Serbian (Latin) translation [BlytheScythe](https://github.com/BlytheScythe) * Angelo Elias Dalzotto makes packages in AUR * Phoenix[Msc] provides his shiny Mac M1 for debug ### 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 @@ -77,7 +78,9 @@ 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.1.0 | none | +| v1.1.1 | v7.3+ | where '+' means 'any next NS-USBloader version'. @@ -247,7 +250,7 @@ We have this situation because of weird behaviour inside usb4java library used i If you want to see this app translated to your language, go grab [this file](https://github.com/developersu/ns-usbloader/blob/master/src/main/resources/locale.properties) and translate it. -Upload somewhere (create PR, use pastebin/google drive/whatever else). [Create new issue](https://github.com/developersu/ns-usbloader/issues) and post a link. I'll grab it and add. +If you're familiar with pull request, go ahead and create it! No worries it you are not. Just upload somewhere (like pastebin/google drive/whatever else). [Create new issue](https://github.com/developersu/ns-usbloader/issues) and post a link. I'll grab it and add. To convert files of any locale to readable format (and vise-versa) you can use this site [https://itpro.cz/juniconv/](https://itpro.cz/juniconv/) diff --git a/pom.xml b/pom.xml index 82ee300..750b8a8 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ NS-USBloader ns-usbloader - 7.2 + 7.3 https://redrise.ru NS multi-tool @@ -50,7 +50,7 @@ UTF-8 yyyyMMdd.HHmmss 19.0.2.1 - 11 + 17 @@ -205,7 +205,7 @@ maven-compiler-plugin 3.10.1 - 11 + 17 @@ -274,7 +274,7 @@ %PWD%/jdk - 11.0.0 + 17.0.0 ${project.version}.0.0 diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index ceb9542..9124a3c 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.0.0", "v1.1.1"}; private static final Font DEFAULT_FONT = Font.getDefault(); private AppPreferences(){ diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 364cb08..371c55d 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -395,7 +395,7 @@ public class GamesController implements Initializable, ISubscriber { // If USB selected if (isGoldLeaf()){ final SettingsBlockGoldleafController goldleafSettings = settings.getGoldleafSettings(); - usbNetCommunications = new UsbCommunications(nspToUpload, "GoldLeaf" + goldleafSettings.getGlVer(), goldleafSettings.getNSPFileFilterForGL()); + usbNetCommunications = new UsbCommunications(nspToUpload, "GoldLeaf " + goldleafSettings.getGlVer(), goldleafSettings.getNSPFileFilterForGL()); } else { if (getSelectedNetUsb().equals("USB")){ 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/cli/GoldLeafCli.java b/src/main/java/nsusbloader/cli/GoldLeafCli.java index 27f6f65..f7dcfde 100644 --- a/src/main/java/nsusbloader/cli/GoldLeafCli.java +++ b/src/main/java/nsusbloader/cli/GoldLeafCli.java @@ -125,7 +125,7 @@ public class GoldLeafCli { private void runGoldLeafBackend() throws InterruptedException { Runnable task = new UsbCommunications(filesList, - "GoldLeaf"+goldLeafVersion, + "GoldLeaf "+goldLeafVersion, filterForNsp); Thread thread = new Thread(task); thread.setDaemon(true); diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java deleted file mode 100644 index 3f420a0..0000000 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java +++ /dev/null @@ -1,1130 +0,0 @@ -/* - Copyright 2019-2024 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.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_010 extends TransferModule { - private boolean nspFilterForGl; - - // CMD - private final byte[] CMD_GLCO_SUCCESS = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}; // used @ writeToUsb_GLCMD - private final byte[] CMD_GLCO_FAILURE = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, (byte) 0xAD, (byte) 0xDE}; // used @ writeToUsb_GLCMD TODO: TEST - - // System.out.println((356 & 0x1FF) | ((1 + 100) & 0x1FFF) << 9); // 52068 // 0x00 0x00 0xCB 0x64 - private final byte[] GL_OBJ_TYPE_FILE = new byte[]{0x01, 0x00, 0x00, 0x00}; - private final byte[] GL_OBJ_TYPE_DIR = new byte[]{0x02, 0x00, 0x00, 0x00}; - - private String recentPath = null; - private String[] recentDirs = null; - private String[] recentFiles = null; - - private String[] nspMapKeySetIndexes; - - private String openReadFileNameAndPath; - private RandomAccessFile randAccessFile; - private NSSplitReader splitReader; - - private HashMap writeFilesMap; - private long virtDriveSize; - private HashMap splitFileSize; - - private final boolean isWindows; - private final String homePath; - // For using in CMD_SelectFile with SPEC:/ prefix - private File selectedFile; - - private final CancellableRunnable task; - - GoldLeaf_010(DeviceHandle handler, - LinkedHashMap nspMap, - CancellableRunnable task, - ILogPrinter logPrinter, - boolean nspFilter) - { - super(handler, nspMap, task, logPrinter); - - this.task = task; - - final byte CMD_GetDriveCount = 1; - final byte CMD_GetDriveInfo = 2; - final byte CMD_StatPath = 3; - final byte CMD_GetFileCount = 4; - final byte CMD_GetFile = 5; - final byte CMD_GetDirectoryCount = 6; - final byte CMD_GetDirectory = 7; - final byte CMD_StartFile = 8; // 1 -open read RAF; 2 open write RAF; 3 open write RAF and seek to EOF (???). - final byte CMD_ReadFile = 9; - final byte CMD_WriteFile = 10; - final byte CMD_EndFile = 11; // 1 - closed read RAF; 2 close write RAF. - final byte CMD_Create = 12; - final byte CMD_Delete = 13; - final byte CMD_Rename = 14; - final byte CMD_GetSpecialPathCount = 15; - final byte CMD_GetSpecialPath = 16; - final byte CMD_SelectFile = 17; - - final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; - - this.nspFilterForGl = nspFilter; - - print("=========== GoldLeaf v0.10 ===========\n\t" + - "VIRT:/ equals files added into the application\n\t" + - "HOME:/ equals " - +System.getProperty("user.home"), EMsgType.INFO); - - // Let's collect file names to the array to simplify our life - writeFilesMap = new HashMap<>(); - int i = 0; - nspMapKeySetIndexes = new String[nspMap.size()]; - for (String fileName : nspMap.keySet()) - nspMapKeySetIndexes[i++] = fileName; - - isWindows = System.getProperty("os.name").contains("Windows"); - - homePath = System.getProperty("user.home")+File.separator; - - splitFileSize = new HashMap<>(); - - // Calculate size of VIRT:/ drive - for (File nspFile : nspMap.values()){ - if (nspFile.isDirectory()) { - File[] subFiles = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}")); - long size = 0; - for (File subFile : subFiles) // Validated by parent class - size += subFile.length(); - virtDriveSize += size; - splitFileSize.put(nspFile.getName(), size); - } - else - virtDriveSize += nspFile.length(); - } - - // Go parse commands - byte[] readByte; - int someLength1, - someLength2; - main_loop: - while (true) { // Till user interrupted process. - readByte = readGL(); - - if (readByte == null) // Issue @ readFromUsbGL method - return; - - //RainbowHexDump.hexDumpUTF16LE(readByte); // DEBUG - //System.out.println("CHOICE: "+readByte[4]); // DEBUG - - if (Arrays.equals(Arrays.copyOfRange(readByte, 0,4), CMD_GLCI)) { - switch (readByte[4]) { - case CMD_GetDriveCount: - if (getDriveCount()) - break main_loop; - break; - case CMD_GetDriveInfo: - if (getDriveInfo(arrToIntLE(readByte,8))) - break main_loop; - break; - case CMD_GetSpecialPathCount: - if (getSpecialPathCount()) - break main_loop; - break; - case CMD_GetSpecialPath: - if (getSpecialPath(arrToIntLE(readByte,8))) - break main_loop; - break; - case CMD_GetDirectoryCount: - someLength1 = arrToIntLE(readByte, 8); - 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.size() != 0){ - for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ - try{ - fBufOutStream.close(); - }catch (IOException | NullPointerException ignored){} - } - } - closeOpenedReadFilesGl(); - } - - /** - * Close files opened for read/write - */ - private void closeOpenedReadFilesGl(){ - if (openReadFileNameAndPath != null){ // Perfect time to close our opened files - try{ - randAccessFile.close(); - } - catch (IOException | NullPointerException ignored){} - try{ - splitReader.close(); - } - catch (IOException | NullPointerException ignored){} - openReadFileNameAndPath = null; - randAccessFile = null; - splitReader = null; - } - } - /** - * Handle StartFile & EndFile - * NOTE: It's something internal for GL and used somehow by GL-PC-app, so just ignore this, at least for v0.8. - * @return true if failed - * false if everything is ok - * */ - private boolean startOrEndFile(){ - if (writeGL_PASS()){ - print("GL Handle 'StartFile' command", EMsgType.FAIL); - return true; - } - return false; - } - /** - * Handle GetDriveCount - * @return true if failed - * false if everything is ok - */ - private boolean getDriveCount(){ - // Let's declare 2 drives - byte[] drivesCnt = intToArrLE(2); //2 - // Write count of drives - if (writeGL_PASS(drivesCnt)) { - print("GL Handle 'ListDrives' command", EMsgType.FAIL); - return true; - } - return false; - } - /** - * Handle GetDriveInfo - * @return true if failed - * false if everything is ok - */ - private boolean getDriveInfo(int driveNo){ - if (driveNo < 0 || driveNo > 1){ - return writeGL_FAIL("GL Handle 'GetDriveInfo' command [no such drive]"); - } - - byte[] driveLabel, - driveLabelLen, - driveLetter, - driveLetterLen, - totalFreeSpace, - totalSize; - long totalSizeLong; - - // 0 == VIRTUAL DRIVE - if (driveNo == 0){ - driveLabel = "Virtual".getBytes(StandardCharsets.UTF_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 if failed - * false if everything is ok - * */ - private boolean getSpecialPathCount(){ - // Let's declare nothing =) - byte[] specialPathCnt = intToArrLE(0); - // Write count of special paths - if (writeGL_PASS(specialPathCnt)) { - print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); - return true; - } - return false; - } - /** - * Handle SpecialPath - * @return true if failed - * false if everything is ok - * */ - private boolean getSpecialPath(int specialPathNo){ - return writeGL_FAIL("GL Handle 'SpecialPath' command [not supported]"); - } - /** - * Handle GetDirectoryCount & GetFileCount - * @return true if failed - * false if everything is ok - * */ - private boolean getDirectoryOrFileCount(String path, boolean isGetDirectoryCount) { - if (path.equals("VIRT:/")) { - if (isGetDirectoryCount){ - if (writeGL_PASS()) { - 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 if failed - * false if everything is ok - * */ - private boolean getDirectory(String dirName, int subDirNo){ - if (dirName.startsWith("HOME:/")) { - dirName = updateHomePath(dirName); - - List command = new LinkedList<>(); - - if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){ - byte[] dirNameBytes = recentDirs[subDirNo].getBytes(StandardCharsets.UTF_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 if failed - * false if everything is ok - * */ - private boolean getFile(String dirName, int subDirNo){ - List command = new LinkedList<>(); - - if (dirName.startsWith("HOME:/")) { - dirName = updateHomePath(dirName); - - if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ - byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_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.size() != 0){ // therefore nspMapKeySetIndexes also != 0 - byte[] 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 if failed - * false if everything is ok - * */ - private boolean statPath(String filePath){ - List command = new LinkedList<>(); - - if (filePath.startsWith("HOME:/")){ - filePath = updateHomePath(filePath); - - File fileDirElement = new File(filePath); - if (fileDirElement.exists()){ - if (fileDirElement.isDirectory()) - command.add(GL_OBJ_TYPE_DIR); - else { - command.add(GL_OBJ_TYPE_FILE); - command.add(longToArrLE(fileDirElement.length())); - } - if (writeGL_PASS(command)) { - 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 if failed - * false if everything is ok - * */ - private boolean rename(String fileName, String newFileName){ - if (fileName.startsWith("HOME:/")){ - // This shit takes too much time to explain, but such behaviour won't let GL to fail - this.recentPath = null; - this.recentFiles = null; - this.recentDirs = null; - fileName = updateHomePath(fileName); - newFileName = updateHomePath(newFileName); - - File currentFile = new File(fileName); - File newFile = new File(newFileName); - if (! newFile.exists()){ // Else, report error - try { - if (currentFile.renameTo(newFile)){ - if (writeGL_PASS()) { - print("GL Handle 'Rename' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - catch (SecurityException ignored){} // Ah, leave it - } - } - // For VIRT:/ and others we don't serve requests - return writeGL_FAIL("GL Handle 'Rename' command [not supported for virtual drive/wrong drive/file with such name already exists/read-only directory]"); - } - /** - * Handle 'Delete' - * @return true if failed - * false if everything is ok - * */ - private boolean delete(String fileName) { - if (fileName.startsWith("HOME:/")) { - fileName = updateHomePath(fileName); - - File fileToDel = new File(fileName); - try { - if (fileToDel.delete()){ - if (writeGL_PASS()) { - print("GL Handle 'Rename' command.", EMsgType.FAIL); - return true; - } - return false; - } - } - catch (SecurityException ignored){} // Ah, leave it - } - // For VIRT:/ and others we don't serve requests - return writeGL_FAIL("GL Handle 'Delete' command [not supported for virtual drive/wrong drive/read-only directory]"); - } - /** - * Handle 'Create' - * @param type 1 for file - * 2 for folder - * @param fileName full path including new file name in the end - * @return true if failed - * false if everything is ok - * */ - private boolean create(String fileName, byte type) { - if (fileName.startsWith("HOME:/")) { - fileName = updateHomePath(fileName); - File fileToCreate = new File(fileName); - boolean result = false; - if (type == 1){ - try { - result = fileToCreate.createNewFile(); - } - catch (SecurityException | IOException ignored){} - } - else if (type == 2){ - try { - result = fileToCreate.mkdir(); - } - catch (SecurityException ignored){} - } - if (result){ - if (writeGL_PASS()) { - 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 if failed - * false if everything is ok - * */ - private boolean readFile(String fileName, long offset, long size) { - //System.out.println("readFile "+fileName+"\t"+offset+"\t"+size+"\n"); - if (fileName.startsWith("VIRT:/")){ - // Let's find out which file requested - String fNamePath = nspMap.get(fileName.substring(6)).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length - // If we don't have this file opened, let's open it - if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fNamePath))) { - // Try close what opened - if (openReadFileNameAndPath != null){ - try{ - randAccessFile.close(); - }catch (Exception ignored){} - try{ - splitReader.close(); - }catch (Exception ignored){} - } - // Open what has to be opened - try{ - File tempFile = nspMap.get(fileName.substring(6)); - if (tempFile.isDirectory()) { - randAccessFile = null; - splitReader = new NSSplitReader(tempFile, 0); - } - else { - splitReader = null; - randAccessFile = new RandomAccessFile(tempFile, "r"); - } - openReadFileNameAndPath = fNamePath; - } - catch (IOException | NullPointerException ioe){ - return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); - } - } - } - else { - // Let's find out which file requested - fileName = updateHomePath(fileName); - // If we don't have this file opened, let's open it - if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fileName))) { - // Try close what opened - if (openReadFileNameAndPath != null){ - try{ - randAccessFile.close(); - }catch (IOException | NullPointerException ignored){} - } - // Open what has to be opened - try{ - randAccessFile = new RandomAccessFile(fileName, "r"); - openReadFileNameAndPath = fileName; - }catch (IOException | NullPointerException ioe){ - return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); - } - } - } - //----------------------- Actual transfer chain ------------------------ - try{ - if (randAccessFile == null){ - splitReader.seek(offset); - byte[] chunk = new byte[(int)size]; // WTF MAN? - // Let's find out how much bytes we got - int bytesRead = splitReader.read(chunk); - // Let's check that we read expected size - if (bytesRead != (int)size) - return writeGL_FAIL("GL Handle 'ReadFile' command [CMD]" + - "\n At offset: " + offset + - "\n Requested: " + size + - "\n Received: " + bytesRead); - // Let's tell as a command about our result. - if (writeGL_PASS(longToArrLE(size))) { - 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 much bytes we got - int bytesRead = randAccessFile.read(chunk); - // Let's check that we read expected size - if (bytesRead != (int)size) - return writeGL_FAIL("GL Handle 'ReadFile' command [CMD] Requested = "+size+" Read from file = "+bytesRead); - // Let's tell as a command about our result. - if (writeGL_PASS(longToArrLE(size))) { - 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 if failed - * false if everything is ok - * */ - //@param size requested size - //private boolean writeFile(String fileName, long size) { - private boolean writeFile(String fileName) { - if (fileName.startsWith("VIRT:/")){ - return writeGL_FAIL("GL Handle 'WriteFile' command [not supported for virtual drive]"); - } - - fileName = updateHomePath(fileName); - // Check if we didn't see this (or any) file during this session - if (writeFilesMap.size() == 0 || (! writeFilesMap.containsKey(fileName))){ - // Open what we have to open - File writeFile = new File(fileName); - // If this file exists GL will take care - // Otherwise, let's add it - try{ - BufferedOutputStream writeFileBufOutStream = new BufferedOutputStream(new FileOutputStream(writeFile, true)); - writeFilesMap.put(fileName, writeFileBufOutStream); - } catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage()); - } - } - // Now we have stream - BufferedOutputStream myStream = writeFilesMap.get(fileName); - - byte[] transferredData; - - if ((transferredData = readGL_file()) == null){ - 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 if failed - * false if everything is ok - * */ - 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){ - if (isWindows) - glPath = glPath.replaceAll("/", "\\\\"); - glPath = homePath+glPath.substring(6); // Do not use replaceAll since it will consider \ as special directive - return glPath; - } - /** - * Convert INT (Little endian) value to bytes-array representation - * */ - private byte[] intToArrLE(int value){ - ByteBuffer byteBuffer = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.putInt(value); - return byteBuffer.array(); - } - /** - * Convert LONG (Little endian) value to bytes-array representation - * */ - private byte[] longToArrLE(long value){ - ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.putLong(value); - return byteBuffer.array(); - } - /** - * Convert bytes-array to INT value (Little endian) - * */ - private int arrToIntLE(byte[] byteArrayWithInt, int intStartPosition){ - return ByteBuffer.wrap(byteArrayWithInt).order(ByteOrder.LITTLE_ENDIAN).getInt(intStartPosition); - } - /** - * Convert bytes-array to LONG value (Little endian) - * */ - private long arrToLongLE(byte[] byteArrayWithLong, int intStartPosition){ - return ByteBuffer.wrap(byteArrayWithLong).order(ByteOrder.LITTLE_ENDIAN).getLong(intStartPosition); - } - - //------------------------------------------------------------------------------------------------------------------ - - /*----------------------------------------------------*/ - /* GL READ/WRITE USB SPECIFIC */ - /*----------------------------------------------------*/ - - private byte[] readGL(){ - ByteBuffer readBuffer; - readBuffer = ByteBuffer.allocateDirect(4096); // GL really? - - IntBuffer readBufTransferred = IntBuffer.allocate(1); - - int result; - - while (! task.isCancelled()) { - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 - - switch (result) { - case LibUsb.SUCCESS: - int trans = readBufTransferred.get(); - byte[] receivedBytes = new byte[trans]; - readBuffer.get(receivedBytes); - return receivedBytes; - case LibUsb.ERROR_TIMEOUT: - closeOpenedReadFilesGl(); // Could be a problem if GL glitches and slow down process. Or if user has extra-slow SD card. TODO: refactor? - continue; - default: - 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(){ - ByteBuffer readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. - IntBuffer readBufTransferred = IntBuffer.allocate(1); - - int result; - - while (! task.isCancelled() ) { - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 - - switch (result) { - case LibUsb.SUCCESS: - int trans = readBufTransferred.get(); - byte[] receivedBytes = new byte[trans]; - readBuffer.get(receivedBytes); - return receivedBytes; - case LibUsb.ERROR_TIMEOUT: - continue; - default: - 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){ - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - writeBuffer.put(CMD_GLCO_SUCCESS); - writeBuffer.put(message); - return writeToUsb(writeBuffer.array()); - } - private boolean writeGL_PASS(){ - return writeToUsb(Arrays.copyOf(CMD_GLCO_SUCCESS, 4096)); - } - private boolean writeGL_PASS(List messages){ - ByteBuffer writeBuffer = ByteBuffer.allocate(4096); - writeBuffer.put(CMD_GLCO_SUCCESS); - for (byte[] arr : messages) - writeBuffer.put(arr); - return writeToUsb(writeBuffer.array()); - } - - private boolean writeGL_FAIL(String reportToUImsg){ - if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ - 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 - ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; - writeBuffer.put(message); // Don't do writeBuffer.rewind(); - IntBuffer writeBufTransferred = IntBuffer.allocate(1); - int result; - - while (! task.isCancelled()) { - result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01 - - switch (result){ - case LibUsb.SUCCESS: - if (writeBufTransferred.get() == message.length) - return false; - else { - 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/PFS/PFSProvider.java b/src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java index e97352e..9491596 100644 --- a/src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java +++ b/src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2025 Dmitry Isaenko This file is part of NS-USBloader. @@ -33,9 +33,9 @@ import java.util.*; public class PFSProvider { private static final byte[] PFS0 = new byte[]{0x50, 0x46, 0x53, 0x30}; // PFS0 - private String nspFileName; - private NCAFile[] ncaFiles; - private long bodySize; + private final String nspFileName; + private final NCAFile[] ncaFiles; + private final long bodySize; private int ticketID = -1; public PFSProvider(File nspFile, ILogPrinter logPrinter) throws Exception{ @@ -141,7 +141,7 @@ public class PFSProvider { byte[] b = new byte[1]; // Temporary for (int i=0; i(); - randAccessFile.seek(filesCount*24+16+ncaNameOffsets.get(i)); // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them + 12 bit what were in the beginning + randAccessFile.seek(filesCount*24L+16L+ncaNameOffsets.get(i)); // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them + 12 bit what were in the beginning while ((randAccessFile.read(b)) != -1){ if (b[0] == 0x00) break; diff --git a/src/main/java/nsusbloader/com/usb/TransferModule.java b/src/main/java/nsusbloader/com/usb/TransferModule.java index e99a4a8..9ea4694 100644 --- a/src/main/java/nsusbloader/com/usb/TransferModule.java +++ b/src/main/java/nsusbloader/com/usb/TransferModule.java @@ -28,14 +28,20 @@ import java.io.File; import java.util.*; public abstract class TransferModule { - EFileStatus status = EFileStatus.UNKNOWN; + protected static final byte IN_EP = (byte) 0x81; + protected static final byte OUT_EP = (byte) 0x01; - LinkedHashMap nspMap; - ILogPrinter logPrinter; - DeviceHandle handlerNS; - CancellableRunnable task; + protected EFileStatus status = EFileStatus.UNKNOWN; - TransferModule(DeviceHandle handler, LinkedHashMap nspMap, CancellableRunnable task, ILogPrinter printer){ + protected LinkedHashMap nspMap; + protected ILogPrinter logPrinter; + protected DeviceHandle handlerNS; + protected CancellableRunnable task; + + protected TransferModule(DeviceHandle handler, + LinkedHashMap nspMap, + CancellableRunnable task, + ILogPrinter printer){ this.handlerNS = handler; this.nspMap = nspMap; this.task = task; @@ -78,7 +84,7 @@ public abstract class TransferModule { } public EFileStatus getStatus(){ return status; } - void print(String message, EMsgType type){ + protected void print(String message, EMsgType type){ try { logPrinter.print(message, type); } diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index 76063d4..55ac2b9 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. @@ -24,6 +24,8 @@ 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.*; import java.io.*; @@ -66,13 +68,16 @@ public class UsbCommunications extends CancellableRunnable { case "TinFoil": module = new TinFoil(handler, nspMap, this, logPrinter); break; - case "GoldLeafv0.10+": + case "GoldLeaf v1.1.1": + module = new GoldLeaf_111(handler, nspMap, this, logPrinter, nspFilterForGl); + break; + case "GoldLeaf v0.10-1.0.0": module = new GoldLeaf_010(handler, nspMap, this, logPrinter, nspFilterForGl); break; - case "GoldLeafv0.8-0.9": + case "GoldLeaf v0.8-0.9": module = new GoldLeaf_08(handler, nspMap, this, logPrinter, nspFilterForGl); break; - case "GoldLeafv0.7.x": + case "GoldLeaf v0.7.x": module = new GoldLeaf_07(handler, nspMap, this, logPrinter, nspFilterForGl); break; default: diff --git a/src/main/java/nsusbloader/com/usb/gl/Converters.java b/src/main/java/nsusbloader/com/usb/gl/Converters.java new file mode 100644 index 0000000..71a6d44 --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/Converters.java @@ -0,0 +1,53 @@ +/* + 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.gl; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class Converters { + /** + * Convert INT (Little endian) value to bytes-array representation + * */ + public static 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 + * */ + public static byte[] longToArrLE(long value){ + return ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN) + .putLong(value) + .array(); + } + /** + * Convert bytes-array to INT value (Little endian) + * */ + public static int arrToIntLE(byte[] byteArrayWithInt, int intStartPosition){ + return ByteBuffer.wrap(byteArrayWithInt).order(ByteOrder.LITTLE_ENDIAN).getInt(intStartPosition); + } + /** + * Convert bytes-array to LONG value (Little endian) + * */ + public static long arrToLongLE(byte[] byteArrayWithLong, int intStartPosition){ + return ByteBuffer.wrap(byteArrayWithLong).order(ByteOrder.LITTLE_ENDIAN).getLong(intStartPosition); + } +} diff --git a/src/main/java/nsusbloader/com/usb/gl/GlString.java b/src/main/java/nsusbloader/com/usb/gl/GlString.java new file mode 100644 index 0000000..3c90b5c --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/GlString.java @@ -0,0 +1,23 @@ +/* + 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.gl; + +public interface GlString { + int length(); +} diff --git a/src/main/java/nsusbloader/com/usb/gl/GlString010.java b/src/main/java/nsusbloader/com/usb/gl/GlString010.java new file mode 100644 index 0000000..6546205 --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/GlString010.java @@ -0,0 +1,44 @@ +/* + 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.gl; + +import java.nio.charset.StandardCharsets; + +import static nsusbloader.com.usb.gl.Converters.arrToIntLE; + +/* Separated from interface for easier fixes/replacement in future */ +public class GlString010 implements GlString { + + private final int length; + private final String string; + + public GlString010(byte[] inputBytes, int startPosition){ + this.length = arrToIntLE(inputBytes, startPosition); + this.string = new String(inputBytes, startPosition+4, length, StandardCharsets.UTF_8); + } + + public int length(){ + return length; + } + + @Override + public String toString(){ + return string; + } +} diff --git a/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_010.java b/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_010.java new file mode 100644 index 0000000..e608a00 --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_010.java @@ -0,0 +1,897 @@ +/* + 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.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 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 writeFilesMap = new HashMap<>(); + protected long virtDriveSize; + private final HashMap splitFileSize = new HashMap<>(); + + private final boolean isWindows = System.getProperty("os.name").contains("Windows"); + protected final String homePath = System.getProperty("user.home"); + // For using in CMD_SelectFile with SPEC:/ prefix + protected File selectedFile; + + public GoldLeaf_010(DeviceHandle handler, + LinkedHashMap nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter) { + super(handler, nspMap, task, logPrinter); + + this.nspFilter = nspFilter; + + printWelcomeMessage(); + + // 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 + if (workLoop()) + return; + // Close (and flush) all opened streams. + for (var bufferedOutputStream: writeFilesMap.values()){ + try{ + bufferedOutputStream.close(); + } catch (IOException | NullPointerException ignored){} + } + closeOpenedReadFilesGl(); + } + + protected void printWelcomeMessage(){ + print("=========== GoldLeaf v0.10-1.0.0 ===========\n\t" + + "VIRT:/ equals files added into the application\n\t" + + "HOME:/ equals " + homePath, EMsgType.INFO); + } + + protected boolean workLoop(){ + while (true) { // Till user interrupted process. + GlString glString1; + var readByte = readGL(); + + if (readByte == null) // Issue @ readFromUsbGL method + return true; + + //RainbowDump.hexDumpUTF8(readByte); // DEBUG + //print("\t→ "+ GoldleafCmd.get(readByte[4]), EMsgType.INFO); + + if (notGLCI(readByte)) + continue; + + switch (GoldleafCmd.get(readByte[4])) { + case GetDriveCount: + if (getDriveCount()) + return false; + break; + case GetDriveInfo: + if (getDriveInfo(arrToIntLE(readByte,8))) + return false; + break; + case GetSpecialPathCount: + if (getSpecialPathCount()) + return false; + break; + case GetSpecialPath: + if (getSpecialPath(arrToIntLE(readByte,8))) + return false; + break; + case GetDirectoryCount: + if (getDirectoryOrFileCount(readString(readByte, 8).toString(), true)) + return false; + break; + case GetFileCount: + if (getDirectoryOrFileCount(readString(readByte, 8).toString(), false)) + return false; + break; + case GetDirectory: + glString1 = readString(readByte, 8); + if (getDirectory(glString1.toString(), arrToIntLE(readByte, glString1.length()+12))) + return false; + break; + case GetFile: + glString1 = readString(readByte, 8); + if (getFile(glString1.toString(), arrToIntLE(readByte, glString1.length()+12))) + return false; + break; + case StatPath: + if (statPath(readString(readByte, 8).toString())) + return false; + break; + case Rename: + glString1 = readString(readByte, 8); + var glString2 = readString(readByte, 12+glString1.length()); + if (rename(glString1.toString(), glString2.toString())) + return false; + break; + case Delete: + if (delete(readString(readByte, 8).toString())) + return false; + break; + case Create: + if (create(readString(readByte, 8).toString(), readByte[8])) + return false; + break; + case ReadFile: + glString1 = readString(readByte, 8); + if (readFile(glString1.toString(), + arrToLongLE(readByte, 12+glString1.length()), + arrToLongLE(readByte, 12+glString1.length()+8))) + return false; + break; + case WriteFile: + if (writeFile(readString(readByte, 8).toString())) + return false; + break; + case SelectFile: + if (selectFile()) + return false; + break; + case StartFile: + case EndFile: + if (startOrEndFile()) + return false; + break; + case CMD_UNKNOWN: + default: + writeGL_FAIL(EXCEPTION_CAUGHT, "GL Unknown command: "+readByte[4]+" [it's a very bad sign]"); + } + } + } + + protected GlString readString(byte[] readByte, int startPosition){ + return new GlString010(readByte, startPosition); + } + + protected 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 + * */ + protected boolean startOrEndFile(){ + return writeGL_PASS("GL Handle 'StartFile' command"); + } + /** + * Handle GetDriveCount + * 2 drives declared in current implementation + * @return true - failed, false - passed + */ + protected boolean getDriveCount(){ + return writeGL_PASS(intToArrLE(2),"GL Handle 'ListDrives' command"); + } + /** + * Handle GetDriveInfo + * @return true - failed, false - passed + */ + protected 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 + * */ + protected boolean getSpecialPathCount(){ + return writeGL_PASS(intToArrLE(0), "GL Handle 'SpecialPathCount' command"); + } + /** + * Handle SpecialPath + * @return true - failed, false - passed + * */ + protected boolean getSpecialPath(int specialPathNo){ + return writeGL_FAIL(INVALID_INDEX, "GL Handle 'SpecialPath' command [not supported]"); + } + /** + * Handle GetDirectoryCount & GetFileCount + * @return true - failed, false - passed + * */ + protected 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 + * */ + protected boolean getDirectory(String dirName, int subDirNo){ + if (dirName.startsWith("HOME:/")) { + dirName = decodeGlPath(dirName); + + var command = new ArrayList(); + + 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 + * */ + protected boolean getFile(String glDirName, int subDirNo){ + var command = new LinkedList(); + + 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 + * */ + protected boolean statPath(String glFileName){ + var command = new ArrayList(); + + 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 + * */ + protected 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 + * */ + protected 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 + * */ + protected 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("^.*?:/", ""); + 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 + return true; + } + if (writeToUsb(chunk)) { // Bypassing bytes we read total // FIXME: move failure message into method + print("GL Handle 'ReadFile' command", EMsgType.FAIL); + return true; + } + 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 + * */ + 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 + * */ + protected 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 */ + /*----------------------------------------------------*/ + + protected 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 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; + } +} diff --git a/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_111.java b/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_111.java new file mode 100644 index 0000000..7ed498c --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/GoldLeaf_111.java @@ -0,0 +1,118 @@ +/* + 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.gl; + +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import org.usb4java.DeviceHandle; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static nsusbloader.com.usb.gl.Converters.*; + +/** + * GoldLeaf 1.1.1 processing + */ +public class GoldLeaf_111 extends GoldLeaf_010{ + + public GoldLeaf_111(DeviceHandle handler, LinkedHashMap nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter) { + super(handler, nspMap, task, logPrinter, nspFilter); + } + + @Override + protected void printWelcomeMessage(){ + print("=========== GoldLeaf v1.1.1 ===========\n\t" + + "VIRT:/ equals files added into the application\n\t" + + "HOME:/ equals " + homePath + "\n\t" + + "BE CAREFUL!\n\t" + + "Due to some strange behaviour with Goldleaf v1.1.1, you will see last menu entry " + + "'Do not click (crashes Atmosphere)'\n\t" + + "You should better not clicking on it", EMsgType.INFO); + } + + /** + * Fixes issues with incorrect request for 'Home' & 'Virtual'. Forces both to return 'HOME:/' + * v1.1.1 specific fix + * Otherwise v.1.1.1 returns 'HOME:/' once 'Virtual' requested and ':/' once requested 'Home' + * */ + + @Override + protected boolean getDriveCount(){ + return writeGL_PASS(intToArrLE(3),"GL Handle 'ListDrives' command"); + } + + @Override + protected boolean getDriveInfo(int driveNo){ + if (driveNo < 0 || driveNo > 2) + return writeGL_FAIL(INVALID_INDEX, "GL Handle 'GetDriveInfo' command [no such drive]"); + + byte[] driveLabel, + driveLabelLen, + driveLetter, + driveLetterLen, + totalFreeSpace; + long totalSizeLong; + + switch (driveNo){ + case 0: + driveLabel = "Home".getBytes(StandardCharsets.UTF_8); // yes, it's hotfix + driveLabelLen = intToArrLE(driveLabel.length); + driveLetter = "VIRT".getBytes(StandardCharsets.UTF_8); // and this is fine + driveLetterLen = intToArrLE(driveLetter.length); + totalFreeSpace = new byte[4]; + totalSizeLong = virtDriveSize; + break; + case 1: + driveLabel = "Virtual".getBytes(StandardCharsets.UTF_8); // here as well + driveLabelLen = intToArrLE(driveLabel.length); + driveLetter = "HOME".getBytes(StandardCharsets.UTF_8); // and here + driveLetterLen = intToArrLE(driveLetter.length); + var userHomeDir = new File(System.getProperty("user.home")); + totalFreeSpace = Arrays.copyOfRange(longToArrLE(userHomeDir.getFreeSpace()), 0, 4);; + totalSizeLong = userHomeDir.getTotalSpace(); + break; + default: + driveLabel = "Do not click (crashes Atmosphere)".getBytes(StandardCharsets.UTF_8); // and this one is necessary too + driveLabelLen = intToArrLE(driveLabel.length); + driveLetter = "VIRT".getBytes(StandardCharsets.UTF_8); + driveLetterLen = intToArrLE(driveLetter.length); + totalFreeSpace = new byte[4]; + totalSizeLong = virtDriveSize; + break; + } + + 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"); + } +} diff --git a/src/main/java/nsusbloader/com/usb/gl/GoldleafCmd.java b/src/main/java/nsusbloader/com/usb/gl/GoldleafCmd.java new file mode 100644 index 0000000..be6cf76 --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/gl/GoldleafCmd.java @@ -0,0 +1,54 @@ +/* + 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.gl; + +public enum GoldleafCmd { + GetDriveCount((byte) 1), + GetDriveInfo((byte) 2), + StatPath((byte) 3), + GetFileCount((byte) 4), + GetFile((byte) 5), + GetDirectoryCount((byte) 6), + GetDirectory((byte) 7), + StartFile((byte) 8), + ReadFile((byte) 9), + WriteFile((byte) 10), + EndFile((byte) 11), + Create((byte) 12), + Delete((byte) 13), + Rename((byte) 14), + GetSpecialPathCount((byte) 15), + GetSpecialPath((byte) 16), + SelectFile((byte) 17), + CMD_UNKNOWN((byte) 255); + + private final byte id; + + GoldleafCmd(byte id) { + this.id = id; + } + + public static GoldleafCmd get(byte id) { + for(GoldleafCmd cmd : values()) { + if(cmd.id == id) + return cmd; + } + return CMD_UNKNOWN; + } +} \ No newline at end of file diff --git a/src/main/resources/SettingsBlockGeneric.fxml b/src/main/resources/SettingsBlockGeneric.fxml index 2db10eb..a62ebbc 100644 --- a/src/main/resources/SettingsBlockGeneric.fxml +++ b/src/main/resources/SettingsBlockGeneric.fxml @@ -15,8 +15,8 @@