diff --git a/Jenkinsfile b/Jenkinsfile index 893070c..e85efc8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { } post { always { - archiveArtifacts artifacts: 'target/*-jar-with-dependencies.jar', onlyIfSuccessful: true + archiveArtifacts artifacts: 'target/*.jar, target/*.exe', onlyIfSuccessful: true } } } \ No newline at end of file diff --git a/README.md b/README.md index 799ef01..e35b462 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ![License](https://img.shields.io/badge/License-GPLv3-blue.svg) [![Releases](https://img.shields.io/github/downloads/developersu/ns-usbloader/total.svg)]() [![LatestVer](https://img.shields.io/github/release/developersu/ns-usbloader.svg)]() +[Support author](#support-this-app) + NS-USBloader is a PC-side **[Adubbz/TinFoil](https://github.com/Adubbz/Tinfoil/)** (version 0.2.1; USB and Network) and **GoldLeaf** (USB) NSP installer. Replacement for default **usb_install_pc.py**, **remote_install_pc.py** *(never ever use this. even if you brave. no idea why it works.)* and **GoldTree**. [Click here for Android version ;)](https://github.com/developersu/ns-usbloader-mobile) @@ -68,13 +70,15 @@ Set 'Security & Privacy' settings if needed. #### And how to use it? -The first thing you should do it install TinFoil ([Adubbz](https://github.com/Adubbz/Tinfoil/)) or GoldLeaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) on your NS. I recommend using TinFoil, but it ups to you. Take a look on app, find where is the option to install from USB and/or Network. Maybe [this article](https://developersu.blogspot.com/2019/02/ns-usbloader-en.html) will be helpful. +The first thing you should do it install TinFoil ([Adubbz](https://github.com/Adubbz/Tinfoil/)) or GoldLeaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) on your NS. Here is the version of 'not perfect but anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR). Ok, I'm almost sure that this version has bugs. I don't remember where I downloaded it. But it works for me somehow. Let's rephrase, if you have working version of TinFoil **DO NOT** use this one. Ok. let's begin. +Take a look on app, find where is the option to install from USB and/or Network. Maybe [this article](https://developersu.blogspot.com/2019/02/ns-usbloader-en.html) will be helpful. + There are three tabs. First one is main. ##### First tab. @@ -85,13 +89,23 @@ Then you may drag-n-drop folder with NSPs OR files to application or use 'Select Table. -There you can select checkbox for files that will be send to application (TF/GL). Since GoldLeaf allow you only one file transmission per time, only one file is available for selection. Also you can use space to select/un-select files and 'delete' button for deleting. By right-mouse-click you can see context menu where you can delete one OR all items from the table. +There you can select checkbox for files that will be send to application (TF/GL). ~~Since GoldLeaf allow you only one file transmission per time, only one file is available for selection.~~ + +Also you can use space to select/un-select files and 'delete' button for deleting. By right-mouse-click you can see context menu where you can delete one OR all items from the table. + +For GoldLeaf v0.6.1 and NS-USBloader v0.6 (and higher) you will have to use 'Explore content' -> 'Remote PC (via USB)' You will see two drives HOME:/ and VIRT:/. First drive is pointing to your home directory. Second one is reflection of what you've added to table (first application tab). Also VIRT:/ drive have limited functionality in comparison to HOME:/. E.g. you can't write files to this drive since it's not a drive. But don't worry, it won't make any impact on GoldLeaf or your NS if you try. + +Also, for GoldLeaf write files (from NS to PC): You have to 'Stop execution' properly before accessing files transferred from GL. Usually you have to wait 5sec or less. It will guarantee that your files properly written to PC. + +## NOTE: NS-USBloader v0.6 doesn't support writing files from NS to PC if file size is greater than 8mb ##### Second tab. Here you can configure settings for network file transmission. Usually you shouldn't change anything. But it you're cool hacker, go ahead! The most interesting option here is 'Don't serve requests'. Architecture of the TinFoil's NET part is working interesting way. When you select in TF network NSP transfer, application will wait at port 2000 for the information about where should it take files from. Like '192.168.1.5:6060/my file.nsp'. Usually NS-USBloader serves requests by implementing simplified HTTP server and bringing it up and so on. But if this option selected, you can define path to remote location of the files. For example if you set in settings '192.168.4.2:80/ROMS/NS/' and add in table file 'my file.nsp' then NS-USBloader will simply tell TinFoil "Hey, go take files from '192.168.4.2:80/ROMS/NS/my%20file.nsp' ". Of course you have to bring '192.168.4.2' host up and make file accessible from such address (just go install nginx). As I said, this feature is interesting, but I guess won't be popular. -Also here you can check 'Auto-check for updates' or click button to verify if new version released or not. +Also here you can: +* Set 'Auto-check for updates' for checking for updates when application starts, or click button to verify if new version released immediately. +* Set 'Show only *.nsp in GoldLeaf' to filter all files displayed at HOME:/ drive. So only NSP files will appear. ##### Third tab. @@ -102,7 +116,7 @@ Why when 'NET' once started it never ends? Because there is HTTP server inside of application. It can't determine the moment when all transmissions finishes (unless they failed). So you have to look on your NS screen and 'Interrupt' it once done. ### Known bugs -* Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't select anything on NS). Sometimes this issue also appears when network transmission started and nothing received from NS. +* Unable to interrupt transmission when using TinFoil (when user sent NSP list but didn't select anything on NS). Sometimes this issue also appears when network transmission started and nothing received from NS. ### Other notes 'Status' = 'Uploaded' that appears in the table does not mean that file has been installed. It means that it has been sent to NS without any issues! That's what this app about. @@ -112,9 +126,7 @@ usb4java since NS-USBloader-v0.2.3 switched to 1.2.0 instead of 1.3.0. This shou ### Translators! 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 (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. - -NOTE: actually it's not gonna work in real, because we should stay in touch and I'll need you when add something that should be translated =( +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. #### TODO (maybe): - [x] [Android support](https://github.com/developersu/ns-usbloader-mobile) @@ -122,11 +134,11 @@ NOTE: actually it's not gonna work in real, because we should stay in touch and - [ ] More deep file analyze before uploading. - [ ] XCI support -#### Support this app +## Support this app If you like this app, just give a star. -If you want to make a donation* (see below), refer to this page: +Want to support development? Make a donation* (see below): [Yandex.Money](https://money.yandex.ru/to/410014301951665) diff --git a/appicon.ico b/appicon.ico new file mode 100644 index 0000000..c1dbb9d Binary files /dev/null and b/appicon.ico differ diff --git a/pom.xml b/pom.xml index cd2bb17..e7d571d 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ NS-USBloader ns-usbloader - 0.5.3-SNAPSHOT + 0.6-SNAPSHOT https://github.com/developersu/ns-usbloader/ @@ -22,8 +22,8 @@ - GPLv3 - LICENSE + GNU General Public License v3 + http://www.gnu.org/licenses/gpl.txt manual @@ -140,7 +140,7 @@ org.usb4java usb4java - 1.2.0 + 1.3.0 compile @@ -191,6 +191,48 @@ + + + com.akathist.maven.plugins.launch4j + 1.7.25 + launch4j-maven-plugin + + + l4j-NS-USBloader + package + + launch4j + + + gui + appicon.ico + target/NS-USBloader-${project.version}.exe + target/ns-usbloader-${project.version}-jar-with-dependencies.jar + NS-USBloader + + nsusbloader.Main + false + anything + + + 1.8 + + + 0.6.0.0 + ${project.version} + TinFoil and GoldLeaf installer for your NS + GNU General Public License v3, 2019 ${organization.name}. Russia/LPR. + 0.6.0.0 + ${project.version} + ${organization.name} + ${project.name} + ${project.name} + ${project.name}.exe + + + + + \ No newline at end of file diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 8ca7cc5..0e28a13 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -25,7 +25,8 @@ public class AppPreferences { String HostPort, String HostExtra, boolean autoCheck4Updates, - boolean tinfoilXciSupport + boolean tinfoilXciSupport, + boolean nspFileFilterForGl ){ setProtocol(Protocol); setRecent(PreviouslyOpened); @@ -41,6 +42,7 @@ public class AppPreferences { setHostExtra(HostExtra); setAutoCheckUpdates(autoCheck4Updates); setTfXCI(tinfoilXciSupport); + setNspFileFilterGL(nspFileFilterForGl); } public String getTheme(){ String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually @@ -109,4 +111,7 @@ public class AppPreferences { public String getLanguage(){return preferences.get("USR_LANG", Locale.getDefault().getISO3Language());} public void setLanguage(String langStr){preferences.put("USR_LANG", langStr);} + + public boolean getNspFileFilterGL(){return preferences.getBoolean("GL_NSP_FILTER", false); } + public void setNspFileFilterGL(boolean prop){preferences.putBoolean("GL_NSP_FILTER", prop);} } diff --git a/src/main/java/nsusbloader/Controllers/FrontController.java b/src/main/java/nsusbloader/Controllers/FrontController.java index 2e41a46..49a8e22 100644 --- a/src/main/java/nsusbloader/Controllers/FrontController.java +++ b/src/main/java/nsusbloader/Controllers/FrontController.java @@ -8,6 +8,7 @@ import javafx.scene.control.*; import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import nsusbloader.AppPreferences; +import nsusbloader.MediatorControl; import java.net.URL; import java.util.ResourceBundle; @@ -49,6 +50,11 @@ public class FrontController implements Initializable { nsIpTextField.setVisible(true); } } + // Really bad disable-enable upload button function + if (tableFilesListController.isFilesForUploadListEmpty()) + MediatorControl.getInstance().getContoller().disableUploadStopBtn(true); + else + MediatorControl.getInstance().getContoller().disableUploadStopBtn(false); }); // Add listener to notify tableView controller tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem()); // Notify tableView controller diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 51f9a86..69d6c3e 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -17,6 +17,7 @@ import nsusbloader.USB.UsbCommunications; import java.io.File; import java.net.*; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.ResourceBundle; @@ -56,7 +57,10 @@ public class NSLMainController implements Initializable { MediatorControl.getInstance().setController(this); - uploadStopBtn.setDisable(true); + if (FrontTabController.getSelectedProtocol().equals("TinFoil")) + uploadStopBtn.setDisable(true); + else + uploadStopBtn.setDisable(false); selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); }); uploadStopBtn.setOnAction(e->{ uploadBtnAction(); }); @@ -128,23 +132,26 @@ public class NSLMainController implements Initializable { if ((workThread == null || !workThread.isAlive())){ // Collect files List nspToUpload; - if ((nspToUpload = FrontTabController.tableFilesListController.getFilesForUpload()) == null) { + if ((nspToUpload = FrontTabController.tableFilesListController.getFilesForUpload()) == null && FrontTabController.getSelectedProtocol().equals("TinFoil")) { logArea.setText(resourceBundle.getString("logsNoFolderFileSelected")); return; } else { - logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n"); - for (File item: nspToUpload) - logArea.appendText(" "+item.getAbsolutePath()+"\n"); + if ((nspToUpload = FrontTabController.tableFilesListController.getFilesForUpload()) != null){ + logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n"); + for (File item: nspToUpload) + logArea.appendText(" "+item.getAbsolutePath()+"\n"); + } + else { + logArea.clear(); + nspToUpload = new LinkedList<>(); + } } // If USB selected if (FrontTabController.getSelectedProtocol().equals("GoldLeaf") || - ( - FrontTabController.getSelectedProtocol().equals("TinFoil") - && FrontTabController.getSelectedNetUsb().equals("USB") - ) + ( FrontTabController.getSelectedProtocol().equals("TinFoil") && FrontTabController.getSelectedNetUsb().equals("USB") ) ){ - usbNetCommunications = new UsbCommunications(nspToUpload, FrontTabController.getSelectedProtocol()); + usbNetCommunications = new UsbCommunications(nspToUpload, FrontTabController.getSelectedProtocol(), SettingsTabController.getNSPFileFilterForGL()); workThread = new Thread(usbNetCommunications); workThread.setDaemon(true); workThread.start(); @@ -217,7 +224,10 @@ public class NSLMainController implements Initializable { * Crunch. Now you see that I'm not a programmer.. This function called from NSTableViewController * */ public void disableUploadStopBtn(boolean disable){ - uploadStopBtn.setDisable(disable); + if (FrontTabController.getSelectedProtocol().equals("TinFoil")) + uploadStopBtn.setDisable(disable); + else + uploadStopBtn.setDisable(false); } /** * Drag-n-drop support (dragOver consumer) @@ -285,7 +295,8 @@ public class NSLMainController implements Initializable { SettingsTabController.getHostPort(), SettingsTabController.getHostExtra(), SettingsTabController.getAutoCheckForUpdates(), - SettingsTabController.getTfXCISupport() + SettingsTabController.getTfXCISupport(), + SettingsTabController.getNSPFileFilterForGL() ); } } diff --git a/src/main/java/nsusbloader/Controllers/NSTableViewController.java b/src/main/java/nsusbloader/Controllers/NSTableViewController.java index 896fe81..c976d3c 100644 --- a/src/main/java/nsusbloader/Controllers/NSTableViewController.java +++ b/src/main/java/nsusbloader/Controllers/NSTableViewController.java @@ -25,7 +25,6 @@ import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.List; -import java.util.ListIterator; import java.util.ResourceBundle; public class NSTableViewController implements Initializable { @@ -33,8 +32,6 @@ public class NSTableViewController implements Initializable { private TableView table; private ObservableList rowsObsLst; - private String protocol; - @Override public void initialize(URL url, ResourceBundle resourceBundle) { rowsObsLst = FXCollections.observableArrayList(); @@ -55,7 +52,6 @@ public class NSTableViewController implements Initializable { } else if (keyEvent.getCode() == KeyCode.SPACE) { for (NSLRowModel item : table.getSelectionModel().getSelectedItems()) { item.setMarkForUpload(!item.isMarkForUpload()); - restrictSelection(item); } table.refresh(); } @@ -107,7 +103,6 @@ public class NSTableViewController implements Initializable { @Override public void changed(ObservableValue observableValue, Boolean oldValue, Boolean newValue) { model.setMarkForUpload(newValue); - restrictSelection(model); } }); return booleanProperty; @@ -162,7 +157,7 @@ public class NSTableViewController implements Initializable { if (!row.isEmpty() && mouseEvent.getButton() == MouseButton.PRIMARY){ NSLRowModel thisItem = row.getItem(); thisItem.setMarkForUpload(!thisItem.isMarkForUpload()); - restrictSelection(thisItem); + table.refresh(); } mouseEvent.consume(); } @@ -174,18 +169,6 @@ public class NSTableViewController implements Initializable { table.setItems(rowsObsLst); table.getColumns().addAll(statusColumn, fileNameColumn, fileSizeColumn, uploadColumn); } - /** - * See uploadColumn callback. In case of GoldLeaf we have to restrict selection - * */ - private void restrictSelection(NSLRowModel modelChecked){ - if (!protocol.equals("TinFoil") && rowsObsLst.size() > 1) { // Tinfoil doesn't need any restrictions. If only one file in list, also useless - for (NSLRowModel model: rowsObsLst){ - if (model != modelChecked) - model.setMarkForUpload(false); - } - } - table.refresh(); - } /** * Add files when user selected them * */ @@ -196,21 +179,15 @@ public class NSTableViewController implements Initializable { filesAlreayInList.add(model.getNspFileName()); for (File file: newFiles) if (!filesAlreayInList.contains(file.getName())) { - if (protocol.equals("TinFoil")) - rowsObsLst.add(new NSLRowModel(file, true)); - else - rowsObsLst.add(new NSLRowModel(file, false)); + rowsObsLst.add(new NSLRowModel(file, true)); } } else { for (File file: newFiles) - if (protocol.equals("TinFoil")) - rowsObsLst.add(new NSLRowModel(file, true)); - else - rowsObsLst.add(new NSLRowModel(file, false)); + rowsObsLst.add(new NSLRowModel(file, true)); MediatorControl.getInstance().getContoller().disableUploadStopBtn(false); } - rowsObsLst.get(0).setMarkForUpload(true); + //rowsObsLst.get(0).setMarkForUpload(true); table.refresh(); } /** @@ -237,14 +214,16 @@ public class NSTableViewController implements Initializable { return null; } } + public boolean isFilesForUploadListEmpty(){ + return rowsObsLst.isEmpty(); + } /** * Update files in case something is wrong. Requested from UsbCommunications * */ public void setFileStatus(String fileName, EFileStatus status){ for (NSLRowModel model: rowsObsLst){ - if (model.getNspFileName().equals(fileName)){ + if (model.getNspFileName().equals(fileName)) model.setStatus(status); - } } table.refresh(); } @@ -252,25 +231,10 @@ public class NSTableViewController implements Initializable { * Called if selected different USB protocol * */ public void setNewProtocol(String newProtocol){ - protocol = newProtocol; if (rowsObsLst.isEmpty()) return; - if (newProtocol.equals("TinFoil")){ - for (NSLRowModel model: rowsObsLst) - model.setMarkForUpload(true); - } - else { - ListIterator iterator = rowsObsLst.listIterator(); - while (iterator.hasNext()){ - NSLRowModel current = iterator.next(); - if (current.getNspFileName().toLowerCase().endsWith("xci")) - iterator.remove(); - else - current.setMarkForUpload(false); - } - if (!rowsObsLst.isEmpty()) - rowsObsLst.get(0).setMarkForUpload(true); - } + if (! newProtocol.equals("TinFoil")) + rowsObsLst.removeIf(current -> current.getNspFileName().toLowerCase().endsWith("xci")); table.refresh(); } diff --git a/src/main/java/nsusbloader/Controllers/SettingsController.java b/src/main/java/nsusbloader/Controllers/SettingsController.java index fd676c2..d8af8cc 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsController.java @@ -23,7 +23,8 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; public class SettingsController implements Initializable { - + @FXML + private CheckBox nspFilesFilterForGLCB; @FXML private CheckBox validateNSHostNameCb; @FXML @@ -64,6 +65,8 @@ public class SettingsController implements Initializable { @Override public void initialize(URL url, ResourceBundle resourceBundle) { + nspFilesFilterForGLCB.setSelected(AppPreferences.getInstance().getNspFileFilterGL()); + validateNSHostNameCb.setSelected(AppPreferences.getInstance().getNsIpValidationNeeded()); expertSettingsVBox.setDisable(!AppPreferences.getInstance().getExpertMode()); @@ -243,7 +246,7 @@ public class SettingsController implements Initializable { }); } - + public boolean getNSPFileFilterForGL(){return nspFilesFilterForGLCB.isSelected(); } public boolean getExpertModeSelected(){ return expertModeCb.isSelected(); } public boolean getAutoIpSelected(){ return autoDetectIpCb.isSelected(); } public boolean getRandPortSelected(){ return randPortCb.isSelected(); } diff --git a/src/main/java/nsusbloader/NET/NETCommunications.java b/src/main/java/nsusbloader/NET/NETCommunications.java index 52997eb..0a711c9 100644 --- a/src/main/java/nsusbloader/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/NET/NETCommunications.java @@ -49,7 +49,7 @@ public class NETCommunications extends Task { // todo: thows IOException? } catch (UnsupportedEncodingException uee){ isValid = false; - logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. . Returned:\n\t"+uee.getMessage(), EMsgType.FAIL); + logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t"+uee.getMessage(), EMsgType.FAIL); for (File nspFile : filesList) nspMap.put(nspFile.getName(), nspFile); close(EFileStatus.FAILED); diff --git a/src/main/java/nsusbloader/NET/NETPacket.java b/src/main/java/nsusbloader/NET/NETPacket.java index ed4dec3..f4ca5b7 100644 --- a/src/main/java/nsusbloader/NET/NETPacket.java +++ b/src/main/java/nsusbloader/NET/NETPacket.java @@ -24,7 +24,7 @@ public class NETPacket { "Accept-Ranges: bytes\r\n"+ "Content-Range: bytes %d-%d/%d\r\n"+ "Content-Length: %d\r\n"+ - "Last-Modified: Mon, 18 Mar 2019 12:57:33 GMT\r\n\r\n"; + "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n"; private static final String CODE_400 = "HTTP/1.0 400 invalid range\r\n"+ "Server: NS-USBloader-"+NSLMain.appVersion+"\r\n" + diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index dce8296..5a0ab9d 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -12,10 +12,9 @@ import java.util.Locale; import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v0.5.3"; + public static final String appVersion = "v0.6"; @Override public void start(Stage primaryStage) throws Exception{ - FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml")); Locale userLocale = new Locale(AppPreferences.getInstance().getLanguage()); // NOTE: user locale based on ISO3 Language codes diff --git a/src/main/java/nsusbloader/USB/PFS/NCAFile.java b/src/main/java/nsusbloader/USB/PFS/NCAFile.java deleted file mode 100644 index 9b52e12..0000000 --- a/src/main/java/nsusbloader/USB/PFS/NCAFile.java +++ /dev/null @@ -1,25 +0,0 @@ -package nsusbloader.USB.PFS; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Data class to hold NCA, tik, xml etc. meta-information - * */ -public class NCAFile { - //private int ncaNumber; - private byte[] ncaFileName; - private long ncaOffset; - private long ncaSize; - - //public void setNcaNumber(int ncaNumber){ this.ncaNumber = ncaNumber; } - public void setNcaFileName(byte[] ncaFileName) { this.ncaFileName = ncaFileName; } - public void setNcaOffset(long ncaOffset) { this.ncaOffset = ncaOffset; } - public void setNcaSize(long ncaSize) { this.ncaSize = ncaSize; } - - //public int getNcaNumber() {return this.ncaNumber; } - public byte[] getNcaFileName() { return ncaFileName; } - public byte[] getNcaFileNameLength() { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFileName.length).array(); } - public long getNcaOffset() { return ncaOffset; } - public long getNcaSize() { return ncaSize; } -} diff --git a/src/main/java/nsusbloader/USB/PFS/PFSProvider.java b/src/main/java/nsusbloader/USB/PFS/PFSProvider.java deleted file mode 100644 index 0ce87ad..0000000 --- a/src/main/java/nsusbloader/USB/PFS/PFSProvider.java +++ /dev/null @@ -1,218 +0,0 @@ -package nsusbloader.USB.PFS; - -import nsusbloader.ModelControllers.LogPrinter; -import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.ServiceWindow; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.*; - -/** - * Used in GoldLeaf USB protocol - * */ -public class PFSProvider { - private static final byte[] PFS0 = new byte[]{(byte)0x50, (byte)0x46, (byte)0x53, (byte)0x30}; // PFS0, and what did you think? - - private LogPrinter logPrinter; - private ResourceBundle rb; - - private RandomAccessFile randAccessFile; - private String nspFileName; - private NCAFile[] ncaFiles; - private long bodySize; - private int ticketID = -1; - - public PFSProvider(File nspFile, LogPrinter logPrinter){ - this.logPrinter = logPrinter; - try { - this.randAccessFile = new RandomAccessFile(nspFile, "r"); - nspFileName = nspFile.getName(); - } - catch (FileNotFoundException fnfe){ - logPrinter.print("PFS File not founnd: \n "+fnfe.getMessage(), EMsgType.FAIL); - nspFileName = null; - } - Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); - rb = ResourceBundle.getBundle("locale", userLocale); - } - - public boolean init() { - if (nspFileName == null) - return false; - - int filesCount; - int header; - - logPrinter.print("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO); - try { - byte[] fileStartingBytes = new byte[12]; - // Read PFS0, files count, header, padding (4 zero bytes) - if (randAccessFile.read(fileStartingBytes) == 12) - logPrinter.print("PFS Read file starting bytes.", EMsgType.PASS); - else { - logPrinter.print("PFS Read file starting bytes.", EMsgType.FAIL); - randAccessFile.close(); - return false; - } - // Check PFS0 - if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4))) - logPrinter.print("PFS Read 'PFS0'.", EMsgType.PASS); - else { - logPrinter.print("PFS Read 'PFS0'.", EMsgType.WARNING); - if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) { - randAccessFile.close(); - return false; - } - } - // Get files count - filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt(); - if (filesCount > 0 ) { - logPrinter.print("PFS Read files count [" + filesCount + "]", EMsgType.PASS); - } - else { - logPrinter.print("PFS Read files count", EMsgType.FAIL); - randAccessFile.close(); - return false; - } - // Get header - header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt(); - if (header > 0 ) - logPrinter.print("PFS Read header ["+header+"]", EMsgType.PASS); - else { - logPrinter.print("PFS Read header ", EMsgType.FAIL); - randAccessFile.close(); - return false; - } - //********************************************************************************************* - // Create NCA set - this.ncaFiles = new NCAFile[filesCount]; - // Collect files from NSP - byte[] ncaInfoArr = new byte[24]; // should be unsigned long, but.. java.. u know my pain man - - HashMap ncaNameOffsets = new LinkedHashMap<>(); - - int offset; - long nca_offset; - long nca_size; - long nca_name_offset; - - for (int i=0; i= 0?EMsgType.PASS:EMsgType.WARNING); - logPrinter.print(" NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING); - logPrinter.print(" NCA name offset check: "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING); - - NCAFile ncaFile = new NCAFile(); - ncaFile.setNcaOffset(nca_offset); - ncaFile.setNcaSize(nca_size); - this.ncaFiles[i] = ncaFile; - - ncaNameOffsets.put(i, nca_name_offset); - } - // Final offset - byte[] bufForInt = new byte[4]; - if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4]))) - logPrinter.print("PFS Final padding check", EMsgType.PASS); - else - logPrinter.print("PFS Final padding check", EMsgType.WARNING); - - // Calculate position including header for body size offset - bodySize = randAccessFile.getFilePointer()+header; - //********************************************************************************************* - // Collect file names from NCAs - logPrinter.print("PFS Collecting file names", EMsgType.INFO); - List ncaFN; // Temporary - 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 - while ((randAccessFile.read(b)) != -1){ - if (b[0] == 0x00) - break; - else - ncaFN.add(b[0]); - } - byte[] exchangeTempArray = new byte[ncaFN.size()]; - for (int j=0; j < ncaFN.size(); j++) - exchangeTempArray[j] = ncaFN.get(j); - // Find and store ticket (.tik) - if (new String(exchangeTempArray, StandardCharsets.UTF_8).toLowerCase().endsWith(".tik")) - this.ticketID = i; - this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length)); - } - randAccessFile.close(); - } - catch (IOException ioe){ - logPrinter.print("PFS Failed NSP file analyze for ["+nspFileName+"]\n "+ioe.getMessage(), EMsgType.FAIL); - ioe.printStackTrace(); - } - logPrinter.print("PFS Finish NSP file analyze for ["+nspFileName+"]", EMsgType.PASS); - - return true; - } - /** - * Return file name as byte array - * */ - public byte[] getBytesNspFileName(){ - return nspFileName.getBytes(StandardCharsets.UTF_8); - } - /** - * Return file name as String - * */ - public String getStringNspFileName(){ - return nspFileName; - } - /** - * Return file name length as byte array - * */ - public byte[] getBytesNspFileNameLength(){ - return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array(); - } - /** - * Return NCA count inside of file as byte array - * */ - public byte[] getBytesCountOfNca(){ - return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array(); - } - /** - * Return NCA count inside of file as int - * */ - public int getIntCountOfNca(){ - return ncaFiles.length; - } - /** - * Return requested-by-number NCA file inside of file - * */ - public NCAFile getNca(int ncaNumber){ - return ncaFiles[ncaNumber]; - } - /** - * Return bodySize - * */ - public long getBodySize(){ - return bodySize; - } - /** - * Return special NCA file: ticket - * (sugar) - * */ - public int getNcaTicketID(){ - return ticketID; - } -} diff --git a/src/main/java/nsusbloader/USB/UsbCommunications.java b/src/main/java/nsusbloader/USB/UsbCommunications.java index 4b1e9c0..0cabb05 100644 --- a/src/main/java/nsusbloader/USB/UsbCommunications.java +++ b/src/main/java/nsusbloader/USB/UsbCommunications.java @@ -5,45 +5,48 @@ import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.RainbowHexDump; -import nsusbloader.USB.PFS.PFSProvider; import org.usb4java.*; +import javax.swing.plaf.synth.SynthEditorPaneUI; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - +import java.util.*; +// TODO: add filter option to show only NSP files public class UsbCommunications extends Task { private final int DEFAULT_INTERFACE = 0; private LogPrinter logPrinter; private EFileStatus status = EFileStatus.FAILED; - private HashMap nspMap; + private LinkedHashMap nspMap; private Context contextNS; private DeviceHandle handlerNS; private String protocol; - /* - Ok, here is a story. We will pass to NS only file names, not full path. => see nspMap where 'key' is a file name. - File name itself should not be greater then 512 bytes, but in real world it's limited by OS to something like 256 bytes. - For sure, there could be FS that supports more then 256 and even more then 512 bytes. So if user decides to set name greater then 512 bytes, everything will ruin. - There is no extra validations for this situation. - Why we poking around 512 bytes? Because it's the maximum size of byte-array that USB endpoind of NS could return. And in runtime it returns the filename. - Therefore, the file name shouldn't be greater then 512. If file name + path-to-file is greater then 512 bytes, we can handle it: sending only file name instead of full path. - Since this application let user an ability (theoretically) to choose same files in different folders, the latest selected file will be added to the list and handled correctly. - I have no idea why he/she will make a decision to do that. Just in case, we're good in this point. - */ - public UsbCommunications(List nspList, String protocol){ + private boolean nspFilterForGl; + private boolean proxyForGL = false; + + /* + Ok, here is a story. We will pass to NS only file names, not full path. => see nspMap where 'key' is a file name. + File name itself should not be greater then 512 bytes, but in real world it's limited by OS to something like 256 bytes. + For sure, there could be FS that supports more then 256 and even more then 512 bytes. So if user decides to set name greater then 512 bytes, everything will ruin. + There is no extra validations for this situation. + Why we poking around 512 bytes? Because it's the maximum size of byte-array that USB endpoind of NS could return. And in runtime it returns the filename. + Therefore, the file name shouldn't be greater then 512. If file name + path-to-file is greater then 512 bytes, we can handle it: sending only file name instead of full path. + + Since this application let user an ability (theoretically) to choose same files in different folders, the latest selected file will be added to the list and handled correctly. + I have no idea why he/she will make a decision to do that. Just in case, we're good in this point. + */ + public UsbCommunications(List nspList, String protocol, boolean filterNspFilesOnlyForGl){ this.protocol = protocol; - this.nspMap = new HashMap<>(); + this.nspFilterForGl = filterNspFilesOnlyForGl; + this.nspMap = new LinkedHashMap<>(); for (File f: nspList) nspMap.put(f.getName(), f); this.logPrinter = new LogPrinter(); @@ -95,41 +98,6 @@ public class UsbCommunications extends Task { } } // Free device list. - - ////////////////////////////////////////// DEBUG INFORMATION START /////////////////////////////////////////// - /* - ConfigDescriptor configDescriptor = new ConfigDescriptor(); - //result = LibUsb.getConfigDescriptor(deviceNS, (byte)0x01, configDescriptor); - result = LibUsb.getActiveConfigDescriptor(deviceNS, configDescriptor); - - switch (result){ - case 0: - logPrinter.print("DBG: getActiveConfigDescriptor\n"+configDescriptor.dump(), EMsgType.PASS); - break; - case LibUsb.ERROR_NOT_FOUND: - logPrinter.print("DBG: getActiveConfigDescriptor: ERROR_NOT_FOUND", EMsgType.FAIL); - break; - default: - logPrinter.print("DBG: getActiveConfigDescriptor: "+result, EMsgType.FAIL); - break; - } - - LibUsb.freeConfigDescriptor(configDescriptor); - //*/ - /* - * So what did we learn? - * bConfigurationValue 1 - * bInterfaceNumber = 0 - * bEndpointAddress 0x81 EP 1 IN - * Transfer Type Bulk - * bEndpointAddress 0x01 EP 1 OUT - * Transfer Type Bulk - * - * Or simply run this on your *nix host: - * # lsusb -v -d 057e:3000 - * */ - ////////////////////////////////////////// DEBUG INFORMATION END ///////////////////////////////////////////// - if (deviceNS != null){ logPrinter.print("NS in connected USB devices found", EMsgType.PASS); } @@ -250,7 +218,7 @@ public class UsbCommunications extends Task { private boolean sendListOfNSP(){ // Send list of NSP files: // Proceed "TUL0" - if (!writeToUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} + if (writeToUsb("TUL0".getBytes(StandardCharsets.US_ASCII))) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} logPrinter.print("TF Send list of files: handshake", EMsgType.FAIL); return false; } @@ -271,19 +239,19 @@ public class UsbCommunications extends Task { // Sending NSP list logPrinter.print("TF Send list of files", EMsgType.INFO); - if (!writeToUsb(nspListSize)) { // size of the list we're going to transfer goes... + if (writeToUsb(nspListSize)) { // size of the list we're going to transfer goes... logPrinter.print(" [send list length]", EMsgType.FAIL); return false; } logPrinter.print(" [send list length]", EMsgType.PASS); - if (!writeToUsb(new byte[8])) { // 8 zero bytes goes... + if (writeToUsb(new byte[8])) { // 8 zero bytes goes... logPrinter.print(" [send padding]", EMsgType.FAIL); return false; } logPrinter.print(" [send padding]", EMsgType.PASS); - if (!writeToUsb(nspListNames)) { // list of the names goes... + if (writeToUsb(nspListNames)) { // list of the names goes... logPrinter.print(" [send list itself]", EMsgType.FAIL); return false; } @@ -404,7 +372,7 @@ public class UsbCommunications extends Task { return false; } //write to USB - if (!writeToUsb(bufferCurrent)) { + if (writeToUsb(bufferCurrent)) { logPrinter.print("TF Failure during NSP transmission.", EMsgType.FAIL); return false; } @@ -442,7 +410,7 @@ public class UsbCommunications extends Task { * */ private boolean sendResponse(byte[] rangeSize){ // This method as separate function itself for application needed as a cookie in the middle of desert. logPrinter.print("TF Sending response", EMsgType.INFO); - if (!writeToUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0' + if (writeToUsb(new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0' (byte) 0x01, // CMD_TYPE_RESPONSE = 1 (byte) 0x00, (byte) 0x00, (byte) 0x00, // kinda padding. Guys, didn't you want to use integer value for CMD semantic? (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00} ) // Send integer value of '1' in Little-endian format. @@ -451,12 +419,12 @@ public class UsbCommunications extends Task { return false; } logPrinter.print(" [1/3]", EMsgType.PASS); - if(!writeToUsb(rangeSize)) { // Send EXACTLY what has been received + if(writeToUsb(rangeSize)) { // Send EXACTLY what has been received logPrinter.print(" [2/3]", EMsgType.FAIL); return false; } logPrinter.print(" [2/3]", EMsgType.PASS); - if(!writeToUsb(new byte[12])) { // kinda another one padding + if(writeToUsb(new byte[12])) { // kinda another one padding logPrinter.print(" [3/3]", EMsgType.FAIL); return false; } @@ -469,244 +437,950 @@ public class UsbCommunications extends Task { * GoldLeaf processing * */ private class GoldLeaf{ - // CMD G L U C - private final byte[] CMD_GLUC = new byte[]{0x47, 0x4c, 0x55, 0x43}; - private final byte[] CMD_ConnectionRequest = new byte[]{0x00, 0x00, 0x00, 0x00}; // Write-only command - private final byte[] CMD_NSPName = new byte[]{0x02, 0x00, 0x00, 0x00}; // Write-only command - private final byte[] CMD_NSPData = new byte[]{0x04, 0x00, 0x00, 0x00}; // Write-only command + // 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, 0x64, (byte) 0xcb, 0x00, 0x00}; // used @ writeToUsb_GLCMD + private final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; - private final byte[] CMD_ConnectionResponse = new byte[]{0x01, 0x00, 0x00, 0x00}; - private final byte[] CMD_Start = new byte[]{0x03, 0x00, 0x00, 0x00}; - private final byte[] CMD_NSPContent = new byte[]{0x05, 0x00, 0x00, 0x00}; - private final byte[] CMD_NSPTicket = new byte[]{0x06, 0x00, 0x00, 0x00}; - private final byte[] CMD_Finish = new byte[]{0x07, 0x00, 0x00, 0x00}; + // 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 HashMap writeFilesMap; GoldLeaf(){ - logPrinter.print("===========================================================================", EMsgType.INFO); - PFSProvider pfsElement = new PFSProvider(nspMap.get(nspMap.keySet().toArray()[0]), logPrinter); - if (!pfsElement.init()) { - logPrinter.print("GL File provided have incorrect structure and won't be uploaded", EMsgType.FAIL); - status = EFileStatus.INCORRECT_FILE_FAILED; - return; - } - logPrinter.print("GL File structure validated and it will be uploaded", EMsgType.PASS); + final byte CMD_GetDriveCount = 0x00; + final byte CMD_GetDriveInfo = 0x01; + final byte CMD_StatPath = 0x02; // TODO: proxy done [proxy: in case if folder contains ENG+RUS+UKR file names works incorrect] + final byte CMD_GetFileCount = 0x03; + final byte CMD_GetFile = 0x04; // TODO: proxy done + final byte CMD_GetDirectoryCount = 0x05; + final byte CMD_GetDirectory = 0x06; // TODO: proxy done + final byte CMD_ReadFile = 0x07; // TODO: no way to do poxy + final byte CMD_WriteFile = 0x08; // TODO: add predictable behavior + final byte CMD_Create = 0x09; + final byte CMD_Delete = 0x0a;//10 + final byte CMD_Rename = 0x0b;//11 + final byte CMD_GetSpecialPathCount = 0x0c;//12 // Special folders count; simplified usage @ NS-UL + final byte CMD_GetSpecialPath = 0x0d;//13 // Information about special folders; simplified usage @ NS-UL + final byte CMD_SelectFile = 0x0e;//14 // WTF? Ignoring for now. For future: execute another thread within this(?) context for FileChooser + final byte CMD_Max = 0x0f;//15 // not used @ NS-UL & GT + + logPrinter.print("============= GoldLeaf =============\n\tVIRT:/ equals files added into the application\n\tHOME:/ 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; + + status = EFileStatus.UNKNOWN; - if (initGoldLeafProtocol(pfsElement)) - status = EFileStatus.UPLOADED; // else - no change status that is already set to FAILED - } - private boolean initGoldLeafProtocol(PFSProvider pfsElement){ // Go parse commands byte[] readByte; + int someLength; + while (! isCancelled()) { // Till user interrupted process. + readByte = readGL(); - // Go connect to GoldLeaf + if (readByte == null) // Issue @ readFromUsbGL method + return; + else if(readByte.length < 4096) { // Just timeout of waiting for reply; continue loop + closeOpenedReadFilesGl(); + continue; + } - if (!writeToUsb(CMD_GLUC)) { - logPrinter.print("GL Initiating GoldLeaf connection: 1/2", EMsgType.FAIL); - return false; + //RainbowHexDump.hexDumpUTF8(readByte); // TODO: DEBUG + //System.out.println("CHOICE: "+readByte[4]); // TODO: DEBUG + + if (Arrays.equals(Arrays.copyOfRange(readByte, 0,4), CMD_GLCI)) { + switch (readByte[4]) { + case CMD_GetDriveCount: + if (getDriveCount()) + return; + break; + case CMD_GetDriveInfo: + if (getDriveInfo(arrToIntLE(readByte,8))) + return; + break; + case CMD_GetSpecialPathCount: + if (getSpecialPathCount()) + return; + break; + case CMD_GetSpecialPath: + if (getSpecialPath(arrToIntLE(readByte,8))) + return; + break; + case CMD_GetDirectoryCount: + if (getDirectoryOrFileCount(new String(readByte, 12, arrToIntLE(readByte, 8), StandardCharsets.UTF_8), true)) + return; + break; + case CMD_GetFileCount: + if (getDirectoryOrFileCount(new String(readByte, 12, arrToIntLE(readByte, 8), StandardCharsets.UTF_8), false)) + return; + break; + case CMD_GetDirectory: + someLength = arrToIntLE(readByte, 8); + if (getDirectory(new String(readByte, 12, someLength, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength+12))) + return; + break; + case CMD_GetFile: + someLength = arrToIntLE(readByte, 8); + if (getFile(new String(readByte, 12, someLength, StandardCharsets.UTF_8), arrToIntLE(readByte, someLength+12))) + return; + break; + case CMD_StatPath: + if (statPath(new String(readByte, 12, arrToIntLE(readByte, 8), StandardCharsets.UTF_8))) + return; + break; + case CMD_Rename: + someLength = arrToIntLE(readByte, 12); + if (rename(new String(readByte, 16, someLength, StandardCharsets.UTF_8), + new String(readByte, 16+someLength+4, arrToIntLE(readByte, 16+someLength), StandardCharsets.UTF_8))) + return; + break; + case CMD_Delete: + if (delete(new String(readByte, 16, arrToIntLE(readByte, 12), StandardCharsets.UTF_8))) + return; + break; + case CMD_Create: + if (create(new String(readByte, 16, arrToIntLE(readByte, 12), StandardCharsets.UTF_8), readByte[8])) + return; + break; + case CMD_ReadFile: + someLength = arrToIntLE(readByte, 8); + if (readFile(new String(readByte, 12, someLength, StandardCharsets.UTF_8), + arrToLongLE(readByte, 12+someLength), + arrToLongLE(readByte, 12+someLength+8))) + return; + break; + case CMD_WriteFile: + someLength = arrToIntLE(readByte, 8); + if (writeFile(new String(readByte, 12, someLength, StandardCharsets.UTF_8), + arrToLongLE(readByte, 12+someLength))) + return; + break; + default: + writeGL_FAIL("GL Unknown command: "+readByte[4]+" [it's a very bad sign]"); + } + } } - logPrinter.print("GL Initiating GoldLeaf connection: 1/2", EMsgType.PASS); - if (!writeToUsb(CMD_ConnectionRequest)){ - logPrinter.print("GL Initiating GoldLeaf connection: 2/2", EMsgType.FAIL); - return false; + // Close (and flush) all opened streams. + if (writeFilesMap.size() != 0){ + for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ + try{ + fBufOutStream.close(); + }catch (IOException ignored){} + } } - logPrinter.print("GL Initiating GoldLeaf connection: 2/2", EMsgType.PASS); + closeOpenedReadFilesGl(); + } - while (true) { - readByte = readFromUsb(); - if (readByte == null) + /** + * Close files opened for read/write + */ + private void closeOpenedReadFilesGl(){ + if (openReadFileNameAndPath != null){ // Perfect time to close our opened files + try{ + randAccessFile.close(); + } + catch (IOException ignored){} + openReadFileNameAndPath = null; + randAccessFile = null; + } + } + /** + * Handle GetDriveCount + * @return true if failed + * false if everything is ok + */ + private boolean getDriveCount(){ + // Let's declare 2 drives + byte[] drivesCnt = intToArrLE(2);//2 + // Write count of drives + if (writeGL_PASS(drivesCnt)) { + logPrinter.print("GL Handle 'ListDrives' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * Handle GetDriveInfo + * @return true if failed + * false if everything is ok + */ + private boolean getDriveInfo(int driveNo){ + if (driveNo < 0 || driveNo > 1){ + return writeGL_FAIL("GL Handle 'GetDriveInfo' command [no such drive]"); + } + + byte[] driveLabel, + driveLabelLen, + driveLetter, + driveLetterLen, + totalFreeSpace, + totalSize; + long totalSizeLong = 0; + + // 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); + totalFreeSpace = new byte[4]; + for (File nspFile : nspMap.values()){ + totalSizeLong += nspFile.length(); + } + totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! + } + 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); + File userHomeDir = new File(System.getProperty("user.home")); + long totalFreeSpaceLong = userHomeDir.getFreeSpace(); + totalFreeSpace = Arrays.copyOfRange(longToArrLE(totalFreeSpaceLong), 0, 4); // Dirty hack; now for GL!; + totalSizeLong = userHomeDir.getTotalSpace(); + totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! + } + + 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)) { + logPrinter.print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); + return true; + } + + return false; + } + /** + * Handle SpecialPathCount + * @return true if failed + * false if everything is ok + * */ + private boolean getSpecialPathCount(){ + // Let's declare nothing =) + byte[] virtDrivesCnt = intToArrLE(0); + // Write count of special paths + if (writeGL_PASS(virtDrivesCnt)) { + logPrinter.print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); + return true; + } + return false; + } + /** + * Handle SpecialPath + * @return true if failed + * false if everything is ok + * */ + private boolean getSpecialPath(int virtDriveNo){ + return writeGL_FAIL("GL Handle 'SpecialPath' command [not supported]"); + } + /** + * Handle GetDirectoryCount & GetFileCount + * @return true if failed + * false if everything is ok + * */ + private boolean getDirectoryOrFileCount(String path, boolean isGetDirectoryCount) { + if (path.equals("VIRT:/")) { + if (isGetDirectoryCount){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + return true; + } + } + else { + if (writeGL_PASS(intToArrLE(nspMap.size()))) { + logPrinter.print("GL Handle 'GetFileCount' command Count = "+nspMap.size(), EMsgType.FAIL); + return true; + } + } + } + else if (path.startsWith("HOME:/")){ + // Let's make it normal path + path = path.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); // HANDLE 'PATH' SEPARATOR FOR WINDOWS + // 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.getName().startsWith(".")); // TODO: FIX FOR WIN ? + }); + } + else { + if (nspFilterForGl){ + filesOrDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && name.endsWith(".nsp")); // TODO: FIX FOR WIN ? + }); + } + else { + filesOrDirs = pathDir.list((current, name) -> { + File dir = new File(current, name); + return (! dir.isDirectory() && (! name.startsWith("."))); // TODO: MOVE TO PROD + }); + } + } + // If somehow there are no folders, let's say 0; + if (filesOrDirs == null){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + return true; + } + //logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.PASS); return false; - if (Arrays.equals(readByte, CMD_GLUC)) { - readByte = readFromUsb(); - if (readByte == null) + } + // Sorting is mandatory + Arrays.sort(filesOrDirs, String.CASE_INSENSITIVE_ORDER); + + if (isGetDirectoryCount) + this.recentDirs = filesOrDirs; + else + this.recentFiles = filesOrDirs; + + // Otherwise, let's tell how may folders are in there + if (writeGL_PASS(intToArrLE(filesOrDirs.length))) { + logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + return true; + } + } + // If requested drive is not VIRT and not HOME then reply error + else { + return writeGL_FAIL("GL Handle 'GetDirectoryOrFileCount' command [unknown drive request]"); + } + 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 = dirName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); + + List command = new LinkedList<>(); + + if (dirName.equals(recentPath) && recentDirs != null && recentDirs.length != 0){ + command.add(intToArrLE(recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8).length)); + command.add(recentDirs[subDirNo].getBytes(StandardCharsets.UTF_8)); + } + 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.getName().startsWith(".")); // 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 (proxyForGL) + return proxyGetDirFile(true); + else { + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + // VIRT:// and any other + return writeGL_FAIL("GL Handle 'GetDirectory' command for virtual drive [no folders support]"); + } + /** + * Handle GetFile + * @return true if failed + * false if everything is ok + * */ + private boolean getFile(String dirName, int subDirNo){ + List command = new LinkedList<>(); + + if (dirName.startsWith("HOME:/")) { + dirName = dirName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); + + if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ + byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_8); + + command.add(intToArrLE(fileNameBytes.length)); + 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.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() && (! name.startsWith("."))); // 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)); + command.add(fileNameBytes); + } + else + return writeGL_FAIL("GL Handle 'GetFile' command [doesn't exist or not a folder]"); + } + if (proxyForGL) + return proxyGetDirFile(false); + else { + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + else if (dirName.equals("VIRT:/")){ + if (nspMap.size() != 0){ // therefore nspMapKeySetIndexes also != 0 + byte[] fileNameBytes = nspMapKeySetIndexes[subDirNo].getBytes(StandardCharsets.UTF_8); + command.add(intToArrLE(fileNameBytes.length)); + command.add(fileNameBytes); + + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + // any other cases + return writeGL_FAIL("GL Handle 'GetFile' command for virtual drive [no folders support]"); + } + /** + * Handle StatPath + * @return true if failed + * false if everything is ok + * */ + private boolean statPath(String filePath){ + //System.out.println(filePath+recentDirs[0]); // TODO: DEBUG + List command = new LinkedList<>(); + + if (filePath.startsWith("HOME:/")){ + filePath = filePath.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); + + if (proxyForGL) + return proxyStatPath(filePath); // dirty name + + File fileDirElement = new File(filePath); + if (fileDirElement.exists()){ + if (fileDirElement.isDirectory()) + command.add(GL_OBJ_TYPE_DIR); + else { + command.add(GL_OBJ_TYPE_FILE); + command.add(longToArrLE(fileDirElement.length())); + } + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + else if (filePath.startsWith("VIRT:/")) { + filePath = filePath.replaceFirst("VIRT:/", ""); + + if (nspMap.containsKey(filePath)){ + command.add(GL_OBJ_TYPE_FILE); // THIS IS INT + command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG! + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + 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 = fileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator).replaceAll("/", File.separator); + newFileName = newFileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator).replaceAll("/", File.separator); + File currentFile = new File(fileName); + File newFile = new File(newFileName); + if (! newFile.exists()){ // Else, report error + try { + if (currentFile.renameTo(newFile)){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + return true; + } + return false; + } + } + catch (SecurityException ignored){} // Ah, leave it + } + } + // For VIRT:/ and others we don't serve requests + return writeGL_FAIL("GL Handle 'Rename' command [not supported for virtual drive/wrong drive/file with such name already exists/read-only directory]"); + } + /** + * Handle 'Delete' + * @return true if failed + * false if everything is ok + * */ + private boolean delete(String fileName) { + if (fileName.startsWith("HOME:/")) { + fileName = fileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator).replaceAll("/", File.separator); + File fileToDel = new File(fileName); + try { + if (fileToDel.delete()){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + return true; + } return false; - if (Arrays.equals(readByte, CMD_ConnectionResponse)) { - if (!handleConnectionResponse(pfsElement)) - return false; - else - continue; - } - if (Arrays.equals(readByte, CMD_Start)) { - if (!handleStart(pfsElement)) - return false; - else - continue; - } - if (Arrays.equals(readByte, CMD_NSPContent)) { - if (!handleNSPContent(pfsElement, true)) - return false; - else - continue; - } - if (Arrays.equals(readByte, CMD_NSPTicket)) { - if (!handleNSPContent(pfsElement, false)) - return false; - else - continue; - } - if (Arrays.equals(readByte, CMD_Finish)) { - logPrinter.print("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS); - break; } } + catch (SecurityException ignored){} // Ah, leave it } - return true; + // 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]"); } /** - * ConnectionResponse command handler + * 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 handleConnectionResponse(PFSProvider pfsElement){ - logPrinter.print("GL 'ConnectionResponse' command:", EMsgType.INFO); - if (!writeToUsb(CMD_GLUC)) { - logPrinter.print(" [1/4]", EMsgType.FAIL); - return false; + private boolean create(String fileName, byte type) { + if (fileName.startsWith("HOME:/")) { + fileName = fileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator).replaceAll("/", File.separator); + File fileToCreate = new File(fileName); + boolean result = false; + if (type == 1){ + try { + result = fileToCreate.createNewFile(); + } + catch (SecurityException | IOException ignored){} + } + else if (type == 2){ + try { + result = fileToCreate.mkdir(); + } + catch (SecurityException ignored){} + } + if (result){ + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'Create' command.", EMsgType.FAIL); + return true; + } + //logPrinter.print("GL Handle 'Create' command.", EMsgType.PASS); + return false; + } } - logPrinter.print(" [1/4]", EMsgType.PASS); - if (!writeToUsb(CMD_NSPName)) { - logPrinter.print(" [2/4]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [2/4]", EMsgType.PASS); - - if (!writeToUsb(pfsElement.getBytesNspFileNameLength())) { - logPrinter.print(" [3/4]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [3/4]", EMsgType.PASS); - - if (!writeToUsb(pfsElement.getBytesNspFileName())) { - logPrinter.print(" [4/4]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [4/4]", EMsgType.PASS); - - return true; + // 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]"); } + /** - * Start command handler + * 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 handleStart(PFSProvider pfsElement){ - logPrinter.print("GL Handle 'Start' command:", EMsgType.INFO); - if (!writeToUsb(CMD_GLUC)) { - logPrinter.print(" [Send command prepare]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [Send command prepare]", EMsgType.PASS); - - if (!writeToUsb(CMD_NSPData)) { - logPrinter.print(" [Send command]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [Send command]", EMsgType.PASS); - - if (!writeToUsb(pfsElement.getBytesCountOfNca())) { - logPrinter.print(" [Send length]", EMsgType.FAIL); - return false; - } - logPrinter.print(" [Send length]", EMsgType.PASS); - - int ncaCount = pfsElement.getIntCountOfNca(); - logPrinter.print(" [Send information for "+ncaCount+" files]", EMsgType.INFO); - for (int i = 0; i < ncaCount; i++){ - if (!writeToUsb(pfsElement.getNca(i).getNcaFileNameLength())) { - logPrinter.print(" [1/4] File #"+i, EMsgType.FAIL); - return false; + private boolean readFile(String fileName, long offset, long size) { + System.out.println(fileName+" "+offset+" "+size+" "); // TODO: DEBUG + 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 + try{ + randAccessFile.close(); + }catch (IOException ignored){} + try{ + randAccessFile = new RandomAccessFile(nspMap.get(fileName.substring(6)), "r"); + openReadFileNameAndPath = fNamePath; + } + catch (IOException ioe){ // TODO: MOVE THIS SHIT TO METHOD ALREADY! + return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); + } } - logPrinter.print(" [1/4] File #"+i, EMsgType.PASS); - - if (!writeToUsb(pfsElement.getNca(i).getNcaFileName())) { - logPrinter.print(" [2/4] File #"+i, EMsgType.FAIL); - return false; - } - logPrinter.print(" [2/4] File #"+i, EMsgType.PASS); - if (!writeToUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) { // offset. real. - logPrinter.print(" [3/4] File #"+i, EMsgType.FAIL); - return false; - } - logPrinter.print(" [3/4] File #"+i, EMsgType.PASS); - if (!writeToUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) { // size - logPrinter.print(" [4/4] File #"+i, EMsgType.FAIL); - return false; - } - logPrinter.print(" [4/4] File #"+i, EMsgType.PASS); - } - return true; - } - /** - * NSPContent command handler - * isItRawRequest - if True, just ask NS what's needed - * - if False, send ticket - * */ - private boolean handleNSPContent(PFSProvider pfsElement, boolean isItRawRequest){ - int requestedNcaID; - - if (isItRawRequest) { - logPrinter.print("GL Handle 'Content' command", EMsgType.INFO); - byte[] readByte = readFromUsb(); - if (readByte == null || readByte.length != 4) { - logPrinter.print(" [Read requested ID]", EMsgType.FAIL); - return false; - } - requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt(); - logPrinter.print(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS); } else { - requestedNcaID = pfsElement.getNcaTicketID(); - logPrinter.print("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO); - } - - long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize(); - long realNcaSize = pfsElement.getNca(requestedNcaID).getNcaSize(); - - long readFrom = 0; - - int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 - byte[] readBuf; - File nspFile = nspMap.get(pfsElement.getStringNspFileName()); // wuuuut ( >< ) - try{ - BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspFile)); // TODO: refactor? - if (bufferedInStream.skip(realNcaOffset) != realNcaOffset) - return false; - - while (readFrom < realNcaSize){ - - if (isCancelled()) // Check if user interrupted process. - return false; - - if (realNcaSize - readFrom < readPice) - readPice = Math.toIntExact(realNcaSize - readFrom); // it's safe, I guarantee - readBuf = new byte[readPice]; - if (bufferedInStream.read(readBuf) != readPice) - return false; - //System.out.println("S: "+readFrom+" T: "+realNcaSize+" P: "+readPice); // DEBUG - if (!writeToUsb(readBuf)) - return false; - //-----------------------------------------/ - try { - logPrinter.updateProgress((readFrom+readPice)/(realNcaSize/100.0) / 100.0); - }catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } - //-----------------------------------------/ - readFrom += readPice; - } - bufferedInStream.close(); - //-----------------------------------------/ + // Let's find out which file requested + fileName = fileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); + // Try close what opened try{ - logPrinter.updateProgress(1.0); + randAccessFile.close(); + }catch (IOException ignored){} + // If we don't have this file opened, let's open it + if (openReadFileNameAndPath == null || (! openReadFileNameAndPath.equals(fileName))) { + try{ + randAccessFile = new RandomAccessFile(fileName, "r"); + openReadFileNameAndPath = fileName; + }catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); + } } - catch (InterruptedException ie){ - getException().printStackTrace(); // TODO: Do something with this - } - //-----------------------------------------/ } - catch (IOException ioe){ - logPrinter.print(" Failed to read NCA ID "+requestedNcaID+". IO Exception:\n "+ioe.getMessage(), EMsgType.FAIL); - ioe.printStackTrace(); + //----------------------- Actual transfer chain ------------------------ + try{ + randAccessFile.seek(offset); + byte[] chunk = new byte[(int)size]; // WTF MAN? + // Let's find out how much bytes we got + int bytesRead = randAccessFile.read(chunk); + // Let's tell as a command about our result. + if (writeGL_PASS(intToArrLE(bytesRead))) { + logPrinter.print("GL Handle 'ReadFile' command [1/?]", EMsgType.FAIL); + return true; + } + if (bytesRead > 8388608){ + // Let's bypass bytes we read part 1 + if (writeToUsb(Arrays.copyOfRange(chunk, 0, 8388608))) { + logPrinter.print("GL Handle 'ReadFile' command [2/3]", EMsgType.FAIL); + return true; + } + // Let's bypass bytes we read part 2 + if (writeToUsb(Arrays.copyOfRange(chunk, 8388608, chunk.length))) { + logPrinter.print("GL Handle 'ReadFile' command [2/3]", EMsgType.FAIL); + return true; + } + return false; + } + // Let's bypass bytes we read total + if (writeToUsb(chunk)) { + logPrinter.print("GL Handle 'ReadFile' command [2/2]", EMsgType.FAIL); + return true; + } return false; } - return true; + catch (IOException ioe){ + try{ + randAccessFile.close(); + } + catch (IOException ioee){ + logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioee.getMessage(), EMsgType.WARNING); + } + openReadFileNameAndPath = null; + randAccessFile = 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 + * @param size requested size + * @return true if failed + * false if everything is ok + * */ + private boolean writeFile(String fileName, long size) { + if (fileName.startsWith("VIRT:/")){ + return writeGL_FAIL("GL Handle 'WriteFile' command [not supported for virtual drive]"); + } + else { + if ((int)size > 8388608){ + logPrinter.print("GL Handle 'WriteFile' command [Files greater than 8mb are not supported]", EMsgType.FAIL); + return true; + } + + fileName = fileName.replaceFirst("HOME:/", System.getProperty("user.home")+File.separator) + .replaceAll("/", File.separator); + // Check if we didn't see this (or any) file during this session + if (writeFilesMap.size() == 0 || (! writeFilesMap.containsKey(fileName))){ + // Open what we have to open + File writeFile = new File(fileName); + // If this file exists GL will take care + // Otherwise, let's add it + try{ + BufferedOutputStream writeFileBufOutStream = new BufferedOutputStream(new FileOutputStream(writeFile, true)); + writeFilesMap.put(fileName, writeFileBufOutStream); + } catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage()); + } + } + // Now we have stream + BufferedOutputStream myStream = writeFilesMap.get(fileName); + + byte[] transferredData; + + if ((transferredData = readGL_file()) == null){ + logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); + return true; + } + try{ + myStream.write(transferredData, 0, transferredData.length); + } + catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage()); + } + System.out.println("READ COMLETE"); + // Report we're good + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); + return true; + } + return false; + } + } + + /*----------------------------------------------------*/ + /* GL READ/WRITE USB SPECIFIC */ + /*----------------------------------------------------*/ + + /** + * 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))){ + logPrinter.print(reportToUImsg, EMsgType.WARNING); + return true; + } + logPrinter.print(reportToUImsg, EMsgType.FAIL); + return false; + } + private byte[] readGL(){ + ByteBuffer readBuffer = ByteBuffer.allocateDirect(4096); // GL really? + // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. + IntBuffer readBufTransferred = IntBuffer.allocate(1); + + int result; + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 5000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + if (result != LibUsb.SUCCESS && result != LibUsb.ERROR_TIMEOUT){ + logPrinter.print("GL Data transfer (read) issue\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("Execution stopped", EMsgType.FAIL); + return null; + } + else { + int trans = readBufTransferred.get(); + byte[] receivedBytes = new byte[trans]; + readBuffer.get(receivedBytes); + return receivedBytes; + } + } + private byte[] readGL_file(){ + ByteBuffer readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. + // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. + IntBuffer readBufTransferred = IntBuffer.allocate(1); // Works for 8mb + + int result; + result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 0); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81 + + if (result != LibUsb.SUCCESS && result != LibUsb.ERROR_TIMEOUT){ + logPrinter.print("GL Data transfer (read) issue\n Returned: "+UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + logPrinter.print("Execution stopped", EMsgType.FAIL); + return null; + } + else { + int trans = readBufTransferred.get(); + byte[] receivedBytes = new byte[trans]; + readBuffer.get(receivedBytes); + return receivedBytes; + } + } + /*----------------------------------------------------*/ + /* GL HELPERS */ + /*----------------------------------------------------*/ + + /** + * 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 EXPERIMENTAL PART */ + /*----------------------------------------------------*/ + + private boolean proxyStatPath(String path) { + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + List fileBytesSize = new LinkedList<>(); + if ((recentDirs.length == 0) && (recentFiles.length == 0)) { + return writeGL_FAIL("proxyStatPath"); + } + if (recentDirs.length > 0){ + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(GL_OBJ_TYPE_DIR); + byte[] resultingDir = writeBuffer.array(); + writeToUsb(resultingDir); + for (int i = 1; i < recentDirs.length; i++) { + readGL(); + writeToUsb(resultingDir); + } + } + if (recentFiles.length > 0){ + path = path.replaceAll(recentDirs[0]+"$", ""); // Remove the name from path + for (String fileName : recentFiles){ + File f = new File(path+fileName); + fileBytesSize.add(longToArrLE(f.length())); + } + writeBuffer.clear(); + for (int i = 0; i < recentFiles.length; i++){ + readGL(); + writeBuffer.clear(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(GL_OBJ_TYPE_FILE); + writeBuffer.put(fileBytesSize.get(i)); + writeToUsb(writeBuffer.array()); + } + } + return false; + } + + private boolean proxyGetDirFile(boolean forDirs){ + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + List dirBytesNameSize = new LinkedList<>(); + List dirBytesName = new LinkedList<>(); + if (forDirs) { + if (recentDirs.length <= 0) + return writeGL_FAIL("proxyGetDirFile"); + for (String dirName : recentDirs) { + byte[] name = dirName.getBytes(StandardCharsets.UTF_8); + dirBytesNameSize.add(intToArrLE(name.length)); + dirBytesName.add(name); + } + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(0)); + writeBuffer.put(dirBytesName.get(0)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + for (int i = 1; i < recentDirs.length; i++){ + readGL(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(i)); + writeBuffer.put(dirBytesName.get(i)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + } + } + else { + if (recentDirs.length <= 0) + return writeGL_FAIL("proxyGetDirFile"); + for (String dirName : recentFiles){ + byte[] name = dirName.getBytes(StandardCharsets.UTF_8); + dirBytesNameSize.add(intToArrLE(name.length)); + dirBytesName.add(name); + } + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(0)); + writeBuffer.put(dirBytesName.get(0)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + for (int i = 1; i < recentFiles.length; i++){ + readGL(); + writeBuffer.put(CMD_GLCO_SUCCESS); + writeBuffer.put(dirBytesNameSize.get(i)); + writeBuffer.put(dirBytesName.get(i)); + writeToUsb(writeBuffer.array()); + writeBuffer.clear(); + } + } + return false; + } + } + //------------------------------------------------------------------------------------------------------------------ /** * Correct exit @@ -738,8 +1412,8 @@ public class UsbCommunications extends Task { } /** * Sending any byte array to USB device - * @return 'true' if no issues - * 'false' if errors happened + * @return 'false' if no issues + * 'true' if errors happened * */ private boolean writeToUsb(byte[] message){ ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN; @@ -751,14 +1425,15 @@ public class UsbCommunications extends Task { if (result != LibUsb.SUCCESS){ logPrinter.print("Data transfer (write) issue\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); logPrinter.print("Execution stopped", EMsgType.FAIL); - return false; - }else { + return true; + } + else { if (writeBufTransferred.get() != message.length){ logPrinter.print("Data transfer (write) issue\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); - return false; + return true; } else { - return true; + return false; } } } @@ -768,7 +1443,7 @@ public class UsbCommunications extends Task { * 'null' if read failed * */ private byte[] readFromUsb(){ - ByteBuffer readBuffer = ByteBuffer.allocateDirect(512);// //readBuffer.order() equals BIG_ENDIAN; DON'T TOUCH. And we will always allocate readBuffer for max-size endpoint supports (512 bytes) + ByteBuffer readBuffer = ByteBuffer.allocateDirect(512); // We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb. IntBuffer readBufTransferred = IntBuffer.allocate(1); diff --git a/src/main/resources/SettingsTab.fxml b/src/main/resources/SettingsTab.fxml index 4f93bf8..e9c85fa 100644 --- a/src/main/resources/SettingsTab.fxml +++ b/src/main/resources/SettingsTab.fxml @@ -13,9 +13,10 @@ - + + diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index a259a01..f2f8bf7 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -45,3 +45,4 @@ netTabAllowXciCb=Allow XCI files selection for TinFoil netTabAllowXciTextField=Used by some third-party applications that support XCI and utilizes TinFoil transfer protocol. Don't change if not sure. netTabLanguage=Language windowBodyRestartToApplyLang=Please restart application to apply changes. +netTabGLshowNSPonly=Show only *.nsp in GoldLeaf. diff --git a/src/main/resources/locale_rus.properties b/src/main/resources/locale_rus.properties index a38cdfb..5d3d72c 100644 --- a/src/main/resources/locale_rus.properties +++ b/src/main/resources/locale_rus.properties @@ -46,4 +46,5 @@ netTabAllowXciCb=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0432\u netTabAllowXciTextField=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u043C\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0438\u043C\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u043C\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442 XCI \u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u044E\u0442 \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 TinFoil. \u041D\u0435 \u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0435\u0441\u043B\u0438 \u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D\u044B. netTabLanguage=\u042F\u0437\u044B\u043A windowBodyRestartToApplyLang=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443. +netTabGLshowNSPonly=\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u044C \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0444\u0430\u0439\u043B\u044B *.nsp \u0432 GoldLeaf. diff --git a/src/main/resources/locale_ukr.properties b/src/main/resources/locale_ukr.properties index bf436ef..805c8e0 100644 --- a/src/main/resources/locale_ukr.properties +++ b/src/main/resources/locale_ukr.properties @@ -44,4 +44,5 @@ windowBodyNewVersionUnknown=\u0429\u043E\u0441\u044C \u043F\u0456\u0448\u043B\u0 netTabAllowXciCb=\u0414\u043E\u0437\u0432\u043E\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0456\u0440 XCI \u0444\u0430\u0439\u043B\u0456\u0432 \u0434\u043B\u044F \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u043D\u043D\u044F \u0443 TinFoil netTabAllowXciTextField=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0434\u0435\u044F\u043A\u0438\u043C\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0456\u043C\u0438 \u0434\u043E\u0434\u0430\u0442\u043A\u0430\u043C\u0438, \u044F\u043A\u0456 \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u044E\u0442\u044C XCI \u0456 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u044E\u0442\u044C \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0456 TinFoil. \u042F\u043A\u0449\u043E \u043D\u0435 \u0432\u043F\u0435\u0432\u043D\u0435\u043D\u0456 \u2014\u00A0\u043D\u0435 \u0437\u043C\u0456\u043D\u044E\u0439\u0442\u0435. netTabLanguage=\u041C\u043E\u0432\u0430 -windowBodyRestartToApplyLang=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0456\u0442\u044C \u0434\u043E\u0434\u0430\u0442\u043E\u043A \u0449\u043E\u0431 \u0437\u043C\u0456\u043D\u0438 \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443. \ No newline at end of file +windowBodyRestartToApplyLang=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0456\u0442\u044C \u0434\u043E\u0434\u0430\u0442\u043E\u043A \u0449\u043E\u0431 \u0437\u043C\u0456\u043D\u0438 \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443. +netTabGLshowNSPonly=\u0412\u0456\u0434\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0432\u0438\u043A\u043B\u044E\u0447\u043D\u043E *.nsp \u0444\u0430\u0439\u043B\u0438 \u0443 GoldLeaf. \ No newline at end of file diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 7d3d299..6384879 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -262,6 +262,7 @@ -fx-background-color: #71e016; } +.scroll-pane > .corner, .scroll-bar:horizontal, .scroll-bar:vertical { -fx-background-color: #2d2d2d;