From 22b18710a569ce045f7d9ad018beebdf6e6e96e2 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 25 Sep 2020 02:56:09 +0300 Subject: [PATCH 001/134] Fix "Don't serve requests" option, update version number, fix typo --- README.md | 3 ++- pom.xml | 2 +- src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java | 4 ++++ src/main/java/nsusbloader/NSLMain.java | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5c27cac..bb60afb 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", GROUP="plug root # udevadm control --reload-rules && udevadm trigger ``` -Please note: you may have to change 'plugdev' group from example above to the different one. It's depends on you linux distro. +Please note: you may have to change 'plugdev' group from example above to the different one. It depends on you linux distro. ##### Raspberry Pi @@ -225,6 +225,7 @@ To convert files of any locale to readable format (and vise-versa) you can use t ## Support this app + If you like this app, just give a star. If you want to make a donation*, please see below: diff --git a/pom.xml b/pom.xml index 8db9597..a0b0b52 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>4.3-SNAPSHOT</version> + <version>4.4-SNAPSHOT</version> <url>https://github.com/developersu/ns-usbloader/</url> <description> diff --git a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java b/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java index edbf008..b46b418 100644 --- a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java +++ b/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java @@ -189,6 +189,10 @@ public class NetworkSetupValidator { private void parsePort(String hostPortNum) throws Exception{ try { this.hostPort = Integer.parseInt(hostPortNum); + + if (doNotServe) + return; + serverSocket = new ServerSocket(hostPort); logPrinter.print("NET: Using defined port number: " + hostPort, EMsgType.PASS); } diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 0dc65fb..8a76096 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v4.3"; + public static final String appVersion = "v4.4"; public static boolean isCli; @Override From 45696881e2b651b561c58b3d97057382cbcff894 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 29 Sep 2020 02:54:18 +0300 Subject: [PATCH 002/134] Add NS-USBloader network-stopper feature (front-end implementation) --- src/main/java/nsusbloader/COM/NET/NETCommunications.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/nsusbloader/COM/NET/NETCommunications.java b/src/main/java/nsusbloader/COM/NET/NETCommunications.java index 2f2bbff..cb03092 100644 --- a/src/main/java/nsusbloader/COM/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/COM/NET/NETCommunications.java @@ -25,6 +25,7 @@ import nsusbloader.ModelControllers.Log; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.COM.Helpers.NSSplitReader; +import nsusbloader.RainbowHexDump; import java.io.*; import java.net.*; @@ -160,9 +161,7 @@ public class NETCommunications extends CancellableRunnable { String line; LinkedList<String> tcpPacket = new LinkedList<>(); - while ((line = br.readLine()) != null) { - //System.out.println(line); // Debug if (line.trim().isEmpty()) { // If TCP packet is ended handleRequest(tcpPacket); // Proceed required things tcpPacket.clear(); // Clear data and wait for next TCP packet @@ -170,8 +169,6 @@ public class NETCommunications extends CancellableRunnable { else tcpPacket.add(line); // Otherwise collect data } - //System.out.println("<Socket Closed>"); - // and reopen client sock clientSocket.close(); } } @@ -188,6 +185,10 @@ public class NETCommunications extends CancellableRunnable { * @return true if failed * */ private void handleRequest(LinkedList<String> packet) throws Exception{ + if (packet.get(0).startsWith("DROP")){ + throw new Exception("All transfers finished"); + } + File requestedFile; String reqFileName = packet.get(0).replaceAll("(^[A-z\\s]+/)|(\\s+?.*$)", ""); From 85a54cabc2165328d2e1bf586a4592aa92ae6fbb Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 6 Oct 2020 02:19:08 +0300 Subject: [PATCH 003/134] Refactor 'network-stopper' related code --- .../COM/NET/NETCommunications.java | 20 +++++++----- .../Controllers/FrontController.java | 31 +++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/java/nsusbloader/COM/NET/NETCommunications.java b/src/main/java/nsusbloader/COM/NET/NETCommunications.java index cb03092..2f4b671 100644 --- a/src/main/java/nsusbloader/COM/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/COM/NET/NETCommunications.java @@ -25,7 +25,6 @@ import nsusbloader.ModelControllers.Log; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.COM.Helpers.NSSplitReader; -import nsusbloader.RainbowHexDump; import java.io.*; import java.net.*; @@ -53,6 +52,8 @@ public class NETCommunications extends CancellableRunnable { private OutputStream currSockOS; private PrintWriter currSockPW; + + private boolean jobInProgress = true; /** * Simple constructor that everybody uses * */ @@ -150,7 +151,7 @@ public class NETCommunications extends CancellableRunnable { } private void serveRequestsLoop(){ try { - while (true){ + while (jobInProgress){ clientSocket = serverSocket.accept(); BufferedReader br = new BufferedReader( new InputStreamReader(clientSocket.getInputStream()) @@ -178,7 +179,10 @@ public class NETCommunications extends CancellableRunnable { else logPrinter.print(e.getMessage(), EMsgType.INFO); close(EFileStatus.UNKNOWN); + return; } + logPrinter.print("All transfers complete", EMsgType.PASS); + close(EFileStatus.UPLOADED); } /** * Handle requests @@ -186,7 +190,8 @@ public class NETCommunications extends CancellableRunnable { * */ private void handleRequest(LinkedList<String> packet) throws Exception{ if (packet.get(0).startsWith("DROP")){ - throw new Exception("All transfers finished"); + jobInProgress = false; + return; } File requestedFile; @@ -214,11 +219,10 @@ public class NETCommunications extends CancellableRunnable { } if (packet.get(0).startsWith("GET")) { for (String line: packet) { - if (! line.toLowerCase().startsWith("range")) //todo: fix - continue; - - parseGETrange(requestedFile, reqFileName, reqFileSize, line); - return; + if (line.toLowerCase().startsWith("range")){ + parseGETrange(requestedFile, reqFileName, reqFileSize, line); + return; + } } } } diff --git a/src/main/java/nsusbloader/Controllers/FrontController.java b/src/main/java/nsusbloader/Controllers/FrontController.java index 96ddf07..3790f40 100644 --- a/src/main/java/nsusbloader/Controllers/FrontController.java +++ b/src/main/java/nsusbloader/Controllers/FrontController.java @@ -304,16 +304,17 @@ public class FrontController implements Initializable { * It's button listener when transmission in progress * */ private void stopBtnAction(){ - if (workThread != null && workThread.isAlive()){ - usbNetCommunications.cancel(); + if (workThread == null || ! workThread.isAlive()) + return; - if (usbNetCommunications instanceof NETCommunications){ - try{ - ((NETCommunications) usbNetCommunications).getServerSocket().close(); - ((NETCommunications) usbNetCommunications).getClientSocket().close(); - } - catch (Exception ignore){ } + usbNetCommunications.cancel(); + + if (usbNetCommunications instanceof NETCommunications){ + try{ + ((NETCommunications) usbNetCommunications).getServerSocket().close(); + ((NETCommunications) usbNetCommunications).getClientSocket().close(); } + catch (Exception ignore){ } } } /** @@ -358,26 +359,24 @@ public class FrontController implements Initializable { usbNetPane.setDisable(isActive); return; } + selectNspBtn.setDisable(isActive); + selectSplitNspBtn.setDisable(isActive); + btnUpStopImage.getStyleClass().clear(); + if (isActive) { - selectNspBtn.setDisable(true); - selectSplitNspBtn.setDisable(true); - btnUpStopImage.getStyleClass().clear(); btnUpStopImage.getStyleClass().add("regionStop"); uploadStopBtn.setOnAction(e-> stopBtnAction()); uploadStopBtn.setText(resourceBundle.getString("btn_Stop")); - uploadStopBtn.getStyleClass().remove("buttonUp"); + uploadStopBtn.getStyleClass().clear(); uploadStopBtn.getStyleClass().add("buttonStop"); return; } - selectNspBtn.setDisable(false); - selectSplitNspBtn.setDisable(false); - btnUpStopImage.getStyleClass().clear(); btnUpStopImage.getStyleClass().add("regionUpload"); uploadStopBtn.setOnAction(e-> uploadBtnAction()); uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); - uploadStopBtn.getStyleClass().remove("buttonStop"); + uploadStopBtn.getStyleClass().clear(); uploadStopBtn.getStyleClass().add("buttonUp"); } /** From f7b8e4d2652a9d54c9d76da564bd023694e49827 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 6 Oct 2020 15:49:30 +0300 Subject: [PATCH 004/134] Add a notice for Mac users in README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb60afb..5e1e9ec 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ Sometimes I add new posts about this project [on my home page](https://developer ### System requirements -JRE/JDK 8u60 or higher +JRE/JDK 8u60 or higher for Windows + +JDK 11 for MacOS and Linux ### Supported GoldLeaf versions | GoldLeaf version | NS-USBloader version | @@ -106,6 +108,8 @@ Double-click on downloaded .jar file. Follow instructions. Or see 'Linux' sectio Set 'Security & Privacy' settings if needed. +*Please note: JDK 11 is recommended for using on MacOS. There are few really weird issues already reported from JDK 14 users on Mac.* + ##### Windows: * [Download and install Java JRE](http://java.com/download/) (8u60 or higher) From bd624d649a9ad53c7fff2d22e790653ef0dc233e Mon Sep 17 00:00:00 2001 From: Anderson_Cardoso <43047877+andercard0@users.noreply.github.com> Date: Sat, 10 Oct 2020 11:12:39 -0300 Subject: [PATCH 005/134] Update Brazilian Portuguese Translation - Quick Fix typo; - Cleared out some terms in English to proper Portuguese. --- src/main/resources/locale_pt_BR.properties | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/resources/locale_pt_BR.properties b/src/main/resources/locale_pt_BR.properties index 46112af..77c5173 100644 --- a/src/main/resources/locale_pt_BR.properties +++ b/src/main/resources/locale_pt_BR.properties @@ -1,29 +1,29 @@ -katebtn_OpenFile=Selecionar arquivos -btn_Upload=Upar para o switch +katebtn_OpenFile=Selecionar Arquivos +btn_Upload=Carregar Arquivos tab3_Txt_EnteredAsMsg1=Voc\u00EA logou como: tab3_Txt_EnteredAsMsg2=Voc\u00EA precisa de permiss\u00F5es root ou ter configurado as regras 'udev' deste usu\u00E1rio para evitar poss\u00EDveis problemas. -tab3_Txt_FilesToUploadTitle=Arquivos para upar: +tab3_Txt_FilesToUploadTitle=Arquivos Para Carregar: tab3_Txt_GreetingsMessage=Bem vindo ao NS-USBloader -tab3_Txt_NoFolderOrFileSelected=Nenhum arquivo selecionado. Nada para upar. +tab3_Txt_NoFolderOrFileSelected=Nenhum arquivo selecionado. Nada para Carregar. windowBodyConfirmExit=Transfer\u00EAncia de dados em progresso: Fechar ir\u00E1 interromper.\nN\u00E3o \u00E9 aconselh\u00E1vel.\nInterromper processo e sair? windowTitleConfirmExit=N\u00E3o, n\u00E3o fa\u00E7a isso! -btn_Stop=Imterromper! +btn_Stop=Interromper! tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ +Fonte: https://github.com/developersu/ns-usbloader/\n\ Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status -tab1_table_Lbl_FileName=Nome do arquivo +tab1_table_Lbl_FileName=Nome do Arquivo tab1_table_Lbl_Size=Tamanho -tab1_table_Lbl_Upload=Upar +tab1_table_Lbl_Upload=Carregar tab1_table_contextMenu_Btn_BtnDelete=Remover -tab1_table_contextMenu_Btn_DeleteAll=Remover todos +tab1_table_contextMenu_Btn_DeleteAll=Remover Todos tab2_Lbl_HostIP=Host IP tab1_Lbl_NSIP=NS IP: tab2_Cb_ValidateNSHostName=Sempre validar o IP do switch. windowBodyBadIp=Tem certeza que preencheu o endere\u00E7o IP corretamente? windowTitleBadIp=Endere\u00E7o IP do switch provavelmente incorreto. -tab2_Cb_ExpertMode=Modo Expert (Configura\u00E7\u00E3o NET) +tab2_Cb_ExpertMode=Modo Avançado (Configura\u00E7\u00E3o NET) tab2_Lbl_HostPort=porta tab2_Cb_AutoDetectIp=Auto-detectar IP tab2_Cb_RandSelectPort=Usar porta aleat\u00F3ria @@ -44,12 +44,12 @@ tab2_Lbl_Language=Idioma windowBodyRestartToApplyLang=Por favor, reinicie para aplicar as modifica\u00E7\u00F5es. btn_OpenSplitFile=Select split NSP tab2_Lbl_ApplicationSettings=Configura\u00E7\u00F5es principais -tabSplMrg_Lbl_SplitNMergeTitle=Ferramenta de Fragmentar(Split) & mesclar (merge) arquivos -tabSplMrg_RadioBtn_Split=Fragmentar (Split) -tabSplMrg_RadioBtn_Merge=Mesclar (Merge) +tabSplMrg_Lbl_SplitNMergeTitle=Ferramenta de Fragmentar (Dividir) & Mesclar (Juntar) arquivos +tabSplMrg_RadioBtn_Split=Fragmentar (Dividir) +tabSplMrg_RadioBtn_Merge=Mesclar (Juntar) tabSplMrg_Txt_File=Arquivo: tabSplMrg_Txt_Folder=Fragmentar arquivo (pasta): -tabSplMrg_Btn_SelectFile=Selecionar arquivo +tabSplMrg_Btn_SelectFile=Selecionar Arquivo tabSplMrg_Btn_SelectFolder=Selecionar Pasta tabSplMrg_Lbl_SaveToLocation=Salvar em: tabSplMrg_Btn_ChangeSaveToLocation=Trocar @@ -68,4 +68,4 @@ btn_Cancel=Cancelar btn_Close=Fechar tab2_Cb_GlVersion=Vers\u00E3o do GoldLeaf tab2_Cb_GLshowNspOnly=Mostrar apenas *.nsp no GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Por favor, pare outros processos ativos antes de prosseguir \ No newline at end of file +windowBodyPleaseStopOtherProcessFirst=Por favor, pare outros processos ativos antes de prosseguir From 4cb3cbb491ec5c1e119efd3847929bd41adbcd84 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 26 Oct 2020 14:12:14 +0300 Subject: [PATCH 006/134] Resolve #79 --- src/main/java/nsusbloader/AppPreferences.java | 6 ++-- .../Controllers/FrontController.java | 14 +++----- .../Controllers/NxdtController.java | 8 ++--- .../Controllers/SplitMergeController.java | 14 ++++---- src/main/java/nsusbloader/FilesHelper.java | 32 +++++++++++++++++++ 5 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 src/main/java/nsusbloader/FilesHelper.java diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index a2e6828..9da2df5 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -61,7 +61,7 @@ public class AppPreferences { public void setNsIp(String ip){preferences.put("NSIP", ip);} public String getNsIp(){return preferences.get("NSIP", "192.168.1.42");} - public String getRecent(){ return preferences.get("RECENT", System.getProperty("user.home")); } + public String getRecent(){ return FilesHelper.getRealFolder(preferences.get("RECENT", System.getProperty("user.home"))); } public void setRecent(String path){ preferences.put("RECENT", path); } //------------ SETTINGS ------------------// @@ -124,12 +124,12 @@ public class AppPreferences { public int getSplitMergeType(){ return preferences.getInt("SM_TYPE", 0); } public void setSplitMergeType(int value){ preferences.putInt("SM_TYPE", value); } - public String getSplitMergeRecent(){ return preferences.get("SM_RECENT", System.getProperty("user.home")); } + public String getSplitMergeRecent(){ return FilesHelper.getRealFolder(preferences.get("SM_RECENT", System.getProperty("user.home"))); } public void setSplitMergeRecent(String value){ preferences.put("SM_RECENT", value); } // RCM // public String getRecentRcm(int num){ return preferences.get(String.format("RCM_%02d", num), ""); } public void setRecentRcm(int num, String value){ preferences.put(String.format("RCM_%02d", num), value); } // NXDT // - public String getNXDTSaveToLocation(){ return preferences.get("nxdt_saveto", System.getProperty("user.home")); } + public String getNXDTSaveToLocation(){ return FilesHelper.getRealFolder(preferences.get("nxdt_saveto", System.getProperty("user.home"))); } public void setNXDTSaveToLocation(String value){ preferences.put("nxdt_saveto", value); } } diff --git a/src/main/java/nsusbloader/Controllers/FrontController.java b/src/main/java/nsusbloader/Controllers/FrontController.java index 3790f40..0b2f12e 100644 --- a/src/main/java/nsusbloader/Controllers/FrontController.java +++ b/src/main/java/nsusbloader/Controllers/FrontController.java @@ -32,6 +32,7 @@ import javafx.stage.FileChooser; import nsusbloader.AppPreferences; import nsusbloader.COM.NET.NETCommunications; import nsusbloader.COM.USB.UsbCommunications; +import nsusbloader.FilesHelper; import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; @@ -193,11 +194,7 @@ public class FrontController implements Initializable { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(resourceBundle.getString("btn_OpenFile")); - File validator = new File(previouslyOpenedPath); - if (validator.exists() && validator.isDirectory()) - fileChooser.setInitialDirectory(validator); - else - fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + fileChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); if (getSelectedProtocol().equals("TinFoil") && MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport()) fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP/XCI/NSZ/XCZ", "*.nsp", "*.xci", "*.nsz", "*.xcz")); @@ -223,11 +220,8 @@ public class FrontController implements Initializable { DirectoryChooser dirChooser = new DirectoryChooser(); dirChooser.setTitle(resourceBundle.getString("btn_OpenFile")); - File validator = new File(previouslyOpenedPath); - if (validator.exists() && validator.isDirectory()) - dirChooser.setInitialDirectory(validator); - else - dirChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + String saveToLocation = FilesHelper.getRealFolder(previouslyOpenedPath); + dirChooser.setInitialDirectory(new File(saveToLocation)); splitFile = dirChooser.showDialog(usbNetPane.getScene().getWindow()); diff --git a/src/main/java/nsusbloader/Controllers/NxdtController.java b/src/main/java/nsusbloader/Controllers/NxdtController.java index 7d696ec..c824cc9 100644 --- a/src/main/java/nsusbloader/Controllers/NxdtController.java +++ b/src/main/java/nsusbloader/Controllers/NxdtController.java @@ -25,6 +25,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; import nsusbloader.AppPreferences; +import nsusbloader.FilesHelper; import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; @@ -52,11 +53,8 @@ public class NxdtController implements Initializable { public void initialize(URL url, ResourceBundle resourceBundle) { this.rb = resourceBundle; - File saveToValidator = new File(AppPreferences.getInstance().getNXDTSaveToLocation()); - if (saveToValidator.exists()) - saveToLocationLbl.setText(saveToValidator.getAbsolutePath()); - else - saveToLocationLbl.setText(System.getProperty("user.home")); + String saveToLocation = AppPreferences.getInstance().getNXDTSaveToLocation(); + saveToLocationLbl.setText(saveToLocation); btnDumpStopImage = new Region(); btnDumpStopImage.getStyleClass().add("regionDump"); diff --git a/src/main/java/nsusbloader/Controllers/SplitMergeController.java b/src/main/java/nsusbloader/Controllers/SplitMergeController.java index 46be04f..2611e33 100644 --- a/src/main/java/nsusbloader/Controllers/SplitMergeController.java +++ b/src/main/java/nsusbloader/Controllers/SplitMergeController.java @@ -20,7 +20,6 @@ package nsusbloader.Controllers; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.input.DragEvent; import javafx.scene.input.TransferMode; @@ -29,6 +28,7 @@ import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import nsusbloader.AppPreferences; +import nsusbloader.FilesHelper; import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; @@ -97,10 +97,13 @@ public class SplitMergeController implements Initializable { saveToPathLbl.setText(AppPreferences.getInstance().getSplitMergeRecent()); changeSaveToBtn.setOnAction((actionEvent -> { - DirectoryChooser dc = new DirectoryChooser(); - dc.setTitle(resourceBundle.getString("tabSplMrg_Btn_SelectFolder")); - dc.setInitialDirectory(new File(saveToPathLbl.getText())); - File saveToDir = dc.showDialog(changeSaveToBtn.getScene().getWindow()); + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("tabSplMrg_Btn_SelectFolder")); + + String saveToLocation = FilesHelper.getRealFolder(saveToPathLbl.getText()); + directoryChooser.setInitialDirectory(new File(saveToLocation)); + + File saveToDir = directoryChooser.showDialog(changeSaveToBtn.getScene().getWindow()); if (saveToDir != null) saveToPathLbl.setText(saveToDir.getAbsolutePath()); })); @@ -227,7 +230,6 @@ public class SplitMergeController implements Initializable { * */ @FXML private void handleDrop(DragEvent event) { - Node sourceNode = (Node) event.getSource(); File fileDrpd = event.getDragboard().getFiles().get(0); if (fileDrpd.isDirectory()) diff --git a/src/main/java/nsusbloader/FilesHelper.java b/src/main/java/nsusbloader/FilesHelper.java new file mode 100644 index 0000000..5267ada --- /dev/null +++ b/src/main/java/nsusbloader/FilesHelper.java @@ -0,0 +1,32 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FilesHelper { + public static String getRealFolder(String path){ + Path splitMergePath = Paths.get(path); + if (Files.notExists(splitMergePath) || Files.isRegularFile(splitMergePath)) + return System.getProperty("user.home"); + return path; + } +} From 8771d551a4e6fa2d645e519d504a377e34cbd730 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 27 Oct 2020 00:22:26 +0300 Subject: [PATCH 007/134] Fix split-files validation. Add JUnit5 dependency on testing stage. --- Jenkinsfile | 7 +- pom.xml | 19 ++ .../COM/NET/NetworkSetupValidator.java | 11 +- .../nsusbloader/COM/USB/TransferModule.java | 58 +++-- src/main/java/nsusbloader/FilesHelper.java | 8 +- src/main/java/nsusbloader/RainbowHexDump.java | 12 +- .../COM/USB/TransferModuleTest.java | 227 ++++++++++++++++++ 7 files changed, 305 insertions(+), 37 deletions(-) create mode 100644 src/test/java/nsusbloader/COM/USB/TransferModuleTest.java diff --git a/Jenkinsfile b/Jenkinsfile index e85efc8..bf047ac 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,9 +7,14 @@ pipeline { } stages { + stage('Test') { + steps { + sh 'mvn test' + } + } stage('Build') { steps { - sh 'mvn -B -DskipTests clean package' + sh 'mvn clean package' } } } diff --git a/pom.xml b/pom.xml index a0b0b52..2b96fe1 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,25 @@ <version>1.3.0</version> <!-- Must be 1.2.0 for macOS lower than Mojave --> <scope>compile</scope> </dependency> + <!-- Junit5 --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.5.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.5.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>5.5.2</version> + <scope>test</scope> + </dependency> </dependencies> <build> <plugins> diff --git a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java b/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java index b46b418..009cd74 100644 --- a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java +++ b/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java @@ -77,12 +77,21 @@ public class NetworkSetupValidator { Arrays.sort(subFiles, Comparator.comparingInt(file -> Integer.parseInt(file.getName()))); for (int i = subFiles.length - 2; i > 0 ; i--){ - if (subFiles[i].length() < subFiles[i-1].length()) { + if (subFiles[i].length() != subFiles[i-1].length()) { logPrinter.print("NET: Exclude split file: "+f.getName()+ "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); return true; } } + + long firstFileLength = subFiles[0].length(); + long lastFileLength = subFiles[subFiles.length-1].length(); + + if (lastFileLength > firstFileLength){ + logPrinter.print("NET: Exclude split file: "+f.getName()+ + "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); + return true; + } return false; }); } diff --git a/src/main/java/nsusbloader/COM/USB/TransferModule.java b/src/main/java/nsusbloader/COM/USB/TransferModule.java index f7a734b..02dd059 100644 --- a/src/main/java/nsusbloader/COM/USB/TransferModule.java +++ b/src/main/java/nsusbloader/COM/USB/TransferModule.java @@ -41,32 +41,40 @@ public abstract class TransferModule { this.task = task; this.logPrinter = printer; - // Validate split files to be sure that there is no crap - //logPrinter.print("TransferModule: Validating split files ...", EMsgType.INFO); // NOTE: Used for debug - Iterator<Map.Entry<String, File>> iterator = nspMap.entrySet().iterator(); - while (iterator.hasNext()){ - File f = iterator.next().getValue(); - if (f.isDirectory()){ - File[] subFiles = f.listFiles((file, name) -> name.matches("[0-9]{2}")); - if (subFiles == null || subFiles.length == 0) { - logPrinter.print("TransferModule: Removing empty folder: " + f.getName(), EMsgType.WARNING); - iterator.remove(); - } - else { - Arrays.sort(subFiles, Comparator.comparingInt(file -> Integer.parseInt(file.getName()))); - - for (int i = subFiles.length - 2; i > 0 ; i--){ - if (subFiles[i].length() < subFiles[i-1].length()) { - logPrinter.print("TransferModule: Removing strange split file: "+f.getName()+ - "\n (Chunk sizes of the split file are not the same, but has to be.)", EMsgType.WARNING); - iterator.remove(); - } // what - } // a - } // nice - } // stairway - } // here =) - //logPrinter.print("TransferModule: Validation complete.", EMsgType.INFO); // NOTE: Used for debug + filterFiles(); } + void filterFiles(){ + nspMap.values().removeIf(f -> { + if (f.isFile()) + return false; + File[] subFiles = f.listFiles((file, name) -> name.matches("[0-9]{2}")); + + if (subFiles == null || subFiles.length == 0) { + logPrinter.print("TransferModule: Exclude folder: " + f.getName(), EMsgType.WARNING); + return true; + } + + Arrays.sort(subFiles, Comparator.comparingInt(file -> Integer.parseInt(file.getName()))); + + for (int i = subFiles.length - 2; i > 0 ; i--){ + if (subFiles[i].length() != subFiles[i-1].length()) { + logPrinter.print("TransferModule: Exclude split file: "+f.getName()+ + "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); + return true; + } + } + + long firstFileLength = subFiles[0].length(); + long lastFileLength = subFiles[subFiles.length-1].length(); + + if (lastFileLength > firstFileLength){ + logPrinter.print("TransferModule: Exclude split file: "+f.getName()+ + "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); + return true; + } + return false; + }); + } public EFileStatus getStatus(){ return status; } } diff --git a/src/main/java/nsusbloader/FilesHelper.java b/src/main/java/nsusbloader/FilesHelper.java index 5267ada..7988a06 100644 --- a/src/main/java/nsusbloader/FilesHelper.java +++ b/src/main/java/nsusbloader/FilesHelper.java @@ -23,10 +23,10 @@ import java.nio.file.Path; import java.nio.file.Paths; public class FilesHelper { - public static String getRealFolder(String path){ - Path splitMergePath = Paths.get(path); - if (Files.notExists(splitMergePath) || Files.isRegularFile(splitMergePath)) + public static String getRealFolder(String location){ + Path locationAsPath = Paths.get(location); + if (Files.notExists(locationAsPath) || Files.isRegularFile(locationAsPath)) return System.getProperty("user.home"); - return path; + return location; } } diff --git a/src/main/java/nsusbloader/RainbowHexDump.java b/src/main/java/nsusbloader/RainbowHexDump.java index ebdd277..109957d 100644 --- a/src/main/java/nsusbloader/RainbowHexDump.java +++ b/src/main/java/nsusbloader/RainbowHexDump.java @@ -37,10 +37,10 @@ public class RainbowHexDump { public static void hexDumpUTF8(byte[] byteArray){ System.out.print(ANSI_BLUE); for (int i=0; i < byteArray.length; i++) - System.out.print(String.format("%02d-", i%100)); + System.out.printf("%02d-", i%100); System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); for (byte b: byteArray) - System.out.print(String.format("%02x ", b)); + System.out.printf("%02x ", b); System.out.println(); System.out.print("\t\t\t" + new String(byteArray, StandardCharsets.UTF_8) @@ -49,10 +49,10 @@ public class RainbowHexDump { public static void hexDumpUTF8ForWin(byte[] byteArray){ for (int i=0; i < byteArray.length; i++) - System.out.print(String.format("%02d-", i%100)); + System.out.printf("%02d-", i%100); System.out.println(">"+byteArray.length); for (byte b: byteArray) - System.out.print(String.format("%02x ", b)); + System.out.printf("%02x ", b); System.out.println(); System.out.print(new String(byteArray, StandardCharsets.UTF_8) + "\n"); @@ -61,10 +61,10 @@ public class RainbowHexDump { public static void hexDumpUTF16LE(byte[] byteArray){ System.out.print(ANSI_BLUE); for (int i=0; i < byteArray.length; i++) - System.out.print(String.format("%02d-", i%100)); + System.out.printf("%02d-", i%100); System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); for (byte b: byteArray) - System.out.print(String.format("%02x ", b)); + System.out.printf("%02x ", b); System.out.print(new String(byteArray, StandardCharsets.UTF_16LE) + "\n"); } diff --git a/src/test/java/nsusbloader/COM/USB/TransferModuleTest.java b/src/test/java/nsusbloader/COM/USB/TransferModuleTest.java new file mode 100644 index 0000000..12acb65 --- /dev/null +++ b/src/test/java/nsusbloader/COM/USB/TransferModuleTest.java @@ -0,0 +1,227 @@ +package nsusbloader.COM.USB; + +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.ModelControllers.LogPrinterCli; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.usb4java.DeviceHandle; + +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TransferModuleTest{ + + @TempDir + File testFilesLocation; + + final String regularFileName1 = "file1.nsp"; + final String regularFileName2 = "file2.nsz"; + final String regularFileName3 = "file3.xci"; + final String regularFileName4 = "file4.xcz"; + final String splitFileName1 = "splitFile1.nsp"; + final String splitFileName2 = "splitFile2.nsp"; + final String splitFileName3 = "splitFile3.nsp"; + final String splitFileName4 = "splitFile4.nsp"; + final String splitFileName5 = "splitFile5.nsp"; + final String splitFileName6 = "splitFile6.nsp"; + final String splitFileName7 = "splitFile7.nsp"; + final String splitFileName8 = "splitFile8.nsp"; + final String splitFileName9 = "splitFile9.nsp"; + final String splitFileName10 = "splitFile10.nsp"; + final String splitFileName11 = "splitFile11.nsp"; + + TransferModuleImplementation transferModule; + + @BeforeEach + void createFiles() throws Exception{ + String parentTempDirectory = testFilesLocation.getAbsolutePath(); + + File regularFile1 = createFile(parentTempDirectory, regularFileName1); + File regularFile2 = createFile(parentTempDirectory, regularFileName2); + File regularFile3 = createFile(parentTempDirectory, regularFileName3); + File regularFile4 = createFile(parentTempDirectory, regularFileName4); + File splitFile1 = createSplitInvalidEmpty(parentTempDirectory, splitFileName1); + File splitFile2 = createSplitInvalidEmpty(parentTempDirectory, splitFileName2); + File splitFile3 = createSplitValid(parentTempDirectory, splitFileName3); + File splitFile4 = createSplitValid(parentTempDirectory, splitFileName4); + File splitFile5 = createSplitValidWithExtras(parentTempDirectory, splitFileName5); + File splitFile6 = createSplitInvalidVariant1(parentTempDirectory, splitFileName6); + File splitFile7 = createSplitInvalidVariant2(parentTempDirectory, splitFileName7); + File splitFile8 = createSplitInvalidVariant3(parentTempDirectory, splitFileName8); + File splitFile9 = createSplitInvalidVariant4(parentTempDirectory, splitFileName9); + File splitFile10 = createSplitInvalidVariant5(parentTempDirectory, splitFileName10); + File splitFile11 = createSplitValidSingleChunk(parentTempDirectory, splitFileName11); + + + LinkedHashMap<String, File> filesMap = new LinkedHashMap<>(); + + filesMap.put(regularFileName1, regularFile1); + filesMap.put(regularFileName2, regularFile2); + filesMap.put(regularFileName3, regularFile3); + filesMap.put(regularFileName4, regularFile4); + filesMap.put(splitFileName1, splitFile1); + filesMap.put(splitFileName2, splitFile2); + filesMap.put(splitFileName3, splitFile3); + filesMap.put(splitFileName4, splitFile4); + filesMap.put(splitFileName5, splitFile5); + filesMap.put(splitFileName6, splitFile6); + filesMap.put(splitFileName7, splitFile7); + filesMap.put(splitFileName8, splitFile8); + filesMap.put(splitFileName9, splitFile9); + filesMap.put(splitFileName10, splitFile10); + filesMap.put(splitFileName11, splitFile11); + + ILogPrinter printer = new LogPrinterCli(); + this.transferModule = new TransferModuleImplementation((DeviceHandle)null, filesMap, (CancellableRunnable)null, printer); + } + + File createFile(String parent, String name) throws Exception{ + Path file = Paths.get(parent, name); + Files.createFile(file); + return new File(parent, name); + } + + File createSplitInvalidEmpty(String parent, String name) throws Exception{ + Path file = Paths.get(parent, name); + Files.createDirectory(file); + return new File(parent, name); + } + + File createSplitValid(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntryBigger(path, 0); + makeSplitFileEntryBigger(path, 1); + makeSplitFileEntryBigger(path, 2); + makeSplitFileEntryBigger(path, 3); + makeSplitFileEntrySmaller(path, 4); + return new File(parent, name); + } + + File createSplitValidWithExtras(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntrySmaller(path, 0); + makeSplitFileEntrySmaller(path, 1); + makeSplitFileEntrySmaller(path, 2); + makeSplitFileEntryWeired(path); + return new File(parent, name); + } + + File createSplitInvalidVariant1(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntrySmaller(path, 0); + makeSplitFileEntrySmaller(path, 1); + makeSplitFileEntryBigger(path, 2); //incorrect + makeSplitFileEntrySmaller(path, 3); + return new File(parent, name); + } + File createSplitInvalidVariant2(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntryBigger(path, 0); //incorrect + makeSplitFileEntrySmaller(path, 1); + makeSplitFileEntrySmaller(path, 2); + makeSplitFileEntrySmaller(path, 3); + return new File(parent, name); + } + File createSplitInvalidVariant3(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntrySmaller(path, 0); + makeSplitFileEntryBigger(path, 1); //incorrect + makeSplitFileEntrySmaller(path, 2); + makeSplitFileEntrySmaller(path, 3); + return new File(parent, name); + } + File createSplitInvalidVariant4(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntrySmaller(path, 0); //incorrect + makeSplitFileEntryBigger(path, 1); + makeSplitFileEntryBigger(path, 2); + makeSplitFileEntryBigger(path, 3); + return new File(parent, name); + } + File createSplitInvalidVariant5(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntrySmaller(path, 0); + makeSplitFileEntrySmaller(path, 1); + makeSplitFileEntrySmaller(path, 2); + makeSplitFileEntryBigger(path, 3); //incorrect: Could be only smaller + return new File(parent, name); + } + + File createSplitValidSingleChunk(String parent, String name) throws Exception{ + Path path = Paths.get(parent, name); + Files.createDirectory(path); + makeSplitFileEntryBigger(path, 0); + return new File(parent, name); + } + + void makeSplitFileEntrySmaller(Path path, int entryNum) throws Exception{ + try (FileWriter writer = new FileWriter(String.format("%s%s%02x", path.toString(), File.separator, entryNum))){ + writer.write("test"); + writer.flush(); + } + } + void makeSplitFileEntryBigger(Path path, int entryNum) throws Exception{ + try (FileWriter writer = new FileWriter(String.format("%s%s%02x", path.toString(), File.separator, entryNum))){ + writer.write("test_"); + writer.flush(); + } + } + void makeSplitFileEntryWeired(Path path) throws Exception{ + try (FileWriter writer = new FileWriter(String.format("%s%sNOT_A_VALID_FILE.nsp", path.toString(), File.separator))){ + writer.write("literally anything"); + writer.flush(); + } + } + + private static class TransferModuleImplementation extends TransferModule{ + TransferModuleImplementation(DeviceHandle handler, + LinkedHashMap<String, File> nspMap, + CancellableRunnable task, + ILogPrinter printer) + { + super(handler, nspMap, task, printer); + } + + LinkedHashMap<String, File> getFiles(){ return nspMap; } + } + + @DisplayName("Test 'split-files' filter-validator") + @Test + void validateTransferModule() { + LinkedHashMap<String, File> files = transferModule.getFiles(); + + assertTrue(files.containsKey(regularFileName1)); + assertTrue(files.containsKey(regularFileName2)); + assertTrue(files.containsKey(regularFileName3)); + assertTrue(files.containsKey(regularFileName4)); + assertFalse(files.containsKey(splitFileName1)); + assertFalse(files.containsKey(splitFileName1)); + assertFalse(files.containsKey(splitFileName2)); + assertTrue(files.containsKey(splitFileName3)); + assertTrue(files.containsKey(splitFileName4)); + assertTrue(files.containsKey(splitFileName5)); + assertFalse(files.containsKey(splitFileName6)); + assertFalse(files.containsKey(splitFileName7)); + assertFalse(files.containsKey(splitFileName8)); + assertFalse(files.containsKey(splitFileName9)); + assertFalse(files.containsKey(splitFileName10)); + assertTrue(files.containsKey(splitFileName11)); + } +} \ No newline at end of file From 98822de55958a7de36044d96c9bc766d4db9f88d Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 27 Oct 2020 18:15:52 +0300 Subject: [PATCH 008/134] Fix POM, update package name a bit, update NXDT-related part --- pom.xml | 11 + .../Controllers/FrontController.java | 4 +- src/main/java/nsusbloader/Utilities/Rcm.java | 10 +- .../Utilities/nxdumptool/NxdtNspFile.java | 67 +++++ .../Utilities/nxdumptool/NxdtTask.java | 2 +- .../Utilities/nxdumptool/NxdtUsbAbi1.java | 246 +++++++++++++----- .../java/nsusbloader/cli/GoldLeafCli.java | 3 +- .../java/nsusbloader/cli/TinfoilNetCli.java | 2 +- .../java/nsusbloader/cli/TinfoilUsbCli.java | 2 +- .../helpers}/NSSplitReader.java | 2 +- .../NET => com/net}/NETCommunications.java | 4 +- .../{COM/NET => com/net}/NETPacket.java | 2 +- .../net}/NetworkSetupValidator.java | 2 +- .../{COM/NET => com/net}/UniFile.java | 2 +- .../{COM/USB => com/usb}/GoldLeaf_05.java | 6 +- .../{COM/USB => com/usb}/GoldLeaf_07.java | 4 +- .../{COM/USB => com/usb}/GoldLeaf_08.java | 4 +- .../{COM/USB => com/usb}/PFS/NCAFile.java | 2 +- .../{COM/USB => com/usb}/PFS/PFSProvider.java | 2 +- .../{COM/USB => com/usb}/TinFoil.java | 4 +- .../{COM/USB => com/usb}/TransferModule.java | 2 +- .../USB => com/usb}/UsbCommunications.java | 2 +- .../{COM/USB => com/usb}/UsbConnect.java | 3 +- .../{COM/USB => com/usb}/UsbErrorCodes.java | 2 +- .../usb}/common/DeviceInformation.java | 4 +- .../usb}/common/NsUsbEndpointDescriptor.java | 2 +- .../common/NsUsbEndpointDescriptorUtils.java | 2 +- .../usb}/common/NsUsbInterface.java | 2 +- .../usb}/common/NsUsbInterfaceDescriptor.java | 5 +- .../USB => com/usb}/TransferModuleTest.java | 2 +- 30 files changed, 299 insertions(+), 108 deletions(-) create mode 100644 src/main/java/nsusbloader/Utilities/nxdumptool/NxdtNspFile.java rename src/main/java/nsusbloader/{COM/Helpers => com/helpers}/NSSplitReader.java (99%) rename src/main/java/nsusbloader/{COM/NET => com/net}/NETCommunications.java (99%) rename src/main/java/nsusbloader/{COM/NET => com/net}/NETPacket.java (99%) rename src/main/java/nsusbloader/{COM/NET => com/net}/NetworkSetupValidator.java (99%) rename src/main/java/nsusbloader/{COM/NET => com/net}/UniFile.java (97%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/GoldLeaf_05.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/GoldLeaf_07.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/GoldLeaf_08.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/PFS/NCAFile.java (98%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/PFS/PFSProvider.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/TinFoil.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/TransferModule.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/UsbCommunications.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/UsbConnect.java (99%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/UsbErrorCodes.java (98%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/common/DeviceInformation.java (97%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/common/NsUsbEndpointDescriptor.java (98%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/common/NsUsbEndpointDescriptorUtils.java (97%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/common/NsUsbInterface.java (97%) rename src/main/java/nsusbloader/{COM/USB => com/usb}/common/NsUsbInterfaceDescriptor.java (96%) rename src/test/java/nsusbloader/{COM/USB => com/usb}/TransferModuleTest.java (99%) diff --git a/pom.xml b/pom.xml index 2b96fe1..3f6d31c 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,17 @@ </dependencies> <build> <plugins> + <!-- Junit5 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <version>2.22.2</version> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> diff --git a/src/main/java/nsusbloader/Controllers/FrontController.java b/src/main/java/nsusbloader/Controllers/FrontController.java index 0b2f12e..423fe67 100644 --- a/src/main/java/nsusbloader/Controllers/FrontController.java +++ b/src/main/java/nsusbloader/Controllers/FrontController.java @@ -30,8 +30,8 @@ import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import nsusbloader.AppPreferences; -import nsusbloader.COM.NET.NETCommunications; -import nsusbloader.COM.USB.UsbCommunications; +import nsusbloader.com.net.NETCommunications; +import nsusbloader.com.usb.UsbCommunications; import nsusbloader.FilesHelper; import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; diff --git a/src/main/java/nsusbloader/Utilities/Rcm.java b/src/main/java/nsusbloader/Utilities/Rcm.java index fddb849..3072944 100644 --- a/src/main/java/nsusbloader/Utilities/Rcm.java +++ b/src/main/java/nsusbloader/Utilities/Rcm.java @@ -24,8 +24,8 @@ */ package nsusbloader.Utilities; -import nsusbloader.COM.USB.UsbConnect; -import nsusbloader.COM.USB.UsbErrorCodes; +import nsusbloader.com.usb.UsbConnect; +import nsusbloader.com.usb.UsbErrorCodes; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.ModelControllers.Log; import nsusbloader.NSLDataTypes.EModule; @@ -39,14 +39,12 @@ import java.util.Arrays; public class Rcm implements Runnable{ - private boolean status = false; - private enum ECurrentOS { win, lin, mac, unsupported } - private ILogPrinter logPrinter; - private String filePath; + private final ILogPrinter logPrinter; + private final String filePath; private DeviceHandle handler; diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtNspFile.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtNspFile.java new file mode 100644 index 0000000..8cf1911 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtNspFile.java @@ -0,0 +1,67 @@ +/* + Copyright 2019-2020 Dmitry Isaenko, DarkMatterCore + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Utilities.nxdumptool; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class NxdtNspFile { + private final String name; + private final int headerSize; + private final long fullSize; + private long nspRemainingSize; + private final File file; + + NxdtNspFile(String name, int headerSize, long fullSize, File file) throws Exception{ + this.name = name; + this.headerSize = headerSize; + this.fullSize = fullSize; + this.file = file; + this.nspRemainingSize = fullSize - headerSize; + + removeIfExists(); + createHeaderFiller(); + } + private void removeIfExists() throws Exception{ + if (! file.exists()) + return; + + if (file.delete()) + return; + + throw new Exception("Unable to delete leftovers of the NSP file: "+name); + } + private void createHeaderFiller() throws Exception { + try (RandomAccessFile raf = new RandomAccessFile(file, "rw")){ + raf.setLength(headerSize); + } + catch (IOException e){ + throw new Exception("Unable to reserve space for NSP file's header: "+e.getMessage()); + } + } + + public String getName() { return name; } + public int getHeaderSize() { return headerSize; } + public long getFullSize() { return fullSize; } + public File getFile() { return file; } + public long getNspRemainingSize() { return nspRemainingSize; } + + public void setNspRemainingSize(long nspRemainingSize) { this.nspRemainingSize = nspRemainingSize; } +} diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java index 04b76ff..b5cb872 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java @@ -18,7 +18,7 @@ */ package nsusbloader.Utilities.nxdumptool; -import nsusbloader.COM.USB.UsbConnect; +import nsusbloader.com.usb.UsbConnect; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.ModelControllers.Log; diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java index dd2a1bc..738f4c1 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java @@ -18,9 +18,9 @@ */ package nsusbloader.Utilities.nxdumptool; -import nsusbloader.COM.USB.UsbErrorCodes; -import nsusbloader.COM.USB.common.DeviceInformation; -import nsusbloader.COM.USB.common.NsUsbEndpointDescriptor; +import nsusbloader.com.usb.UsbErrorCodes; +import nsusbloader.com.usb.common.DeviceInformation; +import nsusbloader.com.usb.common.NsUsbEndpointDescriptor; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; import org.usb4java.DeviceHandle; @@ -31,7 +31,11 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.HashMap; class NxdtUsbAbi1 { private final ILogPrinter logPrinter; @@ -51,6 +55,7 @@ class NxdtUsbAbi1 { private static final int CMD_HANDSHAKE = 0; private static final int CMD_SEND_FILE_PROPERTIES = 1; + private static final int CMD_SEND_NSP_HEADER = 2; private static final int CMD_ENDSESSION = 3; // Standard set of possible replies @@ -79,19 +84,19 @@ class NxdtUsbAbi1 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - private short endpointMaxPacketSize; - private static final int NXDT_USB_TIMEOUT = 5000; + private HashMap<String, NxdtNspFile> nspFiles; + public NxdtUsbAbi1(DeviceHandle handler, ILogPrinter logPrinter, String saveToPath, NxdtTask parent )throws Exception{ this.handlerNS = handler; - //this.endpointMaxPacketSize = wMaxPacketSize; this.logPrinter = logPrinter; this.parent = parent; + this.nspFiles = new HashMap<>(); this.isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); if (isWindows) @@ -110,7 +115,10 @@ class NxdtUsbAbi1 { private void resolveEndpointMaxPacketSize() throws Exception{ DeviceInformation deviceInformation = DeviceInformation.build(handlerNS); NsUsbEndpointDescriptor endpointInDescriptor = deviceInformation.getSimplifiedDefaultEndpointDescriptorIn(); - this.endpointMaxPacketSize = endpointInDescriptor.getwMaxPacketSize(); + short endpointMaxPacketSize = endpointInDescriptor.getwMaxPacketSize(); + + USBSTATUS_SUCCESS[8] = (byte)(endpointMaxPacketSize & 0xFF); + USBSTATUS_SUCCESS[9] = (byte)((endpointMaxPacketSize >> 8) & 0xFF); } private void readLoop(){ @@ -134,6 +142,9 @@ class NxdtUsbAbi1 { case CMD_SEND_FILE_PROPERTIES: handleSendFileProperties(directive); break; + case CMD_SEND_NSP_HEADER: + handleSendNspHeader(directive); + break; case CMD_ENDSESSION: logPrinter.print("Session successfully ended.", EMsgType.PASS); return; @@ -187,70 +198,112 @@ class NxdtUsbAbi1 { writeUsb(USBSTATUS_UNSUPPORTED_ABI); throw new Exception("ABI v"+versionABI+" is not supported in current version."); } - replyToHandshake(); - } - private void replyToHandshake() throws Exception{ - // Send status response + endpoint max packet size - ByteBuffer buffer = ByteBuffer.allocate(USBSTATUS_SUCCESS.length + 2).order(ByteOrder.LITTLE_ENDIAN); - buffer.put(USBSTATUS_SUCCESS); - buffer.putShort(endpointMaxPacketSize); - byte[] response = buffer.array(); - writeUsb(response); + writeUsb(USBSTATUS_SUCCESS); } private void handleSendFileProperties(byte[] message) throws Exception{ - final long fileSize = getLElong(message, 0x10); + final long fullSize = getLElong(message, 0x10); final int fileNameLen = getLEint(message, 0x18); - String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); + final int headerSize = getLEint(message, 0x1C); - if (fileNameLen <= 0 || fileNameLen > NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH){ - writeUsb(USBSTATUS_MALFORMED_REQUEST); - logPrinter.print("Invalid filename length!", EMsgType.FAIL); + if (checkFileNameLen(fileNameLen)) // In case of negative value we should better handle it before String constructor throws error return; - } - // TODO: Note, in case of a big amount of small files performace decreses dramatically. It's better to handle this only in case of 1-big-file-transfer - logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); - // If RomFs related - if (isRomFs(filename)) { - if (isWindows) - filename = saveToPath + filename.replaceAll("/", "\\\\"); - else - filename = saveToPath + filename; - createPath(filename); + String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); + String absoluteFilePath = getAbsoluteFilePath(filename); + File fileToDump = new File(absoluteFilePath); + NxdtNspFile nspFile = nspFiles.get(filename); // it could be null, but it's not a problem. + boolean nspTransferMode = false; + + if (checkSizes(fullSize, headerSize)) + return; + + if (createPath(absoluteFilePath)) + return; + + + if (checkFileSystem(fileToDump, fullSize)) + return; + + if (headerSize > 0){ // if NSP + logPrinter.print("Receiving NSP file entry: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); + if (nspFiles.containsKey(filename)){ + nspTransferMode = true; + } + else { + createNewNsp(filename, headerSize, fullSize, fileToDump); + return; + } } else { - //logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO); // TODO: see above - filename = saveToPath + filename; + // TODO: Note, in case of a big amount of small files performance decreases dramatically. It's better to handle this only in case of 1-big-file-transfer + logPrinter.print("Receiving: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); } - File fileToDump = new File(filename); + writeUsb(USBSTATUS_SUCCESS); + + if (fullSize == 0) + return; + + if (nspTransferMode) + dumpNspFile(nspFile, fullSize); + else + dumpFile(fileToDump, fullSize); + + writeUsb(USBSTATUS_SUCCESS); + + } + private boolean checkFileNameLen(int fileNameLen) throws Exception{ + if (fileNameLen <= 0 || fileNameLen > NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH){ + logPrinter.print("Invalid filename length!", EMsgType.FAIL); + writeUsb(USBSTATUS_MALFORMED_REQUEST); + return true; + } + return false; + } + private boolean checkSizes(long fileSize, int headerSize) throws Exception{ + if (fileSize >= headerSize){ + logPrinter.print("File size should not be equal to header size for NSP files!", EMsgType.FAIL); + writeUsb(USBSTATUS_MALFORMED_REQUEST); + return true; + } + if (fileSize < 0){ // It's possible to have files of zero-length, so only less is the problem + logPrinter.print("File size should not be less then zero!", EMsgType.FAIL); + writeUsb(USBSTATUS_MALFORMED_REQUEST); + return true; + } + return false; + } + + private boolean checkFileSystem(File fileToDump, long fileSize) throws Exception{ // Check if enough space if (fileToDump.getParentFile().getFreeSpace() <= fileSize){ writeUsb(USBSTATUS_HOSTIOERROR); logPrinter.print("Not enough space on selected volume. Need: "+fileSize+ " while available: "+fileToDump.getParentFile().getFreeSpace(), EMsgType.FAIL); - return; + return true; } // Check if FS is NOT read-only if (! (fileToDump.canWrite() || fileToDump.createNewFile()) ){ writeUsb(USBSTATUS_HOSTIOERROR); logPrinter.print("Unable to write into selected volume: "+fileToDump.getAbsolutePath(), EMsgType.FAIL); + return true; + } + return false; + } + private void createNewNsp(String filename, int headerSize, long fileSize, File fileToDump) throws Exception{ + try { + NxdtNspFile nsp = new NxdtNspFile(filename, headerSize, fileSize, fileToDump); + nspFiles.putIfAbsent(filename, nsp); + } + catch (Exception e){ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + writeUsb(USBSTATUS_HOSTIOERROR); return; } - writeUsb(USBSTATUS_SUCCESS); - - if (fileSize == 0) - return; - - dumpFile(fileToDump, fileSize); - - writeUsb(USBSTATUS_SUCCESS); - } - private int getLEint(byte[] bytes, int fromOffset){ return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt(); } @@ -259,24 +312,30 @@ class NxdtUsbAbi1 { return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong(); } + private String getAbsoluteFilePath(String filename) throws Exception{ + if (isRomFs(filename) && isWindows) // Since RomFS entry starts from '/' it should be replaced to '\'. + return saveToPath + filename.replaceAll("/", "\\\\"); + return saveToPath + filename; + } private boolean isRomFs(String filename){ return filename.startsWith("/"); } - private void createPath(String path) throws Exception{ - File resultingFile = new File(path); - File folderForTheFile = resultingFile.getParentFile(); - - if (folderForTheFile.exists()) - return; - - if (folderForTheFile.mkdirs()) - return; - - writeUsb(USBSTATUS_HOSTIOERROR); - throw new Exception("Unable to create dir(s) for file in "+folderForTheFile); + private boolean createPath(String path) throws Exception{ + try { + Path folderForTheFile = Paths.get(path).getParent(); + Files.createDirectories(folderForTheFile); + return false; + } + catch (Exception e){ + logPrinter.print("Unable to create dir(s) for file "+path, EMsgType.FAIL); + writeUsb(USBSTATUS_HOSTIOERROR); + return true; + } } + + // @see https://bugs.openjdk.java.net/browse/JDK-8146538 private void dumpFile(File file, long size) throws Exception{ FileOutputStream fos = new FileOutputStream(file, true); @@ -295,7 +354,6 @@ class NxdtUsbAbi1 { fd.sync(); bufferSize = readBuffer.length; received += bufferSize; - logPrinter.updateProgress((double)received / (double)size); } int lastChunkSize = (int)(size - received) + 1; @@ -303,15 +361,77 @@ class NxdtUsbAbi1 { bos.write(readBuffer); if (isWindows10) fd.sync(); - } finally { + } + finally { logPrinter.updateProgress(1.0); } } - /* Handle Zero-length terminator - private boolean isAligned(long size){ - return ((size & (endpointMaxPacketSize - 1)) == 0); + + private void dumpNspFile(NxdtNspFile nsp, long size) throws Exception{ + FileOutputStream fos = new FileOutputStream(nsp.getFile(), true); + long nspSize = nsp.getFullSize(); + long nspRemainingSize = nsp.getNspRemainingSize(); + + try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { + FileDescriptor fd = fos.getFD(); + byte[] readBuffer; + long received = 0; + int bufferSize; + + while (received+NXDT_FILE_CHUNK_SIZE < size) { + //readBuffer = readUsbFile(); + readBuffer = readUsbFileDebug(NXDT_FILE_CHUNK_SIZE); + bos.write(readBuffer); + if (isWindows10) + fd.sync(); + bufferSize = readBuffer.length; + received += bufferSize; + + nspRemainingSize -= bufferSize; + logPrinter.updateProgress((double)(nspSize - nspRemainingSize) / (double)nspSize); + } + int lastChunkSize = (int)(size - received) + 1; + readBuffer = readUsbFileDebug(lastChunkSize); + bos.write(readBuffer); + if (isWindows10) + fd.sync(); + nspRemainingSize -= (lastChunkSize - 1); + } + finally { + nsp.setNspRemainingSize(nspRemainingSize); + } + } + + private void handleSendNspHeader(byte[] message) throws Exception{ + final int headerSize = getLEint(message, 0x8); + NxdtNspFile nsp = nspFiles.remove( null ); // <---------------------- //TODO: PLEASE PUT FILENAME HERE + + if (nsp == null) { + writeUsb(USBSTATUS_MALFORMED_REQUEST); + logPrinter.print("Received NSP send header request outside of known NSPs!", EMsgType.FAIL); + return; + } + + if (nsp.getNspRemainingSize() > 0) { + writeUsb(USBSTATUS_MALFORMED_REQUEST); + logPrinter.print("Received NSP send header request without receiving all NSP file entry data!", EMsgType.FAIL); + return; + } + + if (headerSize != nsp.getHeaderSize()) { + writeUsb(USBSTATUS_MALFORMED_REQUEST); + logPrinter.print("Received NSP header size mismatch! "+headerSize+" != "+nsp.getHeaderSize(), EMsgType.FAIL); + return; + } + + try (RandomAccessFile raf = new RandomAccessFile(nsp.getFile(), "rw")) { + byte[] headerData = Arrays.copyOfRange(message, 0x10, headerSize + 0x10); + raf.seek(0); + raf.write(headerData); + } + + writeUsb(USBSTATUS_SUCCESS); } - */ /** Sending any byte array to USB device **/ private void writeUsb(byte[] message) throws Exception{ diff --git a/src/main/java/nsusbloader/cli/GoldLeafCli.java b/src/main/java/nsusbloader/cli/GoldLeafCli.java index ea687af..c9a146f 100644 --- a/src/main/java/nsusbloader/cli/GoldLeafCli.java +++ b/src/main/java/nsusbloader/cli/GoldLeafCli.java @@ -19,8 +19,7 @@ package nsusbloader.cli; import nsusbloader.AppPreferences; -import nsusbloader.COM.USB.UsbCommunications; -import nsusbloader.Controllers.SettingsController; +import nsusbloader.com.usb.UsbCommunications; import java.io.File; import java.util.ArrayList; diff --git a/src/main/java/nsusbloader/cli/TinfoilNetCli.java b/src/main/java/nsusbloader/cli/TinfoilNetCli.java index 88d1831..cb5f39d 100644 --- a/src/main/java/nsusbloader/cli/TinfoilNetCli.java +++ b/src/main/java/nsusbloader/cli/TinfoilNetCli.java @@ -18,7 +18,7 @@ */ package nsusbloader.cli; -import nsusbloader.COM.NET.NETCommunications; +import nsusbloader.com.net.NETCommunications; import java.io.File; import java.util.ArrayList; diff --git a/src/main/java/nsusbloader/cli/TinfoilUsbCli.java b/src/main/java/nsusbloader/cli/TinfoilUsbCli.java index 0d78f35..ad751f0 100644 --- a/src/main/java/nsusbloader/cli/TinfoilUsbCli.java +++ b/src/main/java/nsusbloader/cli/TinfoilUsbCli.java @@ -18,7 +18,7 @@ */ package nsusbloader.cli; -import nsusbloader.COM.USB.UsbCommunications; +import nsusbloader.com.usb.UsbCommunications; import java.io.File; import java.util.ArrayList; diff --git a/src/main/java/nsusbloader/COM/Helpers/NSSplitReader.java b/src/main/java/nsusbloader/com/helpers/NSSplitReader.java similarity index 99% rename from src/main/java/nsusbloader/COM/Helpers/NSSplitReader.java rename to src/main/java/nsusbloader/com/helpers/NSSplitReader.java index 058bacf..cbbd42c 100644 --- a/src/main/java/nsusbloader/COM/Helpers/NSSplitReader.java +++ b/src/main/java/nsusbloader/com/helpers/NSSplitReader.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.Helpers; +package nsusbloader.com.helpers; import java.io.*; diff --git a/src/main/java/nsusbloader/COM/NET/NETCommunications.java b/src/main/java/nsusbloader/com/net/NETCommunications.java similarity index 99% rename from src/main/java/nsusbloader/COM/NET/NETCommunications.java rename to src/main/java/nsusbloader/com/net/NETCommunications.java index 2f4b671..ba03ff6 100644 --- a/src/main/java/nsusbloader/COM/NET/NETCommunications.java +++ b/src/main/java/nsusbloader/com/net/NETCommunications.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.NET; +package nsusbloader.com.net; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; @@ -24,7 +24,7 @@ import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.ModelControllers.Log; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.COM.Helpers.NSSplitReader; +import nsusbloader.com.helpers.NSSplitReader; import java.io.*; import java.net.*; diff --git a/src/main/java/nsusbloader/COM/NET/NETPacket.java b/src/main/java/nsusbloader/com/net/NETPacket.java similarity index 99% rename from src/main/java/nsusbloader/COM/NET/NETPacket.java rename to src/main/java/nsusbloader/com/net/NETPacket.java index 6474bf1..3994ca8 100644 --- a/src/main/java/nsusbloader/COM/NET/NETPacket.java +++ b/src/main/java/nsusbloader/com/net/NETPacket.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.NET; +package nsusbloader.com.net; import nsusbloader.NSLMain; diff --git a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java b/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java similarity index 99% rename from src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java rename to src/main/java/nsusbloader/com/net/NetworkSetupValidator.java index 009cd74..706bd97 100644 --- a/src/main/java/nsusbloader/COM/NET/NetworkSetupValidator.java +++ b/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.NET; +package nsusbloader.com.net; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; diff --git a/src/main/java/nsusbloader/COM/NET/UniFile.java b/src/main/java/nsusbloader/com/net/UniFile.java similarity index 97% rename from src/main/java/nsusbloader/COM/NET/UniFile.java rename to src/main/java/nsusbloader/com/net/UniFile.java index 7aa5eb0..5f385fc 100644 --- a/src/main/java/nsusbloader/COM/NET/UniFile.java +++ b/src/main/java/nsusbloader/com/net/UniFile.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.NET; +package nsusbloader.com.net; import java.io.File; diff --git a/src/main/java/nsusbloader/COM/USB/GoldLeaf_05.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/GoldLeaf_05.java rename to src/main/java/nsusbloader/com/usb/GoldLeaf_05.java index 04ae4d9..53ebc4d 100644 --- a/src/main/java/nsusbloader/COM/USB/GoldLeaf_05.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java @@ -16,14 +16,14 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.COM.Helpers.NSSplitReader; -import nsusbloader.COM.USB.PFS.PFSProvider; +import nsusbloader.com.helpers.NSSplitReader; +import nsusbloader.com.usb.PFS.PFSProvider; import org.usb4java.DeviceHandle; import org.usb4java.LibUsb; diff --git a/src/main/java/nsusbloader/COM/USB/GoldLeaf_07.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/GoldLeaf_07.java rename to src/main/java/nsusbloader/com/usb/GoldLeaf_07.java index 70b733d..00fa19b 100644 --- a/src/main/java/nsusbloader/COM/USB/GoldLeaf_07.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java @@ -16,11 +16,11 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import javafx.application.Platform; import javafx.stage.FileChooser; -import nsusbloader.COM.Helpers.NSSplitReader; +import nsusbloader.com.helpers.NSSplitReader; import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; diff --git a/src/main/java/nsusbloader/COM/USB/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/GoldLeaf_08.java rename to src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index 723d5cb..8e6171f 100644 --- a/src/main/java/nsusbloader/COM/USB/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import javafx.application.Platform; import javafx.stage.FileChooser; @@ -24,7 +24,7 @@ import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.COM.Helpers.NSSplitReader; +import nsusbloader.com.helpers.NSSplitReader; import org.usb4java.DeviceHandle; import org.usb4java.LibUsb; diff --git a/src/main/java/nsusbloader/COM/USB/PFS/NCAFile.java b/src/main/java/nsusbloader/com/usb/PFS/NCAFile.java similarity index 98% rename from src/main/java/nsusbloader/COM/USB/PFS/NCAFile.java rename to src/main/java/nsusbloader/com/usb/PFS/NCAFile.java index f068d5f..24c723b 100644 --- a/src/main/java/nsusbloader/COM/USB/PFS/NCAFile.java +++ b/src/main/java/nsusbloader/com/usb/PFS/NCAFile.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.PFS; +package nsusbloader.com.usb.PFS; import java.nio.ByteBuffer; import java.nio.ByteOrder; diff --git a/src/main/java/nsusbloader/COM/USB/PFS/PFSProvider.java b/src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/PFS/PFSProvider.java rename to src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java index 463135c..e97352e 100644 --- a/src/main/java/nsusbloader/COM/USB/PFS/PFSProvider.java +++ b/src/main/java/nsusbloader/com/usb/PFS/PFSProvider.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.PFS; +package nsusbloader.com.usb.PFS; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; diff --git a/src/main/java/nsusbloader/COM/USB/TinFoil.java b/src/main/java/nsusbloader/com/usb/TinFoil.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/TinFoil.java rename to src/main/java/nsusbloader/com/usb/TinFoil.java index 15d2bda..9ceba47 100644 --- a/src/main/java/nsusbloader/COM/USB/TinFoil.java +++ b/src/main/java/nsusbloader/com/usb/TinFoil.java @@ -16,13 +16,13 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.COM.Helpers.NSSplitReader; +import nsusbloader.com.helpers.NSSplitReader; import org.usb4java.DeviceHandle; import org.usb4java.LibUsb; diff --git a/src/main/java/nsusbloader/COM/USB/TransferModule.java b/src/main/java/nsusbloader/com/usb/TransferModule.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/TransferModule.java rename to src/main/java/nsusbloader/com/usb/TransferModule.java index 02dd059..4eebca6 100644 --- a/src/main/java/nsusbloader/COM/USB/TransferModule.java +++ b/src/main/java/nsusbloader/com/usb/TransferModule.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; diff --git a/src/main/java/nsusbloader/COM/USB/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/UsbCommunications.java rename to src/main/java/nsusbloader/com/usb/UsbCommunications.java index 0fbd9da..cf31dfd 100644 --- a/src/main/java/nsusbloader/COM/USB/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; diff --git a/src/main/java/nsusbloader/COM/USB/UsbConnect.java b/src/main/java/nsusbloader/com/usb/UsbConnect.java similarity index 99% rename from src/main/java/nsusbloader/COM/USB/UsbConnect.java rename to src/main/java/nsusbloader/com/usb/UsbConnect.java index 4602653..91fe5d4 100644 --- a/src/main/java/nsusbloader/COM/USB/UsbConnect.java +++ b/src/main/java/nsusbloader/com/usb/UsbConnect.java @@ -16,9 +16,8 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; -import nsusbloader.COM.USB.common.DeviceInformation; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; import org.usb4java.*; diff --git a/src/main/java/nsusbloader/COM/USB/UsbErrorCodes.java b/src/main/java/nsusbloader/com/usb/UsbErrorCodes.java similarity index 98% rename from src/main/java/nsusbloader/COM/USB/UsbErrorCodes.java rename to src/main/java/nsusbloader/com/usb/UsbErrorCodes.java index 1be69d9..cbaaec5 100644 --- a/src/main/java/nsusbloader/COM/USB/UsbErrorCodes.java +++ b/src/main/java/nsusbloader/com/usb/UsbErrorCodes.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import org.usb4java.LibUsb; diff --git a/src/main/java/nsusbloader/COM/USB/common/DeviceInformation.java b/src/main/java/nsusbloader/com/usb/common/DeviceInformation.java similarity index 97% rename from src/main/java/nsusbloader/COM/USB/common/DeviceInformation.java rename to src/main/java/nsusbloader/com/usb/common/DeviceInformation.java index 831c17a..13e7099 100644 --- a/src/main/java/nsusbloader/COM/USB/common/DeviceInformation.java +++ b/src/main/java/nsusbloader/com/usb/common/DeviceInformation.java @@ -16,9 +16,9 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.common; +package nsusbloader.com.usb.common; -import nsusbloader.COM.USB.UsbErrorCodes; +import nsusbloader.com.usb.UsbErrorCodes; import org.usb4java.*; import java.util.ArrayList; diff --git a/src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptor.java b/src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptor.java similarity index 98% rename from src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptor.java rename to src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptor.java index df55248..a000aed 100644 --- a/src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptor.java +++ b/src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptor.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.common; +package nsusbloader.com.usb.common; import org.usb4java.EndpointDescriptor; diff --git a/src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptorUtils.java b/src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptorUtils.java similarity index 97% rename from src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptorUtils.java rename to src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptorUtils.java index ee897cd..1a6cbc8 100644 --- a/src/main/java/nsusbloader/COM/USB/common/NsUsbEndpointDescriptorUtils.java +++ b/src/main/java/nsusbloader/com/usb/common/NsUsbEndpointDescriptorUtils.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.common; +package nsusbloader.com.usb.common; import org.usb4java.EndpointDescriptor; diff --git a/src/main/java/nsusbloader/COM/USB/common/NsUsbInterface.java b/src/main/java/nsusbloader/com/usb/common/NsUsbInterface.java similarity index 97% rename from src/main/java/nsusbloader/COM/USB/common/NsUsbInterface.java rename to src/main/java/nsusbloader/com/usb/common/NsUsbInterface.java index d4be8ad..dddc092 100644 --- a/src/main/java/nsusbloader/COM/USB/common/NsUsbInterface.java +++ b/src/main/java/nsusbloader/com/usb/common/NsUsbInterface.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.common; +package nsusbloader.com.usb.common; import org.usb4java.Interface; import org.usb4java.InterfaceDescriptor; diff --git a/src/main/java/nsusbloader/COM/USB/common/NsUsbInterfaceDescriptor.java b/src/main/java/nsusbloader/com/usb/common/NsUsbInterfaceDescriptor.java similarity index 96% rename from src/main/java/nsusbloader/COM/USB/common/NsUsbInterfaceDescriptor.java rename to src/main/java/nsusbloader/com/usb/common/NsUsbInterfaceDescriptor.java index 760e157..57ed266 100644 --- a/src/main/java/nsusbloader/COM/USB/common/NsUsbInterfaceDescriptor.java +++ b/src/main/java/nsusbloader/com/usb/common/NsUsbInterfaceDescriptor.java @@ -16,13 +16,10 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.COM.USB.common; +package nsusbloader.com.usb.common; -import org.usb4java.EndpointDescriptor; import org.usb4java.InterfaceDescriptor; -import java.nio.ByteBuffer; - public class NsUsbInterfaceDescriptor { private final byte bLength; private final byte bDescriptorType; diff --git a/src/test/java/nsusbloader/COM/USB/TransferModuleTest.java b/src/test/java/nsusbloader/com/usb/TransferModuleTest.java similarity index 99% rename from src/test/java/nsusbloader/COM/USB/TransferModuleTest.java rename to src/test/java/nsusbloader/com/usb/TransferModuleTest.java index 12acb65..2803d2f 100644 --- a/src/test/java/nsusbloader/COM/USB/TransferModuleTest.java +++ b/src/test/java/nsusbloader/com/usb/TransferModuleTest.java @@ -1,4 +1,4 @@ -package nsusbloader.COM.USB; +package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; From eb07ab1df87ce38ab86f803df3f76efbb6dbb08b Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 27 Oct 2020 20:53:52 +0300 Subject: [PATCH 009/134] Update NXDT-related part --- .../Utilities/nxdumptool/NxdtUsbAbi1.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java index 738f4c1..097563f 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java @@ -35,7 +35,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.HashMap; class NxdtUsbAbi1 { private final ILogPrinter logPrinter; @@ -86,7 +85,7 @@ class NxdtUsbAbi1 { private static final int NXDT_USB_TIMEOUT = 5000; - private HashMap<String, NxdtNspFile> nspFiles; + private NxdtNspFile nspFile; public NxdtUsbAbi1(DeviceHandle handler, ILogPrinter logPrinter, @@ -96,7 +95,6 @@ class NxdtUsbAbi1 { this.handlerNS = handler; this.logPrinter = logPrinter; this.parent = parent; - this.nspFiles = new HashMap<>(); this.isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); if (isWindows) @@ -213,7 +211,7 @@ class NxdtUsbAbi1 { String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); String absoluteFilePath = getAbsoluteFilePath(filename); File fileToDump = new File(absoluteFilePath); - NxdtNspFile nspFile = nspFiles.get(filename); // it could be null, but it's not a problem. + boolean nspTransferMode = false; if (checkSizes(fullSize, headerSize)) @@ -228,7 +226,7 @@ class NxdtUsbAbi1 { if (headerSize > 0){ // if NSP logPrinter.print("Receiving NSP file entry: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); - if (nspFiles.containsKey(filename)){ + if (filename.equals(nspFile.getName())){ nspTransferMode = true; } else { @@ -265,11 +263,13 @@ class NxdtUsbAbi1 { private boolean checkSizes(long fileSize, int headerSize) throws Exception{ if (fileSize >= headerSize){ logPrinter.print("File size should not be equal to header size for NSP files!", EMsgType.FAIL); + resetNsp(); writeUsb(USBSTATUS_MALFORMED_REQUEST); return true; } if (fileSize < 0){ // It's possible to have files of zero-length, so only less is the problem logPrinter.print("File size should not be less then zero!", EMsgType.FAIL); + resetNsp(); writeUsb(USBSTATUS_MALFORMED_REQUEST); return true; } @@ -294,8 +294,7 @@ class NxdtUsbAbi1 { } private void createNewNsp(String filename, int headerSize, long fileSize, File fileToDump) throws Exception{ try { - NxdtNspFile nsp = new NxdtNspFile(filename, headerSize, fileSize, fileToDump); - nspFiles.putIfAbsent(filename, nsp); + nspFile = new NxdtNspFile(filename, headerSize, fileSize, fileToDump); } catch (Exception e){ logPrinter.print(e.getMessage(), EMsgType.FAIL); @@ -334,8 +333,6 @@ class NxdtUsbAbi1 { } } - - // @see https://bugs.openjdk.java.net/browse/JDK-8146538 private void dumpFile(File file, long size) throws Exception{ FileOutputStream fos = new FileOutputStream(file, true); @@ -404,7 +401,8 @@ class NxdtUsbAbi1 { private void handleSendNspHeader(byte[] message) throws Exception{ final int headerSize = getLEint(message, 0x8); - NxdtNspFile nsp = nspFiles.remove( null ); // <---------------------- //TODO: PLEASE PUT FILENAME HERE + NxdtNspFile nsp = nspFile; + resetNsp(); if (nsp == null) { writeUsb(USBSTATUS_MALFORMED_REQUEST); @@ -420,7 +418,7 @@ class NxdtUsbAbi1 { if (headerSize != nsp.getHeaderSize()) { writeUsb(USBSTATUS_MALFORMED_REQUEST); - logPrinter.print("Received NSP header size mismatch! "+headerSize+" != "+nsp.getHeaderSize(), EMsgType.FAIL); + logPrinter.print("Received NSP header size mismatch! "+headerSize+" != "+ nsp.getHeaderSize(), EMsgType.FAIL); return; } @@ -432,6 +430,9 @@ class NxdtUsbAbi1 { writeUsb(USBSTATUS_SUCCESS); } + private void resetNsp(){ + this.nspFile = null; + } /** Sending any byte array to USB device **/ private void writeUsb(byte[] message) throws Exception{ From 6e21c514a8503d5c970e5303698b37576ffc1214 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 27 Oct 2020 21:33:56 +0300 Subject: [PATCH 010/134] Update test, rename FrontTab to GamesTab --- ...ntController.java => GamesController.java} | 18 ++++++------ .../Controllers/NSLMainController.java | 8 +++--- .../Controllers/NSTableViewController.java | 12 ++++---- .../java/nsusbloader/MediatorControl.java | 2 +- .../ModelControllers/MessagesConsumer.java | 3 +- .../{FrontTab.fxml => GamesTab.fxml} | 2 +- src/main/resources/NSLMain.fxml | 2 +- .../nsusbloader/com/usb/NoLogPrinter.java | 28 +++++++++++++++++++ .../com/usb/TransferModuleTest.java | 3 +- 9 files changed, 52 insertions(+), 26 deletions(-) rename src/main/java/nsusbloader/Controllers/{FrontController.java => GamesController.java} (97%) rename src/main/resources/{FrontTab.fxml => GamesTab.fxml} (99%) create mode 100644 src/test/java/nsusbloader/com/usb/NoLogPrinter.java diff --git a/src/main/java/nsusbloader/Controllers/FrontController.java b/src/main/java/nsusbloader/Controllers/GamesController.java similarity index 97% rename from src/main/java/nsusbloader/Controllers/FrontController.java rename to src/main/java/nsusbloader/Controllers/GamesController.java index 423fe67..5d2424c 100644 --- a/src/main/java/nsusbloader/Controllers/FrontController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -44,7 +44,7 @@ import java.util.LinkedList; import java.util.List; import java.util.ResourceBundle; -public class FrontController implements Initializable { +public class GamesController implements Initializable { @FXML private AnchorPane usbNetPane; @@ -353,6 +353,7 @@ public class FrontController implements Initializable { usbNetPane.setDisable(isActive); return; } + selectNspBtn.setDisable(isActive); selectSplitNspBtn.setDisable(isActive); btnUpStopImage.getStyleClass().clear(); @@ -362,16 +363,17 @@ public class FrontController implements Initializable { uploadStopBtn.setOnAction(e-> stopBtnAction()); uploadStopBtn.setText(resourceBundle.getString("btn_Stop")); - uploadStopBtn.getStyleClass().clear(); + uploadStopBtn.getStyleClass().remove("buttonUp"); uploadStopBtn.getStyleClass().add("buttonStop"); - return; } - btnUpStopImage.getStyleClass().add("regionUpload"); + else { + btnUpStopImage.getStyleClass().add("regionUpload"); - uploadStopBtn.setOnAction(e-> uploadBtnAction()); - uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); - uploadStopBtn.getStyleClass().clear(); - uploadStopBtn.getStyleClass().add("buttonUp"); + uploadStopBtn.setOnAction(e-> uploadBtnAction()); + uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); + uploadStopBtn.getStyleClass().remove("buttonStop"); + uploadStopBtn.getStyleClass().add("buttonUp"); + } } /** * Crunch. This function called from NSTableViewController diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 4e564bf..0e74123 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -41,7 +41,7 @@ public class NSLMainController implements Initializable { public ProgressBar progressBar; // Accessible from Mediator @FXML - public FrontController FrontTabController; // Accessible from Mediator | todo: incapsulate + public GamesController GamesTabController; // Accessible from Mediator | todo: incapsulate @FXML private SettingsController SettingsTabController; @FXML @@ -103,8 +103,8 @@ public class NSLMainController implements Initializable { return SettingsTabController; } - public FrontController getFrontCtrlr(){ - return FrontTabController; + public GamesController getGamesCtrlr(){ + return GamesTabController; } public SplitMergeController getSmCtrlr(){ @@ -118,7 +118,7 @@ public class NSLMainController implements Initializable { * Save preferences before exit * */ public void exit(){ - FrontTabController.updatePreferencesOnExit(); + GamesTabController.updatePreferencesOnExit(); SettingsTabController.updatePreferencesOnExit(); SplitMergeTabController.updatePreferencesOnExit(); // NOTE: This shit above should be re-written to similar pattern RcmTabController.updatePreferencesOnExit(); diff --git a/src/main/java/nsusbloader/Controllers/NSTableViewController.java b/src/main/java/nsusbloader/Controllers/NSTableViewController.java index e7368ba..b47e47c 100644 --- a/src/main/java/nsusbloader/Controllers/NSTableViewController.java +++ b/src/main/java/nsusbloader/Controllers/NSTableViewController.java @@ -24,12 +24,10 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.SnapshotParameters; import javafx.scene.control.*; import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.*; -import javafx.scene.paint.Paint; import nsusbloader.MediatorControl; import nsusbloader.NSLDataTypes.EFileStatus; @@ -59,7 +57,7 @@ public class NSTableViewController implements Initializable { if (keyEvent.getCode() == KeyCode.DELETE && !MediatorControl.getInstance().getTransferActive()) { rowsObsLst.removeAll(table.getSelectionModel().getSelectedItems()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getContoller().getFrontCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); } else if (keyEvent.getCode() == KeyCode.SPACE) { for (NSLRowModel item : table.getSelectionModel().getSelectedItems()) { @@ -177,13 +175,13 @@ public class NSTableViewController implements Initializable { deleteMenuItem.setOnAction(actionEvent -> { rowsObsLst.remove(row.getItem()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getContoller().getFrontCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); }); MenuItem deleteAllMenuItem = new MenuItem(resourceBundle.getString("tab1_table_contextMenu_Btn_DeleteAll")); deleteAllMenuItem.setOnAction(actionEvent -> { rowsObsLst.clear(); - MediatorControl.getInstance().getContoller().getFrontCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); }); contextMenu.getItems().addAll(deleteMenuItem, deleteAllMenuItem); @@ -228,7 +226,7 @@ public class NSTableViewController implements Initializable { } else { rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getContoller().getFrontCtrlr().disableUploadStopBtn(false); // TODO: change to something better + MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(false); // TODO: change to something better } table.refresh(); } @@ -248,7 +246,7 @@ public class NSTableViewController implements Initializable { else { for (File file: newFiles) rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getContoller().getFrontCtrlr().disableUploadStopBtn(false); // TODO: change to something better + MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(false); // TODO: change to something better } //rowsObsLst.get(0).setMarkForUpload(true); table.refresh(); diff --git a/src/main/java/nsusbloader/MediatorControl.java b/src/main/java/nsusbloader/MediatorControl.java index e14564a..a644e50 100644 --- a/src/main/java/nsusbloader/MediatorControl.java +++ b/src/main/java/nsusbloader/MediatorControl.java @@ -41,7 +41,7 @@ public class MediatorControl { public synchronized void setBgThreadActive(boolean isActive, EModule appModuleType) { isTransferActive.set(isActive); - mainCtrler.getFrontCtrlr().notifyThreadStarted(isActive, appModuleType); + mainCtrler.getGamesCtrlr().notifyThreadStarted(isActive, appModuleType); mainCtrler.getSmCtrlr().notifySmThreadStarted(isActive, appModuleType); mainCtrler.getRcmCtrlr().notifyThreadStarted(isActive, appModuleType); mainCtrler.getNXDTabController().notifyThreadStarted(isActive, appModuleType); diff --git a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java index 25549d1..2b62f36 100644 --- a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java +++ b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java @@ -26,7 +26,6 @@ import nsusbloader.Controllers.NSTableViewController; import nsusbloader.MediatorControl; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EModule; -import nsusbloader.NSLDataTypes.EMsgType; import java.util.ArrayList; import java.util.HashMap; @@ -62,7 +61,7 @@ public class MessagesConsumer extends AnimationTimer { this.progressBar = MediatorControl.getInstance().getContoller().progressBar; this.statusMap = statusMap; - this.tableViewController = MediatorControl.getInstance().getContoller().FrontTabController.tableFilesListController; + this.tableViewController = MediatorControl.getInstance().getContoller().GamesTabController.tableFilesListController; this.oneLinerStatus = oneLinerStatus; diff --git a/src/main/resources/FrontTab.fxml b/src/main/resources/GamesTab.fxml similarity index 99% rename from src/main/resources/FrontTab.fxml rename to src/main/resources/GamesTab.fxml index 24a1a75..4df7b16 100644 --- a/src/main/resources/FrontTab.fxml +++ b/src/main/resources/GamesTab.fxml @@ -16,7 +16,7 @@ <?import javafx.scene.layout.VBox?> <?import javafx.scene.shape.SVGPath?> -<AnchorPane fx:id="usbNetPane" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.FrontController"> +<AnchorPane fx:id="usbNetPane" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.GamesController"> <children> <VBox layoutX="10.0" layoutY="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml index a2d42d6..635902f 100644 --- a/src/main/resources/NSLMain.fxml +++ b/src/main/resources/NSLMain.fxml @@ -23,7 +23,7 @@ Steps to roll NXDT functionality back: <tabs> <Tab closable="false"> <content> - <fx:include fx:id="FrontTab" source="FrontTab.fxml" VBox.vgrow="ALWAYS" /> + <fx:include fx:id="GamesTab" source="GamesTab.fxml" VBox.vgrow="ALWAYS" /> </content> <graphic> <SVGPath content="M 19.953125 -0.00390625 C 19.945025 0.86963499 19.665113 1.3200779 19.117188 1.6679688 C 18.569261 2.0158596 17.688291 2.2107818 16.572266 2.2636719 C 14.340723 2.369428 11.22952 1.9408017 8.0175781 1.7636719 L 8.015625 1.7636719 C 5.8083004 1.6301338 3.850269 1.7145428 2.421875 2.328125 C 1.7074522 2.6350131 1.1147008 3.0945958 0.765625 3.7402344 C 0.41654922 4.3858729 0.3309909 5.1840438 0.50976562 6.0957031 L 0.515625 6.1269531 L 0.52539062 6.1582031 C 0.74516874 6.8214776 1.3043008 7.2559352 1.9550781 7.46875 C 2.6058554 7.6815648 3.3739015 7.7273729 4.2304688 7.703125 C 5.9436032 7.6546292 8.0253788 7.3082908 10.042969 7.1230469 C 12.060559 6.9378029 14.005763 6.9298258 15.349609 7.4511719 C 16.693456 7.972518 17.5 8.8951351 17.5 11 L 13.023438 11 L 10.837891 11 L 1 11 C 0.156581 11 -0.10111662 11.623241 -0.10351562 12.189453 C -0.10591562 12.759114 -0.10321863 13.218366 -0.10351562 13.919922 C -0.10353762 13.969822 -0.10345962 13.990156 -0.10351562 14.042969 C -0.10332363 14.492063 -0.10337263 14.807528 -0.10351562 15.150391 C -0.10359462 15.343113 -0.10438363 15.566512 -0.10351562 15.771484 C -0.10347763 15.861874 -0.10364063 15.971437 -0.10351562 16.064453 C -0.10316562 16.324274 -0.10337563 16.550139 -0.10351562 16.880859 C -0.10353662 16.930759 -0.10345962 16.951094 -0.10351562 17.003906 C -0.10317063 17.257044 -0.10337962 17.473987 -0.10351562 17.794922 C -0.10353762 17.844822 -0.10345962 17.863203 -0.10351562 17.916016 C -0.10380362 18.172294 -0.10461563 18.463892 -0.10351562 18.732422 C -0.10363862 18.825442 -0.10347763 18.935007 -0.10351562 19.025391 C -0.10359462 19.218111 -0.10438363 19.441511 -0.10351562 19.646484 C -0.10337262 19.98934 -0.10332563 20.306746 -0.10351562 20.755859 C -0.10353662 20.805759 -0.10345962 20.826093 -0.10351562 20.878906 C -0.10321564 21.580432 -0.10591562 22.037789 -0.10351562 22.607422 C -0.10111563 23.173608 0.156706 23.796875 1 23.796875 L 10.833984 23.796875 L 13.019531 23.796875 L 22.857422 23.796875 C 23.70084 23.796875 23.958538 23.17361 23.960938 22.607422 C 23.963338 22.037788 23.960642 21.580432 23.960938 20.878906 C 23.960959 20.829006 23.96088 20.808672 23.960938 20.755859 C 23.960749 20.306747 23.960795 19.989341 23.960938 19.646484 C 23.961015 19.453769 23.961803 19.230374 23.960938 19.025391 C 23.9609 18.935011 23.961059 18.825438 23.960938 18.732422 C 23.96059 18.472592 23.960799 18.246723 23.960938 17.916016 C 23.960959 17.866116 23.96088 17.847735 23.960938 17.794922 C 23.960642 17.093366 23.963337 16.63411 23.960938 16.064453 C 23.96106 15.971433 23.960898 15.861868 23.960938 15.771484 C 23.961015 15.578768 23.961803 15.355372 23.960938 15.150391 C 23.960797 14.807528 23.960748 14.492063 23.960938 14.042969 C 23.960959 13.993069 23.96088 13.972735 23.960938 13.919922 C 23.960642 13.218366 23.963337 12.759114 23.960938 12.189453 C 23.958538 11.623242 23.700715 11 22.857422 11 L 18.5 11 C 18.5 8.6048649 17.347496 7.152482 15.710938 6.5175781 C 14.074378 5.8826742 12.017906 5.9371971 9.9511719 6.1269531 C 7.8844382 6.3167092 5.7997949 6.6578708 4.2011719 6.703125 C 3.4018604 6.7257521 2.725744 6.6699978 2.265625 6.5195312 C 1.8171096 6.3728594 1.6083191 6.1804127 1.4941406 5.859375 C 1.3628245 5.141091 1.4300216 4.6115935 1.6445312 4.2148438 C 1.8648981 3.8072608 2.2462454 3.4910124 2.8164062 3.2460938 C 3.9567281 2.7562562 5.8156963 2.6320489 7.9570312 2.7617188 L 7.9589844 2.7617188 C 11.116926 2.9357557 14.220255 3.3773586 16.619141 3.2636719 C 17.818583 3.2068289 18.856796 3.0180713 19.654297 2.5117188 C 20.451798 2.0053661 20.942623 1.130365 20.953125 0.00390625 L 19.953125 -0.00390625 z M 4.4277344 13.271484 L 7 13.271484 L 7 16 L 9.71875 16 L 9.71875 18.5625 L 7 18.5625 L 7 21.291016 L 4.4277344 21.291016 L 4.4277344 18.5625 L 1.7089844 18.5625 L 1.7089844 16 L 4.4277344 16 L 4.4277344 13.271484 z M 20 14 A 1.9161212 1.9161212 0 0 1 21.916016 15.916016 A 1.9161212 1.9161212 0 0 1 20 17.832031 A 1.9161212 1.9161212 0 0 1 18.083984 15.916016 A 1.9161212 1.9161212 0 0 1 20 14 z M 16.421875 17.667969 A 1.9168563 1.9168563 0 0 1 18.337891 19.583984 A 1.9168563 1.9168563 0 0 1 16.421875 21.5 A 1.9168563 1.9168563 0 0 1 14.505859 19.583984 A 1.9168563 1.9168563 0 0 1 16.421875 17.667969 z " /> diff --git a/src/test/java/nsusbloader/com/usb/NoLogPrinter.java b/src/test/java/nsusbloader/com/usb/NoLogPrinter.java new file mode 100644 index 0000000..be380ae --- /dev/null +++ b/src/test/java/nsusbloader/com/usb/NoLogPrinter.java @@ -0,0 +1,28 @@ +package nsusbloader.com.usb; + +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EFileStatus; +import nsusbloader.NSLDataTypes.EMsgType; + +import java.io.File; +import java.util.HashMap; + +public class NoLogPrinter implements ILogPrinter { + @Override + public void print(String message, EMsgType type) { } + + @Override + public void updateProgress(Double value) { } + + @Override + public void update(HashMap<String, File> nspMap, EFileStatus status) { } + + @Override + public void update(File file, EFileStatus status) { } + + @Override + public void updateOneLinerStatus(boolean status) { } + + @Override + public void close() { } +} diff --git a/src/test/java/nsusbloader/com/usb/TransferModuleTest.java b/src/test/java/nsusbloader/com/usb/TransferModuleTest.java index 2803d2f..604bf90 100644 --- a/src/test/java/nsusbloader/com/usb/TransferModuleTest.java +++ b/src/test/java/nsusbloader/com/usb/TransferModuleTest.java @@ -2,7 +2,6 @@ package nsusbloader.com.usb; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; -import nsusbloader.ModelControllers.LogPrinterCli; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -81,7 +80,7 @@ class TransferModuleTest{ filesMap.put(splitFileName10, splitFile10); filesMap.put(splitFileName11, splitFile11); - ILogPrinter printer = new LogPrinterCli(); + ILogPrinter printer = new NoLogPrinter(); this.transferModule = new TransferModuleImplementation((DeviceHandle)null, filesMap, (CancellableRunnable)null, printer); } From 0cb945a7f4c8af82a3b5eec8fd6d81a5c9093ad7 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 28 Oct 2020 15:59:45 +0300 Subject: [PATCH 011/134] Cosmetic updates of GL code and working prototype of NXDT --- .../Utilities/nxdumptool/NxdtUsbAbi1.java | 45 ++++--- .../java/nsusbloader/com/usb/GoldLeaf_08.java | 114 +++++++++--------- 2 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java index 097563f..e003a99 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java @@ -45,7 +45,7 @@ class NxdtUsbAbi1 { private final boolean isWindows; private boolean isWindows10; - private static final int NXDT_MAX_DIRECTIVE_SIZE = 0x1000; + private static final int NXDT_MAX_DIRECTIVE_SIZE = 0x800000;//0x1000; private static final int NXDT_FILE_CHUNK_SIZE = 0x800000; private static final int NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300; @@ -212,8 +212,6 @@ class NxdtUsbAbi1 { String absoluteFilePath = getAbsoluteFilePath(filename); File fileToDump = new File(absoluteFilePath); - boolean nspTransferMode = false; - if (checkSizes(fullSize, headerSize)) return; @@ -225,14 +223,9 @@ class NxdtUsbAbi1 { return; if (headerSize > 0){ // if NSP - logPrinter.print("Receiving NSP file entry: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); - if (filename.equals(nspFile.getName())){ - nspTransferMode = true; - } - else { - createNewNsp(filename, headerSize, fullSize, fileToDump); - return; - } + logPrinter.print("Receiving NSP file: '"+filename+"' ("+formatByteSize(fullSize)+")", EMsgType.PASS); + createNewNsp(filename, headerSize, fullSize, fileToDump); + return; } else { // TODO: Note, in case of a big amount of small files performance decreases dramatically. It's better to handle this only in case of 1-big-file-transfer @@ -244,7 +237,7 @@ class NxdtUsbAbi1 { if (fullSize == 0) return; - if (nspTransferMode) + if (isNspTransfer()) dumpNspFile(nspFile, fullSize); else dumpFile(fileToDump, fullSize); @@ -261,8 +254,8 @@ class NxdtUsbAbi1 { return false; } private boolean checkSizes(long fileSize, int headerSize) throws Exception{ - if (fileSize >= headerSize){ - logPrinter.print("File size should not be equal to header size for NSP files!", EMsgType.FAIL); + if (headerSize >= fileSize){ + logPrinter.print(String.format("File size (%d) should not be less or equal to header size (%d)!", fileSize, headerSize), EMsgType.FAIL); resetNsp(); writeUsb(USBSTATUS_MALFORMED_REQUEST); return true; @@ -275,7 +268,6 @@ class NxdtUsbAbi1 { } return false; } - private boolean checkFileSystem(File fileToDump, long fileSize) throws Exception{ // Check if enough space if (fileToDump.getParentFile().getFreeSpace() <= fileSize){ @@ -297,7 +289,8 @@ class NxdtUsbAbi1 { nspFile = new NxdtNspFile(filename, headerSize, fileSize, fileToDump); } catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); + logPrinter.print("Unable to create new file for: "+filename+" :"+e.getMessage(), EMsgType.FAIL); + e.printStackTrace(); writeUsb(USBSTATUS_HOSTIOERROR); return; } @@ -310,6 +303,9 @@ class NxdtUsbAbi1 { private long getLElong(byte[] bytes, int fromOffset){ return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong(); } + private boolean isNspTransfer(){ + return nspFile != null; + } private String getAbsoluteFilePath(String filename) throws Exception{ if (isRomFs(filename) && isWindows) // Since RomFS entry starts from '/' it should be replaced to '\'. @@ -367,9 +363,10 @@ class NxdtUsbAbi1 { private void dumpNspFile(NxdtNspFile nsp, long size) throws Exception{ FileOutputStream fos = new FileOutputStream(nsp.getFile(), true); long nspSize = nsp.getFullSize(); - long nspRemainingSize = nsp.getNspRemainingSize(); try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { + long nspRemainingSize = nsp.getNspRemainingSize(); + FileDescriptor fd = fos.getFD(); byte[] readBuffer; long received = 0; @@ -393,8 +390,6 @@ class NxdtUsbAbi1 { if (isWindows10) fd.sync(); nspRemainingSize -= (lastChunkSize - 1); - } - finally { nsp.setNspRemainingSize(nspRemainingSize); } } @@ -427,7 +422,8 @@ class NxdtUsbAbi1 { raf.seek(0); raf.write(headerData); } - + logPrinter.print("NSP file: '"+nsp.getName()+"' successfully received!", EMsgType.PASS); + logPrinter.updateProgress(1.0); writeUsb(USBSTATUS_SUCCESS); } private void resetNsp(){ @@ -534,4 +530,13 @@ class NxdtUsbAbi1 { "\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n (execution stopped)"); } + + private String formatByteSize(double length) { + final String[] unitNames = { "bytes", "KiB", "MiB", "GiB", "TiB"}; + int i; + for (i = 0; length > 1024 && i < unitNames.length - 1; i++) { + length = length / 1024; + } + return String.format("%,.2f %s", length, unitNames[i]); + } } diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index 8e6171f..f6a29ac 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -64,14 +64,19 @@ class GoldLeaf_08 extends TransferModule { private long virtDriveSize; private HashMap<String, Long> splitFileSize; - private boolean isWindows; - private String homePath; + private final boolean isWindows; + private final String homePath; // For using in CMD_SelectFile with SPEC:/ prefix private File selectedFile; - private CancellableRunnable task; + private final CancellableRunnable task; - GoldLeaf_08(DeviceHandle handler, LinkedHashMap<String, File> nspMap, CancellableRunnable task, ILogPrinter logPrinter, boolean nspFilter){ + GoldLeaf_08(DeviceHandle handler, + LinkedHashMap<String, File> nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter) + { super(handler, nspMap, task, logPrinter); this.task = task; @@ -890,43 +895,42 @@ class GoldLeaf_08 extends TransferModule { if (fileName.startsWith("VIRT:/")){ return writeGL_FAIL("GL Handle 'WriteFile' command [not supported for virtual drive]"); } - else { - fileName = updateHomePath(fileName); - // Check if we didn't see this (or any) file during this session - if (writeFilesMap.size() == 0 || (! writeFilesMap.containsKey(fileName))){ - // Open what we have to open - File writeFile = new File(fileName); - // If this file exists GL will take care - // Otherwise, let's add it - try{ - BufferedOutputStream writeFileBufOutStream = new BufferedOutputStream(new FileOutputStream(writeFile, true)); - writeFilesMap.put(fileName, writeFileBufOutStream); - } catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'WriteFile' command [IOException]\n\t"+ioe.getMessage()); - } - } - // Now we have stream - BufferedOutputStream myStream = writeFilesMap.get(fileName); - byte[] transferredData; - - if ((transferredData = readGL_file()) == null){ - logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); - return true; - } + 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{ - myStream.write(transferredData, 0, transferredData.length); + 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()); } - catch (IOException ioe){ - return writeGL_FAIL("GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage()); - } - // Report we're good - if (writeGL_PASS()) { - logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); - return true; - } - return false; } + // Now we have stream + BufferedOutputStream myStream = writeFilesMap.get(fileName); + + byte[] transferredData; + + if ((transferredData = readGL_file()) == null){ + logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); + return true; + } + try{ + myStream.write(transferredData, 0, transferredData.length); + } + catch (IOException ioe){ + return writeGL_FAIL("GL Handle 'WriteFile' command [1/1]\n\t"+ioe.getMessage()); + } + // Report we're good + if (writeGL_PASS()) { + logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); + return true; + } + return false; } /** @@ -938,27 +942,27 @@ class GoldLeaf_08 extends TransferModule { File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); fChooser.setTitle(MediatorControl.getInstance().getContoller().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION - fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a prio. + fChooser.setInitialDirectory(new File(System.getProperty("user.home")));// TODO: Consider fixing; not a prio. fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); return fChooser.showOpenDialog(null); // Leave as is for now. }, Platform::runLater).join(); - if (selectedFile != null){ - List<byte[]> command = new LinkedList<>(); - byte[] selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_16LE); - command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 - command.add(selectedFileNameBytes); - if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); - this.selectedFile = null; - return true; - } - this.selectedFile = selectedFile; - return false; + if (selectedFile == null){ // Nothing selected + this.selectedFile = null; + return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected"); } - // Nothing selected; Report failure. - this.selectedFile = null; - return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected"); + + List<byte[]> command = new LinkedList<>(); + byte[] selectedFileNameBytes = ("SPEC:/"+selectedFile.getName()).getBytes(StandardCharsets.UTF_16LE); + command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 + command.add(selectedFileNameBytes); + if (writeGL_PASS(command)) { + logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); + this.selectedFile = null; + return true; + } + this.selectedFile = selectedFile; + return false; } /*----------------------------------------------------*/ @@ -1039,9 +1043,7 @@ class GoldLeaf_08 extends TransferModule { return null; } private byte[] readGL_file(){ - ByteBuffer readBuffer; - readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. - + ByteBuffer readBuffer = ByteBuffer.allocateDirect(8388608); // Just don't ask.. IntBuffer readBufTransferred = IntBuffer.allocate(1); int result; From 050982f8eaa22c071e7e9416f05a4c440bbd14b4 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 28 Oct 2020 19:10:08 +0300 Subject: [PATCH 012/134] Update Jenkinsfile --- Jenkinsfile | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bf047ac..6809359 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,20 +7,25 @@ pipeline { } stages { + stage('Build') { + steps { + sh 'mvn -B -DskipTests clean package' + } + post { + success { + archiveArtifacts artifacts: 'target/*.jar, target/*.exe' + } + } + } stage('Test') { steps { sh 'mvn test' } - } - stage('Build') { - steps { - sh 'mvn clean package' + post { + always { + junit 'target/surefire-reports/*.xml' + } } } } - post { - always { - archiveArtifacts artifacts: 'target/*.jar, target/*.exe', onlyIfSuccessful: true - } - } } \ No newline at end of file From fbd5ec970b78680292f8c69a58c1328703288df3 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 29 Oct 2020 16:54:41 +0300 Subject: [PATCH 013/134] Small NXDT-code refactoring --- .../nxdumptool/NxdtHostIOException.java | 28 ++++ .../nxdumptool/NxdtMalformedException.java | 28 ++++ .../Utilities/nxdumptool/NxdtUsbAbi1.java | 130 ++++++++---------- 3 files changed, 112 insertions(+), 74 deletions(-) create mode 100644 src/main/java/nsusbloader/Utilities/nxdumptool/NxdtHostIOException.java create mode 100644 src/main/java/nsusbloader/Utilities/nxdumptool/NxdtMalformedException.java diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtHostIOException.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtHostIOException.java new file mode 100644 index 0000000..54d7392 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtHostIOException.java @@ -0,0 +1,28 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Utilities.nxdumptool; + +class NxdtHostIOException extends Exception { + NxdtHostIOException(){ + super(); + } + NxdtHostIOException(String message){ + super(message); + } +} diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtMalformedException.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtMalformedException.java new file mode 100644 index 0000000..43409dd --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtMalformedException.java @@ -0,0 +1,28 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Utilities.nxdumptool; + +class NxdtMalformedException extends Exception { + NxdtMalformedException(){ + super(); + } + NxdtMalformedException(String message){ + super(message); + } +} diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java index e003a99..613597d 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java @@ -201,100 +201,85 @@ class NxdtUsbAbi1 { } private void handleSendFileProperties(byte[] message) throws Exception{ - final long fullSize = getLElong(message, 0x10); - final int fileNameLen = getLEint(message, 0x18); - final int headerSize = getLEint(message, 0x1C); + try { + final long fullSize = getLElong(message, 0x10); + final int fileNameLen = getLEint(message, 0x18); + final int headerSize = getLEint(message, 0x1C); + checkFileNameLen(fileNameLen); // In case of negative value we should better handle it before String constructor throws error + String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); + String absoluteFilePath = getAbsoluteFilePath(filename); + File fileToDump = new File(absoluteFilePath); - if (checkFileNameLen(fileNameLen)) // In case of negative value we should better handle it before String constructor throws error - return; + checkSizes(fullSize, headerSize); + createPath(absoluteFilePath); + checkFileSystem(fileToDump, fullSize); - String filename = new String(message, 0x20, fileNameLen, StandardCharsets.UTF_8); - String absoluteFilePath = getAbsoluteFilePath(filename); - File fileToDump = new File(absoluteFilePath); + if (headerSize > 0){ // if NSP + logPrinter.print("Receiving NSP file: '"+filename+"' ("+formatByteSize(fullSize)+")", EMsgType.PASS); + createNewNsp(filename, headerSize, fullSize, fileToDump); + return; + } + else { + // TODO: Note, in case of a big amount of small files performance decreases dramatically. It's better to handle this only in case of 1-big-file-transfer + logPrinter.print("Receiving: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); + } - if (checkSizes(fullSize, headerSize)) - return; + writeUsb(USBSTATUS_SUCCESS); - if (createPath(absoluteFilePath)) - return; + if (fullSize == 0) + return; + if (isNspTransfer()) + dumpNspFile(fullSize); + else + dumpFile(fileToDump, fullSize); - if (checkFileSystem(fileToDump, fullSize)) - return; - - if (headerSize > 0){ // if NSP - logPrinter.print("Receiving NSP file: '"+filename+"' ("+formatByteSize(fullSize)+")", EMsgType.PASS); - createNewNsp(filename, headerSize, fullSize, fileToDump); - return; + writeUsb(USBSTATUS_SUCCESS); } - else { - // TODO: Note, in case of a big amount of small files performance decreases dramatically. It's better to handle this only in case of 1-big-file-transfer - logPrinter.print("Receiving: '"+filename+"' ("+fullSize+" b)", EMsgType.INFO); + catch (NxdtMalformedException malformed){ + logPrinter.print(malformed.getMessage(), EMsgType.FAIL); + writeUsb(USBSTATUS_MALFORMED_REQUEST); + } + catch (NxdtHostIOException ioException){ + logPrinter.print(ioException.getMessage(), EMsgType.FAIL); + writeUsb(USBSTATUS_HOSTIOERROR); } - - writeUsb(USBSTATUS_SUCCESS); - - if (fullSize == 0) - return; - - if (isNspTransfer()) - dumpNspFile(nspFile, fullSize); - else - dumpFile(fileToDump, fullSize); - - writeUsb(USBSTATUS_SUCCESS); - } - private boolean checkFileNameLen(int fileNameLen) throws Exception{ + private void checkFileNameLen(int fileNameLen) throws NxdtMalformedException{ if (fileNameLen <= 0 || fileNameLen > NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH){ - logPrinter.print("Invalid filename length!", EMsgType.FAIL); - writeUsb(USBSTATUS_MALFORMED_REQUEST); - return true; + throw new NxdtMalformedException("Invalid filename length!"); } - return false; } - private boolean checkSizes(long fileSize, int headerSize) throws Exception{ + private void checkSizes(long fileSize, int headerSize) throws Exception{ if (headerSize >= fileSize){ - logPrinter.print(String.format("File size (%d) should not be less or equal to header size (%d)!", fileSize, headerSize), EMsgType.FAIL); resetNsp(); - writeUsb(USBSTATUS_MALFORMED_REQUEST); - return true; + throw new NxdtMalformedException(String.format("File size (%d) should not be less or equal to header size (%d)!", fileSize, headerSize)); } if (fileSize < 0){ // It's possible to have files of zero-length, so only less is the problem - logPrinter.print("File size should not be less then zero!", EMsgType.FAIL); resetNsp(); - writeUsb(USBSTATUS_MALFORMED_REQUEST); - return true; + throw new NxdtMalformedException("File size should not be less then zero!"); } - return false; } - private boolean checkFileSystem(File fileToDump, long fileSize) throws Exception{ + private void checkFileSystem(File fileToDump, long fileSize) throws Exception{ // Check if enough space if (fileToDump.getParentFile().getFreeSpace() <= fileSize){ - writeUsb(USBSTATUS_HOSTIOERROR); - logPrinter.print("Not enough space on selected volume. Need: "+fileSize+ - " while available: "+fileToDump.getParentFile().getFreeSpace(), EMsgType.FAIL); - return true; + throw new NxdtHostIOException("Not enough space on selected volume. Need: "+fileSize+ + " while available: "+fileToDump.getParentFile().getFreeSpace()); } // Check if FS is NOT read-only if (! (fileToDump.canWrite() || fileToDump.createNewFile()) ){ - writeUsb(USBSTATUS_HOSTIOERROR); - logPrinter.print("Unable to write into selected volume: "+fileToDump.getAbsolutePath(), EMsgType.FAIL); - return true; + throw new NxdtHostIOException("Unable to write into selected volume: "+fileToDump.getAbsolutePath()); } - return false; } - private void createNewNsp(String filename, int headerSize, long fileSize, File fileToDump) throws Exception{ + private void createNewNsp(String filename, int headerSize, long fileSize, File fileToDump) throws NxdtHostIOException{ try { nspFile = new NxdtNspFile(filename, headerSize, fileSize, fileToDump); + writeUsb(USBSTATUS_SUCCESS); } catch (Exception e){ - logPrinter.print("Unable to create new file for: "+filename+" :"+e.getMessage(), EMsgType.FAIL); e.printStackTrace(); - writeUsb(USBSTATUS_HOSTIOERROR); - return; + throw new NxdtHostIOException("Unable to create new file for: "+filename+" :"+e.getMessage()); } - writeUsb(USBSTATUS_SUCCESS); } private int getLEint(byte[] bytes, int fromOffset){ return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt(); @@ -316,16 +301,13 @@ class NxdtUsbAbi1 { return filename.startsWith("/"); } - private boolean createPath(String path) throws Exception{ + private void createPath(String path) throws Exception{ try { Path folderForTheFile = Paths.get(path).getParent(); Files.createDirectories(folderForTheFile); - return false; } catch (Exception e){ - logPrinter.print("Unable to create dir(s) for file "+path, EMsgType.FAIL); - writeUsb(USBSTATUS_HOSTIOERROR); - return true; + throw new NxdtHostIOException("Unable to create dir(s) for file '"+path+"':"+e.getMessage()); } } @@ -333,7 +315,7 @@ class NxdtUsbAbi1 { private void dumpFile(File file, long size) throws Exception{ FileOutputStream fos = new FileOutputStream(file, true); - try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { + try (BufferedOutputStream bos = new BufferedOutputStream(fos)){ FileDescriptor fd = fos.getFD(); byte[] readBuffer; long received = 0; @@ -360,12 +342,12 @@ class NxdtUsbAbi1 { } } - private void dumpNspFile(NxdtNspFile nsp, long size) throws Exception{ - FileOutputStream fos = new FileOutputStream(nsp.getFile(), true); - long nspSize = nsp.getFullSize(); + private void dumpNspFile(long size) throws Exception{ + FileOutputStream fos = new FileOutputStream(nspFile.getFile(), true); + long nspSize = nspFile.getFullSize(); try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { - long nspRemainingSize = nsp.getNspRemainingSize(); + long nspRemainingSize = nspFile.getNspRemainingSize(); FileDescriptor fd = fos.getFD(); byte[] readBuffer; @@ -390,7 +372,7 @@ class NxdtUsbAbi1 { if (isWindows10) fd.sync(); nspRemainingSize -= (lastChunkSize - 1); - nsp.setNspRemainingSize(nspRemainingSize); + nspFile.setNspRemainingSize(nspRemainingSize); } } @@ -398,6 +380,7 @@ class NxdtUsbAbi1 { final int headerSize = getLEint(message, 0x8); NxdtNspFile nsp = nspFile; resetNsp(); + logPrinter.updateProgress(1.0); if (nsp == null) { writeUsb(USBSTATUS_MALFORMED_REQUEST); @@ -423,7 +406,6 @@ class NxdtUsbAbi1 { raf.write(headerData); } logPrinter.print("NSP file: '"+nsp.getName()+"' successfully received!", EMsgType.PASS); - logPrinter.updateProgress(1.0); writeUsb(USBSTATUS_SUCCESS); } private void resetNsp(){ From 5e5a774e34362c1140b1dad2126cee4715c0fec4 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 6 Nov 2020 22:26:35 +0300 Subject: [PATCH 014/134] Fix #81 --- src/main/java/nsusbloader/FilesHelper.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/nsusbloader/FilesHelper.java b/src/main/java/nsusbloader/FilesHelper.java index 7988a06..53771dc 100644 --- a/src/main/java/nsusbloader/FilesHelper.java +++ b/src/main/java/nsusbloader/FilesHelper.java @@ -24,9 +24,12 @@ import java.nio.file.Paths; public class FilesHelper { public static String getRealFolder(String location){ - Path locationAsPath = Paths.get(location); - if (Files.notExists(locationAsPath) || Files.isRegularFile(locationAsPath)) - return System.getProperty("user.home"); + try{ + Path locationAsPath = Paths.get(location); + if (Files.notExists(locationAsPath) || Files.isRegularFile(locationAsPath)) + return System.getProperty("user.home"); + } + catch (Exception ignored){} return location; } } From 953dcb50cbd2799ba600570bec51ea7f80b20449 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 6 Nov 2020 22:29:09 +0300 Subject: [PATCH 015/134] Rally fix #81 --- src/main/java/nsusbloader/FilesHelper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/nsusbloader/FilesHelper.java b/src/main/java/nsusbloader/FilesHelper.java index 53771dc..739a375 100644 --- a/src/main/java/nsusbloader/FilesHelper.java +++ b/src/main/java/nsusbloader/FilesHelper.java @@ -28,8 +28,10 @@ public class FilesHelper { Path locationAsPath = Paths.get(location); if (Files.notExists(locationAsPath) || Files.isRegularFile(locationAsPath)) return System.getProperty("user.home"); + return location; + } + catch (Exception ignored){ + return System.getProperty("user.home"); } - catch (Exception ignored){} - return location; } } From 9a42e42c301b7d57106946168f0241d9dd6cff1e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 6 Nov 2020 23:25:25 +0300 Subject: [PATCH 016/134] Version increment --- pom.xml | 2 +- src/main/java/nsusbloader/NSLMain.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3f6d31c..e900a92 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>4.4-SNAPSHOT</version> + <version>4.5-SNAPSHOT</version> <url>https://github.com/developersu/ns-usbloader/</url> <description> diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 8a76096..606c89c 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v4.4"; + public static final String appVersion = "v4.5"; public static boolean isCli; @Override From 503aa8cbe201c8a4dbe0b1098966f9779e13a703 Mon Sep 17 00:00:00 2001 From: Wolf Posdorfer <wolfposd@gmail.com> Date: Sat, 21 Nov 2020 17:32:11 +0100 Subject: [PATCH 017/134] allows adding files and folders via filechooser and drag and drop need to switch to JFileChooser, as javafx filechooser doesnt allow files+folders selection selection folders (filechooser/dragndrop) now recursively walks the directory and adds every file.xyz it finds --- .../Controllers/GamesController.java | 126 +++++++++++++----- 1 file changed, 95 insertions(+), 31 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 5d2424c..4146b20 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -28,7 +28,6 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; -import javafx.stage.FileChooser; import nsusbloader.AppPreferences; import nsusbloader.com.net.NETCommunications; import nsusbloader.com.usb.UsbCommunications; @@ -40,11 +39,22 @@ import nsusbloader.ServiceWindow; import java.io.File; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.ResourceBundle; +import javax.swing.JFileChooser; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.filechooser.FileFilter; + public class GamesController implements Initializable { + + private static final String REGEX_ONLY_NSP = ".*\\.nsp$"; + private static final String REGEX_ALLFILES_TINFOIL = ".*\\.(nsp$|xci$|nsz$|xcz$)"; + @FXML private AnchorPane usbNetPane; @@ -186,32 +196,88 @@ public class GamesController implements Initializable { return nsIpTextField.getText(); } + + private boolean isGoldLeaf() { + return getSelectedProtocol().equals("GoldLeaf") + && (!MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL()); + } + + private boolean isTinfoil() { + return getSelectedProtocol().equals("TinFoil") + && MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport(); + } + + private String getRegexForFiles() { + if (isTinfoil()) + return REGEX_ALLFILES_TINFOIL; + else if (isGoldLeaf()) + return REGEX_ONLY_NSP; + else + return REGEX_ONLY_NSP; + } + /** * Functionality for selecting NSP button. * */ private void selectFilesBtnAction(){ - List<File> filesList; - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(resourceBundle.getString("btn_OpenFile")); + final String regex = getRegexForFiles(); + if(!UIManager.getLookAndFeel().isNativeLookAndFeel()) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { + // :shrug emoji: + // defaults to Metal Look and Feel + } + } - fileChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + JFileChooser fileChooser = new JFileChooser(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + fileChooser.setMultiSelectionEnabled(true); + fileChooser.setFileFilter(new FileFilter() { + public String getDescription() { + return "Switch Files"; + } + public boolean accept(File f) { + return f.isDirectory() || f.getName().toLowerCase().matches(regex); + } + }); + + if(fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + List<File> files = Arrays.asList(fileChooser.getSelectedFiles()); + List<File> allFiles = new ArrayList<>(); - if (getSelectedProtocol().equals("TinFoil") && MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport()) - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP/XCI/NSZ/XCZ", "*.nsp", "*.xci", "*.nsz", "*.xcz")); - else if (getSelectedProtocol().equals("GoldLeaf") && (! MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL())) - fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Any file", "*.*"), - new FileChooser.ExtensionFilter("NSP ROM", "*.nsp") - ); - else - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp")); + if (files.size() != 0) { + files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); + files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); + } - filesList = fileChooser.showOpenMultipleDialog(usbNetPane.getScene().getWindow()); - if (filesList != null && !filesList.isEmpty()) { - tableFilesListController.setFiles(filesList); - uploadStopBtn.setDisable(false); - previouslyOpenedPath = filesList.get(0).getParent(); + if (allFiles.size() > 0) { + tableFilesListController.setFiles(allFiles); + uploadStopBtn.setDisable(false); + previouslyOpenedPath = allFiles.get(0).getParent(); + } } } + + /** + * used to recursively walk all directories, every file will be added to the storage list + * @param storage used to hold files + * @param startFolder where to start + * @param regex for filenames + */ + private void collectFiles(List<File> storage, File startFolder, final String regex) { + if (startFolder.isDirectory()) { + File[] files = startFolder.listFiles(); + for (File f : files) { + if (f.isDirectory()) { + collectFiles(storage, f, regex); + } else if (f.getName().toLowerCase().matches(regex)) { + storage.add(f); + } + } + } + } + /** * Functionality for selecting Split NSP button. * */ @@ -325,20 +391,18 @@ public class GamesController implements Initializable { * */ @FXML private void handleDrop(DragEvent event){ - List<File> filesDropped = event.getDragboard().getFiles(); - SettingsController settingsController = MediatorControl.getInstance().getContoller().getSettingsCtrlr(); - SettingsBlockTinfoilController tinfoilSettings = settingsController.getTinfoilSettings(); - SettingsBlockGoldleafController goldleafController = settingsController.getGoldleafSettings(); + final String regex = getRegexForFiles(); + + List<File> files = event.getDragboard().getFiles(); + List<File> allFiles = new ArrayList<>(); - if (getSelectedProtocol().equals("TinFoil") && tinfoilSettings.isXciNszXczSupport()) - filesDropped.removeIf(file -> ! file.getName().toLowerCase().matches("(.*\\.nsp$)|(.*\\.xci$)|(.*\\.nsz$)|(.*\\.xcz$)")); - else if (getSelectedProtocol().equals("GoldLeaf") && (! goldleafController.getNSPFileFilterForGL())) - filesDropped.removeIf(file -> (file.isDirectory() && ! file.getName().toLowerCase().matches(".*\\.nsp$"))); - else - filesDropped.removeIf(file -> ! file.getName().toLowerCase().matches(".*\\.nsp$")); - - if ( ! filesDropped.isEmpty() ) - tableFilesListController.setFiles(filesDropped); + if (files.size() != 0) { + files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); + files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); + } + + if ( ! allFiles.isEmpty() ) + tableFilesListController.setFiles(allFiles); event.setDropCompleted(true); event.consume(); From 7a94c48559b0855dc56c7ab7c37124d1df43ab4c Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 22 Nov 2020 23:12:07 +0300 Subject: [PATCH 018/134] Add Arabic by @eslamabdel (#83) --- README.md | 1 + pom.xml | 2 +- src/main/java/nsusbloader/NSLMain.java | 2 +- src/main/resources/locale_ar_AR.properties | 71 ++++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/locale_ar_AR.properties diff --git a/README.md b/README.md index 5e1e9ec..35e7358 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Sometimes I add new posts about this project [on my home page](https://developer * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) * Czech by [Spenaat](https://github.com/spenaat) * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234) +* Arabic by [eslamabdel](https://github.com/eslamabdel) ### System requirements diff --git a/pom.xml b/pom.xml index e900a92..ac9c186 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>4.5-SNAPSHOT</version> + <version>5.0-SNAPSHOT</version> <url>https://github.com/developersu/ns-usbloader/</url> <description> diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 606c89c..f3c9d98 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v4.5"; + public static final String appVersion = "v5.0"; public static boolean isCli; @Override diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties new file mode 100644 index 0000000..1f5db48 --- /dev/null +++ b/src/main/resources/locale_ar_AR.properties @@ -0,0 +1,71 @@ +btn_OpenFile=\u0627\u062E\u062A\u0631 \u0627\u0644\u0645\u0644\u0641\u0627\u062A +btn_Upload=\u0627\u0631\u0641\u0639 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0644\u0644\u062C\u0647\u0627\u0632 +tab3_Txt_EnteredAsMsg1=\u0644\u0642\u062F \u0633\u062C\u0644\u062A \u0627\u0644\u062F\u062E\u0648\u0644 \u0628\u0625\u0633\u0645 : +tab3_Txt_EnteredAsMsg2=\u064A\u0646\u0628\u063A\u064A \u0623\u0646 \u062A\u0643\u0648\u0646 root \u0623\u0648 \u0639\u062F\u0644\u062A \u0635\u0644\u0627\u062D\u064A\u0627\u062A 'udev' \u0644\u0647\u0630\u0627 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 \u0644\u062A\u062C\u0646\u0628 \u0623\u064A \u0645\u0634\u0643\u0644\u0629. +tab3_Txt_FilesToUploadTitle=\u0645\u0644\u0641\u0627\u062A \u0644\u0644\u0631\u0641\u0639 : +tab3_Txt_GreetingsMessage=\u0645\u0631\u062D\u0628\u0627 \u0628\u0643 \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634 "\u064A\u0648 \u0625\u0633 \u0628\u064A \u0644\u0648\u062F\u0631" +tab3_Txt_NoFolderOrFileSelected=\u0644\u0645 \u064A\u062A\u0645 \u0627\u062E\u062A\u064A\u0627\u0631 \u0623\u064A \u0645\u0644\u0641\u0627\u062A : \u0644\u0627 \u0634\u064A\u0626 \u0644\u0644\u0631\u0641\u0639. +windowBodyConfirmExit=\u062C\u0627\u0631\u064A \u0646\u0642\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0627\u0646\u0647\u0627\u0621 \u0627\u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0633\u0648\u0641 \u064A\u0648\u0642\u0641\u0647\u0627.\n\u0630\u0644\u0643 \u0623\u0633\u0648\u0623 \u0645\u0627 \u064A\u0645\u0643\u0646\u0643 \u0639\u0645\u0644\u0647 \u0627\u0644\u0622\u0646.\n\u0627\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0629 \u0645\u0639 \u0627\u0644\u062E\u0631\u0648\u062C? +windowTitleConfirmExit=\u0644\u0627 , \u0644\u0627 \u062A\u0641\u0639\u0644 \u0630\u0644\u0643! +btn_Stop=\u0625\u064A\u0642\u0627\u0641 +tab3_Txt_GreetingsMessage2=--\n\ +\u0627\u0644\u0645\u0635\u062F\u0631: https://github.com/developersu/ns-usbloader/\n\ +\u0627\u0644\u0645\u0648\u0642\u0639: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=\u0627\u0644\u062D\u0627\u0644\u0629 +tab1_table_Lbl_FileName=\u0627\u0633\u0645 \u0627\u0644\u0645\u0644\u0641 +tab1_table_Lbl_Size=\u0627\u0644\u062D\u062C\u0645 +tab1_table_Lbl_Upload=\u0631\u0641\u0639? +tab1_table_contextMenu_Btn_BtnDelete=\u062D\u0630\u0641 +tab1_table_contextMenu_Btn_DeleteAll=\u062D\u0630\u0641 \u0627\u0644\u0643\u0644 +tab2_Lbl_HostIP=\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0640 "\u0623\u064A \u0628\u064A" \u0644\u0644\u0645\u0636\u064A\u0641: +tab1_Lbl_NSIP=\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0640 "\u0623\u064A \u0628\u064A" \u0644\u0644\u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634: +tab2_Cb_ValidateNSHostName=\u062A\u062D\u0642\u0642 \u062F\u0627\u0626\u0645\u0627 \u0645\u0646 \u0625\u062F\u062E\u0627\u0644 \u0639\u0646\u0648\u0627\u0646 \u0627\u0644 "\u0623\u064A \u0628\u064A" \u0644\u0644\u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634. +windowBodyBadIp=\u0647\u0644 \u0627\u0646\u062A \u0645\u062A\u0623\u0643\u062F \u0645\u0646 \u0625\u062F\u062E\u0627\u0644\u0643 \u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0640 "\u0623\u064A \u0628\u064A" \u0644\u0644\u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634 \u0628\u0635\u0648\u0631\u0629 \u0635\u062D\u064A\u062D\u0629\u061F +windowTitleBadIp=\u0639\u0646\u0648\u0627\u0646 \u0627\u0644 \u0623\u064A \u0628\u064A \u0644\u0644\u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634 \u063A\u064A\u0631 \u0635\u062D\u064A\u062D \u0641\u064A \u0627\u0644\u063A\u0627\u0644\u0628. +tab2_Cb_ExpertMode=\u0648\u0636\u0639 \u0627\u0644\u0645\u062D\u062A\u0631\u0641\u064A\u0646 (\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0646\u062A) +tab2_Lbl_HostPort=\u0627\u0644\u0645\u0646\u0641\u0630 +tab2_Cb_AutoDetectIp=\u0643\u0634\u0641 \u062A\u0644\u0642\u0627\u0626\u064A \u0639\u0646 \u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0640 "\u0623\u064A \u0628\u064A" +tab2_Cb_RandSelectPort=\u0627\u0644\u062D\u0635\u0648\u0644 \u0639\u0634\u0648\u0627\u0626\u064A\u0627 \u0639\u0646 \u0645\u0646\u0641\u0630 +tab2_Cb_DontServeRequests=\u0644\u0627 \u062A\u062C\u064A\u0628 \u0639\u0644\u0649 \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0627\u062A +tab2_Lbl_DontServeRequestsDesc=\u0627\u0630\u0627 \u062A\u0645 \u0627\u0644\u0627\u062E\u062A\u064A\u0627\u0631 , \u0641\u0625\u0646 \u0647\u0630\u0627 \u0627\u0644\u062D\u0627\u0633\u0628 \u0644\u0646 \u064A\u0633\u062A\u062C\u064A\u0628 \u0644\u0637\u0644\u0628\u0627\u062A \u0627\u0644\u0646\u064A\u0646\u062A\u0627\u0646\u062F\u0648 \u0633\u0648\u064A\u062A\u0634 \u0628\u0634\u0623\u0646 \u0645\u0644\u0641\u0627\u062A \u0627\u0644\u0640 "\u0625\u0646 \u0625\u0633 \u0628\u064A" (\u0639\u0628\u0631 \u0627\u0644\u0627\u0646\u062A\u0631\u0646\u062A) \u0648\u064A\u0633\u062A\u062E\u062F\u0645 \u0625\u0639\u062F\u0627\u062F\u062A \u0645\u062D\u062F\u062F\u0629 \u0644\u064A\u062E\u0628\u0631 \u0628\u0631\u0646\u0627\u0645\u062C "\u062A\u064A\u0646\u0641\u0648\u064A\u0644" \u0623\u064A\u0646 \u064A\u0646\u0628\u063A\u064A \u0627\u0644\u0628\u062D\u062B \u0639\u0646 \u0627\u0644\u0645\u0644\u0641\u0627\u062A. +tab2_Lbl_HostExtra=\u0625\u0636\u0627\u0641\u0627\u062A +windowTitleErrorPort=\u0625\u0639\u062F\u0627\u062F \u062E\u0627\u0637\u0626 \u0644\u0644\u0645\u0646\u0641\u0630 +windowBodyErrorPort=\u0627\u0644\u0645\u0646\u0641\u0630 \u0644\u0627 \u064A\u0645\u0643\u0646 \u0623\u0646 \u064A\u0643\u0648\u0646 0 \u0623\u0648 \u0623\u0643\u0628\u0631 \u0645\u0646 65535. +tab2_Cb_AutoCheckForUpdates=\u0628\u062D\u062B \u062A\u0644\u0642\u0627\u0626\u064A \u0644\u0644\u062A\u062D\u062F\u064A\u062B\u0627\u062A +windowTitleNewVersionAval=\u064A\u0648\u062C\u062F \u0625\u0635\u062F\u0627\u0631 \u062C\u062F\u064A\u062F \u0644\u0644\u0628\u0631\u0646\u0627\u0645\u062C +windowTitleNewVersionNOTAval=\u0644\u0627 \u064A\u0648\u062C\u062F \u0625\u0635\u062F\u0627\u0631 \u062C\u062F\u064A\u062F +windowTitleNewVersionUnknown=\u0644\u0627 \u064A\u0645\u0643\u0646 \u0627\u0644\u0628\u062D\u062B \u0639\u0646 \u0627\u0644\u062A\u062D\u062F\u064A\u062B\u0627\u062A +windowBodyNewVersionUnknown=\u064A\u0648\u062C\u062F \u062E\u0637\u0623 \u0645\u0627\n\u0631\u0628\u0645\u0627 \u0627\u0646\u0642\u0637\u0639 \u0627\u0644\u0625\u062A\u0635\u0627\u0644 \u0628\u0627\u0644\u0625\u0646\u062A\u0631\u0646\u062A , \u0623\u0648 \u0623\u0646 \u0627\u0644\u0645\u0648\u0642\u0639 \u063A\u064A\u0631 \u0645\u062A\u0627\u062D \u0627\u0644\u0622\u0646 +windowBodyNewVersionNOTAval=\u0644\u062F\u064A\u0643 \u0623\u062D\u062F\u062B \u0625\u0635\u062F\u0627\u0631 +tab2_Cb_AllowXciNszXcz=\u0627\u0644\u0633\u0645\u0627\u062D \u0644\u0628\u0631\u0646\u0627\u0645\u062C "\u062A\u064A\u0646\u0641\u0648\u064A\u0644" \u0628\u0625\u062E\u062A\u064A\u0627\u0631 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0645\u0646 \u0627\u0644\u0646\u0648\u0639 "\u0625\u0643\u0633 \u0633\u064A \u0622\u064A" \u0623\u0648 "\u0625\u0646 \u0625\u0633 \u0632\u062F" \u0623\u0648 "\u0625\u0643\u0633 \u0633\u064A \u0632\u062F" +tab2_Lbl_AllowXciNszXczDesc=\u0645\u0633\u062A\u062E\u062F\u0645 \u0628\u0627\u0644\u0628\u0631\u0627\u0645\u062C \u0627\u0644\u062A\u064A \u062A\u062F\u0639\u0645 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0645\u0646 \u0627\u0644\u0646\u0648\u0639 "\u0625\u0643\u0633 \u0633\u064A \u0622\u064A" \u0623\u0648 "\u0625\u0646 \u0625\u0633 \u0632\u062F" \u0623\u0648 "\u0625\u0643\u0633 \u0633\u064A \u0632\u062F" \u0648\u062A\u0633\u062A\u062E\u062F\u0645 \u0628\u0631\u0648\u062A\u0648\u0643\u0648\u0644 \u0627\u0644\u0646\u0642\u0644 \u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644 "\u062A\u064A\u0646\u0641\u0648\u064A\u0644". \u0644\u0627 \u062A\u0639\u062F\u0644 \u0625\u0630\u0627 \u0644\u0645 \u062A\u0643\u0646 \u0645\u062A\u0623\u0643\u062F . \u0648\u0641\u0639\u0644\u0647 \u0625\u0630\u0627 \u0643\u0646\u062A \u062A\u0633\u062A\u062E\u062F\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u062A\u0646\u0635\u064A\u0628 "\u0623\u0648\u0648\u0648". +tab2_Lbl_Language=\u0627\u0644\u0644\u063A\u0629 +windowBodyRestartToApplyLang=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0623\u0639\u062F \u062A\u0634\u0641\u064A\u0644 \u0627\u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0644\u062A\u0637\u0628\u064A\u0642 \u0627\u0644\u062A\u0639\u062F\u064A\u0644\u0627\u062A. +btn_OpenSplitFile=\u0627\u062E\u062A\u0631 \u062A\u0642\u0633\u064A\u0645 \u0645\u0644\u0641 \u0627\u0644 "\u0625\u0646 \u0625\u0633 \u0628\u064A" +tab2_Lbl_ApplicationSettings=\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629 +tabSplMrg_Lbl_SplitNMergeTitle=\u0623\u062F\u0627\u0629 \u062A\u0642\u0633\u064A\u0645 \u0648\u062F\u0645\u062C \u0627\u0644\u0645\u0644\u0641\u0627\u062A +tabSplMrg_RadioBtn_Split=\u062A\u0642\u0633\u064A\u0645 +tabSplMrg_RadioBtn_Merge=\u062F\u0645\u062C +tabSplMrg_Txt_File=\u0645\u0644\u0641: +tabSplMrg_Txt_Folder=\u062A\u0642\u0633\u064A\u0645 \u0645\u0644\u0641 (\u0627\u0644\u0645\u062C\u0644\u062F): +tabSplMrg_Btn_SelectFile=\u0627\u062E\u062A\u0631 \u0645\u0644\u0641 +tabSplMrg_Btn_SelectFolder=\u0627\u062E\u062A\u0631 \u0645\u062C\u0644\u062F +tabSplMrg_Lbl_SaveToLocation=\u062D\u0641\u0638 \u0625\u0644\u0649: +tabSplMrg_Btn_ChangeSaveToLocation=\u062A\u0639\u062F\u064A\u0644 +tabSplMrg_Btn_Convert=\u062A\u062D\u0648\u064A\u0644 +windowTitleError=\u062E\u0637\u0623 +windowBodyPleaseFinishTransfersFirst=\u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u0642\u0633\u064A\u0645 \u0623\u0648 \u062F\u0645\u062C \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0623\u062B\u0646\u0627\u0621 \u0642\u064A\u0627\u0645 \u0627\u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0628\u0639\u0645\u0644\u064A\u0627\u062A \u0623\u062E\u0631\u0649 \u0645\u0646 \u062E\u0644\u0627\u0644 \u0627\u0644 "\u064A\u0648 \u0625\u0633 \u0628\u064A" \u0623\u0648 \u0627\u0644\u0634\u0628\u0643\u0629. \u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0623\u064A \u0639\u0645\u0644\u064A\u0629 \u0623\u062E\u0631\u0649 \u0623\u0648\u0644\u0627. +done_txt=\u062A\u0645! +failure_txt=\u0641\u0634\u0644 +btn_Select=\u0627\u062E\u062A\u0631 +btn_InjectPayloader=\u0627\u062F\u062E\u0644 \u0645\u0644\u0641 \u0627\u0644\u0640 "\u0628\u0627\u064A \u0644\u0648\u062F" +tabNXDT_Btn_Start=\u0627\u0628\u062F\u0623! +tab2_Btn_InstallDrivers=\u062D\u0645\u0644 \u0648\u0646\u0635\u0628 \u0627\u0644\u0640 "\u062F\u0631\u0627\u064A\u0641\u0631\u0632" +windowTitleDownloadDrivers=\u062D\u0645\u0644 \u0648\u0646\u0635\u0628 \u0627\u0644\u0640 "\u062F\u0631\u0627\u064A\u0641\u0631\u0632" +windowBodyDownloadDrivers=\u062C\u0627\u0631\u064A \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0640 "\u062F\u0631\u0627\u064A\u0641\u0631\u0632" (libusbK v3.0.7.0)... +btn_Cancel=\u0625\u0644\u063A\u0627\u0621 +btn_Close=\u0625\u063A\u0644\u0627\u0642 +tab2_Cb_GlVersion=\u0625\u0635\u062F\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641" +tab2_Cb_GLshowNspOnly=\u0627\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0625\u0645\u062A\u062F\u0627\u062F "\u0625\u0646 \u0625\u0633 \u0628\u064A" \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641". +windowBodyPleaseStopOtherProcessFirst=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649 \u0642\u0628\u0644 \u0627\u0644\u0625\u0633\u062A\u0645\u0631\u0627\u0631. From 9af20a98ccb427a3dcfacdb0d40aab4688b33d03 Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Tue, 24 Nov 2020 17:53:29 +0900 Subject: [PATCH 019/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 80 +++++++++++++++------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 258dab0..789b0f3 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -1,43 +1,71 @@ -btn_OpenFile=.NSP \uD30C\uC77C \uC120\uD0DD +btn_OpenFile=\uD30C\uC77C \uC120\uD0DD btn_Upload=NS\uC5D0 \uC5C5\uB85C\uB4DC tab3_Txt_EnteredAsMsg1=\uB2E4\uC74C\uACFC \uAC19\uC774 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4: -tab3_Txt_EnteredAsMsg2=\uC774 \uBB38\uC81C\uAC00 \uBC1C\uC0DD\uD558\uC9C0 \uC54A\uB3C4\uB85D\uD558\uB824\uBA74 root\uC774\uAC70\uB098 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD55C 'udev' \uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. +tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD574 \uB8E8\uD2B8\uC774\uAC70\uB098 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. tab3_Txt_FilesToUploadTitle=\uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C: -tab3_Txt_GreetingsMessage=NS-USBloader\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4. -tab3_Txt_NoFolderOrFileSelected=\uC120\uD0DD\uD55C \uD30C\uC77C \uC5C6\uC74C: \uC5C5\uB85C\uB4DC \uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. -windowBodyConfirmExit=\uB370\uC774\uD130 \uC804\uC1A1\uC774 \uC9C4\uD589 \uC911\uC774\uACE0 \uC774 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2EB\uC73C\uBA74 \uC911\uB2E8\uB429\uB2C8\uB2E4.\n\uC9C0\uAE08 \uD560 \uC218 \uC788\uB294 \uAC00\uC7A5 \uC548 \uC88B\uC740 \uC77C\uC785\uB2C8\uB2E4.\n\uD504\uB85C\uC138\uC2A4\uB97C \uC911\uB2E8\uD558\uACE0 \uC885\uB8CC \uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? -windowTitleConfirmExit=\uC544\uB2C8\uC624, \uC774\uAC83\uC744 \uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624! -btn_Stop=\uC778\uD130\uB7FD\uD2B8 +tab3_Txt_GreetingsMessage=NS-USBloader\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4 +tab3_Txt_NoFolderOrFileSelected=\uC120\uD0DD\uD55C \uD30C\uC77C \uC5C6\uC74C: \uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. +windowBodyConfirmExit=\uB370\uC774\uD130 \uC804\uC1A1\uC774 \uC9C4\uD589 \uC911\uC774\uBA70 \uC774 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2EB\uC73C\uBA74 \uC911\uB2E8\uB429\uB2C8\uB2E4.\n\uC9C0\uAE08 \uD560 \uC218 \uC788\uB294 \uAC83\uC740 \uB354 \uB098\uC05C \uC77C\uC785\uB2C8\uB2E4.\n\uD504\uB85C\uC138\uC2A4\uB97C \uC911\uB2E8\uD558\uACE0 \uC885\uB8CC\uD558\uACA0\uC2B5\uB2C8\uAE4C? +windowTitleConfirmExit=\uC544\uB2C8\uC624, \uC774\uAC83\uC740 \uD558\uC9C0\uB9C8\uC138\uC694! +btn_Stop=\uC911\uB2E8 tab3_Txt_GreetingsMessage2=--\n\ -\uC18C\uC2A4: https://github.com/developersu/ns-usbloader/\n\ -\uC0AC\uC774\uD2B8: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://github.com/developersu/ns-usbloader/\n\ +Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=\uC0C1\uD0DC tab1_table_Lbl_FileName=\uD30C\uC77C \uC774\uB984 tab1_table_Lbl_Size=\uD06C\uAE30 -tab1_table_Lbl_Upload=\uC5C5\uB85C\uB4DC \uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? +tab1_table_Lbl_Upload=\uC5C5\uB85C\uB4DC\uD569\uB2C8\uAE4C? tab1_table_contextMenu_Btn_BtnDelete=\uC0AD\uC81C tab1_table_contextMenu_Btn_DeleteAll=\uBAA8\uB450 \uC0AD\uC81C tab2_Lbl_HostIP=\uD638\uC2A4\uD2B8 IP tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=\uD56D\uC0C1 NS IP \uC785\uB825\uC758 \uC720\uD6A8\uC131\uC744 \uD655\uC778\uD558\uC2ED\uC2DC\uC624. -windowBodyBadIp=NS IP \uC8FC\uC18C\uB97C \uC62C\uBC14\uB974\uAC8C \uC785\uB825 \uD588\uC2B5\uB2C8\uAE4C? -windowTitleBadIp=NS\uC758 IP \uC8FC\uC18C\uAC00 \uC798\uBABB\uB418\uC5C8\uC744 \uAC00\uB2A5\uC131\uC774 \uD07D\uB2C8\uB2E4. -tab2_Cb_ExpertMode=\uC0C1\uAE09\uC790 \uBAA8\uB4DC +tab2_Cb_ValidateNSHostName=\uD56D\uC0C1 NS IP \uC785\uB825\uC744 \uD655\uC778\uD558\uC138\uC694. +windowBodyBadIp=NS IP \uC8FC\uC18C\uB97C \uC815\uD655\uD788 \uC785\uB825 \uD588\uC2B5\uB2C8\uAE4C? +windowTitleBadIp=NS\uC758 IP \uC8FC\uC18C\uB294 \uB300\uBD80\uBD84 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +tab2_Cb_ExpertMode=\uC804\uBB38\uAC00 \uBAA8\uB4DC (NET \uC124\uC815) tab2_Lbl_HostPort=\uD3EC\uD2B8 -tab2_Cb_AutoDetectIp=\uC790\uB3D9 \uAC10\uC9C0 IP -tab2_Cb_RandSelectPort=\uC784\uC758\uB85C \uD3EC\uD2B8 \uAC00\uC838 \uC624\uAE30 -tab2_Cb_DontServeRequests=\uC694\uCCAD\uC744 \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC74C -tab2_Lbl_DontServeRequestsDesc=\uC774 \uCEF4\uD4E8\uD130\uB97C \uC120\uD0DD\uD558\uBA74 NS\uC5D0\uC11C \uC624\uB294 NSP \uD30C\uC77C \uC694\uCCAD\uC5D0 \uD68C\uC2E0\uD558\uC9C0 \uC54A\uC73C\uBA70 \uC815\uC758\uB41C \uD638\uC2A4\uD2B8 \uC124\uC815\uC744 \uC0AC\uC6A9\uD558\uC5EC TinFoil\uC5D0\uAC8C \uD30C\uC77C\uC744 \uC5B4\uB514\uC5D0\uC11C \uCC3E\uC544\uC57C \uD558\uB294\uC9C0 \uC54C\uB824\uC90D\uB2C8\uB2E4. +tab2_Cb_AutoDetectIp=IP \uC790\uB3D9\uAC10\uC9C0 +tab2_Cb_RandSelectPort=\uBB34\uC791\uC704\uB85C \uD3EC\uD2B8 \uAC00\uC838\uC624\uAE30 +tab2_Cb_DontServeRequests=\uC694\uCCAD\uC744 \uCC98\uB9AC\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624 +tab2_Lbl_DontServeRequestsDesc=\uC774 \uC635\uC158\uC744 \uC120\uD0DD\uD558\uBA74\uC774 \uCEF4\uD4E8\uD130\uB294 NS (\uB124\uD2B8\uC6CC\uD06C\uB97C \uD1B5\uD574)\uC5D0\uC11C \uC624\uB294 NSP \uD30C\uC77C \uC694\uCCAD\uC5D0 \uC751\uB2F5\uD558\uC9C0 \uC54A\uACE0 \uC815\uC758\uB41C \uD638\uC2A4\uD2B8 \uC124\uC815\uC744 \uC0AC\uC6A9\uD558\uC5EC TinFoil\uC5D0 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC704\uCE58\uB97C \uC54C\uB824\uC90D\uB2C8\uB2E4. tab2_Lbl_HostExtra=\uCD94\uAC00 windowTitleErrorPort=\uD3EC\uD2B8\uAC00 \uC798\uBABB \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4! -windowBodyErrorPort=\uD3EC\uD2B8\uB294 0 \uB610\uB294 65535\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +windowBodyErrorPort=\uD3EC\uD2B8\uB294 0\uC774\uAC70\uB098 65535\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. tab2_Cb_AutoCheckForUpdates=\uC5C5\uB370\uC774\uD2B8 \uC790\uB3D9 \uD655\uC778 windowTitleNewVersionAval=\uC0C8\uB85C\uC6B4 \uBC84\uC804 \uC0AC\uC6A9 \uAC00\uB2A5 -windowTitleNewVersionNOTAval=\uC0C8\uB85C\uC6B4 \uBC84\uC804\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. -windowTitleNewVersionUnknown=\uC0C8 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. -windowBodyNewVersionUnknown=\uBB38\uC81C\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.\n\uC778\uD130\uB137\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uAC70\uB098 GitHub\uC774 \uB2E4\uC6B4\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. -windowBodyNewVersionNOTAval=\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4. -tab2_Cb_AllowXciNszXcz=TinFoil\uC5D0 \uB300\uD55C XCI / NSZ / XCZ \uD30C\uC77C \uC120\uD0DD \uD5C8\uC6A9 -tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ\uB97C \uC9C0\uC6D0\uD558\uACE0 TinFoil \uC804\uC1A1 \uD504\uB85C\uD1A0\uCF5C\uC744 \uC0AC\uC6A9\uD558\uB294 \uC77C\uBD80 \uD0C0\uC0AC \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C \uC0AC\uC6A9\uB429\uB2C8\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC73C\uBA74 \uBCC0\uACBD\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624. -tab2_Lbl_Language=\uC5B8\uC5B4 \ No newline at end of file +windowTitleNewVersionNOTAval=\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804 \uC5C6\uC74C +windowTitleNewVersionUnknown=\uC0C8\uB85C\uC6B4 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +windowBodyNewVersionUnknown=\uBB38\uC81C\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.\n\uC778\uD130\uB137\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uAC70\uB098 GitHub\uAC00 \uB2E4\uC6B4\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. +windowBodyNewVersionNOTAval=\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9\uD558\uACE0 \uC788\uC2B5\uB2C8\uB2E4. +tab2_Cb_AllowXciNszXcz=Tinfoil \uC6A9 XCI / NSZ / XCZ \uD30C\uC77C \uC120\uD0DD \uD5C8\uC6A9 +tab2_Lbl_AllowXciNszXczDesc=XCI / NSZ / XCZ\uB97C \uC9C0\uC6D0\uD558\uACE0 Tinfoil \uC804\uC1A1 \uD504\uB85C\uD1A0\uCF5C\uC744 \uD65C\uC6A9\uD558\uB294 \uC560\uD50C\uB9AC\uCF00\uC774\uC158\uC5D0\uC11C \uC0AC\uC6A9\uB429\uB2C8\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC740 \uACBD\uC6B0 \uBCC0\uACBD\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624. Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uC5D0 \uB300\uD574 \uD65C\uC131\uD654\uD569\uB2C8\uB2E4. +tab2_Lbl_Language=\uC5B8\uC5B4 +windowBodyRestartToApplyLang=\uBCC0\uACBD \uC0AC\uD56D\uC744 \uC801\uC6A9\uD558\uB824\uBA74 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC2ED\uC2DC\uC624. +btn_OpenSplitFile=\uBD84\uD560 NSP \uC120\uD0DD +tab2_Lbl_ApplicationSettings=\uC8FC\uC694 \uC124\uC815 +tabSplMrg_Lbl_SplitNMergeTitle=\uD30C\uC77C \uBD84\uD560 & \uBCD1\uD569 \uB3C4\uAD6C +tabSplMrg_RadioBtn_Split=\uBD84\uD560 +tabSplMrg_RadioBtn_Merge=\uBCD1\uD569 +tabSplMrg_Txt_File=\uD30C\uC77C: +tabSplMrg_Txt_Folder=\uD30C\uC77C \uBD84\uD560 (\uD3F4\uB354): +tabSplMrg_Btn_SelectFile=\uD30C\uC77C \uC120\uD0DD +tabSplMrg_Btn_SelectFolder=\uD3F4\uB354 \uC120\uD0DD +tabSplMrg_Lbl_SaveToLocation=\uC800\uC7A5: +tabSplMrg_Btn_ChangeSaveToLocation=\uBCC0\uACBD +tabSplMrg_Btn_Convert=\uBCC0\uD658 +windowTitleError=\uC624\uB958 +windowBodyPleaseFinishTransfersFirst=\uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8 USB/\uB124\uD2B8\uC6CC\uD06C \uD504\uB85C\uC138\uC2A4\uAC00 \uD65C\uC131\uD654\uB418\uBA74 \uD30C\uC77C\uC744 \uBD84\uD560/\uBCD1\uD569 \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uC804\uC1A1 \uD65C\uC131\uD654\uB97C \uC911\uB2E8\uD558\uC2ED\uC2DC\uC624. +done_txt=\uC644\uB8CC! +failure_txt=\uC2E4\uD328 +btn_Select=\uC120\uD0DD +btn_InjectPayloader=\uD398\uC774\uB85C\uB4DC \uC8FC\uC785 +tabNXDT_Btn_Start=\uC2DC\uC791! +tab2_Btn_InstallDrivers=\uB4DC\uB77C\uC774\uBC84 \uB2E4\uC6B4\uB85C\uB4DC \uBC0F \uC124\uCE58 +windowTitleDownloadDrivers=\uB4DC\uB77C\uC774\uBC84 \uB2E4\uC6B4\uB85C\uB4DC \uBC0F \uC124\uCE58 +windowBodyDownloadDrivers=\uB4DC\uB77C\uC774\uBC84 \uB2E4\uC6B4\uB85C\uB4DC (libusbK v3.0.7.0)... +btn_Cancel=\uCDE8\uC18C +btn_Close=\uB2EB\uAE30 +tab2_Cb_GlVersion=GoldLeaf \uBC84\uC804 +tab2_Cb_GLshowNspOnly=GoldLeaf\uC5D0\uC11C *.nsp \uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4. +windowBodyPleaseStopOtherProcessFirst=\uACC4\uC18D\uD558\uAE30 \uC804\uC5D0 \uB2E4\uB978 \uD65C\uC131 \uD504\uB85C\uC138\uC2A4\uB97C \uC911\uC9C0\uD558\uC2ED\uC2DC\uC624. From b2202cf483cc0db5c39353ad8a21305b3216a40a Mon Sep 17 00:00:00 2001 From: Wolf Posdorfer <wolfposd@gmail.com> Date: Tue, 24 Nov 2020 19:12:19 +0100 Subject: [PATCH 020/134] Reverting FileChooser and adding DirChooser - old filechooser - directory chooser can choose any directory and recursively adds all *.xyz files - add button template --- .../Controllers/GamesController.java | 126 ++++++++++-------- src/main/resources/GamesTab.fxml | 5 + src/main/resources/locale.properties | 1 + src/main/resources/locale_de_DE.properties | 2 + src/main/resources/locale_en_US.properties | 1 + 5 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 4146b20..9c89719 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -28,6 +28,7 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; import nsusbloader.AppPreferences; import nsusbloader.com.net.NETCommunications; import nsusbloader.com.usb.UsbCommunications; @@ -40,16 +41,10 @@ import nsusbloader.ServiceWindow; import java.io.File; import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.ResourceBundle; -import javax.swing.JFileChooser; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.filechooser.FileFilter; - public class GamesController implements Initializable { private static final String REGEX_ONLY_NSP = ".*\\.nsp$"; @@ -70,7 +65,7 @@ public class GamesController implements Initializable { public NSTableViewController tableFilesListController; // Accessible from Mediator (for drag-n-drop support) @FXML - private Button selectNspBtn, selectSplitNspBtn, uploadStopBtn; + private Button selectNspBtn, selectSplitNspBtn, selectFolderBtn, uploadStopBtn; private String previouslyOpenedPath; private Region btnUpStopImage; private ResourceBundle resourceBundle; @@ -140,16 +135,17 @@ public class GamesController implements Initializable { switchThemeBtn.setGraphic(btnSwitchImage); this.switchThemeBtn.setOnAction(e->switchTheme()); - - uploadStopBtn.setDisable(getSelectedProtocol().equals("TinFoil")); selectNspBtn.setOnAction(e-> selectFilesBtnAction()); + selectNspBtn.getStyleClass().add("buttonSelect"); + + selectFolderBtn.setOnAction(e-> selectFoldersBtnAction()); + selectFolderBtn.getStyleClass().add("buttonSelect"); selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction()); selectSplitNspBtn.getStyleClass().add("buttonSelect"); uploadStopBtn.setOnAction(e-> uploadBtnAction()); - - selectNspBtn.getStyleClass().add("buttonSelect"); + uploadStopBtn.setDisable(getSelectedProtocol().equals("TinFoil")); this.btnUpStopImage = new Region(); btnUpStopImage.getStyleClass().add("regionUpload"); @@ -196,69 +192,89 @@ public class GamesController implements Initializable { return nsIpTextField.getText(); } - private boolean isGoldLeaf() { - return getSelectedProtocol().equals("GoldLeaf") - && (!MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL()); + return getSelectedProtocol().equals("GoldLeaf"); } private boolean isTinfoil() { - return getSelectedProtocol().equals("TinFoil") - && MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport(); + return getSelectedProtocol().equals("TinFoil"); } + private boolean isNSPFileFilterForGL() { + return MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL(); + } + + private boolean isXciNszXczSupport() { + return MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport(); + } + + /** + * regex for selected program and selected file filter </br> + * tinfoil + xcinszxcz </br> + * tinfoil + nsponly </br> + * goldleaf </br> + * etc.. + */ private String getRegexForFiles() { - if (isTinfoil()) + if (isTinfoil() && isXciNszXczSupport()) return REGEX_ALLFILES_TINFOIL; - else if (isGoldLeaf()) - return REGEX_ONLY_NSP; else return REGEX_ONLY_NSP; + // currently only tinfoil supports all filetypes + // everything else only supports nsp + // else if (isGoldLeaf()) + // return REGEX_ONLY_NSP; + // else } /** * Functionality for selecting NSP button. - * */ - private void selectFilesBtnAction(){ - final String regex = getRegexForFiles(); - if(!UIManager.getLookAndFeel().isNativeLookAndFeel()) { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { - // :shrug emoji: - // defaults to Metal Look and Feel - } + */ + private void selectFilesBtnAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(resourceBundle.getString("btn_OpenFile")); + + fileChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + + if (isTinfoil() && isXciNszXczSupport()) { + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP/XCI/NSZ/XCZ", "*.nsp", "*.xci", "*.nsz", "*.xcz")); + } else if (isGoldLeaf() && !isNSPFileFilterForGL()) { + fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Any file", "*.*"), + new FileChooser.ExtensionFilter("NSP ROM", "*.nsp")); + } else { + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp")); } - JFileChooser fileChooser = new JFileChooser(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); - fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - fileChooser.setMultiSelectionEnabled(true); - fileChooser.setFileFilter(new FileFilter() { - public String getDescription() { - return "Switch Files"; - } - public boolean accept(File f) { - return f.isDirectory() || f.getName().toLowerCase().matches(regex); - } - }); - - if(fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { - List<File> files = Arrays.asList(fileChooser.getSelectedFiles()); - List<File> allFiles = new ArrayList<>(); - - if (files.size() != 0) { - files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); - files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); - } - - if (allFiles.size() > 0) { - tableFilesListController.setFiles(allFiles); - uploadStopBtn.setDisable(false); - previouslyOpenedPath = allFiles.get(0).getParent(); - } + List<File> filesList = fileChooser.showOpenMultipleDialog(usbNetPane.getScene().getWindow()); + if (filesList != null && !filesList.isEmpty()) { + tableFilesListController.setFiles(filesList); + uploadStopBtn.setDisable(false); + previouslyOpenedPath = filesList.get(0).getParent(); } } + /** + * Functionality for selecting folders button. + * will scan all folders recursively for nsp-files + */ + private void selectFoldersBtnAction() { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle(resourceBundle.getString("btn_OpenFolders")); + chooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + + File startFolder = chooser.showDialog(usbNetPane.getScene().getWindow()); + if (startFolder != null) { + List<File> allFiles = new ArrayList<>(); + collectFiles(allFiles, startFolder, getRegexForFiles()); + + if (!allFiles.isEmpty()) { + tableFilesListController.setFiles(allFiles); + uploadStopBtn.setDisable(false); + previouslyOpenedPath = startFolder.getParent(); + } + } + } + /** * used to recursively walk all directories, every file will be added to the storage list * @param storage used to hold files diff --git a/src/main/resources/GamesTab.fxml b/src/main/resources/GamesTab.fxml index 4df7b16..15a924a 100644 --- a/src/main/resources/GamesTab.fxml +++ b/src/main/resources/GamesTab.fxml @@ -58,6 +58,11 @@ <SVGPath content="M 8,0 C 6.8954305,0 6,0.8954305 6,2 v 16 c 0,1.1 0.89,2 2,2 h 12 c 1.104569,0 2,-0.895431 2,-2 V 2 C 22,0.90484721 21.089844,0 20,0 Z m 2.1,1.2 h 7.8 C 18,1.20208 18,1.2002604 18,1.3 v 0.1 c 0,0.095833 0,0.097917 -0.1,0.1 H 10.1 C 10,1.5057292 10,1.5036458 10,1.4 V 1.3 C 10,1.20026 10,1.1981771 10.1,1.2 Z M 8,2 h 12 c 0.303385,0 0.5,0.2044271 0.5,0.5 v 12 C 20.5,14.789959 20.29836,15 20,15 H 8 C 7.7044271,15 7.5,14.803385 7.5,14.5 V 2.5 C 7.5,2.2083333 7.7122396,2 8,2 Z M 2,4 v 18 c 0,1.104569 0.8954305,2 2,2 H 20 V 22 H 4 V 4 Z m 8,12 h 8 l -4,3 z" fill="#289de8" /> </graphic> </Button> + <Button fx:id="selectFolderBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenFolders"> + <graphic> + <SVGPath content="M 2.4003906,2 C 1.0683906,2 0,3.1125 0,4.5 v 15 c -1.7556433e-8,1.380871 1.0747547,2.500225 2.4003906,2.5 H 21.599609 C 22.925245,22.000225 24,20.880871 24,19.5 V 7 C 24,5.6125 22.919609,4.5 21.599609,4.5 H 12 L 9.5996094,2 Z" fill="#289de8" /> + </graphic> + </Button> <Button fx:id="selectSplitNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenSplitFile"> <graphic> <SVGPath content="M 2.4003906 2 C 1.0683906 2 0 3.1125 0 4.5 L 0 19.5 A 2.4 2.5 0 0 0 2.4003906 22 L 21.599609 22 A 2.4 2.5 0 0 0 24 19.5 L 24 7 C 24 5.6125 22.919609 4.5 21.599609 4.5 L 12 4.5 L 9.5996094 2 L 2.4003906 2 z M 13.193359 10.962891 C 14.113498 10.962891 14.814236 11.348741 15.296875 12.123047 C 15.779514 12.89388 16.021484 13.935113 16.021484 15.244141 C 16.021484 16.556641 15.779514 17.598741 15.296875 18.373047 C 14.814236 19.14388 14.113498 19.529297 13.193359 19.529297 C 12.276693 19.529297 11.575955 19.14388 11.089844 18.373047 C 10.607205 17.598741 10.365234 16.556641 10.365234 15.244141 C 10.365234 13.935113 10.607205 12.89388 11.089844 12.123047 C 11.575955 11.348741 12.276693 10.962891 13.193359 10.962891 z M 19.589844 10.962891 C 20.509983 10.962891 21.21072 11.348741 21.693359 12.123047 C 22.175998 12.89388 22.417969 13.935113 22.417969 15.244141 C 22.417969 16.556641 22.175998 17.598741 21.693359 18.373047 C 21.21072 19.14388 20.509983 19.529297 19.589844 19.529297 C 18.673177 19.529297 17.970486 19.14388 17.484375 18.373047 C 17.001736 17.598741 16.761719 16.556641 16.761719 15.244141 C 16.761719 13.935113 17.001736 12.89388 17.484375 12.123047 C 17.970486 11.348741 18.673177 10.962891 19.589844 10.962891 z M 13.193359 11.769531 C 12.613498 11.769531 12.173177 12.092448 11.871094 12.738281 C 11.56901 13.380642 11.417969 14.195964 11.417969 15.185547 C 11.417969 15.411241 11.423611 15.655599 11.4375 15.916016 C 11.451389 16.176432 11.511068 16.528212 11.615234 16.972656 L 14.412109 12.591797 C 14.235026 12.26888 14.042318 12.052517 13.833984 11.941406 C 13.629123 11.826823 13.415582 11.769531 13.193359 11.769531 z M 19.589844 11.769531 C 19.009983 11.769531 18.567708 12.092448 18.265625 12.738281 C 17.963542 13.380642 17.8125 14.195964 17.8125 15.185547 C 17.8125 15.411241 17.820095 15.655599 17.833984 15.916016 C 17.847873 16.176432 17.907552 16.528212 18.011719 16.972656 L 20.808594 12.591797 C 20.63151 12.26888 20.438802 12.052517 20.230469 11.941406 C 20.025608 11.826823 19.812066 11.769531 19.589844 11.769531 z M 14.761719 13.556641 L 11.984375 17.962891 C 12.133681 18.216363 12.305556 18.406684 12.5 18.535156 C 12.694444 18.660156 12.91276 18.722656 13.152344 18.722656 C 13.812066 18.722656 14.280816 18.355252 14.558594 17.619141 C 14.836372 16.879557 14.974609 16.059462 14.974609 15.160156 C 14.974609 14.604601 14.90408 14.07053 14.761719 13.556641 z M 21.15625 13.556641 L 18.380859 17.962891 C 18.530165 18.216363 18.70204 18.406684 18.896484 18.535156 C 19.090929 18.660156 19.307292 18.722656 19.546875 18.722656 C 20.206597 18.722656 20.675347 18.355252 20.953125 17.619141 C 21.230903 16.879557 21.371094 16.059462 21.371094 15.160156 C 21.371094 14.604601 21.298611 14.07053 21.15625 13.556641 z" fill="#289de8" /> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index d1fd9ea..2cbc57d 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -1,4 +1,5 @@ btn_OpenFile=Select files +btn_OpenFolders=Select folder btn_Upload=Upload to NS tab3_Txt_EnteredAsMsg1=You have been entered as: tab3_Txt_EnteredAsMsg2=You should be root or have configured 'udev' rules for this user to avoid any issues. diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index b82464a..e18703f 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -1,4 +1,5 @@ btn_OpenFile=.NSP Dateien ausw\u00E4hlen +btn_OpenFolders=Ordner ausw�hlen btn_Upload=Hochladen zu NS tab3_Txt_EnteredAsMsg1=Du wurdest eingelassen als: tab3_Txt_EnteredAsMsg2=Du brauchst root oder konfigurierte 'udev'-Regeln um Probleme zu vermeiden. @@ -42,6 +43,7 @@ tab2_Cb_AllowXciNszXcz=Erlaube XCI- NSZ- XCZ-Dateien-Verwendung f\u00FCr Tinfoil tab2_Lbl_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt Tinfoil Transfer Protocol. Nicht \u00E4ndern, wenn unsicher. tab2_Lbl_Language=Sprache windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen. +btn_OpenSplitFile=Split-NSP ausw�hlen tab2_Cb_GLshowNspOnly=Nur *.nsp in GoldLeaf zeigen. btn_Cancel=Abbrechen diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index d1fd9ea..2cbc57d 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -1,4 +1,5 @@ btn_OpenFile=Select files +btn_OpenFolders=Select folder btn_Upload=Upload to NS tab3_Txt_EnteredAsMsg1=You have been entered as: tab3_Txt_EnteredAsMsg2=You should be root or have configured 'udev' rules for this user to avoid any issues. From 2936d65a31974b2b776fbdc4984648af31d18547 Mon Sep 17 00:00:00 2001 From: Wolf Posdorfer <wolfposd@gmail.com> Date: Tue, 24 Nov 2020 19:54:06 +0100 Subject: [PATCH 021/134] looking through the whole hdd takes time, peform on background, update list on ui-thread --- .../Controllers/GamesController.java | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 9c89719..051fb85 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -18,6 +18,7 @@ */ package nsusbloader.Controllers; +import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -44,6 +45,8 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ResourceBundle; +import java.util.function.Consumer; +import java.util.function.Supplier; public class GamesController implements Initializable { @@ -140,6 +143,7 @@ public class GamesController implements Initializable { selectFolderBtn.setOnAction(e-> selectFoldersBtnAction()); selectFolderBtn.getStyleClass().add("buttonSelect"); + selectFolderBtn.setTooltip(new Tooltip("this is my tooltip")); selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction()); selectSplitNspBtn.getStyleClass().add("buttonSelect"); @@ -263,18 +267,20 @@ public class GamesController implements Initializable { chooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); File startFolder = chooser.showDialog(usbNetPane.getScene().getWindow()); - if (startFolder != null) { - List<File> allFiles = new ArrayList<>(); + + performInBackgroundAndUpdate(() -> { + final List<File> allFiles = new ArrayList<>(); collectFiles(allFiles, startFolder, getRegexForFiles()); - - if (!allFiles.isEmpty()) { - tableFilesListController.setFiles(allFiles); + return allFiles; + }, (files) -> { + if (!files.isEmpty()) { + tableFilesListController.setFiles(files); uploadStopBtn.setDisable(false); previouslyOpenedPath = startFolder.getParent(); } - } + }); } - + /** * used to recursively walk all directories, every file will be added to the storage list * @param storage used to hold files @@ -284,13 +290,14 @@ public class GamesController implements Initializable { private void collectFiles(List<File> storage, File startFolder, final String regex) { if (startFolder.isDirectory()) { File[] files = startFolder.listFiles(); - for (File f : files) { - if (f.isDirectory()) { - collectFiles(storage, f, regex); - } else if (f.getName().toLowerCase().matches(regex)) { - storage.add(f); + if(files != null) + for (File f : files) { + if (f.isDirectory()) { + collectFiles(storage, f, regex); + } else if (f.getName().toLowerCase().matches(regex)) { + storage.add(f); + } } - } } } @@ -406,23 +413,27 @@ public class GamesController implements Initializable { * Drag-n-drop support (drop consumer) * */ @FXML - private void handleDrop(DragEvent event){ + private void handleDrop(DragEvent event) { final String regex = getRegexForFiles(); - + List<File> files = event.getDragboard().getFiles(); - List<File> allFiles = new ArrayList<>(); - if (files.size() != 0) { - files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); - files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); - } - - if ( ! allFiles.isEmpty() ) - tableFilesListController.setFiles(allFiles); + performInBackgroundAndUpdate(() -> { + List<File> allFiles = new ArrayList<>(); + if (files != null && files.size() != 0) { + files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); + files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); + } + return allFiles; + }, allFiles -> { + if (!allFiles.isEmpty()) + tableFilesListController.setFiles(allFiles); - event.setDropCompleted(true); - event.consume(); + event.setDropCompleted(true); + event.consume(); + }); } + /** * This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission" * Called from mediator @@ -464,6 +475,21 @@ public class GamesController implements Initializable { else uploadStopBtn.setDisable(false); } + + /** + * Utility function to perform a task in the background and pass the results to a task on the javafx-ui-thread + * @param background performed in background + * @param update performed with results on ui-thread + */ + private <T> void performInBackgroundAndUpdate(Supplier<T> background, Consumer<T> update) { + new Thread(() -> { + final T result = background.get(); + Platform.runLater(() -> { + update.accept(result); + }); + }).start(); + } + /** * Get 'Recent' path */ From 112a0f80e0fca29a6d20880875607844c5c9de15 Mon Sep 17 00:00:00 2001 From: Wolf Posdorfer <wolfposd@gmail.com> Date: Tue, 24 Nov 2020 20:05:18 +0100 Subject: [PATCH 022/134] tooltips for a great user experience =) --- src/main/java/nsusbloader/Controllers/GamesController.java | 2 +- src/main/resources/locale.properties | 1 + src/main/resources/locale_de_DE.properties | 3 ++- src/main/resources/locale_en_US.properties | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 051fb85..07ea869 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -143,7 +143,7 @@ public class GamesController implements Initializable { selectFolderBtn.setOnAction(e-> selectFoldersBtnAction()); selectFolderBtn.getStyleClass().add("buttonSelect"); - selectFolderBtn.setTooltip(new Tooltip("this is my tooltip")); + selectFolderBtn.setTooltip(new Tooltip(resourceBundle.getString("btn_OpenFolders_tooltip"))); selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction()); selectSplitNspBtn.getStyleClass().add("buttonSelect"); diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index 2cbc57d..53e2684 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -1,6 +1,7 @@ btn_OpenFile=Select files btn_OpenFolders=Select folder btn_Upload=Upload to NS +btn_OpenFolders_tooltip=Select a folder to be scanned.\nThis folder and all of its subfolders will be scanned.\nAll matching files will be added to the list. tab3_Txt_EnteredAsMsg1=You have been entered as: tab3_Txt_EnteredAsMsg2=You should be root or have configured 'udev' rules for this user to avoid any issues. tab3_Txt_FilesToUploadTitle=Files to upload: diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index e18703f..0e92adf 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -1,6 +1,7 @@ btn_OpenFile=.NSP Dateien ausw\u00E4hlen -btn_OpenFolders=Ordner ausw�hlen +btn_OpenFolders=Ordner ausw\u00E4hlen btn_Upload=Hochladen zu NS +btn_OpenFolders_tooltip=W\u00E4hle einen Ordner aus.\nDieser Ordner und alle seine Unterordner werden durchsucht.\nAlle passenden Dateien werden dann zur Liste hinzugef\u00FCgt. tab3_Txt_EnteredAsMsg1=Du wurdest eingelassen als: tab3_Txt_EnteredAsMsg2=Du brauchst root oder konfigurierte 'udev'-Regeln um Probleme zu vermeiden. tab3_Txt_FilesToUploadTitle=Dateien zum Hochladen: diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index 2cbc57d..53e2684 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -1,6 +1,7 @@ btn_OpenFile=Select files btn_OpenFolders=Select folder btn_Upload=Upload to NS +btn_OpenFolders_tooltip=Select a folder to be scanned.\nThis folder and all of its subfolders will be scanned.\nAll matching files will be added to the list. tab3_Txt_EnteredAsMsg1=You have been entered as: tab3_Txt_EnteredAsMsg2=You should be root or have configured 'udev' rules for this user to avoid any issues. tab3_Txt_FilesToUploadTitle=Files to upload: From 0a8440ef3499524caf9252d90ebca7e7eecce7bd Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 29 Nov 2020 02:18:35 +0300 Subject: [PATCH 023/134] Minor corrections to wolfposd's implementation --- .../Controllers/GamesController.java | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 07ea869..e193c13 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2020 Dmitry Isaenko, wolfposd This file is part of NS-USBloader. @@ -52,6 +52,7 @@ public class GamesController implements Initializable { private static final String REGEX_ONLY_NSP = ".*\\.nsp$"; private static final String REGEX_ALLFILES_TINFOIL = ".*\\.(nsp$|xci$|nsz$|xcz$)"; + private static final String REGEX_ALLFILES = ".*"; @FXML private AnchorPane usbNetPane; @@ -204,8 +205,8 @@ public class GamesController implements Initializable { return getSelectedProtocol().equals("TinFoil"); } - private boolean isNSPFileFilterForGL() { - return MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL(); + private boolean isAllFiletypesAllowedForGL() { + return ! MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL(); } private boolean isXciNszXczSupport() { @@ -222,13 +223,18 @@ public class GamesController implements Initializable { private String getRegexForFiles() { if (isTinfoil() && isXciNszXczSupport()) return REGEX_ALLFILES_TINFOIL; + else if (isGoldLeaf() && isAllFiletypesAllowedForGL()) + return REGEX_ALLFILES; else return REGEX_ONLY_NSP; - // currently only tinfoil supports all filetypes - // everything else only supports nsp - // else if (isGoldLeaf()) - // return REGEX_ONLY_NSP; - // else + } + private String getRegexForFolders() { + final String regexForFiles = getRegexForFiles(); + + if (regexForFiles.equals(REGEX_ALLFILES)) + return REGEX_ALLFILES_TINFOIL; + else + return regexForFiles; } /** @@ -242,10 +248,12 @@ public class GamesController implements Initializable { if (isTinfoil() && isXciNszXczSupport()) { fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP/XCI/NSZ/XCZ", "*.nsp", "*.xci", "*.nsz", "*.xcz")); - } else if (isGoldLeaf() && !isNSPFileFilterForGL()) { + } + else if (isGoldLeaf() && isAllFiletypesAllowedForGL()) { fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Any file", "*.*"), new FileChooser.ExtensionFilter("NSP ROM", "*.nsp")); - } else { + } + else { fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp")); } @@ -270,7 +278,7 @@ public class GamesController implements Initializable { performInBackgroundAndUpdate(() -> { final List<File> allFiles = new ArrayList<>(); - collectFiles(allFiles, startFolder, getRegexForFiles()); + collectFiles(allFiles, startFolder, getRegexForFiles(), getRegexForFolders()); return allFiles; }, (files) -> { if (!files.isEmpty()) { @@ -285,20 +293,34 @@ public class GamesController implements Initializable { * used to recursively walk all directories, every file will be added to the storage list * @param storage used to hold files * @param startFolder where to start - * @param regex for filenames + * @param filesRegex for filenames */ - private void collectFiles(List<File> storage, File startFolder, final String regex) { - if (startFolder.isDirectory()) { - File[] files = startFolder.listFiles(); - if(files != null) - for (File f : files) { - if (f.isDirectory()) { - collectFiles(storage, f, regex); - } else if (f.getName().toLowerCase().matches(regex)) { - storage.add(f); - } - } + // TODO: Too sophisticated. Should be moved to simple class to keep things simplier + private void collectFiles(List<File> storage, + File startFolder, + final String filesRegex, + final String foldersRegex) + { + final String startFolderNameInLowercase = startFolder.getName().toLowerCase(); + + if (startFolder.isFile()) { + if (startFolderNameInLowercase.matches(filesRegex)) { + storage.add(startFolder); + } + return; } + + if (startFolderNameInLowercase.matches(foldersRegex)) { + storage.add(startFolder); + return; + } + + File[] files = startFolder.listFiles(); + if (files == null) + return; + + for (File file : files) + collectFiles(storage, file, filesRegex, foldersRegex); } /** @@ -414,15 +436,15 @@ public class GamesController implements Initializable { * */ @FXML private void handleDrop(DragEvent event) { - final String regex = getRegexForFiles(); + final String regexForFiles = getRegexForFiles(); + final String regexForFolders = getRegexForFolders(); List<File> files = event.getDragboard().getFiles(); performInBackgroundAndUpdate(() -> { List<File> allFiles = new ArrayList<>(); if (files != null && files.size() != 0) { - files.stream().filter(File::isDirectory).forEach(f -> collectFiles(allFiles, f, regex)); - files.stream().filter(f -> f.getName().toLowerCase().matches(regex)).forEach(allFiles::add); + files.forEach(f -> collectFiles(allFiles, f, regexForFiles, regexForFolders)); } return allFiles; }, allFiles -> { @@ -484,9 +506,7 @@ public class GamesController implements Initializable { private <T> void performInBackgroundAndUpdate(Supplier<T> background, Consumer<T> update) { new Thread(() -> { final T result = background.get(); - Platform.runLater(() -> { - update.accept(result); - }); + Platform.runLater(() -> update.accept(result)); }).start(); } @@ -505,4 +525,4 @@ public class GamesController implements Initializable { preferences.setNetUsb(getSelectedNetUsb()); preferences.setNsIp(getNsIp()); } -} +} \ No newline at end of file From 619a2b157e12d5e8bb832b0f29741cd977567494 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 29 Nov 2020 17:19:57 +0300 Subject: [PATCH 024/134] Remove select-folders button, add settings option Hide 'Select split-files' button when 'Search-for-roms' option enabled in options Add settings option for this Update l10n Update icons, correct css. --- src/main/java/nsusbloader/AppPreferences.java | 3 ++ .../Controllers/GamesController.java | 49 +++++++++++++------ .../Controllers/NSLMainController.java | 2 +- .../Controllers/NSTableViewController.java | 12 ++--- .../SettingsBlockGenericController.java | 17 ++++++- .../Controllers/SplitMergeController.java | 2 +- .../java/nsusbloader/MediatorControl.java | 29 +++++++---- .../ModelControllers/MessagesConsumer.java | 8 +-- .../java/nsusbloader/com/usb/GoldLeaf_07.java | 2 +- .../java/nsusbloader/com/usb/GoldLeaf_08.java | 2 +- src/main/resources/GamesTab.fxml | 8 --- src/main/resources/SettingsBlockGeneric.fxml | 13 +++++ src/main/resources/locale.properties | 4 +- src/main/resources/locale_ar_AR.properties | 2 + src/main/resources/locale_de_DE.properties | 2 +- src/main/resources/locale_en_US.properties | 4 +- src/main/resources/locale_es_ES.properties | 2 +- src/main/resources/locale_fr_FR.properties | 2 +- src/main/resources/locale_pt_BR.properties | 6 +-- src/main/resources/locale_ru_RU.properties | 2 + src/main/resources/locale_uk_UA.properties | 2 + src/main/resources/res/app_dark.css | 19 ++++++- src/main/resources/res/app_light.css | 17 +++++++ 23 files changed, 151 insertions(+), 58 deletions(-) diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 9da2df5..0a7119a 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -102,6 +102,9 @@ public class AppPreferences { public boolean getAutoCheckUpdates(){return preferences.getBoolean("AUTOCHECK4UPDATES", false); } public void setAutoCheckUpdates(boolean prop){preferences.putBoolean("AUTOCHECK4UPDATES", prop); } + public boolean getDirectoriesChooserForRoms(){return preferences.getBoolean("dirchooser4roms", false); } + public void setDirectoriesChooserForRoms(boolean prop){preferences.putBoolean("dirchooser4roms", prop); } + public boolean getTfXCI(){return preferences.getBoolean("TF_XCI", true);} public void setTfXCI(boolean prop){ preferences.putBoolean("TF_XCI", prop); } diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index e193c13..d204c82 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -69,9 +69,9 @@ public class GamesController implements Initializable { public NSTableViewController tableFilesListController; // Accessible from Mediator (for drag-n-drop support) @FXML - private Button selectNspBtn, selectSplitNspBtn, selectFolderBtn, uploadStopBtn; + private Button selectNspBtn, selectSplitNspBtn, uploadStopBtn; private String previouslyOpenedPath; - private Region btnUpStopImage; + private Region btnUpStopImage, btnSelectImage; private ResourceBundle resourceBundle; private CancellableRunnable usbNetCommunications; private Thread workThread; @@ -79,11 +79,12 @@ public class GamesController implements Initializable { @Override public void initialize(URL url, ResourceBundle resourceBundle) { this.resourceBundle = resourceBundle; + AppPreferences preferences = AppPreferences.getInstance(); ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("TinFoil", "GoldLeaf"); choiceProtocol.setItems(choiceProtocolList); - choiceProtocol.getSelectionModel().select(AppPreferences.getInstance().getProtocol()); + choiceProtocol.getSelectionModel().select(preferences.getProtocol()); choiceProtocol.setOnAction(e-> { tableFilesListController.setNewProtocol(getSelectedProtocol()); if (getSelectedProtocol().equals("GoldLeaf")) { @@ -106,7 +107,7 @@ public class GamesController implements Initializable { ObservableList<String> choiceNetUsbList = FXCollections.observableArrayList("USB", "NET"); choiceNetUsb.setItems(choiceNetUsbList); - choiceNetUsb.getSelectionModel().select(AppPreferences.getInstance().getNetUsb()); + choiceNetUsb.getSelectionModel().select(preferences.getNetUsb()); if (getSelectedProtocol().equals("GoldLeaf")) { choiceNetUsb.setDisable(true); choiceNetUsb.getSelectionModel().select("USB"); @@ -122,7 +123,7 @@ public class GamesController implements Initializable { } }); // Set and configure NS IP field behavior - nsIpTextField.setText(AppPreferences.getInstance().getNsIp()); + nsIpTextField.setText(preferences.getNsIp()); if (getSelectedProtocol().equals("TinFoil") && getSelectedNetUsb().equals("NET")){ nsIpLbl.setVisible(true); nsIpTextField.setVisible(true); @@ -139,12 +140,9 @@ public class GamesController implements Initializable { switchThemeBtn.setGraphic(btnSwitchImage); this.switchThemeBtn.setOnAction(e->switchTheme()); - selectNspBtn.setOnAction(e-> selectFilesBtnAction()); selectNspBtn.getStyleClass().add("buttonSelect"); - - selectFolderBtn.setOnAction(e-> selectFoldersBtnAction()); - selectFolderBtn.getStyleClass().add("buttonSelect"); - selectFolderBtn.setTooltip(new Tooltip(resourceBundle.getString("btn_OpenFolders_tooltip"))); + this.btnSelectImage = new Region(); + setFilesSelectorButtonBehaviour(preferences.getDirectoriesChooserForRoms()); selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction()); selectSplitNspBtn.getStyleClass().add("buttonSelect"); @@ -158,7 +156,7 @@ public class GamesController implements Initializable { uploadStopBtn.getStyleClass().add("buttonUp"); uploadStopBtn.setGraphic(btnUpStopImage); - this.previouslyOpenedPath = AppPreferences.getInstance().getRecent(); + this.previouslyOpenedPath = preferences.getRecent(); } /** * Changes UI theme on the go @@ -206,11 +204,11 @@ public class GamesController implements Initializable { } private boolean isAllFiletypesAllowedForGL() { - return ! MediatorControl.getInstance().getContoller().getSettingsCtrlr().getGoldleafSettings().getNSPFileFilterForGL(); + return ! MediatorControl.getInstance().getSettingsController().getGoldleafSettings().getNSPFileFilterForGL(); } private boolean isXciNszXczSupport() { - return MediatorControl.getInstance().getContoller().getSettingsCtrlr().getTinfoilSettings().isXciNszXczSupport(); + return MediatorControl.getInstance().getSettingsController().getTinfoilSettings().isXciNszXczSupport(); } /** @@ -301,6 +299,9 @@ public class GamesController implements Initializable { final String filesRegex, final String foldersRegex) { + if (startFolder == null) + return; + final String startFolderNameInLowercase = startFolder.getName().toLowerCase(); if (startFolder.isFile()) { @@ -368,7 +369,7 @@ public class GamesController implements Initializable { nspToUpload = new LinkedList<>(); } - SettingsController settings = MediatorControl.getInstance().getContoller().getSettingsCtrlr(); + SettingsController settings = MediatorControl.getInstance().getSettingsController(); // If USB selected if (getSelectedProtocol().equals("GoldLeaf") ){ final SettingsBlockGoldleafController goldleafSettings = settings.getGoldleafSettings(); @@ -509,7 +510,25 @@ public class GamesController implements Initializable { Platform.runLater(() -> update.accept(result)); }).start(); } - + + public void updateFilesSelectorButtonBehaviour(boolean isDirectoryChooser){ + btnSelectImage.getStyleClass().clear(); + setFilesSelectorButtonBehaviour(isDirectoryChooser); + } + private void setFilesSelectorButtonBehaviour(boolean isDirectoryChooser){ + if (isDirectoryChooser){ + selectNspBtn.setOnAction(e -> selectFoldersBtnAction()); + btnSelectImage.getStyleClass().add("regionScanFolders"); + selectSplitNspBtn.setVisible(false); + } + else { + selectNspBtn.setOnAction(e -> selectFilesBtnAction()); + btnSelectImage.getStyleClass().add("regionSelectFiles"); + selectSplitNspBtn.setVisible(true); + } + selectNspBtn.setGraphic(btnSelectImage); + //selectFolderBtn.setTooltip(new Tooltip(resourceBundle.getString("btn_OpenFolders_tooltip"))); + } /** * Get 'Recent' path */ diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 0e74123..21dc530 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -41,7 +41,7 @@ public class NSLMainController implements Initializable { public ProgressBar progressBar; // Accessible from Mediator @FXML - public GamesController GamesTabController; // Accessible from Mediator | todo: incapsulate + private GamesController GamesTabController; // Accessible from Mediator | todo: incapsulate @FXML private SettingsController SettingsTabController; @FXML diff --git a/src/main/java/nsusbloader/Controllers/NSTableViewController.java b/src/main/java/nsusbloader/Controllers/NSTableViewController.java index b47e47c..5017829 100644 --- a/src/main/java/nsusbloader/Controllers/NSTableViewController.java +++ b/src/main/java/nsusbloader/Controllers/NSTableViewController.java @@ -38,8 +38,6 @@ import java.util.List; import java.util.ResourceBundle; public class NSTableViewController implements Initializable { - private static final DataFormat SERIALIZED_MIME_TYPE = new DataFormat("application/x-java-serialized-object"); - @FXML private TableView<NSLRowModel> table; private ObservableList<NSLRowModel> rowsObsLst; @@ -57,7 +55,7 @@ public class NSTableViewController implements Initializable { if (keyEvent.getCode() == KeyCode.DELETE && !MediatorControl.getInstance().getTransferActive()) { rowsObsLst.removeAll(table.getSelectionModel().getSelectedItems()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); } else if (keyEvent.getCode() == KeyCode.SPACE) { for (NSLRowModel item : table.getSelectionModel().getSelectedItems()) { @@ -175,13 +173,13 @@ public class NSTableViewController implements Initializable { deleteMenuItem.setOnAction(actionEvent -> { rowsObsLst.remove(row.getItem()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); }); MenuItem deleteAllMenuItem = new MenuItem(resourceBundle.getString("tab1_table_contextMenu_Btn_DeleteAll")); deleteAllMenuItem.setOnAction(actionEvent -> { rowsObsLst.clear(); - MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(true); // TODO: change to something better + MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better table.refresh(); }); contextMenu.getItems().addAll(deleteMenuItem, deleteAllMenuItem); @@ -226,7 +224,7 @@ public class NSTableViewController implements Initializable { } else { rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(false); // TODO: change to something better + MediatorControl.getInstance().getGamesController().disableUploadStopBtn(false); // TODO: change to something better } table.refresh(); } @@ -246,7 +244,7 @@ public class NSTableViewController implements Initializable { else { for (File file: newFiles) rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getContoller().getGamesCtrlr().disableUploadStopBtn(false); // TODO: change to something better + MediatorControl.getInstance().getGamesController().disableUploadStopBtn(false); // TODO: change to something better } //rowsObsLst.get(0).setMarkForUpload(true); table.refresh(); diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java index 00a416b..95ef545 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java @@ -28,6 +28,7 @@ import javafx.scene.control.ChoiceBox; import javafx.scene.control.Hyperlink; import javafx.scene.layout.Region; import nsusbloader.AppPreferences; +import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.UpdatesChecker; import nsusbloader.ServiceWindow; import nsusbloader.UI.LocaleHolder; @@ -47,7 +48,8 @@ public class SettingsBlockGenericController implements Initializable { driversInstallBtn, checkForUpdBtn; @FXML - private CheckBox autoCheckForUpdatesCB; + private CheckBox autoCheckForUpdatesCB, + direcroriesChooserForRomsCB; @FXML private Hyperlink newVersionHyperlink; @@ -61,6 +63,10 @@ public class SettingsBlockGenericController implements Initializable { final AppPreferences preferences = AppPreferences.getInstance(); autoCheckForUpdatesCB.setSelected(preferences.getAutoCheckUpdates()); + direcroriesChooserForRomsCB.setSelected(preferences.getDirectoriesChooserForRoms()); + direcroriesChooserForRomsCB.setOnAction(actionEvent -> + MediatorControl.getInstance().getGamesController().updateFilesSelectorButtonBehaviour(direcroriesChooserForRomsCB.isSelected()) + ); Region btnSwitchImage = new Region(); btnSwitchImage.getStyleClass().add("regionUpdatesCheck"); @@ -124,7 +130,13 @@ public class SettingsBlockGenericController implements Initializable { ResourceBundle.getBundle("locale", newLocale).getString("windowBodyRestartToApplyLang")); } - private boolean getAutoCheckForUpdates(){ return autoCheckForUpdatesCB.isSelected(); } + private boolean getAutoCheckForUpdates(){ + return autoCheckForUpdatesCB.isSelected(); + } + + public boolean isDirectoriesChooserForRoms(){ + return direcroriesChooserForRomsCB.isSelected(); + } protected void registerHostServices(HostServices hostServices){ this.hostServices = hostServices;} @@ -135,5 +147,6 @@ public class SettingsBlockGenericController implements Initializable { void updatePreferencesOnExit() { AppPreferences.getInstance().setAutoCheckUpdates(getAutoCheckForUpdates()); + AppPreferences.getInstance().setDirectoriesChooserForRoms(isDirectoriesChooserForRoms()); } } diff --git a/src/main/java/nsusbloader/Controllers/SplitMergeController.java b/src/main/java/nsusbloader/Controllers/SplitMergeController.java index 2611e33..59656ac 100644 --- a/src/main/java/nsusbloader/Controllers/SplitMergeController.java +++ b/src/main/java/nsusbloader/Controllers/SplitMergeController.java @@ -151,7 +151,7 @@ public class SplitMergeController implements Initializable { convertBtn.setOnAction(actionEvent -> setConvertBtnAction()); } - public void notifySmThreadStarted(boolean isStart, EModule type){ // todo: refactor: remove everything, place to separate container and just disable. + public void notifyThreadStarted(boolean isStart, EModule type){ // todo: refactor: remove everything, place to separate container and just disable. if (! type.equals(EModule.SPLIT_MERGE_TOOL)){ smToolPane.setDisable(isStart); return; diff --git a/src/main/java/nsusbloader/MediatorControl.java b/src/main/java/nsusbloader/MediatorControl.java index a644e50..3717ad2 100644 --- a/src/main/java/nsusbloader/MediatorControl.java +++ b/src/main/java/nsusbloader/MediatorControl.java @@ -18,14 +18,15 @@ */ package nsusbloader; -import nsusbloader.Controllers.NSLMainController; +import nsusbloader.Controllers.*; import nsusbloader.NSLDataTypes.EModule; +import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicBoolean; public class MediatorControl { - private AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure - private NSLMainController mainCtrler; + private final AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure + private NSLMainController mainController; public static MediatorControl getInstance(){ return MediatorControlHold.INSTANCE; @@ -35,16 +36,26 @@ public class MediatorControl { private static final MediatorControl INSTANCE = new MediatorControl(); } public void setController(NSLMainController controller){ - this.mainCtrler = controller; + this.mainController = controller; + } + + public NSLMainController getContoller(){ return mainController; } + public GamesController getGamesController(){ return mainController.getGamesCtrlr(); }; + public SettingsController getSettingsController(){ return mainController.getSettingsCtrlr(); }; + public SplitMergeController getSplitMergeController(){ return mainController.getSmCtrlr(); }; + public RcmController getRcmController(){ return mainController.getRcmCtrlr(); }; + public NxdtController getNxdtController(){ return mainController.getNXDTabController(); }; + + public ResourceBundle getResourceBundle(){ + return mainController.getResourceBundle(); } - public NSLMainController getContoller(){ return this.mainCtrler; } public synchronized void setBgThreadActive(boolean isActive, EModule appModuleType) { isTransferActive.set(isActive); - mainCtrler.getGamesCtrlr().notifyThreadStarted(isActive, appModuleType); - mainCtrler.getSmCtrlr().notifySmThreadStarted(isActive, appModuleType); - mainCtrler.getRcmCtrlr().notifyThreadStarted(isActive, appModuleType); - mainCtrler.getNXDTabController().notifyThreadStarted(isActive, appModuleType); + getGamesController().notifyThreadStarted(isActive, appModuleType); + getSplitMergeController().notifyThreadStarted(isActive, appModuleType); + getRcmController().notifyThreadStarted(isActive, appModuleType); + getNxdtController().notifyThreadStarted(isActive, appModuleType); } public synchronized boolean getTransferActive() { return this.isTransferActive.get(); } } diff --git a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java index 2b62f36..8baa5be 100644 --- a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java +++ b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java @@ -61,7 +61,7 @@ public class MessagesConsumer extends AnimationTimer { this.progressBar = MediatorControl.getInstance().getContoller().progressBar; this.statusMap = statusMap; - this.tableViewController = MediatorControl.getInstance().getContoller().GamesTabController.tableFilesListController; + this.tableViewController = MediatorControl.getInstance().getGamesController().tableFilesListController; this.oneLinerStatus = oneLinerStatus; @@ -100,13 +100,13 @@ public class MessagesConsumer extends AnimationTimer { switch (appModuleType){ case RCM: - MediatorControl.getInstance().getContoller().getRcmCtrlr().setOneLineStatus(oneLinerStatus.get()); + MediatorControl.getInstance().getRcmController().setOneLineStatus(oneLinerStatus.get()); break; case NXDT: - MediatorControl.getInstance().getContoller().getNXDTabController().setOneLineStatus(oneLinerStatus.get()); + MediatorControl.getInstance().getNxdtController().setOneLineStatus(oneLinerStatus.get()); break; case SPLIT_MERGE_TOOL: - MediatorControl.getInstance().getContoller().getSmCtrlr().setOneLineStatus(oneLinerStatus.get()); + MediatorControl.getInstance().getSplitMergeController().setOneLineStatus(oneLinerStatus.get()); break; } diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java index 00fa19b..eb3eca3 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java @@ -914,7 +914,7 @@ class GoldLeaf_07 extends TransferModule { private boolean selectFile(){ File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getContoller().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + fChooser.setTitle(MediatorControl.getInstance().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION fChooser.setInitialDirectory(new File(System.getProperty("user.home"))); // TODO: Consider fixing; not a prio. fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); return fChooser.showOpenDialog(null); // Leave as is for now. diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index f6a29ac..4e260cc 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -941,7 +941,7 @@ class GoldLeaf_08 extends TransferModule { private boolean selectFile(){ File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getContoller().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + fChooser.setTitle(MediatorControl.getInstance().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION fChooser.setInitialDirectory(new File(System.getProperty("user.home")));// TODO: Consider fixing; not a prio. fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); return fChooser.showOpenDialog(null); // Leave as is for now. diff --git a/src/main/resources/GamesTab.fxml b/src/main/resources/GamesTab.fxml index 15a924a..8f23f8e 100644 --- a/src/main/resources/GamesTab.fxml +++ b/src/main/resources/GamesTab.fxml @@ -54,14 +54,6 @@ <HBox.margin> <Insets /> </HBox.margin> - <graphic> - <SVGPath content="M 8,0 C 6.8954305,0 6,0.8954305 6,2 v 16 c 0,1.1 0.89,2 2,2 h 12 c 1.104569,0 2,-0.895431 2,-2 V 2 C 22,0.90484721 21.089844,0 20,0 Z m 2.1,1.2 h 7.8 C 18,1.20208 18,1.2002604 18,1.3 v 0.1 c 0,0.095833 0,0.097917 -0.1,0.1 H 10.1 C 10,1.5057292 10,1.5036458 10,1.4 V 1.3 C 10,1.20026 10,1.1981771 10.1,1.2 Z M 8,2 h 12 c 0.303385,0 0.5,0.2044271 0.5,0.5 v 12 C 20.5,14.789959 20.29836,15 20,15 H 8 C 7.7044271,15 7.5,14.803385 7.5,14.5 V 2.5 C 7.5,2.2083333 7.7122396,2 8,2 Z M 2,4 v 18 c 0,1.104569 0.8954305,2 2,2 H 20 V 22 H 4 V 4 Z m 8,12 h 8 l -4,3 z" fill="#289de8" /> - </graphic> - </Button> - <Button fx:id="selectFolderBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenFolders"> - <graphic> - <SVGPath content="M 2.4003906,2 C 1.0683906,2 0,3.1125 0,4.5 v 15 c -1.7556433e-8,1.380871 1.0747547,2.500225 2.4003906,2.5 H 21.599609 C 22.925245,22.000225 24,20.880871 24,19.5 V 7 C 24,5.6125 22.919609,4.5 21.599609,4.5 H 12 L 9.5996094,2 Z" fill="#289de8" /> - </graphic> </Button> <Button fx:id="selectSplitNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenSplitFile"> <graphic> diff --git a/src/main/resources/SettingsBlockGeneric.fxml b/src/main/resources/SettingsBlockGeneric.fxml index ea7e76e..2105d4b 100644 --- a/src/main/resources/SettingsBlockGeneric.fxml +++ b/src/main/resources/SettingsBlockGeneric.fxml @@ -43,5 +43,18 @@ <Insets left="5.0" /> </VBox.margin> </HBox> + <HBox spacing="5.0"> + <children> + <VBox spacing="5.0"> + <children> + <CheckBox fx:id="direcroriesChooserForRomsCB" mnemonicParsing="false" text="%tab2_Cb_foldersSelectorForRoms" /> + <Label disable="true" text="%tab2_Cb_foldersSelectorForRomsDesc" wrapText="true" /> + </children> + </VBox> + </children> + <VBox.margin> + <Insets left="5.0" /> + </VBox.margin> + </HBox> </children> </VBox> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index 53e2684..7e9c763 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -70,4 +70,6 @@ btn_Cancel=Cancel btn_Close=Close tab2_Cb_GlVersion=GoldLeaf version tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. \ No newline at end of file +windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. +tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. \ No newline at end of file diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties index 1f5db48..7a5e66b 100644 --- a/src/main/resources/locale_ar_AR.properties +++ b/src/main/resources/locale_ar_AR.properties @@ -69,3 +69,5 @@ btn_Close=\u0625\u063A\u0644\u0627\u0642 tab2_Cb_GlVersion=\u0625\u0635\u062F\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641" tab2_Cb_GLshowNspOnly=\u0627\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0625\u0645\u062A\u062F\u0627\u062F "\u0625\u0646 \u0625\u0633 \u0628\u064A" \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641". windowBodyPleaseStopOtherProcessFirst=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649 \u0642\u0628\u0644 \u0627\u0644\u0625\u0633\u062A\u0645\u0631\u0627\u0631. +tab2_Cb_foldersSelectorForRoms= +tab2_Cb_foldersSelectorForRomsDesc= diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index 0e92adf..d57b730 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -44,7 +44,7 @@ tab2_Cb_AllowXciNszXcz=Erlaube XCI- NSZ- XCZ-Dateien-Verwendung f\u00FCr Tinfoil tab2_Lbl_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt Tinfoil Transfer Protocol. Nicht \u00E4ndern, wenn unsicher. tab2_Lbl_Language=Sprache windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen. -btn_OpenSplitFile=Split-NSP ausw�hlen +btn_OpenSplitFile=Split-NSP ausw\uFFFDhlen tab2_Cb_GLshowNspOnly=Nur *.nsp in GoldLeaf zeigen. btn_Cancel=Abbrechen diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index 53e2684..7e9c763 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -70,4 +70,6 @@ btn_Cancel=Cancel btn_Close=Close tab2_Cb_GlVersion=GoldLeaf version tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. \ No newline at end of file +windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. +tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. \ No newline at end of file diff --git a/src/main/resources/locale_es_ES.properties b/src/main/resources/locale_es_ES.properties index c150660..a2a3362 100644 --- a/src/main/resources/locale_es_ES.properties +++ b/src/main/resources/locale_es_ES.properties @@ -1,4 +1,4 @@ -btn_OpenFile=Seleccionar los archivos .NSP +btn_OpenFile=Seleccionar los archivos btn_Upload=Enviar a NS tab3_Txt_EnteredAsMsg1=Est\u00E1 conectado como: tab3_Txt_EnteredAsMsg2=Deber\u00EDa ser root o haber configurado las reglas 'udev' de este usuario para evitar problemas. diff --git a/src/main/resources/locale_fr_FR.properties b/src/main/resources/locale_fr_FR.properties index e048009..5dcc313 100644 --- a/src/main/resources/locale_fr_FR.properties +++ b/src/main/resources/locale_fr_FR.properties @@ -1,4 +1,4 @@ -btn_OpenFile=Selectionner les fichiers .NSP +btn_OpenFile=Selectionner les fichiers btn_Upload=Envoyer vers NS tab3_Txt_EnteredAsMsg1=Vous etes connect\u00E9 en tant que: tab3_Txt_EnteredAsMsg2=Vous devez \u00EAtre root ou avoir configur\u00E9 les r\u00E8gles 'udev' pour cet utilisateur afin d'\u00E9viter tout probl\u00E8me. diff --git a/src/main/resources/locale_pt_BR.properties b/src/main/resources/locale_pt_BR.properties index 77c5173..f496294 100644 --- a/src/main/resources/locale_pt_BR.properties +++ b/src/main/resources/locale_pt_BR.properties @@ -1,4 +1,4 @@ -katebtn_OpenFile=Selecionar Arquivos +btn_OpenFile=Selecionar Arquivos btn_Upload=Carregar Arquivos tab3_Txt_EnteredAsMsg1=Voc\u00EA logou como: tab3_Txt_EnteredAsMsg2=Voc\u00EA precisa de permiss\u00F5es root ou ter configurado as regras 'udev' deste usu\u00E1rio para evitar poss\u00EDveis problemas. @@ -23,7 +23,7 @@ tab1_Lbl_NSIP=NS IP: tab2_Cb_ValidateNSHostName=Sempre validar o IP do switch. windowBodyBadIp=Tem certeza que preencheu o endere\u00E7o IP corretamente? windowTitleBadIp=Endere\u00E7o IP do switch provavelmente incorreto. -tab2_Cb_ExpertMode=Modo Avançado (Configura\u00E7\u00E3o NET) +tab2_Cb_ExpertMode=Modo Avan\u00E7ado (Configura\u00E7\u00E3o NET) tab2_Lbl_HostPort=porta tab2_Cb_AutoDetectIp=Auto-detectar IP tab2_Cb_RandSelectPort=Usar porta aleat\u00F3ria @@ -44,7 +44,7 @@ tab2_Lbl_Language=Idioma windowBodyRestartToApplyLang=Por favor, reinicie para aplicar as modifica\u00E7\u00F5es. btn_OpenSplitFile=Select split NSP tab2_Lbl_ApplicationSettings=Configura\u00E7\u00F5es principais -tabSplMrg_Lbl_SplitNMergeTitle=Ferramenta de Fragmentar (Dividir) & Mesclar (Juntar) arquivos +tabSplMrg_Lbl_SplitNMergeTitle=Ferramenta de Fragmentar (Split) & Mesclar (Merge) arquivos tabSplMrg_RadioBtn_Split=Fragmentar (Dividir) tabSplMrg_RadioBtn_Merge=Mesclar (Juntar) tabSplMrg_Txt_File=Arquivo: diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 8c62350..c8d6bc5 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -69,4 +69,6 @@ btn_Cancel=\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C btn_Close=\u0417\u0430\u043A\u0440\u044B\u0442\u044C tab2_Cb_GlVersion=\u0412\u0435\u0440\u0441\u0438\u044F GoldLeaf windowBodyPleaseStopOtherProcessFirst=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0435\u0440\u0435\u0434 \u0442\u0435\u043C, \u043A\u0430\u043A \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C. +tab2_Cb_foldersSelectorForRomsDesc=\u041C\u0435\u043D\u044F\u0435\u0442 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u043A\u043D\u043E\u043F\u043A\u0438 "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B\u044B" \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 '\u0418\u0433\u0440\u044B'. \u0412\u043C\u0435\u0441\u0442\u043E \u0432\u044B\u0431\u043E\u0440\u0430 ROM \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0432\u044B \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0435 \u043F\u0430\u043F\u043A\u0443, \u043F\u043E\u0441\u043B\u0435 \u0447\u0435\u0433\u043E \u0432\u0441\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0435 \u043E\u0431\u0440\u0430\u0437\u044B \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F. +tab2_Cb_foldersSelectorForRoms=\u0412\u044B\u0431\u0438\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 \u0441 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0432\u043C\u0435\u0441\u0442\u043E \u0432\u044B\u0431\u043E\u0440\u0430 \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u043E\u0447\u043A\u0435. diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index c387b9e..b5072e2 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -69,3 +69,5 @@ btn_Cancel=\u0412\u0456\u0434\u043C\u0456\u043D\u0438\u0442\u0438 btn_Close=\u0417\u0430\u043A\u0440\u0438\u0442\u0438 tab2_Cb_GlVersion=\u0412\u0435\u0440\u0441\u0456\u044F GoldLeaf windowBodyPleaseStopOtherProcessFirst=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u0437\u0443\u043F\u0438\u043D\u0456\u0442\u044C \u0456\u043D\u0448\u0438\u0439 \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u043F\u0440\u043E\u0446\u0435\u0441 \u043F\u0435\u0440\u0448 \u043D\u0456\u0436 \u043F\u0440\u043E\u0434\u043E\u0432\u0436\u0438\u0442\u0438. +tab2_Cb_foldersSelectorForRomsDesc=\u0417\u043C\u0456\u043D\u044E\u0454 \u043F\u043E\u0432\u0435\u0434\u0456\u043D\u043A\u0443 \u043A\u043D\u043E\u043F\u043A\u0438 \u043A\u043D\u043E\u043F\u043A\u0438 "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B\u0438" \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u0446\u0456 '\u0406\u0433\u0440\u0438'. \u0417\u0430\u043C\u0456\u0441\u0442\u044C \u0432\u0438\u0431\u043E\u0440\u0443 ROM \u0444\u0430\u0439\u043B\u0456\u0432 \u043E\u0434\u0438\u043D \u0437\u0430 \u043E\u0434\u043D\u0438\u043C \u0432\u0438 \u0432\u043A\u0430\u0437\u0443\u0454\u0442\u0435 \u043F\u0430\u043F\u043A\u0443, \u043F\u0456\u0441\u043B\u044F \u0447\u043E\u0433\u043E \u0434\u043E\u0434\u0430\u044E\u0442\u044C\u0441\u044F \u0443\u0441\u0456 \u0444\u0430\u0439\u043B\u0438, \u0449\u043E \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u044E\u0442\u044C\u0441\u044F. +tab2_Cb_foldersSelectorForRoms=\u0412\u0438\u0431\u0438\u0440\u0430\u0442\u0438 \u043F\u0430\u043F\u043A\u0443 \u0437 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0437\u0430\u043C\u0456\u0441\u0442\u044C \u0432\u0438\u0431\u043E\u0440\u0443 \u0444\u0430\u0439\u043B\u0456\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u0446\u0456. diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 473e43a..807a88f 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -92,7 +92,7 @@ .tool-bar{ -fx-background-color: transparent; } -// -======================== Choice box =========================- +/* -======================== Choice box =========================- */ .choice-box { -fx-background-color: #4f4f4f; -fx-border-color: #4f4f4f; @@ -101,6 +101,9 @@ -fx-mark-color: #eea11e; -fx-effect: none; } +.choice-box .arrow{ + -fx-background-color: #eea11e; +} .choice-box > .label { -fx-text-fill: #f7fafa; } @@ -411,6 +414,20 @@ -fx-min-height: -size; -fx-min-width: 17.5; } +.regionSelectFiles { + -fx-shape: "M 8,0 C 6.8954305,0 6,0.8954305 6,2 v 16 c 0,1.1 0.89,2 2,2 h 12 c 1.104569,0 2,-0.895431 2,-2 V 2 C 22,0.90484721 21.089844,0 20,0 Z m 2.1,1.2 h 7.8 C 18,1.20208 18,1.2002604 18,1.3 v 0.1 c 0,0.095833 0,0.097917 -0.1,0.1 H 10.1 C 10,1.5057292 10,1.5036458 10,1.4 V 1.3 C 10,1.20026 10,1.1981771 10.1,1.2 Z M 8,2 h 12 c 0.303385,0 0.5,0.2044271 0.5,0.5 v 12 C 20.5,14.789959 20.29836,15 20,15 H 8 C 7.7044271,15 7.5,14.803385 7.5,14.5 V 2.5 C 7.5,2.2083333 7.7122396,2 8,2 Z M 2,4 v 18 c 0,1.104569 0.8954305,2 2,2 H 20 V 22 H 4 V 4 Z m 8,12 h 8 l -4,3 z"; + -fx-background-color: #289de8; + -size: 24; + -fx-min-height: -size; + -fx-min-width: 20; +} +.regionScanFolders { + -fx-shape: "M16.5,12C19,12 21,14 21,16.5C21,17.38 20.75,18.21 20.31,18.9L23.39,22L22,23.39L18.88,20.32C18.19,20.75 17.37,21 16.5,21C14,21 12,19 12,16.5C12,14 14,12 16.5,12M16.5,14A2.5,2.5 0 0,0 14,16.5A2.5,2.5 0 0,0 16.5,19A2.5,2.5 0 0,0 19,16.5A2.5,2.5 0 0,0 16.5,14M9,4L11,6H19A2,2 0 0,1 21,8V11.81C19.83,10.69 18.25,10 16.5,10A6.5,6.5 0 0,0 10,16.5C10,17.79 10.37,19 11,20H3C1.89,20 1,19.1 1,18V6C1,4.89 1.89,4 3,4H9Z"; + -fx-background-color: #289de8; + -size: 22; + -fx-min-height: -size; + -fx-min-width: 24; +} // //.lineGradient { // -fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #00c8fc 30%, transparent 45%); diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index b3cda7e..c13c160 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -118,6 +118,9 @@ -fx-mark-color: #eea11e; -fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0); } +.choice-box .arrow{ + -fx-background-color: #eea11e; +} .choice-box > .label { -fx-text-fill: #2c2c2c; } @@ -327,4 +330,18 @@ -size: 17.5; -fx-min-height: -size; -fx-min-width: 17.5; +} +.regionSelectFiles { + -fx-shape: "M 8,0 C 6.8954305,0 6,0.8954305 6,2 v 16 c 0,1.1 0.89,2 2,2 h 12 c 1.104569,0 2,-0.895431 2,-2 V 2 C 22,0.90484721 21.089844,0 20,0 Z m 2.1,1.2 h 7.8 C 18,1.20208 18,1.2002604 18,1.3 v 0.1 c 0,0.095833 0,0.097917 -0.1,0.1 H 10.1 C 10,1.5057292 10,1.5036458 10,1.4 V 1.3 C 10,1.20026 10,1.1981771 10.1,1.2 Z M 8,2 h 12 c 0.303385,0 0.5,0.2044271 0.5,0.5 v 12 C 20.5,14.789959 20.29836,15 20,15 H 8 C 7.7044271,15 7.5,14.803385 7.5,14.5 V 2.5 C 7.5,2.2083333 7.7122396,2 8,2 Z M 2,4 v 18 c 0,1.104569 0.8954305,2 2,2 H 20 V 22 H 4 V 4 Z m 8,12 h 8 l -4,3 z"; + -fx-background-color: #289de8; + -size: 24; + -fx-min-height: -size; + -fx-min-width: 20; +} +.regionScanFolders { + -fx-shape: "M16.5,12C19,12 21,14 21,16.5C21,17.38 20.75,18.21 20.31,18.9L23.39,22L22,23.39L18.88,20.32C18.19,20.75 17.37,21 16.5,21C14,21 12,19 12,16.5C12,14 14,12 16.5,12M16.5,14A2.5,2.5 0 0,0 14,16.5A2.5,2.5 0 0,0 16.5,19A2.5,2.5 0 0,0 19,16.5A2.5,2.5 0 0,0 16.5,14M9,4L11,6H19A2,2 0 0,1 21,8V11.81C19.83,10.69 18.25,10 16.5,10A6.5,6.5 0 0,0 10,16.5C10,17.79 10.37,19 11,20H3C1.89,20 1,19.1 1,18V6C1,4.89 1.89,4 3,4H9Z"; + -fx-background-color: #289de8; + -size: 22; + -fx-min-height: -size; + -fx-min-width: 24; } \ No newline at end of file From d8ea426fa437bd8e395362b7acf6d3bbf4ec8acf Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 10 Dec 2020 00:07:44 +0300 Subject: [PATCH 025/134] Update scan-folder-to-add-files function: set pop-up window with indicator showing how many files did we scan and how many files would be added. Minor color corrections on light theme for whoever use it Fix TF progress-bar (can't recall when I broke it..) --- .../Controllers/FilesDropHandle.java | 120 ++++++++++++++++++ .../Controllers/FilesDropHandleTask.java | 96 ++++++++++++++ .../Controllers/GamesController.java | 27 +--- .../Controllers/NSTableViewController.java | 2 +- .../java/nsusbloader/com/usb/TinFoil.java | 4 +- src/main/resources/locale.properties | 4 +- src/main/resources/locale_en_US.properties | 4 +- src/main/resources/locale_ru_RU.properties | 2 + src/main/resources/locale_uk_UA.properties | 2 + src/main/resources/res/app_dark.css | 6 +- src/main/resources/res/app_light.css | 12 +- 11 files changed, 247 insertions(+), 32 deletions(-) create mode 100644 src/main/java/nsusbloader/Controllers/FilesDropHandle.java create mode 100644 src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java new file mode 100644 index 0000000..4fb9e96 --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java @@ -0,0 +1,120 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Controllers; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.image.Image; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import nsusbloader.AppPreferences; +import nsusbloader.MediatorControl; + +import java.io.File; +import java.util.List; +import java.util.ResourceBundle; + +public class FilesDropHandle { + + public FilesDropHandle(List<File> files, String filesRegex, String foldersRegex){ + + // + // TODO: ADD GRAPHICS BEFORE RELEASE ! + // + + FilesDropHandleTask filesDropHandleTask = new FilesDropHandleTask(files, filesRegex, foldersRegex); + + ResourceBundle resourceBundle = MediatorControl.getInstance().getResourceBundle(); + Button cancelButton = new Button(resourceBundle.getString("btn_Cancel")); + + ProgressIndicator progressIndicator = new ProgressIndicator(); + progressIndicator.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS); + + Label downloadStatusLabel = new Label(); + downloadStatusLabel.setWrapText(true); + downloadStatusLabel.textProperty().bind(filesDropHandleTask.messageProperty()); + + Pane fillerPane1 = new Pane(); + Pane fillerPane2 = new Pane(); + + VBox parentVBox = new VBox(); + parentVBox.setAlignment(Pos.TOP_CENTER); + parentVBox.setFillWidth(true); + parentVBox.setSpacing(5.0); + parentVBox.setPadding(new Insets(5.0)); + parentVBox.setFillWidth(true); + parentVBox.getChildren().addAll( + downloadStatusLabel, + fillerPane1, + progressIndicator, + fillerPane2, + cancelButton + ); // TODO:FIX + + VBox.setVgrow(fillerPane1, Priority.ALWAYS); + VBox.setVgrow(fillerPane2, Priority.ALWAYS); + + Stage stage = new Stage(); + stage.setTitle(resourceBundle.getString("windowTitleAddingFiles")); + stage.getIcons().addAll( + new Image("/res/dwnload_ico32x32.png"), //TODO: REDRAW + new Image("/res/dwnload_ico48x48.png"), + new Image("/res/dwnload_ico64x64.png"), + new Image("/res/dwnload_ico128x128.png") + ); + stage.setMinWidth(300); + stage.setMinHeight(175); + stage.setAlwaysOnTop(true); + Scene mainScene = new Scene(parentVBox, 310, 185); + + mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); + + stage.setOnHidden(windowEvent -> filesDropHandleTask.cancel(true ) ); + + stage.setScene(mainScene); + stage.show(); + stage.toFront(); + + filesDropHandleTask.setOnSucceeded(event -> { + cancelButton.setText(resourceBundle.getString("btn_Close")); + + List<File> allFiles = filesDropHandleTask.getValue(); + + if (! allFiles.isEmpty()) { + MediatorControl.getInstance().getGamesController().tableFilesListController.setFiles(allFiles); + } + stage.close(); + }); + + new Thread(filesDropHandleTask).start(); + + cancelButton.setOnAction(actionEvent -> { + filesDropHandleTask.cancel(true); + stage.close(); + }); + } +} diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java b/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java new file mode 100644 index 0000000..93bc8ad --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java @@ -0,0 +1,96 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Controllers; + +import javafx.concurrent.Task; +import nsusbloader.MediatorControl; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class FilesDropHandleTask extends Task<List<File>> { + private final String filesRegex; + private final String foldersRegex; + + private final List<File> filesDropped; + private final List<File> allFiles; + + private String messageTemplate; + private long filesScanned = 0; + private long filesAdded = 0; + + FilesDropHandleTask(List<File> files, + String filesRegex, + String foldersRegex) { + this.filesDropped = files; + this.filesRegex = filesRegex; + this.foldersRegex = foldersRegex; + this.allFiles = new ArrayList<>(); + this.messageTemplate = MediatorControl.getInstance().getResourceBundle().getString("windowBodyFilesScanned"); + } + + @Override + protected List<File> call() { + if (filesDropped == null || filesDropped.size() == 0) + return allFiles; + + for (File file : filesDropped){ + if (isCancelled()) + return new ArrayList<>(); + collectFiles(file); + updateMessage(String.format(messageTemplate, filesScanned++, filesAdded)); + } + + return allFiles; + } + + private void collectFiles(File startFolder) { + if (startFolder == null) + return; + + final String startFolderNameInLowercase = startFolder.getName().toLowerCase(); + + if (startFolder.isFile()) { + if (startFolderNameInLowercase.matches(filesRegex)) { + allFiles.add(startFolder); + filesAdded++; + } + return; + } + + if (startFolderNameInLowercase.matches(foldersRegex)) { + allFiles.add(startFolder); + filesAdded++; + return; + } + + File[] files = startFolder.listFiles(); + if (files == null) + return; + + for (File file : files) { + if (isCancelled()) + return; + collectFiles(file); + updateMessage(String.format(messageTemplate, filesScanned++, filesAdded)); + } + } + +} diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index d204c82..3ccccd9 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -41,10 +41,7 @@ import nsusbloader.ServiceWindow; import java.io.File; import java.net.URL; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.ResourceBundle; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; @@ -294,6 +291,7 @@ public class GamesController implements Initializable { * @param filesRegex for filenames */ // TODO: Too sophisticated. Should be moved to simple class to keep things simplier + private void collectFiles(List<File> storage, File startFolder, final String filesRegex, @@ -437,24 +435,10 @@ public class GamesController implements Initializable { * */ @FXML private void handleDrop(DragEvent event) { - final String regexForFiles = getRegexForFiles(); - final String regexForFolders = getRegexForFolders(); - List<File> files = event.getDragboard().getFiles(); - - performInBackgroundAndUpdate(() -> { - List<File> allFiles = new ArrayList<>(); - if (files != null && files.size() != 0) { - files.forEach(f -> collectFiles(allFiles, f, regexForFiles, regexForFolders)); - } - return allFiles; - }, allFiles -> { - if (!allFiles.isEmpty()) - tableFilesListController.setFiles(allFiles); - - event.setDropCompleted(true); - event.consume(); - }); + new FilesDropHandle(files, getRegexForFiles(), getRegexForFolders()); + event.setDropCompleted(true); + event.consume(); } /** @@ -527,7 +511,6 @@ public class GamesController implements Initializable { selectSplitNspBtn.setVisible(true); } selectNspBtn.setGraphic(btnSelectImage); - //selectFolderBtn.setTooltip(new Tooltip(resourceBundle.getString("btn_OpenFolders_tooltip"))); } /** * Get 'Recent' path diff --git a/src/main/java/nsusbloader/Controllers/NSTableViewController.java b/src/main/java/nsusbloader/Controllers/NSTableViewController.java index 5017829..267da43 100644 --- a/src/main/java/nsusbloader/Controllers/NSTableViewController.java +++ b/src/main/java/nsusbloader/Controllers/NSTableViewController.java @@ -231,7 +231,7 @@ public class NSTableViewController implements Initializable { /** * Add files when user selected them * */ - public void setFiles(List<File> newFiles){ + public synchronized void setFiles(List<File> newFiles){ if (!rowsObsLst.isEmpty()){ List<String> filesAlreayInList = new ArrayList<>(); for (NSLRowModel model : rowsObsLst) diff --git a/src/main/java/nsusbloader/com/usb/TinFoil.java b/src/main/java/nsusbloader/com/usb/TinFoil.java index 9ceba47..d033f08 100644 --- a/src/main/java/nsusbloader/com/usb/TinFoil.java +++ b/src/main/java/nsusbloader/com/usb/TinFoil.java @@ -222,7 +222,6 @@ class TinFoil extends TransferModule { if ((currentOffset + chunk) >= size ) chunk = Math.toIntExact(size - currentOffset); //System.out.println("CO: "+currentOffset+"\t\tEO: "+size+"\t\tRP: "+chunk); // NOTE: DEBUG - logPrinter.updateProgress((currentOffset + chunk) / (size / 100.0) / 100.0); readBuffer = new byte[chunk]; // TODO: not perfect moment, consider refactoring. @@ -232,6 +231,7 @@ class TinFoil extends TransferModule { if (writeUsb(readBuffer)) throw new IOException("TF Failure during file transfer."); currentOffset += chunk; + logPrinter.updateProgress((double)currentOffset / (double)size); } nsSplitReader.close(); logPrinter.updateProgress(1.0); @@ -251,7 +251,6 @@ class TinFoil extends TransferModule { if ((currentOffset + chunk) >= size) chunk = Math.toIntExact(size - currentOffset); //System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+chunk); // NOTE: DEBUG - logPrinter.updateProgress((currentOffset + chunk) / (size / 100.0) / 100.0); readBuffer = new byte[chunk]; @@ -261,6 +260,7 @@ class TinFoil extends TransferModule { if (writeUsb(readBuffer)) throw new IOException("TF Failure during file transfer."); currentOffset += chunk; + logPrinter.updateProgress((double)currentOffset / (double)size); } bufferedInStream.close(); logPrinter.updateProgress(1.0); diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index 7e9c763..b629b93 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -72,4 +72,6 @@ tab2_Cb_GlVersion=GoldLeaf version tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. \ No newline at end of file +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +windowTitleAddingFiles=Searching for files... +windowBodyFilesScanned=Files scanned: %d\nWould be added: %d \ No newline at end of file diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index 7e9c763..b629b93 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -72,4 +72,6 @@ tab2_Cb_GlVersion=GoldLeaf version tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. \ No newline at end of file +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +windowTitleAddingFiles=Searching for files... +windowBodyFilesScanned=Files scanned: %d\nWould be added: %d \ No newline at end of file diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index c8d6bc5..1dc4885 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -71,4 +71,6 @@ tab2_Cb_GlVersion=\u0412\u0435\u0440\u0441\u0438\u044F GoldLeaf windowBodyPleaseStopOtherProcessFirst=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0435\u0440\u0435\u0434 \u0442\u0435\u043C, \u043A\u0430\u043A \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C. tab2_Cb_foldersSelectorForRomsDesc=\u041C\u0435\u043D\u044F\u0435\u0442 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u043A\u043D\u043E\u043F\u043A\u0438 "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B\u044B" \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 '\u0418\u0433\u0440\u044B'. \u0412\u043C\u0435\u0441\u0442\u043E \u0432\u044B\u0431\u043E\u0440\u0430 ROM \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0432\u044B \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0435 \u043F\u0430\u043F\u043A\u0443, \u043F\u043E\u0441\u043B\u0435 \u0447\u0435\u0433\u043E \u0432\u0441\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0435 \u043E\u0431\u0440\u0430\u0437\u044B \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F. tab2_Cb_foldersSelectorForRoms=\u0412\u044B\u0431\u0438\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 \u0441 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0432\u043C\u0435\u0441\u0442\u043E \u0432\u044B\u0431\u043E\u0440\u0430 \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u043E\u0447\u043A\u0435. +windowTitleAddingFiles=\u0418\u0449\u0435\u043C \u0444\u0430\u0439\u043B\u044B... +windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u043E\u0432 \u043F\u0440\u043E\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u043E: %d\n\u0418\u0437 \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u0431\u0443\u0434\u0435\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E: %d diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index b5072e2..0152896 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -71,3 +71,5 @@ tab2_Cb_GlVersion=\u0412\u0435\u0440\u0441\u0456\u044F GoldLeaf windowBodyPleaseStopOtherProcessFirst=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u0437\u0443\u043F\u0438\u043D\u0456\u0442\u044C \u0456\u043D\u0448\u0438\u0439 \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u0439 \u043F\u0440\u043E\u0446\u0435\u0441 \u043F\u0435\u0440\u0448 \u043D\u0456\u0436 \u043F\u0440\u043E\u0434\u043E\u0432\u0436\u0438\u0442\u0438. tab2_Cb_foldersSelectorForRomsDesc=\u0417\u043C\u0456\u043D\u044E\u0454 \u043F\u043E\u0432\u0435\u0434\u0456\u043D\u043A\u0443 \u043A\u043D\u043E\u043F\u043A\u0438 \u043A\u043D\u043E\u043F\u043A\u0438 "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B\u0438" \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u0446\u0456 '\u0406\u0433\u0440\u0438'. \u0417\u0430\u043C\u0456\u0441\u0442\u044C \u0432\u0438\u0431\u043E\u0440\u0443 ROM \u0444\u0430\u0439\u043B\u0456\u0432 \u043E\u0434\u0438\u043D \u0437\u0430 \u043E\u0434\u043D\u0438\u043C \u0432\u0438 \u0432\u043A\u0430\u0437\u0443\u0454\u0442\u0435 \u043F\u0430\u043F\u043A\u0443, \u043F\u0456\u0441\u043B\u044F \u0447\u043E\u0433\u043E \u0434\u043E\u0434\u0430\u044E\u0442\u044C\u0441\u044F \u0443\u0441\u0456 \u0444\u0430\u0439\u043B\u0438, \u0449\u043E \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u044E\u0442\u044C\u0441\u044F. tab2_Cb_foldersSelectorForRoms=\u0412\u0438\u0431\u0438\u0440\u0430\u0442\u0438 \u043F\u0430\u043F\u043A\u0443 \u0437 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0437\u0430\u043C\u0456\u0441\u0442\u044C \u0432\u0438\u0431\u043E\u0440\u0443 \u0444\u0430\u0439\u043B\u0456\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u0446\u0456. +windowTitleAddingFiles=\u0428\u0443\u043A\u0430\u0454\u043C\u043E \u0444\u0430\u0439\u043B\u0438... +windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u0456\u0432 \u043F\u0440\u043E\u0441\u043A\u0430\u043D\u043E\u0432\u0430\u043D\u043E: %d\n\u0417 \u044F\u043A\u0438\u0445 \u0431\u0443\u0434\u0435 \u0434\u043E\u0434\u0430\u043D\u043E: %d diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 807a88f..5ee4192 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -78,6 +78,10 @@ -fx-padding: 0.23em; } +.progress-indicator { + -fx-progress-color: #00bce4; +} + .dialog-pane { -fx-background-color: #4f4f4f; } @@ -131,7 +135,7 @@ } -// -======================== TAB PANE =========================- +/* -======================== TAB PANE =========================- */ .tab-pane .tab SVGPath{ -fx-fill: #f7fafa; } diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index c13c160..fde9c6a 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -24,7 +24,7 @@ -fx-effect: dropshadow(three-pass-box, #00caca, 2, 0, 0, 0); } .button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{ - -fx-background-color: #cccccc; + -fx-background-color: #fefefe; -fx-background-insets: 0 0 0 0, 0, 1, 2; -fx-border-color: #cccccc; -fx-border-radius: 3; @@ -95,6 +95,10 @@ -fx-padding: 0.23em; } +.progress-indicator { + -fx-progress-color: #00bce4; +} + .dialog-pane { -fx-background-color: #fefefe; } @@ -183,9 +187,9 @@ .tab-pane > .tab-header-area > .tab-header-background { - -fx-background-color: #ebebeb; - + -fx-background-color: linear-gradient(to right, #ebebeb 0%, #fefefe 7.5%, #fefefe 100%); } + .tab-pane > .tab-header-area > .headers-region > .tab { -fx-padding: 10; } @@ -220,7 +224,7 @@ -fx-text-fill: #2c2c2c; } .table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{ - -fx-background-color: -fx-table-cell-border-color, #d3fffd; + -fx-background-color: -fx-table-cell-border-color, #ebfffe; -fx-background-insets: 0, 0 0 1 0; -fx-padding: 0.0em; /* 0 */ -fx-table-cell-border-color: #b0b0b0; From da623518d1f39c2e2543bf7d6bfc667b4eadf06f Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 10 Dec 2020 00:32:25 +0300 Subject: [PATCH 026/134] UI fixes and enhancements --- .../Controllers/FilesDropHandle.java | 27 +++++++----------- src/main/resources/res/info_ico128x128.png | Bin 0 -> 14485 bytes src/main/resources/res/info_ico32x32.png | Bin 0 -> 8245 bytes src/main/resources/res/info_ico48x48.png | Bin 0 -> 11772 bytes src/main/resources/res/info_ico64x64.png | Bin 0 -> 12318 bytes 5 files changed, 11 insertions(+), 16 deletions(-) create mode 100644 src/main/resources/res/info_ico128x128.png create mode 100644 src/main/resources/res/info_ico32x32.png create mode 100644 src/main/resources/res/info_ico48x48.png create mode 100644 src/main/resources/res/info_ico64x64.png diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java index 4fb9e96..f66be03 100644 --- a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java @@ -23,13 +23,12 @@ import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressIndicator; import javafx.scene.image.Image; -import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.scene.text.TextAlignment; import javafx.stage.Stage; import nsusbloader.AppPreferences; import nsusbloader.MediatorControl; @@ -41,11 +40,6 @@ import java.util.ResourceBundle; public class FilesDropHandle { public FilesDropHandle(List<File> files, String filesRegex, String foldersRegex){ - - // - // TODO: ADD GRAPHICS BEFORE RELEASE ! - // - FilesDropHandleTask filesDropHandleTask = new FilesDropHandleTask(files, filesRegex, foldersRegex); ResourceBundle resourceBundle = MediatorControl.getInstance().getResourceBundle(); @@ -54,9 +48,10 @@ public class FilesDropHandle { ProgressIndicator progressIndicator = new ProgressIndicator(); progressIndicator.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS); - Label downloadStatusLabel = new Label(); - downloadStatusLabel.setWrapText(true); - downloadStatusLabel.textProperty().bind(filesDropHandleTask.messageProperty()); + Label statusLabel = new Label(); + statusLabel.setWrapText(true); + statusLabel.setTextAlignment(TextAlignment.CENTER); + statusLabel.textProperty().bind(filesDropHandleTask.messageProperty()); Pane fillerPane1 = new Pane(); Pane fillerPane2 = new Pane(); @@ -68,12 +63,12 @@ public class FilesDropHandle { parentVBox.setPadding(new Insets(5.0)); parentVBox.setFillWidth(true); parentVBox.getChildren().addAll( - downloadStatusLabel, + statusLabel, fillerPane1, progressIndicator, fillerPane2, cancelButton - ); // TODO:FIX + ); VBox.setVgrow(fillerPane1, Priority.ALWAYS); VBox.setVgrow(fillerPane2, Priority.ALWAYS); @@ -81,10 +76,10 @@ public class FilesDropHandle { Stage stage = new Stage(); stage.setTitle(resourceBundle.getString("windowTitleAddingFiles")); stage.getIcons().addAll( - new Image("/res/dwnload_ico32x32.png"), //TODO: REDRAW - new Image("/res/dwnload_ico48x48.png"), - new Image("/res/dwnload_ico64x64.png"), - new Image("/res/dwnload_ico128x128.png") + new Image("/res/info_ico32x32.png"), + new Image("/res/info_ico48x48.png"), + new Image("/res/info_ico64x64.png"), + new Image("/res/info_ico128x128.png") ); stage.setMinWidth(300); stage.setMinHeight(175); diff --git a/src/main/resources/res/info_ico128x128.png b/src/main/resources/res/info_ico128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..96a9b2dc903b167a017c88b3d7df07747a6d2fcc GIT binary patch literal 14485 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/res/info_ico32x32.png b/src/main/resources/res/info_ico32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..75e4f7c44a858f440c8d2d042d1bfd28933fe7d7 GIT binary patch literal 8245 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 RN$jEpSeV+F6rztD{}%$iVnhG{ literal 0 HcmV?d00001 diff --git a/src/main/resources/res/info_ico48x48.png b/src/main/resources/res/info_ico48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4ea83bb07c3a00caf13b8e78f44c78a1d5ccad GIT binary patch literal 11772 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/res/info_ico64x64.png b/src/main/resources/res/info_ico64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..02d3e3d1b0fe492428e9d6c79e3ecc930e6a9999 GIT binary patch literal 12318 zcmeHsXH-<nwr-P?A~_>XBRMrW=bUpA0h^|QCN(xq&N*j9f`}kFBS|EKAVGpilpqR- zib&4E+qn1H$Mf!a=e{$>yZ=s)v3jkl`sO#QzM8Yvs<mSEb=7VX(h&jxfSVfX%7*B( z{Pls4i~g^GKUf9;sPO!Z%~6IBZ=gF8VGnnK0a3p0Fd)naZVv$XysyrId##G6MEqQ( z@W8gkOA0^iXzgJL?gVho*L1X1F1ik7;A6HbE3@67`FXi5`m?W#!dZa%bDEPT^-sl{ z(;~|mn^&{r2>;0a^9!PJwTsL3m%hO(FV=hykMpNxW~pa=5&2!4`y}UQSI$po=@)#c z<zyYVT9>=5x5q<j0;DE{un^nqHv6p~X$RXJRL7@Y3q3Q|x_aIy@q|5k!|?P(?&Q0n zwkTc@p7;7=uI>0X#epYA<cFuySAw<d9enLqL4nf(>@Dqw)LnVr$2^yp!K*K&y7>F3 zYdwa1gZUz%Cq!m~IJh;(-ECF~Xi)QLbYjrnwqE98#1KJv%j_~Vn4D?b`$L49+qh@2 z%}*MR4x6qQhn`CVh??_b=;9;Vhso@1<3f82nf%UIYttPL^Zt4Kxf6=EqhBqioH;ib zZN5LBY31EbKb6~)&Ceg!Yke=Wb09nmd?p!eyt(tKxkq>5d-G;Hu~e3=`v}ZBkA5uQ z8OO$0Y)33;Cb@nAW^;KE37~luxAEM6y#L9@%>IiW(1r-_2UKPch0-?D{%P{hz5OM5 ze{P4Wj^PE<HzS;d*-_G^9|DM?=Pu_cl5?Q8=b{H2!N{uD;k#L_b=_Wv{xP?=)LeW| zE#{d{32xcWOXp*NNyn442mGYnSvQ~Q91V}boa>=Ia*5eeO!r#s&FP=zzL~_?CLd!a z@)13@^Y7d&Y~v4qA`UuR6RIii{8&W-`k0QTZ>bb78$|<+lgKoOYRPi;84hcJ7oU$a zzkZAyW1U@$<1cuXrpZ@4HCa))40u<cmfSRT*K)NWF_qq1tn=+%@1^4jsGiL6q%$IX zhZgcGRrNM(>TOBxvNoA-Nw!0_XEThtps_cOySjb2qS95-^_<ab5x!yNwY0U>^t#ff z<EVNgXiMbWXOvORx3Bi%i0+X`&`k&qyMb&Ul+RrH$vOt+`ymIjdS-FonsgD|WoGU| zXr{qfw(0G@aEJwUOW^sBJ`cHrT!M9*)6u|936|N*o8z>tKj_B;w_BKeT;pyG?9L4- zdL$4MV?sS^C+{KVDmTc^Wlg_5*v{Om3QSzOwAKz9pZB--Vi|9BaH`Dt>U=|_Ew#bh z^|i^y^rVx(8U-P~$Rizp=uo@;a^*}O8I~Z|t=ia)^~TS_M0*Po+jCy{Oh)xv+Bi46 z?dYI$4uY$1SdS%-?)f`mRM}FlYQDU?9+uC2V0!#;gf4#kUhU{-;)5gbRt|S*rx*9z zD93tujl#Afg^b$1-~ORUQVKJ^ePddlpU%7OJX>hDH6fg(GU;QstkCjE!tK{QScV_T zgnIQ-RlfTrFby2}1oF2;ZwBsR)B7)r^0P%EyoOk6r(59hVr{p~j(4iQuLQ-84u#(6 z+dpg;)+n@T&(Whc4U!@Nou}r?K0X*yBiP<>k^K-SVAC?Qq~zyz_%hwH;X~{8ggGgH ztctaHqI@F3{oE|}%pNSZKCy(=lu}~7!$zk?C^T$=Kw<``GheO-VKaKn1fUpUN-p>z zNJZo)=L`xNWRlz4&FadumP-Gyq?6u*-4!p@)1q!<coG1?>DsGVzBO65Jt>xt`0Uv) zd>qsE>M>>f#QBfWmHn~Y{@J}w8aN<~pn>t0i=3C`>@M@(Ds}DWeH^)3&ku4M)FlV& z#3XejVG-D^?^i|G^&?quTOpM_80t%ewkZ-)qp9W`SJ_#2baJDUx$VE#bywDZ&Z{^H zgx1~8%Y8X-)<Evw;HSB^oMX1wO}4V~uHT0okwnWP`pMGn5qr5%YNwjnQ|!mjCE;St z8V_(1#6s)qPm(stG2<Tak|f{Q#uj_GmOkaG!TD16w&RMmdbw_M<a;ksFG1G{k}s_3 zY_z%|y3AR!-CH&J`ZNAw6E>b(;=^yMqi0l|SY|)(fy3e}RfBbQnq+C-0zL-E08~kK z#7MgFF|U|0<=L6F^EK3h0n}wxZ0YG|O0Bm7Y=^!m_49!`afR&TziXFiWqspe>tT7* z{+`ksw!7J@RYppK%V7TW)pHzalGarfW??>~=RV@h+AUamt%Bhxc*`5bAq_am!(XGa zz6mwo@Ob$Gyu!-k9{N4xfiu^gyr_L<GpUe+Wr0onVP{i9*^eKaF|i&7=GC$le_1A5 z@LPTf%?ePzPb05_uRGK=;aX#|*$~;kLci%GRzFWxyZ$|<P@Kcdld4PcHpy)o;Ya+h zpWlvZJbs?xUK^?7qSQ=t{{e|8EeAuQdQ`;6?JUSoT(uJVcVS!DDxJHoq#+CQd!hG> zqU-GxZ;Q*3Vo2L^VaG66zdBO4=97v`!wjJ}@@<PKzBejl*Uhd{%ovxbr^rLQ##(!! z2x`nQtzS=irlNXug-Nj?KyTB4xgjE;;wpdm1UH6L(*j&{>@Xj6%aD(oxW!eh5wnZ^ z1oyce(;};zrNv+kA*+k#TL`OXbGQnfhKQe1)5=!m2e!A57wLzK?=I_K5e(|c!hKUN zR$ko2-p3l0yMG}Dbn8^wWeO8FI1-|f+ik8YPc*g^;;CxL3&}6HPF~bZvrnOS1Bj;H zC78VTB;huEOIh@_sH-PTd({XRF*rWRAQiPo@%d!l?M`7L#$p7h)DXi2d!T;phw~;j z|2v!|t8!*y@(qOsvZCm{H{Qc(*>-P;uOfECllQ&8Vk?pMVnj9#pa9N!1T+u&KsL-# zq!X1;!i^9-s&D&wmm<W4R?M+OLf>e#_Ba*=Ko~icvG2gXBsX!|>lB*WJcr?pEDUBP zBTrw~cr3mU9rz}<D*Fk)G4`CUjG#T)SH;gfDa1|4Oq;s=g+{c{H1r6E7BwFSZ_u^R zjkn+hLVSr;A3#znI`8Wk;4@>fP((=eVq<dN+?5+FSd<n1G~A7Kclacahi#r)tbhe| z2lio;x4L4Sj^);1W|ra!0yBhTD9rJK`Bs{jKh4hDq|S;p(L25uSQA$BdSrMM23wPe z1npD%%KoaDp6FV2x|3n|jRJ1lV<8BqjNF|~rk#hUEJJ5lonaB3;LKC&MlyG02#Euc z^_}iTRb<f}`vOcBo>+EwwiVUQ-H4D;75|NkR6eCXBg@e>2o=$se+D~^Iq+1cpK`EX z!_}bjc6e-&jfoXl|Dg-)&0K$~+6QYPqjW=QP%fKnKLK*dc$7rdNJK4ApteW&zK*c5 z;N4RTqus3z$+Yj*mq|0w$z5yN!`CsoS;+|+#e9#N$-U^7D2;;{ofB@WnnJKK@65y) zbx$#TfY<st0-r%G2&xBMIxtcG;pWc(*iv-_lI5gGJGyeRNWH<P^7l0s<-CT>8svpk z0c)hX)AEBkQDXk;cmTdsjWE8&_GIMirG+?Hf%`W87OyvuQ9vlIQyz0(K<h9zD>}9p zNJ$;toLl<5vPv~5QyOE$oSm{7^QCSw_mE7x#(i_GKpkrbCVK(ziP8gvQ{y-W;z}#= zNMdDloFZp#i%SjZtbZS;VD!vju=Ir$Sr1pE37^6wE<2Ac{kjmREA1VpvD`0>?nzU* zX)HR~;XZV{HXd3yb6zYeSoEg*E2NPoU7PuEx!{9!@|as(jS1Oi6j(eioyFQD$Q>Xo zQ<LaIJ}t%~*3g%<pO_m@fM9)r1#qcX)9~Um32CCsqX^6>A_h!mi>&PE<NPtbLmb16 zPbF=JOZ^;g`k!jSO1BF6zAh(hm1uu3IC#ZJFu9bPewcwlZZhMS@rAd$ej9fPtlGTu z{O}dsgiaQ=qft6?pX$!xaAgMdr|`>tWu~E+++hRH&(W9R`5i$DUu%J`S2CKxi5C0Y z203M}nkw>&8u21+2~GE~a-GY0-{7Z2z0y%iP#bz0mY9=ltR@xSSO6@2P${i7rTAg= zu~)bao{@gDzPs@~3ioFwg2mJ{=}18J2cEYs&*-r2dbMcnwQ!uCS<I&9sApUng0AAp zLT~P#XE?-~2ouMp79aY&8i(BD%63iSi)8`Tabl7{nvC29$v$G?r(tCf?Z#rNQQVVD z!rYJOG2<5#smI?h@`F?ckd#yMKQZH-0=PN?l9=T`iXckAW~^dShR+Y~_U#(-!bU-! z`Nq6Tt`WR0A?Dvj4SInd>5>jSo}swmp&6uGMIQ)FDPqasK9fsxHQ)SoC&S*@t52?l zMGoC4ncj}FlH-+6FAgo=r_8Fn&*HN)v+%_}zD0)QL)PK<lX-52SVFw2DShP!*{>MB z_I%gV$;szD|3KisUpjo{-Mf6DI<G~}SGiJ+mq0*N0U{MR);|J(@tmqS$+$i_+GEW; zEi6w57%Y%HF>B#^xvxowOH~Uq#^!qX_FecJjI)*)NtPx;F^(|}_A*i=9t+nhh+j;P zrD*6Z>dHX_yj8Tf^*Sd)q+nDi#sMpZ9mG^n_o-JVf`8}5^Ow65)!rPjC8i_fEwllN zAJ4yt=F9(xbxV6$QS#C4d9wDSVKr=v$R-=hNCLy#5lwh&y}Rt3pY(7tjIZE&m0DF= zU;T(4`hVR#Tp{Jeyd<C0EmoCz57e6RrcO|f>Ac}WL2^eSZRl$!59CgopC)ZsI}0_} z+*r7Yp0nsxs%anU?NX{KpKr7ay$H{YuB1UN8(H*sN>QWPrWfV!os%i=8)7)<`OB;0 z#;-h>wIb@ZY+`7fazY3{clA}xB}`dS*<8GdSr2TJHBK(}0h_Vem<xrG;Ft}To2Dob zd?X0Ic_BwQCNJ8W6;rxYBQ0f-O#f{w>fNT`d7^*}WIzk^NJ?5>hjmsSxD<O-Hb^?r zAygeB$GlCbs7g>BxrRk`vTO=vFMU0n3i8y=<B0K@JR{5TbegY4L}Z^D!J6e#8ESsk z+;qNDDg0S?(^)sCvK5<|ZBJF^)?2ggM@t4b_%d#+GkxZ6h?OW>zOYjEY9+^Ev=t`S zfl^ka6vT-G3U>|qr0VXfCotJ^rW~OFvtLc6wDJhnsLD#pq6v8D*S86RK?nVI1wv_S z#~M5)8VjuUb-KlsTn%;0@igAtJ;my^Zugu9tAZ5V^n(J=q~tGdbUAVzXYECzUY}?b z-X3<ltKL<1Qu$KNAb?;s%t3rq0k8B<FX7WJmAT8T7k(5cmeZack1U~tCM>K{hz8NL z7-ovx775}9Qy4v0E*A`#hIi+jz<lKj$_jhFKm#zJvgpvQN-`C_{?q7bE{tgm7eylA z{lsF&PW(3PI!+zNu7YJwg4G^knek87rZn!1D_+JV69x?x-8H2L>iYPdzQil}JcN)6 z%kZm$v22M^F989DqM64IRgAcbA$T!v+%I^y3YT9UjG08A&@AYKV+k?BpBWatT=SH5 zst9&2!VNRl7hZcNes&<E7k_RMpsz5UP=rCrgMV1HnC`abo1lWLfNg!K_l<4e$}emo zU)b^N-OHg1)9|YoZkqM>zTt<S597$5X^9q>jMh}DFII%Ad!;08GaPmJ7X!WZ%gL%Q z@X|0F+{71!J>=`*W+}q;1zl(CBGz`&L?h2qzZTx#q9M`9Vy-hT%REy;-HC^~Z$H-v zQ3gITtNEm9L%yfYm{6zp?%Z9x(pah`-&O)}(^!y0zldplAdFJK=}33f=^ejT;=Y2s z^SUClL`4qUD7>$EF%V(&(3XZ*m%)TW<`aQ5Dq@7L{Mnn-bc+1h3cGQaph5z!`fPQj z8Ju_WDKN!4395{`nHuA{QWMWFDjkXxRg%-*r;7DCP39*A&qHTl!^_rO4e2Y5JZ^G+ z#Jl%$55FY$kym_oLpX1>jS`OCCd)Ejnys2fnFBvf`TjDK{i$HdrbWj_xM*hKisKEF z#FL2D?u7>Y^Nsn7ieXlf3@=eUWhA|wvyrw1D|w;$Cx9>TMQDVLlXp*1;YAKfTY&_a zr4qka)jLl$DzcfEM0MxQt-PM8Cu2@jl>Fb`yu4K!Y1D^LnZ**GYcb@8OQ6oKmY2W( zb?#P0X$y3U(WGNBBQWD!+?Y|BVkr$*+^=^r4AgIFLF{eNjFIt#@OCwY9v-a8hO$5m zki|q<u<j9jd29c}g~H9Vz^h8)dX0j07fDOQd5Y?+$Fwo}CG9cP_pquff=e?S=vc|W zYN%Kg1yzTu3tn83aK~hABW3!MxbaMavoJQ=0`dcaH=>I`(qVX@-Ugo60SXZ)PUQjS z4}o4&34@aZlDm|wYzB#q+^*P7C!{Oyi5Q83UImf{s+X$*ZzuX}Kk6R8!;~SzH*k#H zKpwNT-%VRTkju767$K$9#S`G6mFWX|+%;tqF5AHmKWi@=D(v(<Z|H!!p5mWMa}MUH z%Aty7SE|^oOo|)45_rKc%52#nS&xzu=2laYjVmpQ?_Z8E+y?<A!;qsG_@kLcV_ivv z`d#VTI)h<;kC|-GNS41wV4T0dXX{A4ZTQw}fAZaZvXG{AKU3lWucoM(9{NapRiyNI zb78gX!K0Ttz3nObJG15R?Iq>yqQJMex2et^Gs(QqkDV%a6`!vAtj2!R$?SX#sq?N; zNkl~84SDok@5}i6tP>9fKR*=WXvNHM=*^nV)0$#~D*G)h{V?N>h;s)8H5~nv2Fs06 z4c>K7b#hPlZd(;m6QNposJq>#x$(XGn*!%F;`Cr_ZEQ~6AR_L)xRqosxNEXG;yy7U zVJ3FF#t+tz38z^t%l?z6ZyHP%ZC<6Q+^|Zt<H2PNHN`$EoHS4U8TFDHTbum=_kJT? ziUX;7&CRW#fnrf)hd+t+xp>69<HC!8=SE?5{VSHwuxCxXB#RPB*k};7x<8jkzV$he zIC0kM2VM%3xR~-wEo~^1`2<(gX+6MGFJvB1WqzJgf%`meu~yP1nj_5-FQm#TAUlxy zMfDN}_C}e1wUQBKwdZStovzWIk&7esLA~*sr_plEt*2DO*|Pd7JHW{Y(lKoYqo*`x z8^Z4|7Fq9^`B!jzJTk-DdeJcEm$L55N00sTql-r>M~fteZ+4$n?yL7$>qN`bxO8Xu z>>CYgPjBBGbAL~=+E*yMWWXyzw5od=y<Q$^=}FOL$Mg7Ou3SALj?2k-g$v^bSFal2 z^hBR=HI}WVV~Iekgl)^~3Ax5`6sn85Lw8B9Nz-7V6qMr<`Z|t(Dl9TJ7KP1<xqm?Q z&B`sWL`zDF+{F5c8LetG&Zfsn*@bn}<-zpDakztb@Z9xDOf2_j(9ZWDk>egARd0{h zv-K5=q5NA<4rhBBb|rmE-a#X_14~blJK8Nl4APsm;SQ^wJK7F2M~#dMdN44Ah>>K? zXsy09Ds(WFBrvkjkeAU8pd#%#pqGRn^wa4o>h&6UUrd@UI=kzmK!?)^o)7P3!|c_> z0k@H*3!_&eAKk~x#-f#pdM0cw<9+Z*w=w4W8}RxExq*P~Pa9v#Tux>CosLV5s^sT9 z;tu_E>t!8OJ5b=cSm0`shkYa86q|d@T6$hj^d0NB*#?z~G22SL7xvy8$x1yJc|uV4 zpK`K7M<Ydn+mK3EqN^*p=?8iGLyJ33002e=TuDh^LrLjxuY}Prf*%K_%Bc6rGxXb7 z7IRT5aL0(-Ip*=i#ECmm#_2Op5mrlP-8;k4p-ZEnR_ke6S$S<c-P3PjhG(WgashaN zL>Zz|$$mbfxckb-eKFbl__p^pAh_d=aRZb=Ut~m`^^+o78yB_>siGL}+vKuW+JU+h z={pQI$2+@+d;<;b<ChYC4<8Q!^5vyRP<^0f;FIM>)Y$neh-n7JCy!48;e6^BgP!y2 zVq-Ybm(oHmMMnFtAhrb)14#OY!U8fqQZ)*bX92+ju*iKXho*b5QU5*aDCGNL!C80` zFjR#{Ap$^alA#|JNmEV7$!6Hdbi8v*Dk$}yV5{c+HFJ3!t~;-9UEy`LiR`D7yBTs2 zqdMn~)F|+OU?%3c1~V;qtzzR#W0~C@u_?zA$}gI|dv^{0%K|Q6jb%<d-n}0HCB2t+ z>PZw&zm9K&9t)k7@POTk4ysx^M2;Vv0gB2i*KWGI<0nvY_%E$4S#5P(`j^aqxOFcM z`6PKL-4Xz}6%R+hqBqykmV_c)`5<-(TNs~@t2_D?J^&ys=i?56I>S&vTbKjfO$M~x z)&&B>?PNe^B069lcO{r3T-^@|GxF0lhWa@}CG0?QvV_t;l4t@~7zzUPadmO?ko1uO z{o<8GUtd@AgMhyvC}$avxsE<i34w$Gh53Z}z`QCxa4$iSEFn-DX=g8KsI2-Y1-c{y zazvrrCHeWiy}kLoh4>Ii2Yvww2?>6%Aitm>FB-w?;p>Ki`0%=Uuv}C8!J!QEfFj}U zC^*6mc+Cm1MR=lQKp=EG@K17dD^$e?UHPYj>+&yp50o9h2D%`O9uFG8F9-&U^MVC= z1ts`@w@0_?==^2v=JBVBXg&FTAnyDEd|-Z8*MG3^K&g2B-QPd8@GwSyDd9JSc_2KI zP?(Ar%nilzyHj@;PmkYydV0XFtA6#{#m=4|-Kk%mf45Q7(9!?P=2}JvxU2gwi)-}n zNIU3XICoE^%P))_lpp2-b47Q=1I;Y(4|o*Z{_hj?597IR`EMOTyZej(AJBi(>sMcX z=_;v=fO=j#)lilJUH4bg4grPRN&dPN5V5lp6GzX!n23lVuQ1Hko>#(F6v8VcE+GOH z5`aR5?S+4%(s1)YLENCQYbrE39~{jC69NmsY#|c7c4ES43K57sF9c#I#0wJ_mJksW z6%iB^7x;}r4+%$S0>tI_UR_h!p{c|KVYU*2A`o6XJ9Ll;!^A~-A-1*<UV9-42oxeA z2)4Hu`bBj;50VP{8Zsb3KJedb^j#n*dj!%|2BZyl^Yr<9gE8C{W`u%VizXln784N` zmq7Oo3>FakV|2gxOkqe5bdX<j3V`_p1%H{_K_ykuj1Y9N!CfH^Fn)J8hhGiX(;|r; z3|d&ob#9=Uf7PSMBB_LgK~M;!F#_Qt1G?r0UQ_-`DWLS9$s(zZfc`T41%}yO=i8qd zrwDQ2|5cRc|5xDu#$@D(@OJxu<M})EFBSzP${T@n)<f#qI>Df*f6epHz<)6rqSrbP z6w+7Ye;CyNfs_6tT-DLG2&C_C{*7P{{wV#iAi2PQNd*M{S_C8^&_Cq&fOx^|eoX;d z$3L2&ju1Bo7<#q+Gu8f95C11&EDpA{6%!N|;)O`SY<Y#n1fjeVF!U7+EGz*PwH1d6 zirW4oy9dG^<qbi?6dceofSzY`YW<pLAjhxh<NQaow<8Q4BivviNiZ1n$MgcF`L7q$ zzYS0Nddbnzk^Ef%>FXs)QsX-Ej6B`lUEna}KMM1=K>5GmezX5GDE}w(-(i1PD<Rx{ z(Mjuw((`uvm+t=w@DB!UI27jQf%sQl{~hv&EWh1%(0%@+4t+mE-yZq@x<CFI7T1~c zzxewz-2N9mpsD}k<loZwKXUy?u768`e+&Gd==zUb|CR#(7WhBW_5V#S!oTmZVQ%OL zJ#X~>SvbAmC3^3TYpbQE4A{8dhC^GP)}c=X?&=mE000sB^??D%c}#<@#6xN5sNk*L zAfTka<;79Xg07-PshFdb5U$soCcvMM=Rj{b$`N?|EN>P{Obr0wYilSg82h|m&$V)) z*r1HcN7x^YZ#M0Q=~&e&*}A+=e2iJF!iBlURZpm+k^y1`e$inu=}^z05)s?Nq2?40 z4Vx4$piYbdDU+v^<*+4&=}$(*P<#vPCJCa#OR#X68IW`K?~vQyKeW~8zCX^lo51|N zHSb}k#P;L;C;OfGrYZbKuQBC`cJS*MvWQLqJ;uG@l}B=+4Qdwn7mO2GR}(H|$5_rG zhuz^J(f;5@jkJ6`7p%;sf?FPV7;{YL7<EKtCHRWDz|#(S92K%*9avXoR$3Rae9}%c zrPjQjF@|#usrB1PFW0f!pYIz2IU>qxaCMYQ-c8Knv9REz-XCd_uyOYZ#j%du&h#xi zEKi_G6XFd2nLvlKMEa4}Rw(*^<GkQD`;;w&*T4Omj-G`+G0ksZr>et$A9H7Li$@|c zXJs}OD3^qty>4QFtxjN}g>(V;9gUHCv7-{;nw@Lwk2nKA5m0mmFS6J`SwdCfX?A?- zmV*{Du`;Rsmcw&ano4LdT-+q4gKj2+^dsesuvv3@KMeycbNZwij|z0ve%9O-f$RbB zLrqO0gQqLn?})dg^kHJo%;Lhc8ReS{&|ifTKZrhTc(pByy$l=C59k~U24ZVkSXcuB zA{mYt^LtlCiXA~+xD^CGK4-bCHSu_)grQ64=2V~8M|?3j)Td=6`(E?dKx|A%>)bQ* z@2b2Y40$^rD?F-^zuD1Y=e5n}PTr_QmqkBFYF%LP*#N)%2WHArXP&Vo{UjKveFMlk z2_AebJ30|{)BOA>P(>m@C`ZsciUZ8PlxAmjJ9cl1GitVm^>8iShniOrJKQ`Ld%jup zjrX#G>NBGqEH`QV6XDeLQo}-u7<rrbcHKEq2JTnVXYu@ndKNd<W3l=q8J|TmLe+<& zcPToMIgdbM7$=Jv-(P@xQ{2|8GyBZmeUB-9asICBCUISeoQ;kV8RMtcC?r?=H{R90 zl(e5m4s#?r6A;P#PDhuB;o;sOmz+WK8@2)t0D!vYTmWl-wn4b3h{ET{z=#gQC>$%O zB-25{uAg+jL|-I=iI6nl&?JnPH<GjnF8BgYen!MEjt7vx+xvtxFZc;#p)O@cIi`|g zGIOj2*{o0Pb27a<VLKGf)*5}?(x|~!s;n|PT^h24uU}CgkD<lyzAP;{3ORrRj)<yA zlUep%LR(bAyd~Gljc(Lhk9NmV-f&sWvJCoulwy9v_v>T_79b=+HFax?=KPq&bIBBo zhLI767G%Q>=yZu5){GGKJ=PpiltaFnmsxQm^t=ktXAAapMp5kVK#nnX2+K^dAp+C! zy$koO>F7vA0chugS3wTc1-Yw`<X(>^g-)41EyWfe?o7K`{mg^~C1wgm{}l{N6Z?DZ z_#5MGc=Ao4xkYo;O|h^tP|Fox)lNa<t(`OdnWd}R>V6|jXRVO4Y@spdQ2N#LV!Dxv z8cQf<*-k}O5;o_9R%&IPn|DnfZebQNNq^x36jyXhDG;1de0+Jcgx4Z$$B&)==T*>J ztH%#a5=Qj5@;8<d6RABnSol0bW)@wvLR5L8+L8Sh08MOR$1pmTr+}X$<}QhB;`viU zchewCpO!{6tiRIsVJc$g2rD#!9_JCK;@_6S%pkk$#rd-3sY@*8PZ8u1%t_QMOJ)%t zj7r|Ui-?muEI#!SA8CI&U}80yDI8;&w$4h|l|{Z*fMg1Uhv!*oc-^ocl+>pMF$@?P zLAwBqaz7q3=|982nlK-DHamBFr2xr~IaK14^NcqBS@vethcW`uAZJ`6t^oX}1yf2! z*^k!hgS?10jd11RZmQK;7f045?`ZM^K0RhR=y<BCWis?4J|(4O$J{ez2ixSNkJkD& z-*FQ6IbrSaH#&!s@Ma$YMH5#_;O+|#Kn91cBKM-$5CwkuFmFjY05eDEU5}9sq78Es zJ&#XrZve!f2!GK|;<qx=Ogbq2n%lj1g%Tf`e&ds!({zXFxR~|{7v9u#!Tf=op)Jaj zvIszgyV3}$m&kb<$e}dcNHdso!JBF4!4r$|hD^Vf@P`pLmpd5toHgeu)R^5>(@%&k zxI?_x#_yexsJaTAGbfRWgO_N(H*x{N-4;z%2L+FWLNA3+Owvl$l-V|pYdPMYm{`S} zhta`}_I|A2v8*XNfg*AO!!3RsSKjza^;)Pzs7=9FLesa^6(`HeY7BD+2j4)6jIqP| z{b9w5w^*G__oCq~0;T6o{G%F6pNf7|NtI1a&-(#dgGQ3bb!uoq`{8t>V)cRLB|FdN zF~Z#7q4M3GDik>5lf^Lus>)hKJyvZKPkaX$MZV<O^}VAwW}x!@N|wc@ZHg18IG-c( zMe9*vRG;HV@bZ}-?uXzHrgXu@$1<tHFMQsiCU}Pmu5LWzgJbyqV3?O?D<#8Uadwo? zl@Jo6^z$2Ni8KNPlvrL|xr99nr>(Kq;VZu3WzF#MAvfpj4;(vTP2{m#l`+>(glFE{ zz;l|wBMS#al-r#Fv8d+(sH*(=93V@9Qp>&bGaKZh7Jur_DTh@HarJY-Qo=6t_9EpR zHdX1dFHdCbnq5b7FEV%p2v9j`x*l}3@@lGs(m$o=W&C%l?#0ebMUWAO!XT_<U)Xi; zw;$hr-m%mw;3F30;>@xz6Gi?x7r6zca^;XOnZr0~M;}ko2gyV{ZAdVdLp*iJjC$N+ zmr8<dH6WxV1goD&-uaHs4;vPl{<|0xbP~i>IJoiTAq@CGF-<9rSQojXJ%CjJSuR}L z$8)y^oBE*L<Du!tdr!cv%#(B)@92E7%(113U2tTvCXdrX&g6zWq*XQ6F}l$Y9{>#% LUF90Z`(ghDYCZ{a literal 0 HcmV?d00001 From f5b0c1549fc451a6d71f479235c2951c2de3183f Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 10 Dec 2020 01:29:16 +0300 Subject: [PATCH 027/134] Remove empty lines in Arabic translation --- src/main/resources/locale_ar_AR.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties index 7a5e66b..1f5db48 100644 --- a/src/main/resources/locale_ar_AR.properties +++ b/src/main/resources/locale_ar_AR.properties @@ -69,5 +69,3 @@ btn_Close=\u0625\u063A\u0644\u0627\u0642 tab2_Cb_GlVersion=\u0625\u0635\u062F\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641" tab2_Cb_GLshowNspOnly=\u0627\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0625\u0645\u062A\u062F\u0627\u062F "\u0625\u0646 \u0625\u0633 \u0628\u064A" \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641". windowBodyPleaseStopOtherProcessFirst=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649 \u0642\u0628\u0644 \u0627\u0644\u0625\u0633\u062A\u0645\u0631\u0627\u0631. -tab2_Cb_foldersSelectorForRoms= -tab2_Cb_foldersSelectorForRomsDesc= From 48cdd4f83005b3ce93f00c8c0bda03af0d4a3de9 Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Sun, 13 Dec 2020 20:12:28 +0900 Subject: [PATCH 028/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 789b0f3..c447dd2 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -1,7 +1,9 @@ btn_OpenFile=\uD30C\uC77C \uC120\uD0DD +btn_OpenFolders=\uD30C\uC77C \uC120\uD0DD btn_Upload=NS\uC5D0 \uC5C5\uB85C\uB4DC tab3_Txt_EnteredAsMsg1=\uB2E4\uC74C\uACFC \uAC19\uC774 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4: tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD574 \uB8E8\uD2B8\uC774\uAC70\uB098 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. +tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uB8E8\uD2B8\uC774\uAC70\uB098 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD55C 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. tab3_Txt_FilesToUploadTitle=\uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C: tab3_Txt_GreetingsMessage=NS-USBloader\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4 tab3_Txt_NoFolderOrFileSelected=\uC120\uD0DD\uD55C \uD30C\uC77C \uC5C6\uC74C: \uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. @@ -69,3 +71,7 @@ btn_Close=\uB2EB\uAE30 tab2_Cb_GlVersion=GoldLeaf \uBC84\uC804 tab2_Cb_GLshowNspOnly=GoldLeaf\uC5D0\uC11C *.nsp \uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4. windowBodyPleaseStopOtherProcessFirst=\uACC4\uC18D\uD558\uAE30 \uC804\uC5D0 \uB2E4\uB978 \uD65C\uC131 \uD504\uB85C\uC138\uC2A4\uB97C \uC911\uC9C0\uD558\uC2ED\uC2DC\uC624. +tab2_Cb_foldersSelectorForRoms=ROM\uC744 \uAC1C\uBCC4\uC801\uC73C\uB85C \uC120\uD0DD\uD558\uB294 \uB300\uC2E0 ROM \uD30C\uC77C\uC774 \uC788\uB294 \uD3F4\uB354\uB97C \uC120\uD0DD\uD558\uC2ED\uC2DC\uC624. +tab2_Cb_foldersSelectorForRomsDesc='\uAC8C\uC784' \uD0ED\uC5D0\uC11C '\uD30C\uC77C \uC120\uD0DD' \uBC84\uD2BC \uB3D9\uC791 \uBCC0\uACBD: ROM \uD30C\uC77C\uC744 \uD558\uB098\uC529 \uC120\uD0DD\uD558\uB294 \uB300\uC2E0 \uD3F4\uB354\uB97C \uC120\uD0DD\uD558\uC5EC \uC9C0\uC6D0\uB418\uB294 \uBAA8\uB4E0 \uD30C\uC77C\uC744 \uD55C \uBC88\uC5D0 \uCD94\uAC00 \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. +windowTitleAddingFiles=\uD30C\uC77C \uAC80\uC0C9 \uC911... +windowBodyFilesScanned=\uC2A4\uCE94 \uB41C \uD30C\uC77C: %d\n\uCD94\uAC00 \uB420 \uAC83: %d From d759bf01dec884e8f747771834bfc6a80a38defd Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 14 Dec 2020 22:10:17 +0300 Subject: [PATCH 029/134] Update settings screenshot --- screenshots/4.png | Bin 35125 -> 41977 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/4.png b/screenshots/4.png index 632b199938241c48e2c5fd4aae17acc558305b37..525944f7fd13d0ec09ef2e3977fd2fbe2492f30f 100644 GIT binary patch literal 41977 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%0DpDho2~t@MjEviA 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 VvHbU73HWWD^(mAE+5Ezd{{UOJR@eXl literal 35125 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 From 4d92e536ae7a19982fa650bb76231326bbd344de Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 11 Jan 2021 00:30:46 +0300 Subject: [PATCH 030/134] Always open tab which had been opened before application was closed last time. --- pom.xml | 2 +- src/main/java/nsusbloader/AppPreferences.java | 3 ++ .../Controllers/NSLMainController.java | 38 ++++++++++++++++++- src/main/java/nsusbloader/NSLMain.java | 2 +- src/main/resources/NSLMain.fxml | 8 ++-- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index ac9c186..120c316 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>5.0-SNAPSHOT</version> + <version>5.1-SNAPSHOT</version> <url>https://github.com/developersu/ns-usbloader/</url> <description> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 0a7119a..b5caac3 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -135,4 +135,7 @@ public class AppPreferences { // NXDT // public String getNXDTSaveToLocation(){ return FilesHelper.getRealFolder(preferences.get("nxdt_saveto", System.getProperty("user.home"))); } public void setNXDTSaveToLocation(String value){ preferences.put("nxdt_saveto", value); } + + public String getLastOpenedTab(){ return preferences.get("recent_tab", ""); } + public void setLastOpenedTab(String tabId){ preferences.put("recent_tab", tabId); } } diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 21dc530..152af1d 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -40,6 +40,11 @@ public class NSLMainController implements Initializable { @FXML public ProgressBar progressBar; // Accessible from Mediator + @FXML + private TabPane mainTabPane; + @FXML + private Tab GamesTabHolder, RCMTabHolder, SMTabHolder; + @FXML private GamesController GamesTabController; // Accessible from Mediator | todo: incapsulate @FXML @@ -70,16 +75,22 @@ public class NSLMainController implements Initializable { if (result != null){ if (!result.get(0).isEmpty()) { SettingsTabController.getGenericSettings().setNewVersionLink(result.get(0)); - ServiceWindow.getInfoNotification(resourceBundle.getString("windowTitleNewVersionAval"), resourceBundle.getString("windowTitleNewVersionAval") + ": " + result.get(0) + "\n\n" + result.get(1)); + ServiceWindow.getInfoNotification( + resourceBundle.getString("windowTitleNewVersionAval"), + resourceBundle.getString("windowTitleNewVersionAval") + ": " + result.get(0) + "\n\n" + result.get(1)); } } else - ServiceWindow.getInfoNotification(resourceBundle.getString("windowTitleNewVersionUnknown"), resourceBundle.getString("windowBodyNewVersionUnknown")); + ServiceWindow.getInfoNotification( + resourceBundle.getString("windowTitleNewVersionUnknown"), + resourceBundle.getString("windowBodyNewVersionUnknown")); }); Thread updates = new Thread(updTask); updates.setDaemon(true); updates.start(); } + + openLastOpenedTab(); } /** @@ -123,5 +134,28 @@ public class NSLMainController implements Initializable { SplitMergeTabController.updatePreferencesOnExit(); // NOTE: This shit above should be re-written to similar pattern RcmTabController.updatePreferencesOnExit(); NXDTabController.updatePreferencesOnExit(); + + saveLastOpenedTab(); + } + + private void openLastOpenedTab(){ + String tabId = AppPreferences.getInstance().getLastOpenedTab(); + switch (tabId){ + case "GamesTabHolder": + mainTabPane.getSelectionModel().select(GamesTabHolder); + break; + case "RCMTabHolder": + mainTabPane.getSelectionModel().select(RCMTabHolder); + break; + case "SMTabHolder": + mainTabPane.getSelectionModel().select(SMTabHolder); + break; + } + } + private void saveLastOpenedTab(){ + String tabId = mainTabPane.getSelectionModel().getSelectedItem().getId(); + if (tabId == null || tabId.isEmpty()) + return; + AppPreferences.getInstance().setLastOpenedTab(tabId); } } diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index f3c9d98..6c26e1d 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v5.0"; + public static final String appVersion = "v5.1"; public static boolean isCli; @Override diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml index 635902f..398e4b7 100644 --- a/src/main/resources/NSLMain.fxml +++ b/src/main/resources/NSLMain.fxml @@ -19,9 +19,9 @@ Steps to roll NXDT functionality back: <children> <VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> - <TabPane prefHeight="200.0" prefWidth="200.0" side="LEFT" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS"> + <TabPane fx:id="mainTabPane" prefHeight="200.0" prefWidth="200.0" side="LEFT" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS"> <tabs> - <Tab closable="false"> + <Tab fx:id="GamesTabHolder" closable="false"> <content> <fx:include fx:id="GamesTab" source="GamesTab.fxml" VBox.vgrow="ALWAYS" /> </content> @@ -29,7 +29,7 @@ Steps to roll NXDT functionality back: <SVGPath content="M 19.953125 -0.00390625 C 19.945025 0.86963499 19.665113 1.3200779 19.117188 1.6679688 C 18.569261 2.0158596 17.688291 2.2107818 16.572266 2.2636719 C 14.340723 2.369428 11.22952 1.9408017 8.0175781 1.7636719 L 8.015625 1.7636719 C 5.8083004 1.6301338 3.850269 1.7145428 2.421875 2.328125 C 1.7074522 2.6350131 1.1147008 3.0945958 0.765625 3.7402344 C 0.41654922 4.3858729 0.3309909 5.1840438 0.50976562 6.0957031 L 0.515625 6.1269531 L 0.52539062 6.1582031 C 0.74516874 6.8214776 1.3043008 7.2559352 1.9550781 7.46875 C 2.6058554 7.6815648 3.3739015 7.7273729 4.2304688 7.703125 C 5.9436032 7.6546292 8.0253788 7.3082908 10.042969 7.1230469 C 12.060559 6.9378029 14.005763 6.9298258 15.349609 7.4511719 C 16.693456 7.972518 17.5 8.8951351 17.5 11 L 13.023438 11 L 10.837891 11 L 1 11 C 0.156581 11 -0.10111662 11.623241 -0.10351562 12.189453 C -0.10591562 12.759114 -0.10321863 13.218366 -0.10351562 13.919922 C -0.10353762 13.969822 -0.10345962 13.990156 -0.10351562 14.042969 C -0.10332363 14.492063 -0.10337263 14.807528 -0.10351562 15.150391 C -0.10359462 15.343113 -0.10438363 15.566512 -0.10351562 15.771484 C -0.10347763 15.861874 -0.10364063 15.971437 -0.10351562 16.064453 C -0.10316562 16.324274 -0.10337563 16.550139 -0.10351562 16.880859 C -0.10353662 16.930759 -0.10345962 16.951094 -0.10351562 17.003906 C -0.10317063 17.257044 -0.10337962 17.473987 -0.10351562 17.794922 C -0.10353762 17.844822 -0.10345962 17.863203 -0.10351562 17.916016 C -0.10380362 18.172294 -0.10461563 18.463892 -0.10351562 18.732422 C -0.10363862 18.825442 -0.10347763 18.935007 -0.10351562 19.025391 C -0.10359462 19.218111 -0.10438363 19.441511 -0.10351562 19.646484 C -0.10337262 19.98934 -0.10332563 20.306746 -0.10351562 20.755859 C -0.10353662 20.805759 -0.10345962 20.826093 -0.10351562 20.878906 C -0.10321564 21.580432 -0.10591562 22.037789 -0.10351562 22.607422 C -0.10111563 23.173608 0.156706 23.796875 1 23.796875 L 10.833984 23.796875 L 13.019531 23.796875 L 22.857422 23.796875 C 23.70084 23.796875 23.958538 23.17361 23.960938 22.607422 C 23.963338 22.037788 23.960642 21.580432 23.960938 20.878906 C 23.960959 20.829006 23.96088 20.808672 23.960938 20.755859 C 23.960749 20.306747 23.960795 19.989341 23.960938 19.646484 C 23.961015 19.453769 23.961803 19.230374 23.960938 19.025391 C 23.9609 18.935011 23.961059 18.825438 23.960938 18.732422 C 23.96059 18.472592 23.960799 18.246723 23.960938 17.916016 C 23.960959 17.866116 23.96088 17.847735 23.960938 17.794922 C 23.960642 17.093366 23.963337 16.63411 23.960938 16.064453 C 23.96106 15.971433 23.960898 15.861868 23.960938 15.771484 C 23.961015 15.578768 23.961803 15.355372 23.960938 15.150391 C 23.960797 14.807528 23.960748 14.492063 23.960938 14.042969 C 23.960959 13.993069 23.96088 13.972735 23.960938 13.919922 C 23.960642 13.218366 23.963337 12.759114 23.960938 12.189453 C 23.958538 11.623242 23.700715 11 22.857422 11 L 18.5 11 C 18.5 8.6048649 17.347496 7.152482 15.710938 6.5175781 C 14.074378 5.8826742 12.017906 5.9371971 9.9511719 6.1269531 C 7.8844382 6.3167092 5.7997949 6.6578708 4.2011719 6.703125 C 3.4018604 6.7257521 2.725744 6.6699978 2.265625 6.5195312 C 1.8171096 6.3728594 1.6083191 6.1804127 1.4941406 5.859375 C 1.3628245 5.141091 1.4300216 4.6115935 1.6445312 4.2148438 C 1.8648981 3.8072608 2.2462454 3.4910124 2.8164062 3.2460938 C 3.9567281 2.7562562 5.8156963 2.6320489 7.9570312 2.7617188 L 7.9589844 2.7617188 C 11.116926 2.9357557 14.220255 3.3773586 16.619141 3.2636719 C 17.818583 3.2068289 18.856796 3.0180713 19.654297 2.5117188 C 20.451798 2.0053661 20.942623 1.130365 20.953125 0.00390625 L 19.953125 -0.00390625 z M 4.4277344 13.271484 L 7 13.271484 L 7 16 L 9.71875 16 L 9.71875 18.5625 L 7 18.5625 L 7 21.291016 L 4.4277344 21.291016 L 4.4277344 18.5625 L 1.7089844 18.5625 L 1.7089844 16 L 4.4277344 16 L 4.4277344 13.271484 z M 20 14 A 1.9161212 1.9161212 0 0 1 21.916016 15.916016 A 1.9161212 1.9161212 0 0 1 20 17.832031 A 1.9161212 1.9161212 0 0 1 18.083984 15.916016 A 1.9161212 1.9161212 0 0 1 20 14 z M 16.421875 17.667969 A 1.9168563 1.9168563 0 0 1 18.337891 19.583984 A 1.9168563 1.9168563 0 0 1 16.421875 21.5 A 1.9168563 1.9168563 0 0 1 14.505859 19.583984 A 1.9168563 1.9168563 0 0 1 16.421875 17.667969 z " /> </graphic> </Tab> - <Tab closable="false"> + <Tab fx:id="RCMTabHolder" closable="false"> <content> <fx:include fx:id="RcmTab" source="RcmTab.fxml" VBox.vgrow="ALWAYS" /> </content> @@ -37,7 +37,7 @@ Steps to roll NXDT functionality back: <SVGPath content="M 5.2753906 0.9453125 C 3.4702091 0.94491305 2.0128532 1.7453477 1.0566406 2.9082031 C 0.10042811 4.0710585 -0.40065633 5.5585011 -0.55664062 7.0488281 C -0.71262492 8.5391552 -0.52822452 10.042928 0.0078125 11.292969 C 0.54008474 12.534229 1.4899019 13.5834 2.8300781 13.826172 L 2.828125 13.837891 L 4.2050781 13.837891 L 4.6484375 11.080078 L 5.3496094 11.080078 L 5.9257812 13.837891 L 7.4042969 13.837891 L 7.4042969 13.753906 L 6.703125 10.685547 C 7.49408 10.281262 7.9297095 9.5624699 8.0097656 8.5292969 C 8.0610016 7.8485775 7.9209243 7.3118876 7.5878906 6.9179688 C 7.254857 6.5240499 6.7748288 6.3176076 6.1503906 6.296875 L 4.0371094 6.2910156 L 3.0976562 12.150391 C 2.4734416 12.023142 1.945837 11.518943 1.5625 10.625 C 1.1696133 9.7087867 0.99863233 8.4506302 1.1269531 7.2246094 C 1.2552739 5.9985885 1.6798073 4.8135983 2.3632812 3.9824219 C 3.0467553 3.1512454 3.9413986 2.6383771 5.2734375 2.6386719 L 20.007812 2.640625 C 20.496454 2.6407331 20.818797 2.788345 21.136719 3.0976562 C 21.454641 3.4069676 21.743658 3.910529 21.949219 4.5761719 C 22.36034 5.9074576 22.421621 7.8407685 22.128906 9.7714844 C 21.836191 11.7022 21.195943 13.639966 20.339844 15.023438 C 19.483744 16.406908 18.498727 17.154297 17.46875 17.154297 L -0.59375 17.154297 L -0.59375 18.845703 L 17.46875 18.845703 C 19.298148 18.845703 20.755291 17.568872 21.779297 15.914062 C 22.803302 14.259253 23.481257 12.145818 23.802734 10.025391 C 24.124212 7.904966 24.093647 5.7854271 23.566406 4.078125 C 23.302786 3.2244739 22.911503 2.4618437 22.318359 1.8847656 C 21.725216 1.3076876 20.907952 0.94941793 20.007812 0.94921875 L 5.2753906 0.9453125 z M 11.574219 6.1875 C 10.831297 6.1702229 10.207831 6.4450285 9.7050781 7.0117188 C 9.2055276 7.578409 8.8809744 8.3951633 8.7304688 9.4628906 L 8.5527344 10.712891 C 8.5207119 10.975503 8.5072674 11.234984 8.5136719 11.494141 C 8.5328854 12.254335 8.7132962 12.848871 9.0527344 13.277344 C 9.3921725 13.705817 9.8729047 13.927585 10.494141 13.941406 C 11.217848 13.962139 11.814426 13.735112 12.285156 13.261719 C 12.759089 12.78487 13.038539 12.137296 13.125 11.318359 L 11.775391 11.328125 C 11.698537 11.846439 11.565182 12.208239 11.373047 12.412109 C 11.180912 12.612524 10.923036 12.704777 10.599609 12.6875 C 10.080845 12.663312 9.8371182 12.277623 9.8691406 11.53125 C 9.8723429 11.403399 9.8965748 11.131448 9.9414062 10.716797 L 10.113281 9.4160156 C 10.190135 8.7145637 10.339592 8.209426 10.560547 7.8984375 C 10.781502 7.5839935 11.081823 7.4334439 11.462891 7.4472656 C 11.956037 7.4645428 12.209143 7.763238 12.21875 8.34375 L 12.208984 8.8574219 L 13.595703 8.8613281 C 13.595703 7.9974711 13.421311 7.3393799 13.072266 6.8867188 C 12.723221 6.4306022 12.224275 6.1978663 11.574219 6.1875 z M 14.869141 6.2910156 L 13.658203 13.837891 L 15.037109 13.837891 L 15.353516 11.847656 L 15.753906 8.5976562 L 16.28125 13.837891 L 17.21875 13.837891 L 19.361328 8.7675781 L 18.755859 11.748047 L 18.419922 13.837891 L 19.802734 13.837891 L 21.017578 6.2910156 L 19.201172 6.2910156 L 17.054688 11.716797 L 16.646484 6.2910156 L 14.869141 6.2910156 z M 5.2148438 7.5605469 L 6.09375 7.5664062 C 6.4491994 7.5940497 6.6336754 7.8344483 6.6464844 8.2871094 C 6.6496866 8.7466813 6.5554161 9.1146416 6.3632812 9.3945312 C 6.1711464 9.6709655 5.9072524 9.8134016 5.5742188 9.8203125 L 4.8496094 9.8105469 L 5.2148438 7.5605469 z" /> </graphic> </Tab> - <Tab closable="false"> + <Tab fx:id="SMTabHolder" closable="false"> <content> <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> <fx:include fx:id="SplitMergeTab" source="SplitMergeTab.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" VBox.vgrow="ALWAYS" /> From 79c519b1f3deb628df4da7a2c80b12973926b286 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 11 Jan 2021 00:44:37 +0300 Subject: [PATCH 031/134] Hotfix for 'open recent tab' feature: if non-functional tab selected then 'Games' (default) tab would be opened. --- src/main/java/nsusbloader/Controllers/NSLMainController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 152af1d..618cee9 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -155,7 +155,7 @@ public class NSLMainController implements Initializable { private void saveLastOpenedTab(){ String tabId = mainTabPane.getSelectionModel().getSelectedItem().getId(); if (tabId == null || tabId.isEmpty()) - return; + tabId = ""; AppPreferences.getInstance().setLastOpenedTab(tabId); } } From 62de68ba89a94a2e4c4b36ad79d896e2a99d1399 Mon Sep 17 00:00:00 2001 From: Calin Ilie <calin@ilie.io> Date: Wed, 28 Jul 2021 22:51:14 +0100 Subject: [PATCH 032/134] Add Romanian translation --- src/main/resources/locale copy.properties | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/main/resources/locale copy.properties diff --git a/src/main/resources/locale copy.properties b/src/main/resources/locale copy.properties new file mode 100644 index 0000000..df92a06 --- /dev/null +++ b/src/main/resources/locale copy.properties @@ -0,0 +1,77 @@ +btn_OpenFile=Selectează fișierele +btn_OpenFolders=Selectează directorul +btn_Upload=Încarcă în NS +btn_OpenFolders_tooltip=Alege un director pentru a fi scanat. \nAcest director și toate subdirectoarele lui vor fi scanate. \nToate fișierele găsite vor fi adăugate la listă. +tab3_Txt_EnteredAsMsg1=Ai fost adăugat ca: +tab3_Txt_EnteredAsMsg2=Ar trebui să fii 'root' sau să fi configurat reguli 'udev' pentru acest utilizator pentru a nu întâmpina probleme. +tab3_Txt_FilesToUploadTitle=Fișiere de încărcat: +tab3_Txt_GreetingsMessage=Bine ai venit în NS-USBloader +tab3_Txt_NoFolderOrFileSelected=Nici un fișier selectat: nimic de încărcat. +windowBodyConfirmExit=Transferul de date este în desfășurare și închiderea acestei aplicații îl va întrerupe. Este cel mai rău lucru pe care îl poți face acum. Întrerupi procesul și ieși? +windowTitleConfirmExit=Nu, nu face asta! +btn_Stop=Întrerupere +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://github.com/developersu/ns-usbloader/\n\ +Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=Status +tab1_table_Lbl_FileName=Numele fișierului +tab1_table_Lbl_Size=Mărime +tab1_table_Lbl_Upload=Încarcă? +tab1_table_contextMenu_Btn_BtnDelete=Elimină +tab1_table_contextMenu_Btn_DeleteAll=Elimină tot +tab2_Lbl_HostIP=IP-ul hostului: +tab1_Lbl_NSIP=IP-ul NS-ului: +tab2_Cb_ValidateNSHostName=Verifică de fiecare dată IP-ul NS-ului. +windowBodyBadIp=Ești sigur că ai introdus adresa IP a NS-ului corect? +windowTitleBadIp=Adresa IP NS-ului a fost cel mai probabil introdusă incorect +tab2_Cb_ExpertMode=Mod expert (NET setup) +tab2_Lbl_HostPort=port +tab2_Cb_AutoDetectIp=Auto-detectează IP +tab2_Cb_RandSelectPort=Alege portul aleatoriu +tab2_Cb_DontServeRequests=Nu servii request-uri +tab2_Lbl_DontServeRequestsDesc=Dacă selectat, acest computer nu va răspunde la request-uri de fișiere NSP venite de la NS (pe rețea) și va folosi setările de host definite pentru a îi comunica lui Tinfoil unde să caute fișierele. +tab2_Lbl_HostExtra=extra +windowTitleErrorPort=Port setat incorect! +windowBodyErrorPort=Portul nu poate fii 0 sau mai mare de 65535. +tab2_Cb_AutoCheckForUpdates=Verifică automat pentru actualizări +windowTitleNewVersionAval=O nouă actualizare disponibilă +windowTitleNewVersionNOTAval=Nu există actualizări disponibile +windowTitleNewVersionUnknown=Nu pot verifica actualizările disponibile +windowBodyNewVersionUnknown=A apărut o problemă.\n Poate nu ești conectat la Internet, sau GitHub e mort. +windowBodyNewVersionNOTAval=Folosești cea mai nouă actualizare +tab2_Cb_AllowXciNszXcz=Adaugă fișiere XCI / NSZ / XCZ pentru selecție în Tinfoil +tab2_Lbl_AllowXciNszXczDesc=Folosit de aplicații care suportă XCI/NSZ/XCZ și folosesc protocolul de transfer al lui Tinfoil. Nu schimba dacă nu ești sigur. Bifează pentru Awoo Installer. +tab2_Lbl_Language=Limbă +windowBodyRestartToApplyLang=Te rog restartează aplicația pentru a aplica setările noi. +btn_OpenSplitFile=Selectează pentru NSP fracționat +tab2_Lbl_ApplicationSettings=Setări principale +tabSplMrg_Lbl_SplitNMergeTitle=Unealtă pentru împărțire și lipire de fișiere +tabSplMrg_RadioBtn_Split=Împarte +tabSplMrg_RadioBtn_Merge=Lipește +tabSplMrg_Txt_File=Fișier: +tabSplMrg_Txt_Folder=Împarte Fișier (director): +tabSplMrg_Btn_SelectFile=Alege Fișier +tabSplMrg_Btn_SelectFolder=Alege Director +tabSplMrg_Lbl_SaveToLocation=Salvează la: +tabSplMrg_Btn_ChangeSaveToLocation=Schimbă +tabSplMrg_Btn_Convert=Convertesțe +windowTitleError=Eroare +windowBodyPleaseFinishTransfersFirst=Nu pot împărți/lipii fișierele când un proces USB/Network este activ. Te rog întrerupe orice transfer activ înainte. +done_txt=Gata! +failure_txt=Eșuat +btn_Select=Selectează +btn_InjectPayloader=Injectează payload +tabNXDT_Btn_Start=Start! +tab2_Btn_InstallDrivers=Descarcă și instalează drivere +windowTitleDownloadDrivers=Descarcă și instalează drivere +windowBodyDownloadDrivers=Descarcă drivere (libusbK v3.0.7.0)... +btn_Cancel=Oprește +btn_Close=Închide +tab2_Cb_GlVersion=Versiune GoldLeaf +tab2_Cb_GLshowNspOnly=Arată doar fișiere *.nsp în GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Te rog oprește toate celălalte procese înainte. +tab2_Cb_foldersSelectorForRoms=Selectează directorul cu fișiere ROM în loc să selectezi fișiere ROM individual. +tab2_Cb_foldersSelectorForRomsDesc=Face ca 'Selectează fișierele' în tab-ul 'Games' să selecteze toate fișierele de-o dată în loc de a selecta fișiere ROM unul câte unul. +windowTitleAddingFiles=Caut fișiere... +windowBodyFilesScanned=Fișiere scanate: %d\nVor fi adăugate: %d \ No newline at end of file From ba1ba20d43569c9b1e4ba785f68ede1105ce42bd Mon Sep 17 00:00:00 2001 From: Calin Ilie <calin@ilie.io> Date: Wed, 28 Jul 2021 23:00:24 +0100 Subject: [PATCH 033/134] Convert Romanian translation to Java entities --- src/main/resources/locale copy.properties | 77 ---------------------- src/main/resources/locale_ro_RO.properties | 77 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 77 deletions(-) delete mode 100644 src/main/resources/locale copy.properties create mode 100644 src/main/resources/locale_ro_RO.properties diff --git a/src/main/resources/locale copy.properties b/src/main/resources/locale copy.properties deleted file mode 100644 index df92a06..0000000 --- a/src/main/resources/locale copy.properties +++ /dev/null @@ -1,77 +0,0 @@ -btn_OpenFile=Selectează fișierele -btn_OpenFolders=Selectează directorul -btn_Upload=Încarcă în NS -btn_OpenFolders_tooltip=Alege un director pentru a fi scanat. \nAcest director și toate subdirectoarele lui vor fi scanate. \nToate fișierele găsite vor fi adăugate la listă. -tab3_Txt_EnteredAsMsg1=Ai fost adăugat ca: -tab3_Txt_EnteredAsMsg2=Ar trebui să fii 'root' sau să fi configurat reguli 'udev' pentru acest utilizator pentru a nu întâmpina probleme. -tab3_Txt_FilesToUploadTitle=Fișiere de încărcat: -tab3_Txt_GreetingsMessage=Bine ai venit în NS-USBloader -tab3_Txt_NoFolderOrFileSelected=Nici un fișier selectat: nimic de încărcat. -windowBodyConfirmExit=Transferul de date este în desfășurare și închiderea acestei aplicații îl va întrerupe. Este cel mai rău lucru pe care îl poți face acum. Întrerupi procesul și ieși? -windowTitleConfirmExit=Nu, nu face asta! -btn_Stop=Întrerupere -tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ -Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=Status -tab1_table_Lbl_FileName=Numele fișierului -tab1_table_Lbl_Size=Mărime -tab1_table_Lbl_Upload=Încarcă? -tab1_table_contextMenu_Btn_BtnDelete=Elimină -tab1_table_contextMenu_Btn_DeleteAll=Elimină tot -tab2_Lbl_HostIP=IP-ul hostului: -tab1_Lbl_NSIP=IP-ul NS-ului: -tab2_Cb_ValidateNSHostName=Verifică de fiecare dată IP-ul NS-ului. -windowBodyBadIp=Ești sigur că ai introdus adresa IP a NS-ului corect? -windowTitleBadIp=Adresa IP NS-ului a fost cel mai probabil introdusă incorect -tab2_Cb_ExpertMode=Mod expert (NET setup) -tab2_Lbl_HostPort=port -tab2_Cb_AutoDetectIp=Auto-detectează IP -tab2_Cb_RandSelectPort=Alege portul aleatoriu -tab2_Cb_DontServeRequests=Nu servii request-uri -tab2_Lbl_DontServeRequestsDesc=Dacă selectat, acest computer nu va răspunde la request-uri de fișiere NSP venite de la NS (pe rețea) și va folosi setările de host definite pentru a îi comunica lui Tinfoil unde să caute fișierele. -tab2_Lbl_HostExtra=extra -windowTitleErrorPort=Port setat incorect! -windowBodyErrorPort=Portul nu poate fii 0 sau mai mare de 65535. -tab2_Cb_AutoCheckForUpdates=Verifică automat pentru actualizări -windowTitleNewVersionAval=O nouă actualizare disponibilă -windowTitleNewVersionNOTAval=Nu există actualizări disponibile -windowTitleNewVersionUnknown=Nu pot verifica actualizările disponibile -windowBodyNewVersionUnknown=A apărut o problemă.\n Poate nu ești conectat la Internet, sau GitHub e mort. -windowBodyNewVersionNOTAval=Folosești cea mai nouă actualizare -tab2_Cb_AllowXciNszXcz=Adaugă fișiere XCI / NSZ / XCZ pentru selecție în Tinfoil -tab2_Lbl_AllowXciNszXczDesc=Folosit de aplicații care suportă XCI/NSZ/XCZ și folosesc protocolul de transfer al lui Tinfoil. Nu schimba dacă nu ești sigur. Bifează pentru Awoo Installer. -tab2_Lbl_Language=Limbă -windowBodyRestartToApplyLang=Te rog restartează aplicația pentru a aplica setările noi. -btn_OpenSplitFile=Selectează pentru NSP fracționat -tab2_Lbl_ApplicationSettings=Setări principale -tabSplMrg_Lbl_SplitNMergeTitle=Unealtă pentru împărțire și lipire de fișiere -tabSplMrg_RadioBtn_Split=Împarte -tabSplMrg_RadioBtn_Merge=Lipește -tabSplMrg_Txt_File=Fișier: -tabSplMrg_Txt_Folder=Împarte Fișier (director): -tabSplMrg_Btn_SelectFile=Alege Fișier -tabSplMrg_Btn_SelectFolder=Alege Director -tabSplMrg_Lbl_SaveToLocation=Salvează la: -tabSplMrg_Btn_ChangeSaveToLocation=Schimbă -tabSplMrg_Btn_Convert=Convertesțe -windowTitleError=Eroare -windowBodyPleaseFinishTransfersFirst=Nu pot împărți/lipii fișierele când un proces USB/Network este activ. Te rog întrerupe orice transfer activ înainte. -done_txt=Gata! -failure_txt=Eșuat -btn_Select=Selectează -btn_InjectPayloader=Injectează payload -tabNXDT_Btn_Start=Start! -tab2_Btn_InstallDrivers=Descarcă și instalează drivere -windowTitleDownloadDrivers=Descarcă și instalează drivere -windowBodyDownloadDrivers=Descarcă drivere (libusbK v3.0.7.0)... -btn_Cancel=Oprește -btn_Close=Închide -tab2_Cb_GlVersion=Versiune GoldLeaf -tab2_Cb_GLshowNspOnly=Arată doar fișiere *.nsp în GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Te rog oprește toate celălalte procese înainte. -tab2_Cb_foldersSelectorForRoms=Selectează directorul cu fișiere ROM în loc să selectezi fișiere ROM individual. -tab2_Cb_foldersSelectorForRomsDesc=Face ca 'Selectează fișierele' în tab-ul 'Games' să selecteze toate fișierele de-o dată în loc de a selecta fișiere ROM unul câte unul. -windowTitleAddingFiles=Caut fișiere... -windowBodyFilesScanned=Fișiere scanate: %d\nVor fi adăugate: %d \ No newline at end of file diff --git a/src/main/resources/locale_ro_RO.properties b/src/main/resources/locale_ro_RO.properties new file mode 100644 index 0000000..c32a837 --- /dev/null +++ b/src/main/resources/locale_ro_RO.properties @@ -0,0 +1,77 @@ +btn_OpenFile=Selecteaz\u0103 fi\u0219ierele +btn_OpenFolders=Selecteaz\u0103\u00A0directorul +btn_Upload=\u00CEncarc\u0103\u00A0\u00EEn NS +btn_OpenFolders_tooltip=Alege un director pentru a fi scanat. \nAcest director \u0219i toate subdirectoarele lui vor fi scanate. \nToate fi\u0219ierele g\u0103site vor fi ad\u0103ugate la list\u0103. +tab3_Txt_EnteredAsMsg1=Ai fost ad\u0103ugat ca: +tab3_Txt_EnteredAsMsg2=Ar trebui s\u0103 fii 'root' sau s\u0103 fi configurat reguli 'udev' pentru acest utilizator pentru a nu \u00EEnt\u00E2mpina probleme. +tab3_Txt_FilesToUploadTitle=Fi\u0219iere de \u00EEnc\u0103rcat: +tab3_Txt_GreetingsMessage=Bine ai venit \u00EEn NS-USBloader +tab3_Txt_NoFolderOrFileSelected=Nici un fi\u0219ier selectat: nimic de \u00EEnc\u0103rcat. +windowBodyConfirmExit=Transferul de date este \u00EEn desf\u0103\u0219urare \u0219i \u00EEnchiderea acestei aplica\u021Bii \u00EEl va \u00EEntrerupe. Este cel mai r\u0103u lucru pe care \u00EEl po\u021Bi face acum. \u00CEntrerupi procesul \u0219i ie\u0219i? +windowTitleConfirmExit=Nu, nu face asta! +btn_Stop=\u00CEntrerupere +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://github.com/developersu/ns-usbloader/\n\ +Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=Status +tab1_table_Lbl_FileName=Numele fi\u0219ierului +tab1_table_Lbl_Size=M\u0103rime +tab1_table_Lbl_Upload=\u00CEncarc\u0103? +tab1_table_contextMenu_Btn_BtnDelete=Elimin\u0103 +tab1_table_contextMenu_Btn_DeleteAll=Elimin\u0103 tot +tab2_Lbl_HostIP=IP-ul hostului: +tab1_Lbl_NSIP=IP-ul NS-ului: +tab2_Cb_ValidateNSHostName=Verific\u0103 de fiecare dat\u0103\u00A0IP-ul NS-ului. +windowBodyBadIp=E\u0219ti sigur c\u0103 ai introdus adresa IP a NS-ului corect? +windowTitleBadIp=Adresa IP NS-ului a fost cel mai probabil introdus\u0103 incorect +tab2_Cb_ExpertMode=Mod expert (NET setup) +tab2_Lbl_HostPort=port +tab2_Cb_AutoDetectIp=Auto-detecteaz\u0103 IP +tab2_Cb_RandSelectPort=Alege portul aleatoriu +tab2_Cb_DontServeRequests=Nu servii request-uri +tab2_Lbl_DontServeRequestsDesc=Dac\u0103\u00A0selectat, acest computer nu va r\u0103spunde la request-uri de fi\u0219iere NSP venite de la NS (pe re\u021Bea) \u0219i va folosi set\u0103rile de host definite pentru a \u00EEi comunica lui Tinfoil unde s\u0103 caute fi\u0219ierele. +tab2_Lbl_HostExtra=extra +windowTitleErrorPort=Port setat incorect! +windowBodyErrorPort=Portul nu poate fii 0 sau mai mare de 65535. +tab2_Cb_AutoCheckForUpdates=Verific\u0103 automat pentru actualiz\u0103ri +windowTitleNewVersionAval=O nou\u0103 actualizare disponibil\u0103 +windowTitleNewVersionNOTAval=Nu exist\u0103 actualiz\u0103ri disponibile +windowTitleNewVersionUnknown=Nu pot verifica actualiz\u0103rile disponibile +windowBodyNewVersionUnknown=A ap\u0103rut o problem\u0103.\n Poate nu e\u0219ti conectat la Internet, sau GitHub e mort. +windowBodyNewVersionNOTAval=Folose\u0219ti cea mai nou\u0103 actualizare +tab2_Cb_AllowXciNszXcz=Adaug\u0103 fi\u0219iere XCI / NSZ / XCZ pentru selec\u021Bie \u00EEn Tinfoil +tab2_Lbl_AllowXciNszXczDesc=Folosit de aplica\u021Bii care suport\u0103 XCI/NSZ/XCZ \u0219i folosesc protocolul de transfer al lui Tinfoil. Nu schimba dac\u0103\u00A0nu e\u0219ti sigur. Bifeaz\u0103 pentru Awoo Installer. +tab2_Lbl_Language=Limb\u0103 +windowBodyRestartToApplyLang=Te rog restarteaz\u0103 aplica\u021Bia pentru a aplica set\u0103rile noi. +btn_OpenSplitFile=Selecteaz\u0103 pentru NSP frac\u021Bionat +tab2_Lbl_ApplicationSettings=Set\u0103ri principale +tabSplMrg_Lbl_SplitNMergeTitle=Unealt\u0103 pentru \u00EEmp\u0103r\u021Bire \u0219i lipire de fi\u0219iere +tabSplMrg_RadioBtn_Split=\u00CEmparte +tabSplMrg_RadioBtn_Merge=Lipe\u0219te +tabSplMrg_Txt_File=Fi\u0219ier: +tabSplMrg_Txt_Folder=\u00CEmparte Fi\u0219ier (director): +tabSplMrg_Btn_SelectFile=Alege Fi\u0219ier +tabSplMrg_Btn_SelectFolder=Alege Director +tabSplMrg_Lbl_SaveToLocation=Salveaz\u0103 la: +tabSplMrg_Btn_ChangeSaveToLocation=Schimb\u0103 +tabSplMrg_Btn_Convert=Convertes\u021Be +windowTitleError=Eroare +windowBodyPleaseFinishTransfersFirst=Nu pot \u00EEmp\u0103r\u021Bi/lipii fi\u0219ierele c\u00E2nd un proces USB/Network este activ. Te rog \u00EEntrerupe orice transfer activ \u00EEnainte. +done_txt=Gata! +failure_txt=E\u0219uat +btn_Select=Selecteaz\u0103 +btn_InjectPayloader=Injecteaz\u0103\u00A0payload +tabNXDT_Btn_Start=Start! +tab2_Btn_InstallDrivers=Descarc\u0103\u00A0\u0219i instaleaz\u0103 drivere +windowTitleDownloadDrivers=Descarc\u0103\u00A0\u0219i instaleaz\u0103 drivere +windowBodyDownloadDrivers=Descarc\u0103\u00A0drivere (libusbK v3.0.7.0)... +btn_Cancel=Opre\u0219te +btn_Close=\u00CEnchide +tab2_Cb_GlVersion=Versiune GoldLeaf +tab2_Cb_GLshowNspOnly=Arat\u0103 doar fi\u0219iere *.nsp \u00EEn GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Te rog opre\u0219te toate cel\u0103lalte procese \u00EEnainte. +tab2_Cb_foldersSelectorForRoms=Selecteaz\u0103 directorul cu fi\u0219iere ROM \u00EEn loc s\u0103 selectezi fi\u0219iere ROM individual. +tab2_Cb_foldersSelectorForRomsDesc=Face ca 'Selecteaz\u0103 fi\u0219ierele' \u00EEn tab-ul 'Games' s\u0103 selecteze toate fi\u0219ierele de-o dat\u0103\u00A0\u00EEn loc de a selecta fi\u0219iere ROM unul c\u00E2te unul. +windowTitleAddingFiles=Caut fi\u0219iere... +windowBodyFilesScanned=Fi\u0219iere scanate: %d\nVor fi ad\u0103ugate: %d \ No newline at end of file From 1176ad9e8382dd46d7cba0bb4c6a0668abf21af8 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 9 Aug 2021 22:47:52 +0300 Subject: [PATCH 034/134] Solve - #87. Break LogPrinterGui --- .../Controllers/BlockListViewController.java | 102 ++++++++++ .../Controllers/NSLMainController.java | 44 +++-- .../Controllers/SplitMergeController.java | 68 +++---- .../ModelControllers/ILogPrinter.java | 4 +- .../ModelControllers/LogPrinterGui.java | 48 ++--- .../ModelControllers/MessagesConsumer.java | 56 +++--- src/main/java/nsusbloader/Utilities/Rcm.java | 43 ++-- .../Utilities/nxdumptool/NxdtTask.java | 17 +- .../Utilities/nxdumptool/NxdtUsbAbi1.java | 4 +- .../Utilities/splitmerge/MergeSubTask.java | 178 +++++++++++++++++ .../Utilities/splitmerge/MergeTask.java | 169 ---------------- .../MultithreadingPrintAdapter.java | 44 +++++ .../splitmerge/SplitMergeTaskExecutor.java | 147 ++++++++++++++ .../Utilities/splitmerge/SplitSubTask.java | 186 ++++++++++++++++++ .../Utilities/splitmerge/SplitTask.java | 173 ---------------- src/main/java/nsusbloader/cli/MergeCli.java | 25 ++- src/main/java/nsusbloader/cli/SplitCli.java | 28 +-- .../com/net/NETCommunications.java | 44 +++-- .../com/net/NetworkSetupValidator.java | 33 ++-- .../java/nsusbloader/com/usb/GoldLeaf_05.java | 102 +++++----- .../java/nsusbloader/com/usb/GoldLeaf_07.java | 84 ++++---- .../java/nsusbloader/com/usb/GoldLeaf_08.java | 80 ++++---- .../java/nsusbloader/com/usb/TinFoil.java | 48 ++--- .../nsusbloader/com/usb/TransferModule.java | 15 +- .../com/usb/UsbCommunications.java | 12 +- .../java/nsusbloader/com/usb/UsbConnect.java | 25 ++- src/main/resources/BlockListView.fxml | 10 + src/main/resources/SplitMergeTab.fxml | 71 +++---- src/main/resources/res/app_dark.css | 33 +++- src/main/resources/res/app_light.css | 29 +++ .../java/nsusbloader/com/usb/MergeTest.java | 86 ++++++++ .../java/nsusbloader/com/usb/SplitTest.java | 78 ++++++++ 32 files changed, 1348 insertions(+), 738 deletions(-) create mode 100644 src/main/java/nsusbloader/Controllers/BlockListViewController.java create mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/MergeSubTask.java delete mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/MergeTask.java create mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/MultithreadingPrintAdapter.java create mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/SplitMergeTaskExecutor.java create mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/SplitSubTask.java delete mode 100644 src/main/java/nsusbloader/Utilities/splitmerge/SplitTask.java create mode 100644 src/main/resources/BlockListView.fxml create mode 100644 src/test/java/nsusbloader/com/usb/MergeTest.java create mode 100644 src/test/java/nsusbloader/com/usb/SplitTest.java diff --git a/src/main/java/nsusbloader/Controllers/BlockListViewController.java b/src/main/java/nsusbloader/Controllers/BlockListViewController.java new file mode 100644 index 0000000..bba5d6b --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/BlockListViewController.java @@ -0,0 +1,102 @@ +/* + Copyright 2019-2021 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; + +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class BlockListViewController implements Initializable { + + @FXML + private ListView<File> splitMergeListView; + private ObservableList<File> filesList; + + private ResourceBundle resourceBundle; + + private static class FileListCell extends ListCell<File>{ + @Override + public void updateItem(File file, boolean isEmpty){ + super.updateItem(file, isEmpty); + + if (file == null || isEmpty){ + setText(null); + return; + } + String fileName = file.getName(); + setText(fileName); + } + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + setFilesListView(); + filesList = splitMergeListView.getItems(); + } + private void setFilesListView(){ + splitMergeListView.setCellFactory(fileListView -> { + ListCell<File> item = new FileListCell(); + setContextMenuToItem(item); + return item; + }); + } + + private <T> void setContextMenuToItem(ListCell<T> item){ + ContextMenu contextMenu = new ContextMenu(); + MenuItem deleteMenuItem = new MenuItem(resourceBundle.getString("tab1_table_contextMenu_Btn_BtnDelete")); + deleteMenuItem.setOnAction(actionEvent -> { + filesList.remove(item.getItem()); + splitMergeListView.refresh(); + }); + MenuItem deleteAllMenuItem = new MenuItem(resourceBundle.getString("tab1_table_contextMenu_Btn_DeleteAll")); + deleteAllMenuItem.setOnAction(actionEvent -> { + filesList.clear(); + splitMergeListView.refresh(); + }); + contextMenu.getItems().addAll(deleteMenuItem, deleteAllMenuItem); + + item.setContextMenu(contextMenu); + } + + public void add(File file){ + if (filesList.contains(file)) + return; + filesList.add(file); + } + public void addAll(List<File> files){ + for (File file : files) { + add(file); + } + } + public ObservableList<File> getItems(){ return filesList; } + public void clear(){ + filesList.clear(); + splitMergeListView.refresh(); + } +} diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 618cee9..99fa2d0 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -46,7 +46,7 @@ public class NSLMainController implements Initializable { private Tab GamesTabHolder, RCMTabHolder, SMTabHolder; @FXML - private GamesController GamesTabController; // Accessible from Mediator | todo: incapsulate + private GamesController GamesTabController; @FXML private SettingsController SettingsTabController; @FXML @@ -69,30 +69,32 @@ public class NSLMainController implements Initializable { MediatorControl.getInstance().setController(this); if (AppPreferences.getInstance().getAutoCheckUpdates()){ - Task<List<String>> updTask = new UpdatesChecker(); - updTask.setOnSucceeded(event->{ - List<String> result = updTask.getValue(); - if (result != null){ - if (!result.get(0).isEmpty()) { - SettingsTabController.getGenericSettings().setNewVersionLink(result.get(0)); - ServiceWindow.getInfoNotification( - resourceBundle.getString("windowTitleNewVersionAval"), - resourceBundle.getString("windowTitleNewVersionAval") + ": " + result.get(0) + "\n\n" + result.get(1)); - } - } - else - ServiceWindow.getInfoNotification( - resourceBundle.getString("windowTitleNewVersionUnknown"), - resourceBundle.getString("windowBodyNewVersionUnknown")); - }); - Thread updates = new Thread(updTask); - updates.setDaemon(true); - updates.start(); + checkForUpdates(); } openLastOpenedTab(); } - + private void checkForUpdates(){ + Task<List<String>> updTask = new UpdatesChecker(); + updTask.setOnSucceeded(event->{ + List<String> result = updTask.getValue(); + if (result != null){ + if (!result.get(0).isEmpty()) { + SettingsTabController.getGenericSettings().setNewVersionLink(result.get(0)); + ServiceWindow.getInfoNotification( + resourceBundle.getString("windowTitleNewVersionAval"), + resourceBundle.getString("windowTitleNewVersionAval") + ": " + result.get(0) + "\n\n" + result.get(1)); + } + } + else + ServiceWindow.getInfoNotification( + resourceBundle.getString("windowTitleNewVersionUnknown"), + resourceBundle.getString("windowBodyNewVersionUnknown")); + }); + Thread updates = new Thread(updTask); + updates.setDaemon(true); + updates.start(); + } /** * Get resources * TODO: Find better solution; used in UsbCommunications() -> GL -> SelectFile command diff --git a/src/main/java/nsusbloader/Controllers/SplitMergeController.java b/src/main/java/nsusbloader/Controllers/SplitMergeController.java index 59656ac..b7a6be9 100644 --- a/src/main/java/nsusbloader/Controllers/SplitMergeController.java +++ b/src/main/java/nsusbloader/Controllers/SplitMergeController.java @@ -18,6 +18,7 @@ */ package nsusbloader.Controllers; +import javafx.beans.binding.Bindings; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; @@ -33,11 +34,11 @@ import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.ServiceWindow; -import nsusbloader.Utilities.splitmerge.MergeTask; -import nsusbloader.Utilities.splitmerge.SplitTask; +import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; import java.io.File; import java.net.URL; +import java.util.List; import java.util.ResourceBundle; public class SplitMergeController implements Initializable { @@ -53,40 +54,39 @@ public class SplitMergeController implements Initializable { changeSaveToBtn, convertBtn; @FXML - private Label fileFolderLabelLbl, - fileFolderActualPathLbl, - saveToPathLbl, + private Label saveToPathLbl, statusLbl; + @FXML + private BlockListViewController BlockListViewController; + private ResourceBundle resourceBundle; private Region convertRegion; private Thread smThread; - private CancellableRunnable smTask; + private Runnable smTask; @Override public void initialize(URL url, ResourceBundle resourceBundle) { this.resourceBundle = resourceBundle; + convertRegion = new Region(); convertBtn.setGraphic(convertRegion); + convertBtn.disableProperty().bind(Bindings.isEmpty(BlockListViewController.getItems())); splitRad.setOnAction((actionEvent -> { statusLbl.setText(""); convertRegion.getStyleClass().clear(); convertRegion.getStyleClass().add("regionSplitToOne"); - fileFolderLabelLbl.setText(resourceBundle.getString("tabSplMrg_Txt_File")); selectFileFolderBtn.setText(resourceBundle.getString("tabSplMrg_Btn_SelectFile")); - fileFolderActualPathLbl.setText(""); - convertBtn.setDisable(true); + BlockListViewController.clear(); })); mergeRad.setOnAction((actionEvent -> { statusLbl.setText(""); convertRegion.getStyleClass().clear(); convertRegion.getStyleClass().add("regionOneToSplit"); - fileFolderLabelLbl.setText(resourceBundle.getString("tabSplMrg_Txt_Folder")); selectFileFolderBtn.setText(resourceBundle.getString("tabSplMrg_Btn_SelectFolder")); - fileFolderActualPathLbl.setText(""); - convertBtn.setDisable(true); + BlockListViewController.clear(); })); if (AppPreferences.getInstance().getSplitMergeType() == 0) @@ -110,32 +110,27 @@ public class SplitMergeController implements Initializable { selectFileFolderBtn.setOnAction(actionEvent -> { statusLbl.setText(""); + List<File> alreadyAddedFiles = BlockListViewController.getItems(); if (splitRad.isSelected()) { FileChooser fc = new FileChooser(); fc.setTitle(resourceBundle.getString("tabSplMrg_Btn_SelectFile")); - if (! fileFolderActualPathLbl.getText().isEmpty()){ - File temporaryFile = new File(fileFolderActualPathLbl.getText()).getParentFile(); - if (temporaryFile != null && temporaryFile.exists()) - fc.setInitialDirectory(temporaryFile); - else - fc.setInitialDirectory(new File(System.getProperty("user.home"))); + if (! alreadyAddedFiles.isEmpty()){ + String recentLocation = FilesHelper.getRealFolder(alreadyAddedFiles.get(0).getParentFile().getAbsolutePath()); + fc.setInitialDirectory(new File(recentLocation)); } else fc.setInitialDirectory(new File(System.getProperty("user.home"))); - File fileFile = fc.showOpenDialog(changeSaveToBtn.getScene().getWindow()); - if (fileFile == null) + List<File> files = fc.showOpenMultipleDialog(changeSaveToBtn.getScene().getWindow()); + if (files == null || files.isEmpty()) return; - fileFolderActualPathLbl.setText(fileFile.getAbsolutePath()); + this.BlockListViewController.addAll(files); } else{ DirectoryChooser dc = new DirectoryChooser(); dc.setTitle(resourceBundle.getString("tabSplMrg_Btn_SelectFolder")); - if (! fileFolderActualPathLbl.getText().isEmpty()){ - File temporaryFile = new File(fileFolderActualPathLbl.getText()); - if (temporaryFile.exists()) - dc.setInitialDirectory(temporaryFile); - else - dc.setInitialDirectory(new File(System.getProperty("user.home"))); + if (! alreadyAddedFiles.isEmpty()){ + String recentLocation = FilesHelper.getRealFolder(alreadyAddedFiles.get(0).getParentFile().getAbsolutePath()); + dc.setInitialDirectory(new File(recentLocation)); } else dc.setInitialDirectory(new File(System.getProperty("user.home"))); @@ -143,9 +138,8 @@ public class SplitMergeController implements Initializable { File folderFile = dc.showDialog(changeSaveToBtn.getScene().getWindow()); if (folderFile == null) return; - fileFolderActualPathLbl.setText(folderFile.getAbsolutePath()); + this.BlockListViewController.add(folderFile); } - convertBtn.setDisable(false); }); convertBtn.setOnAction(actionEvent -> setConvertBtnAction()); @@ -192,7 +186,7 @@ public class SplitMergeController implements Initializable { * */ private void stopBtnAction(){ if (smThread != null && smThread.isAlive()) { - smTask.cancel(); + smThread.interrupt(); } } /** @@ -209,9 +203,9 @@ public class SplitMergeController implements Initializable { } if (splitRad.isSelected()) - smTask = new SplitTask(fileFolderActualPathLbl.getText(), saveToPathLbl.getText()); + smTask = new SplitMergeTaskExecutor(true, BlockListViewController.getItems(), saveToPathLbl.getText()); else - smTask = new MergeTask(fileFolderActualPathLbl.getText(), saveToPathLbl.getText()); + smTask = new SplitMergeTaskExecutor(false, BlockListViewController.getItems(), saveToPathLbl.getText()); smThread = new Thread(smTask); smThread.setDaemon(true); smThread.start(); @@ -230,14 +224,16 @@ public class SplitMergeController implements Initializable { * */ @FXML private void handleDrop(DragEvent event) { - File fileDrpd = event.getDragboard().getFiles().get(0); + List<File> files = event.getDragboard().getFiles(); + File firstFile = files.get(0); - if (fileDrpd.isDirectory()) + if (firstFile.isDirectory()) mergeRad.fire(); else splitRad.fire(); - fileFolderActualPathLbl.setText(fileDrpd.getAbsolutePath()); - convertBtn.setDisable(false); + + this.BlockListViewController.addAll(files); + event.setDropCompleted(true); event.consume(); } diff --git a/src/main/java/nsusbloader/ModelControllers/ILogPrinter.java b/src/main/java/nsusbloader/ModelControllers/ILogPrinter.java index 09b1e8e..7c96ac4 100644 --- a/src/main/java/nsusbloader/ModelControllers/ILogPrinter.java +++ b/src/main/java/nsusbloader/ModelControllers/ILogPrinter.java @@ -26,8 +26,8 @@ import java.io.File; import java.util.HashMap; public interface ILogPrinter { - void print(String message, EMsgType type); - void updateProgress(Double value); + void print(String message, EMsgType type) throws InterruptedException; + void updateProgress(Double value) throws InterruptedException; void update(HashMap<String, File> nspMap, EFileStatus status); void update(File file, EFileStatus status); void updateOneLinerStatus(boolean status); diff --git a/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java b/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java index a18d544..d557964 100644 --- a/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java +++ b/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java @@ -32,9 +32,11 @@ public class LogPrinterGui implements ILogPrinter { private final MessagesConsumer msgConsumer; private final BlockingQueue<String> msgQueue; private final BlockingQueue<Double> progressQueue; - private final HashMap<String, EFileStatus> statusMap; // BlockingQueue for literally one object. TODO: read more books ; replace to hashMap + private final HashMap<String, EFileStatus> statusMap; private final AtomicBoolean oneLinerStatus; + /* TODO: Rewrite 'print()' implementation everywhere */ + LogPrinterGui(EModule whoIsAsking){ this.msgQueue = new LinkedBlockingQueue<>(); this.progressQueue = new LinkedBlockingQueue<>(); @@ -47,38 +49,30 @@ public class LogPrinterGui implements ILogPrinter { * This is what will print to textArea of the application. * */ @Override - public void print(String message, EMsgType type){ - try { - switch (type){ - case PASS: - msgQueue.put("[ PASS ] "+message+"\n"); - break; - case FAIL: - msgQueue.put("[ FAIL ] "+message+"\n"); - break; - case INFO: - msgQueue.put("[ INFO ] "+message+"\n"); - break; - case WARNING: - msgQueue.put("[ WARN ] "+message+"\n"); - break; - default: - msgQueue.put(message); - } - } - catch (InterruptedException ie){ - ie.printStackTrace(); + public void print(String message, EMsgType type) throws InterruptedException{ + switch (type){ + case PASS: + msgQueue.put("[ PASS ] "+message+"\n"); + break; + case FAIL: + msgQueue.put("[ FAIL ] "+message+"\n"); + break; + case INFO: + msgQueue.put("[ INFO ] "+message+"\n"); + break; + case WARNING: + msgQueue.put("[ WARN ] "+message+"\n"); + break; + default: + msgQueue.put(message); } } /** * Update progress for progress bar * */ @Override - public void updateProgress(Double value) { - try { - progressQueue.put(value); - } - catch (InterruptedException ignored){} // TODO: Do something with this + public void updateProgress(Double value) throws InterruptedException { + progressQueue.put(value); } /** * When we're done - update status diff --git a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java index 8baa5be..ba4b294 100644 --- a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java +++ b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java @@ -42,7 +42,7 @@ public class MessagesConsumer extends AnimationTimer { private final NSTableViewController tableViewController; private final EModule appModuleType; - private AtomicBoolean oneLinerStatus; + private final AtomicBoolean oneLinerStatus; private boolean isInterrupted; @@ -50,7 +50,7 @@ public class MessagesConsumer extends AnimationTimer { BlockingQueue<String> msgQueue, BlockingQueue<Double> progressQueue, HashMap<String, EFileStatus> statusMap, - AtomicBoolean oneLinerStatus) { + AtomicBoolean oneLinerStatus){ this.appModuleType = appModuleType; this.isInterrupted = false; @@ -72,15 +72,15 @@ public class MessagesConsumer extends AnimationTimer { } @Override - public void handle(long l) { + public void handle(long l){ ArrayList<String> messages = new ArrayList<>(); - int msgRecieved = msgQueue.drainTo(messages); - if (msgRecieved > 0) + int msgReceived = msgQueue.drainTo(messages); + if (msgReceived > 0) messages.forEach(logsArea::appendText); ArrayList<Double> progress = new ArrayList<>(); - int progressRecieved = progressQueue.drainTo(progress); - if (progressRecieved > 0) { + int progressReceived = progressQueue.drainTo(progress); + if (progressReceived > 0) { progress.forEach(prg -> { if (prg != 1.0) progressBar.setProgress(prg); @@ -89,29 +89,31 @@ public class MessagesConsumer extends AnimationTimer { }); } - if (isInterrupted) { // It's safe 'cuz it's could't be interrupted while HashMap populating - MediatorControl.getInstance().setBgThreadActive(false, appModuleType); - progressBar.setProgress(0.0); + if (isInterrupted) // It's safe 'cuz it's could't be interrupted while HashMap populating + updateElementsAndStop(); + } - if (statusMap.size() > 0){ - for (String key : statusMap.keySet()) - tableViewController.setFileStatus(key, statusMap.get(key)); - } + private void updateElementsAndStop(){ + MediatorControl.getInstance().setBgThreadActive(false, appModuleType); + progressBar.setProgress(0.0); - switch (appModuleType){ - case RCM: - MediatorControl.getInstance().getRcmController().setOneLineStatus(oneLinerStatus.get()); - break; - case NXDT: - MediatorControl.getInstance().getNxdtController().setOneLineStatus(oneLinerStatus.get()); - break; - case SPLIT_MERGE_TOOL: - MediatorControl.getInstance().getSplitMergeController().setOneLineStatus(oneLinerStatus.get()); - break; - } - - this.stop(); + if (statusMap.size() > 0){ + for (String key : statusMap.keySet()) + tableViewController.setFileStatus(key, statusMap.get(key)); } + + switch (appModuleType){ + case RCM: + MediatorControl.getInstance().getRcmController().setOneLineStatus(oneLinerStatus.get()); + break; + case NXDT: + MediatorControl.getInstance().getNxdtController().setOneLineStatus(oneLinerStatus.get()); + break; + case SPLIT_MERGE_TOOL: + MediatorControl.getInstance().getSplitMergeController().setOneLineStatus(oneLinerStatus.get()); + break; + } + this.stop(); } public void interrupt(){ diff --git a/src/main/java/nsusbloader/Utilities/Rcm.java b/src/main/java/nsusbloader/Utilities/Rcm.java index 3072944..33a9268 100644 --- a/src/main/java/nsusbloader/Utilities/Rcm.java +++ b/src/main/java/nsusbloader/Utilities/Rcm.java @@ -72,8 +72,8 @@ public class Rcm implements Runnable{ @Override public void run() { - logPrinter.print("Selected: "+filePath, EMsgType.INFO); - logPrinter.print("=============== RCM ===============", EMsgType.INFO); + print("Selected: "+filePath, EMsgType.INFO); + print("=============== RCM ===============", EMsgType.INFO); ECurrentOS ecurrentOS; String realOsName = System.getProperty("os.name").toLowerCase().replace(" ", ""); @@ -85,11 +85,11 @@ public class Rcm implements Runnable{ ecurrentOS = ECurrentOS.lin; else ecurrentOS = ECurrentOS.unsupported; - logPrinter.print("Found your OS: "+System.getProperty("os.name"), EMsgType.PASS); + print("Found your OS: "+System.getProperty("os.name"), EMsgType.PASS); if (! ecurrentOS.equals(ECurrentOS.mac)){ if (! RcmSmash.isSupported()){ - logPrinter.print("Unfortunately your platform '"+System.getProperty("os.name")+ + print("Unfortunately your platform '"+System.getProperty("os.name")+ "' of '"+System.getProperty("os.arch")+"' is not supported :("+ "\n But you could file a bug with request."+ "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); @@ -124,14 +124,14 @@ public class Rcm implements Runnable{ // Send payload for (int i=0; i < fullPayload.length / 4096 ; i++){ if (writeUsb(Arrays.copyOfRange(fullPayload, i*4096, (i+1)*4096))){ - logPrinter.print("Failed to sent payload ["+i+"]"+ + print("Failed to sent payload ["+i+"]"+ "\n\n Execution stopped.", EMsgType.FAIL); usbConnect.close(); logPrinter.close(); return; } } - logPrinter.print("Information sent to NS.", EMsgType.PASS); + print("Information sent to NS.", EMsgType.PASS); if (ecurrentOS.equals(ECurrentOS.mac)){ if (smashMacOS()){ @@ -149,7 +149,7 @@ public class Rcm implements Runnable{ retval = RcmSmash.smashWindows(); else { // ( ?_?) - logPrinter.print("Failed to smash the stack since your OS is not supported. Please report this issue."+ + print("Failed to smash the stack since your OS is not supported. Please report this issue."+ "\n\n Execution stopped and failed. And it's strange.", EMsgType.FAIL); usbConnect.close(); logPrinter.close(); @@ -157,18 +157,27 @@ public class Rcm implements Runnable{ } if (retval != 0){ - logPrinter.print("Failed to smash the stack ("+retval+")"+ + print("Failed to smash the stack ("+retval+")"+ "\n\n Execution stopped and failed.", EMsgType.FAIL); usbConnect.close(); logPrinter.close(); return; } } - logPrinter.print(".:: Payload complete ::.", EMsgType.PASS); + print(".:: Payload complete ::.", EMsgType.PASS); usbConnect.close(); logPrinter.updateOneLinerStatus(true); logPrinter.close(); } + + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException intr){ + intr.printStackTrace(); + } + } /** * Prepare the 'big' or full-size byte-buffer that is actually is a payload that we're about to use. * @return false for issues @@ -179,7 +188,7 @@ public class Rcm implements Runnable{ // 126296 b <- biggest size per CTCaer; 16384 selected randomly as minimum threshold. It's probably wrong. if (pldrFile.length() > 126296 || pldrFile.length() < 16384) { - logPrinter.print("File size of this payload looks wired. It's "+pldrFile.length()+" bytes."+ + print("File size of this payload looks wired. It's "+pldrFile.length()+" bytes."+ "\n 1. Double-check that you're using the right payload." + "\n 2. Please report this issue in case you're sure that you're doing everything right." + "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); @@ -194,7 +203,7 @@ public class Rcm implements Runnable{ totalSize += 4096; // Double-check if (totalSize > 0x30298){ - logPrinter.print("File size of the payload is too big. Comparing to maximum size, it's greater to "+(totalSize - 0x30298)+" bytes!"+ + print("File size of the payload is too big. Comparing to maximum size, it's greater to "+(totalSize - 0x30298)+" bytes!"+ "\n 1. Double-check that you're using the right payload." + "\n 2. Please report this issue in case you're sure that you're doing everything right." + "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); // Occurs: never. I'm too lazy to check. @@ -209,7 +218,7 @@ public class Rcm implements Runnable{ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(pldrFile)); int readSize; if ((readSize = bis.read(dataPldFile)) != pldFileSize){ - logPrinter.print("Failed to retrieve data from payload file." + + print("Failed to retrieve data from payload file." + "\n Got only "+readSize+" bytes while "+pldFileSize+" expected." + "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); bis.close(); @@ -218,7 +227,7 @@ public class Rcm implements Runnable{ bis.close(); } catch (Exception e){ - logPrinter.print("Failed to retrieve data from payload file: " +e.getMessage()+ + print("Failed to retrieve data from payload file: " +e.getMessage()+ "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); return true; } @@ -241,7 +250,7 @@ public class Rcm implements Runnable{ IntBuffer readBufTransferred = IntBuffer.allocate(1); int result = LibUsb.bulkTransfer(handler, (byte) 0x81, readBuffer, readBufTransferred, 1000); if (result != LibUsb.SUCCESS) { - logPrinter.print("Unable to get device ID" + + print("Unable to get device ID" + "\n\n Nothing has been sent to NS. Execution stopped.", EMsgType.FAIL); return true; } @@ -251,7 +260,7 @@ public class Rcm implements Runnable{ StringBuilder idStrBld = new StringBuilder("Found device with ID: "); for (byte b: receivedBytes) idStrBld.append(String.format("%02x ", b)); - logPrinter.print(idStrBld.toString(), EMsgType.PASS); + print(idStrBld.toString(), EMsgType.PASS); return false; } /** @@ -269,13 +278,13 @@ public class Rcm implements Runnable{ if (writeBufTransferred.get() == 4096) return false; - logPrinter.print("RCM Data transfer issue [write]" + + print("RCM Data transfer issue [write]" + "\n Requested: " + message.length + "\n Transferred: " + writeBufTransferred.get()+ "\n\n Execution stopped.", EMsgType.FAIL); return true; } - logPrinter.print("RCM Data transfer issue [write]" + + print("RCM Data transfer issue [write]" + "\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n\n Execution stopped.", EMsgType.FAIL); return true; diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java index b5cb872..4ea63b3 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtTask.java @@ -38,8 +38,8 @@ public class NxdtTask extends CancellableRunnable { @Override public void run() { - logPrinter.print("Save to location: "+ saveToLocation, EMsgType.INFO); - logPrinter.print("=============== nxdumptool ===============", EMsgType.INFO); + print("Save to location: "+ saveToLocation, EMsgType.INFO); + print("=============== nxdumptool ===============", EMsgType.INFO); UsbConnect usbConnect = UsbConnect.connectHomebrewMode(logPrinter); @@ -54,13 +54,22 @@ public class NxdtTask extends CancellableRunnable { new NxdtUsbAbi1(handler, logPrinter, saveToLocation, this); } catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); + print(e.getMessage(), EMsgType.FAIL); } - logPrinter.print(".:: Complete ::.", EMsgType.PASS); + print(".:: Complete ::.", EMsgType.PASS); usbConnect.close(); logPrinter.updateOneLinerStatus(true); logPrinter.close(); } + + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java index 613597d..b01a571 100644 --- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java +++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java @@ -119,7 +119,7 @@ class NxdtUsbAbi1 { USBSTATUS_SUCCESS[9] = (byte)((endpointMaxPacketSize >> 8) & 0xFF); } - private void readLoop(){ + private void readLoop() throws InterruptedException{ logPrinter.print("Awaiting for handshake", EMsgType.INFO); try { byte[] directive; @@ -292,7 +292,7 @@ class NxdtUsbAbi1 { return nspFile != null; } - private String getAbsoluteFilePath(String filename) throws Exception{ + private String getAbsoluteFilePath(String filename) { if (isRomFs(filename) && isWindows) // Since RomFS entry starts from '/' it should be replaced to '\'. return saveToPath + filename.replaceAll("/", "\\\\"); return saveToPath + filename; diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/MergeSubTask.java b/src/main/java/nsusbloader/Utilities/splitmerge/MergeSubTask.java new file mode 100644 index 0000000..2d1a27e --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/splitmerge/MergeSubTask.java @@ -0,0 +1,178 @@ +/* + Copyright 2019-2021 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Utilities.splitmerge; + +import nsusbloader.NSLDataTypes.EMsgType; + +import java.io.*; +import java.util.Arrays; +import java.util.concurrent.Callable; + +public class MergeSubTask implements Callable<Boolean> { + private final int id; + private final String saveToPath; + private final MultithreadingPrintAdapter printAdapter; + private final File splitFile; + + private File[] chunkFiles; + private long chunksTotalSize; + private File resultFile; + + public MergeSubTask(int id, File splitFile, String saveToPath, MultithreadingPrintAdapter printAdapter){ + this.id = id; + this.splitFile = splitFile; + this.saveToPath = saveToPath; + this.printAdapter = printAdapter; + } + + @Override + public Boolean call(){ + try{ + collectChunks(); + validateChunks(); + sortChunks(); + calculateChunksSizeSum(); + + createFile(); + mergeChunksToFile(); + validateFile(); + return true; + } + catch (InterruptedException ie){ + cleanup(); + return false; + } + catch (Exception e){ + e.printStackTrace(); + try { + printAdapter.print("["+id+"] "+e.getMessage(), EMsgType.FAIL); + } + catch (InterruptedException ignore) {} + return false; + } + } + + private void collectChunks(){ + chunkFiles = splitFile.listFiles((file, s) -> s.matches("^[0-9][0-9]$")); + } + + private void validateChunks() throws Exception{ + if (chunkFiles == null || chunkFiles.length == 0){ + throw new Exception("Selected folder doesn't have any chunks. Nothing to do here."); + } + } + + private void sortChunks(){ + Arrays.sort(chunkFiles); + } + + private void calculateChunksSizeSum() throws InterruptedException{ + StringBuilder builder = new StringBuilder("["+id+"] Next files will be merged in following order: "); + + for (File cnk : chunkFiles){ + builder.append(cnk.getName()); + builder.append(" "); + chunksTotalSize += cnk.length(); + } + printAdapter.print(builder.toString(), EMsgType.INFO); + } + + private void createFile() throws Exception{ + final String splitFileName = splitFile.getName(); + + resultFile = new File(saveToPath+File.separator+"!_"+splitFileName); + + for (int i = 0; i < 50 ; i++){ + if (interrupted()) + throw new InterruptedException(); + + if (resultFile.exists()){ + printAdapter.print("["+id+"] Trying to create a good new file...", EMsgType.WARNING); + resultFile = new File(saveToPath+File.separator+"!_"+i+"_"+splitFileName); + continue; + } + + printAdapter.print("["+id+"] Save results to: "+resultFile.getAbsolutePath(), EMsgType.INFO); + return; + } + throw new Exception("Can't create new file."); + } + + private void mergeChunksToFile() throws Exception{ + if ( interrupted()) + throw new InterruptedException(); + + try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(resultFile))){ + BufferedInputStream bis; + byte[] chunk; + int readBytesCnt; + + printAdapter.reportFileSize(chunksTotalSize); + + for (File chunkFile : chunkFiles){ + bis = new BufferedInputStream(new FileInputStream(chunkFile)); + while (true){ + chunk = new byte[4194240]; + readBytesCnt = bis.read(chunk); + + if (readBytesCnt < 4194240){ + if (readBytesCnt > 0) + bos.write(chunk, 0, readBytesCnt); + break; + } + + if (interrupted()) + throw new InterruptedException(); + + bos.write(chunk); + + printAdapter.updateProgressBySize(readBytesCnt); + } + bis.close(); + } + } + } + + private void validateFile() throws Exception{ + if ( interrupted()) + throw new Exception("Merge task interrupted!"); + + long resultFileSize = resultFile.length(); + printAdapter.print("["+id+"] Total chunks size: " + chunksTotalSize + +"\n Merged file size: " + resultFileSize, EMsgType.INFO); + + if (chunksTotalSize != resultFileSize) + throw new Exception("Sizes are different! Do NOT use this file for installations!"); + + printAdapter.print("["+id+"] Sizes are the same! Resulting file should be good!", EMsgType.PASS); + } + + private void cleanup(){ + boolean isDeleted = resultFile.delete(); + try { + printAdapter.print( + "[" + id + "] Merge task interrupted and file " + + (isDeleted ? "deleted." : "is NOT deleted."), EMsgType.FAIL); + } + catch (InterruptedException ignore) {} + } + private boolean interrupted(){ + return Thread.interrupted(); // NOTE: it's not isInterrupted(); And it's handled properly for now. + } +} diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/MergeTask.java b/src/main/java/nsusbloader/Utilities/splitmerge/MergeTask.java deleted file mode 100644 index 4119a0f..0000000 --- a/src/main/java/nsusbloader/Utilities/splitmerge/MergeTask.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - Copyright 2019-2020 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. -*/ -package nsusbloader.Utilities.splitmerge; - -import nsusbloader.ModelControllers.CancellableRunnable; -import nsusbloader.ModelControllers.ILogPrinter; -import nsusbloader.ModelControllers.Log; -import nsusbloader.NSLDataTypes.EModule; -import nsusbloader.NSLDataTypes.EMsgType; - -import java.io.*; -import java.util.Arrays; - -public class MergeTask extends CancellableRunnable { - - private final ILogPrinter logPrinter; - private final String saveToPath; - private final String filePath; - - private File splitFile; - - private File[] chunkFiles; - private long chunksTotalSize; - private File resultFile; - - public MergeTask(String filePath, String saveToPath) { - this.filePath = filePath; - this.saveToPath = saveToPath; - logPrinter = Log.getPrinter(EModule.SPLIT_MERGE_TOOL); - } - - @Override - public void run() { - try { - logPrinter.print("Merge file: " + filePath, EMsgType.INFO); - splitFile = new File(filePath); - - collectChunks(); - validateChunks(); - sortChunks(); - calculateChunksSizeSum(); - - createFile(); - mergeChunksToFile(); - validateFile(); - - logPrinter.print(".:: Merge complete ::.", EMsgType.INFO); - logPrinter.updateOneLinerStatus(true); - logPrinter.close(); - } - catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); - logPrinter.updateOneLinerStatus(false); - logPrinter.close(); - } - } - - private void collectChunks(){ - chunkFiles = splitFile.listFiles((file, s) -> s.matches("^[0-9][0-9]$")); - } - - private void validateChunks() throws Exception{ - if (chunkFiles == null || chunkFiles.length == 0){ - throw new Exception("Selected folder doesn't have any chunks. Nothing to do here."); - } - } - - private void sortChunks(){ - Arrays.sort(chunkFiles); - } - - private void calculateChunksSizeSum(){ - logPrinter.print("Next files will be merged in following order: ", EMsgType.INFO); - for (File cnk : chunkFiles){ - logPrinter.print(" "+cnk.getName(), EMsgType.INFO); - chunksTotalSize += cnk.length(); - } - } - - private void createFile() throws Exception{ - final String splitFileName = splitFile.getName(); - - resultFile = new File(saveToPath+File.separator+"!_"+splitFileName); - - for (int i = 0; i < 50 ; i++){ - if (isCancelled()){ - throw new InterruptedException("Split task interrupted!"); - } - - if (resultFile.exists()){ - logPrinter.print("Trying to create a good new file...", EMsgType.WARNING); - resultFile = new File(saveToPath+File.separator+"!_"+i+"_"+splitFileName); - continue; - } - - logPrinter.print("Save results to: "+resultFile.getAbsolutePath(), EMsgType.INFO); - return; - } - throw new Exception("Can't create new file."); - } - - private void mergeChunksToFile() throws Exception{ - double chunkPercent = (4194240.0 / (chunksTotalSize / 100.0) / 100.0); - long totalSizeCnt = 0; - - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(resultFile)); - - BufferedInputStream bis; - byte[] chunk; - int readBytesCnt; - - for (File chunkFile : chunkFiles){ - bis = new BufferedInputStream(new FileInputStream(chunkFile)); - while (true){ - - if (isCancelled()){ - bos.close(); - bis.close(); - boolean isDeleted = resultFile.delete(); - throw new InterruptedException("Merge task interrupted and file " - + (isDeleted ? "deleted." : "is not deleted.")); - } - - chunk = new byte[4194240]; - readBytesCnt = bis.read(chunk); - - logPrinter.updateProgress(chunkPercent * totalSizeCnt); - totalSizeCnt++; - - if (readBytesCnt < 4194240){ - if (readBytesCnt > 0) - bos.write(chunk, 0, readBytesCnt); - break; - } - - bos.write(chunk); - } - bis.close(); - } - bos.close(); - } - - private void validateFile() throws Exception{ - long resultFileSize = resultFile.length(); - logPrinter.print("Total chunks size: " + chunksTotalSize, EMsgType.INFO); - logPrinter.print("Merged file size: " + resultFileSize, EMsgType.INFO); - - if (chunksTotalSize != resultFileSize) - throw new Exception("Sizes are different! Do NOT use this file for installations!"); - - logPrinter.print("Sizes are the same! Resulting file should be good!", EMsgType.PASS); - } -} diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/MultithreadingPrintAdapter.java b/src/main/java/nsusbloader/Utilities/splitmerge/MultithreadingPrintAdapter.java new file mode 100644 index 0000000..b050498 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/splitmerge/MultithreadingPrintAdapter.java @@ -0,0 +1,44 @@ +/* + Copyright 2019-2021 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.splitmerge; + +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; + +public class MultithreadingPrintAdapter { + private final ILogPrinter printer; + private long totalFilesSize; + private long bytesComplete; + + public MultithreadingPrintAdapter(ILogPrinter printer){ + this.printer = printer; + } + + public void print(String message, EMsgType type) throws InterruptedException{ + printer.print(message, type); + } + + public void reportFileSize(long fileSize){ + totalFilesSize += fileSize; + } + public void updateProgressBySize(long chunkSize) throws InterruptedException{ + bytesComplete += chunkSize; + printer.updateProgress((double) bytesComplete / (double) totalFilesSize); + } +} diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/SplitMergeTaskExecutor.java b/src/main/java/nsusbloader/Utilities/splitmerge/SplitMergeTaskExecutor.java new file mode 100644 index 0000000..6ac7110 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/splitmerge/SplitMergeTaskExecutor.java @@ -0,0 +1,147 @@ +/* + Copyright 2019-2020 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.Utilities.splitmerge; + +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.ModelControllers.Log; +import nsusbloader.NSLDataTypes.EModule; +import nsusbloader.NSLDataTypes.EMsgType; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; +/* +* TODO: Kill this on application exit (?) +*/ +public class SplitMergeTaskExecutor implements Runnable { + private final boolean isSplit; + private final List<File> files; + private final String saveToPath; + private final ILogPrinter logPrinter; + private final ExecutorService executorService; + + private final MultithreadingPrintAdapter printAdapter; + + public SplitMergeTaskExecutor(boolean isSplit, List<File> files, String saveToPath){ + this.isSplit = isSplit; + this.files = files; + this.saveToPath = saveToPath; + this.logPrinter = Log.getPrinter(EModule.SPLIT_MERGE_TOOL); + this.executorService = Executors.newFixedThreadPool( + files.size(), + runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + return thread; + }); + this.printAdapter = new MultithreadingPrintAdapter(logPrinter); + } + + public void run(){ + try { + List<Future<Boolean>> futuresResults = executorService.invokeAll(getSubTasksCollection()); + boolean onelinerResult = true; + for (Future<Boolean> future : futuresResults){ + onelinerResult &= future.get(); + } + executorService.shutdown(); + logPrinter.updateOneLinerStatus(onelinerResult); + } + catch (InterruptedException ie){ + //ie.printStackTrace(); + executorService.shutdownNow(); + boolean interruptedSuccessfully = false; + try { + interruptedSuccessfully = executorService.awaitTermination(20, TimeUnit.SECONDS); + } + catch (InterruptedException awaitInterrupt){ + print("Force interrupting task...", EMsgType.WARNING); + } + logPrinter.updateOneLinerStatus(false); + print(( + isSplit? + "Split tasks interrupted ": + "Merge tasks interrupted ")+ + (interruptedSuccessfully? + "successfully": + "with some issues"), EMsgType.WARNING); + } + catch (Exception e){ + logPrinter.updateOneLinerStatus(false); + print( + isSplit? + "Split task failed: ": + "Merge task failed: "+e.getMessage(), EMsgType.FAIL); + e.printStackTrace(); + } + + print( + isSplit? + ".:: Split complete ::.": + ".:: Merge complete ::.", EMsgType.INFO); + logPrinter.close(); + } + private List<Callable<Boolean>> getSubTasksCollection() throws InterruptedException{ + List<Callable<Boolean>> subTasks = new ArrayList<>(); + StringBuilder stringBuilder = new StringBuilder(); + + // TODO: Optimize? + + if (isSplit){ + stringBuilder.append("Split files:\n"); + for (int i = 0; i < files.size(); i++){ + File file = files.get(i); + stringBuilder.append("["); + stringBuilder.append(i); + stringBuilder.append("] "); + stringBuilder.append(file.getName()); + stringBuilder.append("\n"); + Callable<Boolean> task = new SplitSubTask(i, file, saveToPath, printAdapter); + subTasks.add(task); + } + } + else { + stringBuilder.append("Merge files:\n"); + for (int i = 0; i < files.size(); i++){ + File file = files.get(i); + stringBuilder.append("["); + stringBuilder.append(i); + stringBuilder.append("] "); + stringBuilder.append(file.getName()); + stringBuilder.append("\n"); + Callable<Boolean> task = new MergeSubTask(i, file, saveToPath, printAdapter); + subTasks.add(task); + } + } + + logPrinter.print(stringBuilder.toString(), EMsgType.INFO); + + return subTasks; + } + + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/SplitSubTask.java b/src/main/java/nsusbloader/Utilities/splitmerge/SplitSubTask.java new file mode 100644 index 0000000..6533a02 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/splitmerge/SplitSubTask.java @@ -0,0 +1,186 @@ +/* + Copyright 2019-2021 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.splitmerge; + +import nsusbloader.NSLDataTypes.EMsgType; + +import java.io.*; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +public class SplitSubTask implements Callable<Boolean> { + private final int id; + private final String saveToPath; + + private final File file; + private File splitFile; + private long originalFileLen; + private final MultithreadingPrintAdapter printAdapter; + + SplitSubTask(int id, File file, String saveToPath, MultithreadingPrintAdapter printAdapter){ + this.id = id; + this.file = file; + this.saveToPath = saveToPath; + this.printAdapter = printAdapter; + } + + @Override + public Boolean call(){ + try { + createSplitFile(); + splitFileToChunks(); + validateSplitFile(); + return true; + } + catch (InterruptedException | ExecutionException ie){ + ie.printStackTrace(); + cleanup(); + return false; + } + catch (Exception e){ + e.printStackTrace(); + try { + printAdapter.print("["+id+"] "+e.getMessage(), EMsgType.FAIL); + } + catch (InterruptedException ignore) {} + return false; + } + } + + private void createSplitFile() throws Exception{ + if ( interrupted()) + throw new Exception("Split task interrupted!"); + + splitFile = new File(saveToPath+File.separator+"!_"+file.getName()); + + for (int i = 0; i < 50; i++){ + if (splitFile.mkdirs()){ + printAdapter.print("["+id+"] Save results to: "+splitFile.getAbsolutePath(), EMsgType.INFO); + return; + } + + if (splitFile.exists()){ + printAdapter.print("["+id+"] Trying to create a good new folder...", EMsgType.WARNING); + splitFile = new File(saveToPath+File.separator+"!_"+i+"_"+file.getName()); + continue; + } + + throw new Exception("Folder " + splitFile.getAbsolutePath() + + " could not be created. Not enough rights or something like that?"); + } + throw new Exception("Can't create new file."); + } + + private void splitFileToChunks() throws Exception{ + try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){ + long counter; + + originalFileLen = file.length(); + printAdapter.reportFileSize(originalFileLen); + + byte[] chunk; + int readBytesCnt; + + main_loop: + for (int i = 0; ; i++){ + String pathname = splitFile.getAbsolutePath()+File.separator+String.format("%02d", i); + BufferedOutputStream fragmentBos = new BufferedOutputStream(new FileOutputStream(pathname)); + + counter = 0; + + while (counter < 1024){ // 0xffff0000 total + chunk = new byte[4194240]; + + if ((readBytesCnt = bis.read(chunk)) < 4194240){ + if (readBytesCnt > 0) + fragmentBos.write(chunk, 0, readBytesCnt); + fragmentBos.close(); + printAdapter.updateProgressBySize(readBytesCnt); + break main_loop; + } + if (interrupted()) + throw new InterruptedException(); + + fragmentBos.write(chunk); + counter++; + printAdapter.updateProgressBySize(readBytesCnt); + } + fragmentBos.close(); + } + } + } + + private void validateSplitFile() throws Exception{ + if (interrupted()) + throw new Exception("Split task interrupted!"); + + printAdapter.print("["+id+"] Original file: "+splitFile.getAbsolutePath()+" (size: "+originalFileLen+")", EMsgType.INFO); + + long totalChunksSize = 0; + File[] chunkFileArr = splitFile.listFiles(); + + if (chunkFileArr == null) + throw new Exception("Unable to check results. It means that something went wrong."); + + Arrays.sort(chunkFileArr); + + StringBuilder stringBuilder = new StringBuilder("["+id+"] Chunks"); + + for (File chunkFile : chunkFileArr) { + stringBuilder.append("\n"); + stringBuilder.append(" "); + stringBuilder.append(chunkFile.getName()); + stringBuilder.append(" size: "); + stringBuilder.append(chunkFile.length()); + totalChunksSize += chunkFile.length(); + } + stringBuilder.append("\n"); + stringBuilder.append("Total chunks size: "); + stringBuilder.append(totalChunksSize); + + printAdapter.print(stringBuilder.toString(), EMsgType.INFO); + + if (originalFileLen != totalChunksSize) + throw new Exception("Sizes are different! Do NOT use this file for installations!"); + + printAdapter.print("["+id+"] Sizes are the same! Split file should be good!", EMsgType.PASS); + } + + private void cleanup(){ + boolean isDeleted = splitFile.delete(); + File[] chunksToDelete = splitFile.listFiles(); + if (! isDeleted && chunksToDelete != null){ + isDeleted = true; + for (File chunkFile : chunksToDelete) + isDeleted &= chunkFile.delete(); + isDeleted &= splitFile.delete(); + } + try { + printAdapter.print( + "["+id+"] Split task interrupted and folder " + + (isDeleted?"deleted.":"is NOT deleted.") + , EMsgType.FAIL); + } + catch (InterruptedException ignore) {} + } + private boolean interrupted(){ + return Thread.interrupted(); // NOTE: it's not isInterrupted(); And it's handled properly for now. + } +} diff --git a/src/main/java/nsusbloader/Utilities/splitmerge/SplitTask.java b/src/main/java/nsusbloader/Utilities/splitmerge/SplitTask.java deleted file mode 100644 index 1c617b8..0000000 --- a/src/main/java/nsusbloader/Utilities/splitmerge/SplitTask.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - Copyright 2019-2020 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. -*/ -package nsusbloader.Utilities.splitmerge; - -import nsusbloader.ModelControllers.CancellableRunnable; -import nsusbloader.ModelControllers.ILogPrinter; -import nsusbloader.ModelControllers.Log; -import nsusbloader.NSLDataTypes.EModule; -import nsusbloader.NSLDataTypes.EMsgType; - -import java.io.*; -import java.util.Arrays; - -public class SplitTask extends CancellableRunnable { - - private final ILogPrinter logPrinter; - private final String saveToPath; - private final String filePath; - - private File file; - private File splitFile; - private long originalFileLen; - - public SplitTask(String filePath, String saveToPath){ - this.filePath = filePath; - this.saveToPath = saveToPath; - this.logPrinter = Log.getPrinter(EModule.SPLIT_MERGE_TOOL); - } - - @Override - public void run() { - try { - logPrinter.print("Split file: "+filePath, EMsgType.INFO); - this.file = new File(filePath); - - createSplitFile(); - splitFileToChunks(); - validateSplitFile(); - - logPrinter.print(".:: Split complete ::.", EMsgType.INFO); - logPrinter.updateOneLinerStatus(true); - logPrinter.close(); - } - catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); - logPrinter.updateOneLinerStatus(false); - logPrinter.close(); - } - } - - private void createSplitFile() throws Exception{ - splitFile = new File(saveToPath+File.separator+"!_"+file.getName()); - - for (int i = 0; i < 50 ; i++){ - if (isCancelled()){ - throw new InterruptedException("Split task interrupted!"); - } - - if (splitFile.mkdir()){ - logPrinter.print("Save results to: "+splitFile.getAbsolutePath(), EMsgType.INFO); - return; - } - - if (splitFile.exists()){ - logPrinter.print("Trying to create a good new folder...", EMsgType.WARNING); - splitFile = new File(saveToPath+File.separator+"!_"+i+"_"+file.getName()); - continue; - } - - throw new Exception("Folder " + splitFile.getAbsolutePath() - + " could not be created. Not enough rights or something like that?"); - } - throw new Exception("Can't create new file."); - } - - private void splitFileToChunks() throws Exception{ - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - - long counter; - - originalFileLen = file.length(); - - double chunkPercent = (4194240.0 / (originalFileLen / 100.0) / 100.0); - long totalSizeCnt = 0; - - byte[] chunk; - int readBytesCnt; - - main_loop: - for (int i = 0; ; i++){ - String pathname = splitFile.getAbsolutePath()+File.separator+String.format("%02d", i); - BufferedOutputStream fragmentBos = new BufferedOutputStream(new FileOutputStream(new File(pathname))); - - counter = 0; - - while (counter < 1024){ // 0xffff0000 total - - if (isCancelled()){ - fragmentBos.close(); - bis.close(); - boolean isDeleted = splitFile.delete(); - File[] chunksToDelete = splitFile.listFiles(); - if (! isDeleted && chunksToDelete != null){ - isDeleted = true; - for (File chunkFile : chunksToDelete) - isDeleted &= chunkFile.delete(); - isDeleted &= splitFile.delete(); - } - - throw new InterruptedException("Split task interrupted and folder " - + (isDeleted?"deleted.":"is not deleted.")); - } - - chunk = new byte[4194240]; - - if ((readBytesCnt = bis.read(chunk)) < 4194240){ - if (readBytesCnt > 0) - fragmentBos.write(chunk, 0, readBytesCnt); - fragmentBos.close(); - logPrinter.updateProgress(1.0); - break main_loop; - } - - fragmentBos.write(chunk); - - logPrinter.updateProgress(chunkPercent * totalSizeCnt); - counter++; // NOTE: here we have some redundancy of variables. It has to be fixed one day. - totalSizeCnt++; - } - fragmentBos.close(); - } - bis.close(); - } - - private void validateSplitFile() throws Exception{ - logPrinter.print("Original file size: "+originalFileLen, EMsgType.INFO); - long totalChunksSize = 0; - File[] chunkFileArr = splitFile.listFiles(); - - if (chunkFileArr == null) - throw new Exception("Unable to check results. It means that something went wrong."); - - Arrays.sort(chunkFileArr); - - for (File chunkFile : chunkFileArr) { - logPrinter.print("Chunk " + chunkFile.getName() + " size: " + chunkFile.length(), EMsgType.INFO); - totalChunksSize += chunkFile.length(); - } - - logPrinter.print("Total chunks size: " + totalChunksSize, EMsgType.INFO); - - if (originalFileLen != totalChunksSize) - throw new Exception("Sizes are different! Do NOT use this file for installations!"); - - logPrinter.print("Sizes are the same! Split file should be good!", EMsgType.PASS); - } -} \ No newline at end of file diff --git a/src/main/java/nsusbloader/cli/MergeCli.java b/src/main/java/nsusbloader/cli/MergeCli.java index ae28f5d..99fba28 100644 --- a/src/main/java/nsusbloader/cli/MergeCli.java +++ b/src/main/java/nsusbloader/cli/MergeCli.java @@ -18,8 +18,7 @@ */ package nsusbloader.cli; -import nsusbloader.Utilities.splitmerge.MergeTask; -import nsusbloader.Utilities.splitmerge.SplitTask; +import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; import java.io.File; import java.util.ArrayList; @@ -27,7 +26,7 @@ import java.util.List; public class MergeCli { - private String[] arguments; + private final String[] arguments; private String saveTo; private String[] splitFiles; @@ -97,12 +96,20 @@ public class MergeCli { } private void runBackend() throws InterruptedException{ - for (String filePath : splitFiles){ - Runnable mergeTask = new MergeTask(filePath, saveTo); - Thread thread = new Thread(mergeTask); - thread.setDaemon(true); - thread.start(); - thread.join(); + Runnable mergeTask = new SplitMergeTaskExecutor( + false, + getFilesFromStrings(), + saveTo); + Thread thread = new Thread(mergeTask); + thread.setDaemon(true); + thread.start(); + thread.join(); + } + private List<File> getFilesFromStrings(){ + ArrayList<File> realFiles = new ArrayList<>(); + for (String splitFileString : splitFiles){ + realFiles.add(new File(splitFileString)); } + return realFiles; } } diff --git a/src/main/java/nsusbloader/cli/SplitCli.java b/src/main/java/nsusbloader/cli/SplitCli.java index b9e6922..1f093cf 100644 --- a/src/main/java/nsusbloader/cli/SplitCli.java +++ b/src/main/java/nsusbloader/cli/SplitCli.java @@ -18,16 +18,14 @@ */ package nsusbloader.cli; -import nsusbloader.Utilities.splitmerge.SplitTask; +import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; import java.io.File; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class SplitCli { - private String[] arguments; + private final String[] arguments; private String saveTo; private String[] files; @@ -95,12 +93,20 @@ public class SplitCli { } private void runBackend() throws InterruptedException{ - for (String filePath : files){ - Runnable splitTaks = new SplitTask(filePath, saveTo); - Thread thread = new Thread(splitTaks); - thread.setDaemon(true); - thread.start(); - thread.join(); + Runnable splitTasks = new SplitMergeTaskExecutor( + true, + getFilesFromStrings(), + saveTo); + Thread thread = new Thread(splitTasks); + thread.setDaemon(true); + thread.start(); + thread.join(); + } + private List<File> getFilesFromStrings(){ + ArrayList<File> realFiles = new ArrayList<>(); + for (String fileString : files){ + realFiles.add(new File(fileString)); } + return realFiles; } } diff --git a/src/main/java/nsusbloader/com/net/NETCommunications.java b/src/main/java/nsusbloader/com/net/NETCommunications.java index ba03ff6..b26e8bd 100644 --- a/src/main/java/nsusbloader/com/net/NETCommunications.java +++ b/src/main/java/nsusbloader/com/net/NETCommunications.java @@ -90,7 +90,7 @@ public class NETCommunications extends CancellableRunnable { if (! isValid || isCancelled() ) return; - logPrinter.print("\tStart chain", EMsgType.INFO); + print("\tStart chain", EMsgType.INFO); final String handshakeContent = buildHandshakeContent(); @@ -102,11 +102,11 @@ public class NETCommunications extends CancellableRunnable { // Check if we should serve requests if (this.doNotServe){ - logPrinter.print("List of files transferred. Replies won't be served.", EMsgType.PASS); + print("List of files transferred. Replies won't be served.", EMsgType.PASS); close(EFileStatus.UNKNOWN); return; } - logPrinter.print("Initiation files list has been sent to NS.", EMsgType.PASS); + print("Initiation files list has been sent to NS.", EMsgType.PASS); // Go transfer serveRequestsLoop(); @@ -142,7 +142,7 @@ public class NETCommunications extends CancellableRunnable { handshakeSocket.close(); } catch (IOException uhe){ - logPrinter.print("Unable to connect to NS and send files list:\n " + print("Unable to connect to NS and send files list:\n " + uhe.getMessage(), EMsgType.FAIL); close(EFileStatus.UNKNOWN); return true; @@ -175,13 +175,13 @@ public class NETCommunications extends CancellableRunnable { } catch (Exception e){ if (isCancelled()) - logPrinter.print("Interrupted by user.", EMsgType.INFO); + print("Interrupted by user.", EMsgType.INFO); else - logPrinter.print(e.getMessage(), EMsgType.INFO); + print(e.getMessage(), EMsgType.INFO); close(EFileStatus.UNKNOWN); return; } - logPrinter.print("All transfers complete", EMsgType.PASS); + print("All transfers complete", EMsgType.PASS); close(EFileStatus.UPLOADED); } /** @@ -199,7 +199,7 @@ public class NETCommunications extends CancellableRunnable { if (! files.containsKey(reqFileName)){ writeToSocket(NETPacket.getCode404()); - logPrinter.print("File "+reqFileName+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL); + print("File "+reqFileName+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL); return; } @@ -208,13 +208,13 @@ public class NETCommunications extends CancellableRunnable { if (! requestedFile.exists() || reqFileSize == 0){ // well.. tell 404 if file exists with 0 length is against standard, but saves time writeToSocket(NETPacket.getCode404()); - logPrinter.print("File "+requestedFile.getName()+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL); + print("File "+requestedFile.getName()+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL); logPrinter.update(requestedFile, EFileStatus.FAILED); return; } if (packet.get(0).startsWith("HEAD")){ writeToSocket(NETPacket.getCode200(reqFileSize)); - logPrinter.print("Replying for requested file: "+requestedFile.getName(), EMsgType.INFO); + print("Replying for requested file: "+requestedFile.getName(), EMsgType.INFO); return; } if (packet.get(0).startsWith("GET")) { @@ -237,7 +237,7 @@ public class NETCommunications extends CancellableRunnable { if (fromRange > toRange){ // If start bytes greater then end bytes writeToSocket(NETPacket.getCode400()); - logPrinter.print("Requested range for " + print("Requested range for " + file.getName() + " is incorrect. Returning 400", EMsgType.FAIL); logPrinter.update(file, EFileStatus.FAILED); @@ -254,7 +254,7 @@ public class NETCommunications extends CancellableRunnable { if (rangeStr[1].isEmpty()) { // If Range not defined: like "Range: bytes=-" writeToSocket(NETPacket.getCode400()); - logPrinter.print("Requested range for " + print("Requested range for " + file.getName() + " is incorrect (empty start & end). Returning 400", EMsgType.FAIL); logPrinter.update(file, EFileStatus.FAILED); @@ -267,7 +267,7 @@ public class NETCommunications extends CancellableRunnable { } // If file smaller than 500 bytes writeToSocket(NETPacket.getCode416()); - logPrinter.print("File size requested for " + print("File size requested for " + file.getName() + " while actual size of it: " + fileSize+". Returning 416", EMsgType.FAIL); @@ -275,7 +275,7 @@ public class NETCommunications extends CancellableRunnable { } catch (NumberFormatException nfe){ writeToSocket(NETPacket.getCode400()); - logPrinter.print("Requested range for " + print("Requested range for " + file.getName() + " has incorrect format. Returning 400\n\t" + nfe.getMessage(), EMsgType.FAIL); @@ -293,7 +293,7 @@ public class NETCommunications extends CancellableRunnable { private void writeToSocket(String fileName, long start, long end) throws Exception{ File file = files.get(fileName).getFile(); - logPrinter.print("Reply to range: "+start+"-"+end, EMsgType.INFO); + print("Reply to range: "+start+"-"+end, EMsgType.INFO); writeToSocket(NETPacket.getCode206(files.get(fileName).getSize(), start, end)); try{ @@ -379,11 +379,11 @@ public class NETCommunications extends CancellableRunnable { try { if (serverSocket != null && ! serverSocket.isClosed()) { serverSocket.close(); - logPrinter.print("Closing server socket.", EMsgType.PASS); + print("Closing server socket.", EMsgType.PASS); } } catch (IOException ioe){ - logPrinter.print("Closing server socket failed. Sometimes it's not an issue.", EMsgType.WARNING); + print("Closing server socket failed. Sometimes it's not an issue.", EMsgType.WARNING); } HashMap<String, File> tempMap = new HashMap<>(); @@ -392,7 +392,15 @@ public class NETCommunications extends CancellableRunnable { logPrinter.update(tempMap, status); - logPrinter.print("\tEnd chain", EMsgType.INFO); + print("\tEnd chain", EMsgType.INFO); logPrinter.close(); } + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java b/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java index 706bd97..b4908db 100644 --- a/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java +++ b/src/main/java/nsusbloader/com/net/NetworkSetupValidator.java @@ -55,15 +55,21 @@ public class NetworkSetupValidator { resolvePort(hostPortNum); } catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); + try { + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } + catch (InterruptedException ignore){} valid = false; return; } valid = true; } - private void validateFiles(List<File> filesList) { - filesList.removeIf(f -> { + private void validateFiles(List<File> filesList){ + filesList.removeIf(this::validator); + } + private boolean validator(File f){ + try { if (f.isFile()) return false; @@ -76,24 +82,27 @@ public class NetworkSetupValidator { Arrays.sort(subFiles, Comparator.comparingInt(file -> Integer.parseInt(file.getName()))); - for (int i = subFiles.length - 2; i > 0 ; i--){ - if (subFiles[i].length() != subFiles[i-1].length()) { - logPrinter.print("NET: Exclude split file: "+f.getName()+ + for (int i = subFiles.length - 2; i > 0; i--) { + if (subFiles[i].length() != subFiles[i - 1].length()) { + logPrinter.print("NET: Exclude split file: " + f.getName() + "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); return true; } } long firstFileLength = subFiles[0].length(); - long lastFileLength = subFiles[subFiles.length-1].length(); + long lastFileLength = subFiles[subFiles.length - 1].length(); - if (lastFileLength > firstFileLength){ - logPrinter.print("NET: Exclude split file: "+f.getName()+ + if (lastFileLength > firstFileLength) { + logPrinter.print("NET: Exclude split file: " + f.getName() + "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); return true; } return false; - }); + } + catch (InterruptedException ie){ + return false; + } } private void encodeAndAddFilesToMap(List<File> filesList) throws UnsupportedEncodingException, FileNotFoundException { @@ -108,7 +117,7 @@ public class NetworkSetupValidator { } } - private void resolveIp(String hostIPaddr) throws IOException{ + private void resolveIp(String hostIPaddr) throws IOException, InterruptedException{ if (! hostIPaddr.isEmpty()){ this.hostIP = hostIPaddr; logPrinter.print("NET: Host IP defined as: " + hostIP, EMsgType.PASS); @@ -124,7 +133,7 @@ public class NetworkSetupValidator { throw new IOException("Try using 'Expert mode' and set IP manually. " + getAvaliableIpExamples()); } - private boolean findIpUsingHost(String host) { + private boolean findIpUsingHost(String host) throws InterruptedException{ try { Socket scoketK; scoketK = new Socket(); diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java index 53ebc4d..7a1d872 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_05.java @@ -59,17 +59,17 @@ public class GoldLeaf_05 extends TransferModule { this.task = task; status = EFileStatus.FAILED; - logPrinter.print("============= GoldLeaf v0.5 =============\n" + + print("============= GoldLeaf v0.5 =============\n" + " Only one file per time could be sent. In case you selected more the first one would be picked.", EMsgType.INFO); if (nspMap.isEmpty()){ - logPrinter.print("For using this GoldLeaf version you have to add file to the table and select it for upload", EMsgType.INFO); + print("For using this GoldLeaf version you have to add file to the table and select it for upload", EMsgType.INFO); return; } File nspFile = (File) nspMap.values().toArray()[0]; - logPrinter.print("File for upload: "+nspFile.getAbsolutePath(), EMsgType.INFO); + print("File for upload: "+nspFile.getAbsolutePath(), EMsgType.INFO); if (!nspFile.getName().toLowerCase().endsWith(".nsp")) { - logPrinter.print("GL This file doesn't look like NSP", EMsgType.FAIL); + print("GL This file doesn't look like NSP", EMsgType.FAIL); return; } PFSProvider pfsElement; @@ -77,11 +77,11 @@ public class GoldLeaf_05 extends TransferModule { pfsElement = new PFSProvider(nspFile, logPrinter); } catch (Exception e){ - logPrinter.print("GL File provided has incorrect structure and won't be uploaded\n\t"+e.getMessage(), EMsgType.FAIL); + print("GL File provided has incorrect structure and won't be uploaded\n\t"+e.getMessage(), EMsgType.FAIL); status = EFileStatus.INCORRECT_FILE_FAILED; return; } - logPrinter.print("GL File structure validated and it will be uploaded", EMsgType.PASS); + print("GL File structure validated and it will be uploaded", EMsgType.PASS); try{ if (nspFile.isDirectory()) @@ -90,7 +90,7 @@ public class GoldLeaf_05 extends TransferModule { this.raf = new RandomAccessFile(nspFile, "r"); } catch (IOException ioe){ - logPrinter.print("GL File not found\n\t"+ioe.getMessage(), EMsgType.FAIL); + print("GL File not found\n\t"+ioe.getMessage(), EMsgType.FAIL); return; } @@ -99,15 +99,15 @@ public class GoldLeaf_05 extends TransferModule { // Go connect to GoldLeaf if (writeUsb(CMD_GLUC)) { - logPrinter.print("GL Initiating GoldLeaf connection [1/2]", EMsgType.FAIL); + print("GL Initiating GoldLeaf connection [1/2]", EMsgType.FAIL); return; } - logPrinter.print("GL Initiating GoldLeaf connection: [1/2]", EMsgType.PASS); + print("GL Initiating GoldLeaf connection: [1/2]", EMsgType.PASS); if (writeUsb(CMD_ConnectionRequest)){ - logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.FAIL); + print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.FAIL); return; } - logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.PASS); + print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.PASS); while (true) { readByte = readUsb(); @@ -143,7 +143,7 @@ public class GoldLeaf_05 extends TransferModule { continue; } if (Arrays.equals(readByte, CMD_Finish)) { - logPrinter.print("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS); + print("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS); status = EFileStatus.UPLOADED; break; } @@ -164,29 +164,29 @@ public class GoldLeaf_05 extends TransferModule { * false if no issues * */ private boolean handleConnectionResponse(PFSProvider pfsElement){ - logPrinter.print("GL 'ConnectionResponse' command:", EMsgType.INFO); + print("GL 'ConnectionResponse' command:", EMsgType.INFO); if (writeUsb(CMD_GLUC)) { - logPrinter.print(" [1/4]", EMsgType.FAIL); + print(" [1/4]", EMsgType.FAIL); return true; } - logPrinter.print(" [1/4]", EMsgType.PASS); + print(" [1/4]", EMsgType.PASS); if (writeUsb(CMD_NSPName)) { - logPrinter.print(" [2/4]", EMsgType.FAIL); + print(" [2/4]", EMsgType.FAIL); return true; } - logPrinter.print(" [2/4]", EMsgType.PASS); + print(" [2/4]", EMsgType.PASS); if (writeUsb(pfsElement.getBytesNspFileNameLength())) { - logPrinter.print(" [3/4]", EMsgType.FAIL); + print(" [3/4]", EMsgType.FAIL); return true; } - logPrinter.print(" [3/4]", EMsgType.PASS); + print(" [3/4]", EMsgType.PASS); if (writeUsb(pfsElement.getBytesNspFileName())) { - logPrinter.print(" [4/4]", EMsgType.FAIL); + print(" [4/4]", EMsgType.FAIL); return true; } - logPrinter.print(" [4/4]", EMsgType.PASS); + print(" [4/4]", EMsgType.PASS); return false; } @@ -196,50 +196,50 @@ public class GoldLeaf_05 extends TransferModule { * false if no issues * */ private boolean handleStart(PFSProvider pfsElement){ - logPrinter.print("GL Handle 'Start' command:", EMsgType.INFO); + print("GL Handle 'Start' command:", EMsgType.INFO); if (writeUsb(CMD_GLUC)) { - logPrinter.print(" [Prefix]", EMsgType.FAIL); + print(" [Prefix]", EMsgType.FAIL); return true; } - logPrinter.print(" [Prefix]", EMsgType.PASS); + print(" [Prefix]", EMsgType.PASS); if (writeUsb(CMD_NSPData)) { - logPrinter.print(" [Command]", EMsgType.FAIL); + print(" [Command]", EMsgType.FAIL); return true; } - logPrinter.print(" [Command]", EMsgType.PASS); + print(" [Command]", EMsgType.PASS); if (writeUsb(pfsElement.getBytesCountOfNca())) { - logPrinter.print(" [Sub-files count]", EMsgType.FAIL); + print(" [Sub-files count]", EMsgType.FAIL); return true; } - logPrinter.print(" [Sub-files count]", EMsgType.PASS); + print(" [Sub-files count]", EMsgType.PASS); int ncaCount = pfsElement.getIntCountOfNca(); - logPrinter.print(" [Information for "+ncaCount+" sub-files]", EMsgType.INFO); + print(" [Information for "+ncaCount+" sub-files]", EMsgType.INFO); for (int i = 0; i < ncaCount; i++){ - logPrinter.print("File #"+i, EMsgType.INFO); + print("File #"+i, EMsgType.INFO); if (writeUsb(pfsElement.getNca(i).getNcaFileNameLength())) { - logPrinter.print(" [1/4] Name length", EMsgType.FAIL); + print(" [1/4] Name length", EMsgType.FAIL); return true; } - logPrinter.print(" [1/4] Name length", EMsgType.PASS); + print(" [1/4] Name length", EMsgType.PASS); if (writeUsb(pfsElement.getNca(i).getNcaFileName())) { - logPrinter.print(" [2/4] Name", EMsgType.FAIL); + print(" [2/4] Name", EMsgType.FAIL); return true; } - logPrinter.print(" [2/4] Name", EMsgType.PASS); + print(" [2/4] Name", EMsgType.PASS); if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) { // offset. real. - logPrinter.print(" [3/4] Offset", EMsgType.FAIL); + print(" [3/4] Offset", EMsgType.FAIL); return true; } - logPrinter.print(" [3/4] Offset", EMsgType.PASS); + print(" [3/4] Offset", EMsgType.PASS); if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) { // size - logPrinter.print(" [4/4] Size", EMsgType.FAIL); + print(" [4/4] Size", EMsgType.FAIL); return true; } - logPrinter.print(" [4/4] Size", EMsgType.PASS); + print(" [4/4] Size", EMsgType.PASS); } return false; } @@ -254,18 +254,18 @@ public class GoldLeaf_05 extends TransferModule { int requestedNcaID; if (isItRawRequest) { - logPrinter.print("GL Handle 'Content' command", EMsgType.INFO); + print("GL Handle 'Content' command", EMsgType.INFO); byte[] readByte = readUsb(); if (readByte == null || readByte.length != 4) { - logPrinter.print(" [Read requested ID]", EMsgType.FAIL); + print(" [Read requested ID]", EMsgType.FAIL); return true; } requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt(); - logPrinter.print(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS); + print(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS); } else { requestedNcaID = pfsElement.getNcaTicketID(); - logPrinter.print("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO); + print("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO); } long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize(); @@ -317,8 +317,8 @@ public class GoldLeaf_05 extends TransferModule { logPrinter.updateProgress(1.0); //-----------------------------------------/ } - catch (IOException ioe){ - logPrinter.print("GL Failed to read NCA ID "+requestedNcaID+". IO Exception:\n "+ioe.getMessage(), EMsgType.FAIL); + catch (IOException | InterruptedException ioe){ + print("GL Failed to read NCA ID "+requestedNcaID+". Exception:\n "+ioe.getMessage(), EMsgType.FAIL); ioe.printStackTrace(); return true; } @@ -345,18 +345,18 @@ public class GoldLeaf_05 extends TransferModule { if (writeBufTransferred.get() == message.length) return false; else { - logPrinter.print("GL Data transfer issue [write]\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); + print("GL Data transfer issue [write]\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); return true; } case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [write]\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("GL Execution stopped", EMsgType.FAIL); + print("GL Data transfer issue [write]\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + print("GL Execution stopped", EMsgType.FAIL); return true; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return true; } /** @@ -382,12 +382,12 @@ public class GoldLeaf_05 extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL); - logPrinter.print("GL Execution stopped", EMsgType.FAIL); + print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL); + print("GL Execution stopped", EMsgType.FAIL); return null; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return null; } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java index eb3eca3..38adb7e 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java @@ -69,7 +69,11 @@ class GoldLeaf_07 extends TransferModule { // For using in CMD_SelectFile with SPEC:/ prefix private File selectedFile; - GoldLeaf_07(DeviceHandle handler, LinkedHashMap<String, File> nspMap, CancellableRunnable task, ILogPrinter logPrinter, boolean nspFilter){ + GoldLeaf_07(DeviceHandle handler, + LinkedHashMap<String, File> nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter){ super(handler, nspMap, task, logPrinter); final byte CMD_GetDriveCount = 0x00; @@ -93,7 +97,7 @@ class GoldLeaf_07 extends TransferModule { this.nspFilterForGl = nspFilter; - logPrinter.print("============= GoldLeaf v0.7.x =============\n\t" + + print("============= GoldLeaf v0.7.x =============\n\t" + "VIRT:/ equals files added into the application\n\t" + "HOME:/ equals " +System.getProperty("user.home"), EMsgType.INFO); @@ -260,7 +264,7 @@ class GoldLeaf_07 extends TransferModule { byte[] drivesCnt = intToArrLE(2); //2 // Write count of drives if (writeGL_PASS(drivesCnt)) { - logPrinter.print("GL Handle 'ListDrives' command", EMsgType.FAIL); + print("GL Handle 'ListDrives' command", EMsgType.FAIL); return true; } return false; @@ -316,7 +320,7 @@ class GoldLeaf_07 extends TransferModule { command.add(totalSize); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); + print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); return true; } @@ -332,7 +336,7 @@ class GoldLeaf_07 extends TransferModule { byte[] specialPathCnt = intToArrLE(0); // Write count of special paths if (writeGL_PASS(specialPathCnt)) { - logPrinter.print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); + print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); return true; } return false; @@ -354,13 +358,13 @@ class GoldLeaf_07 extends TransferModule { if (path.equals("VIRT:/")) { if (isGetDirectoryCount){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + 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); + print("GL Handle 'GetFileCount' command Count = "+nspMap.size(), EMsgType.FAIL); return true; } } @@ -401,7 +405,7 @@ class GoldLeaf_07 extends TransferModule { // If somehow there are no folders, let's say 0; if (filesOrDirs == null){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); return true; } return false; @@ -415,20 +419,20 @@ class GoldLeaf_07 extends TransferModule { 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); + print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); return true; } } else if (path.startsWith("SPEC:/")){ if (isGetDirectoryCount){ // If dir request then 0 dirs if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); return true; } } else if (selectedFile != null){ // Else it's file request, if we have selected then we will report 1. if (writeGL_PASS(intToArrLE(1))) { - logPrinter.print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); + print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); return true; } } @@ -482,7 +486,7 @@ class GoldLeaf_07 extends TransferModule { // return proxyGetDirFile(true); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); + print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); return true; } return false; @@ -539,7 +543,7 @@ class GoldLeaf_07 extends TransferModule { //if (proxyForGL) // TODO: NOTE: PROXY TAILS // return proxyGetDirFile(false); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -550,7 +554,7 @@ class GoldLeaf_07 extends TransferModule { command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 command.add(fileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -562,7 +566,7 @@ class GoldLeaf_07 extends TransferModule { command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 command.add(fileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -593,7 +597,7 @@ class GoldLeaf_07 extends TransferModule { command.add(longToArrLE(fileDirElement.length())); } if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -610,7 +614,7 @@ class GoldLeaf_07 extends TransferModule { command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG! if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -623,7 +627,7 @@ class GoldLeaf_07 extends TransferModule { command.add(GL_OBJ_TYPE_FILE); command.add(longToArrLE(selectedFile.length())); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -651,7 +655,7 @@ class GoldLeaf_07 extends TransferModule { try { if (currentFile.renameTo(newFile)){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + print("GL Handle 'Rename' command.", EMsgType.FAIL); return true; } return false; @@ -676,7 +680,7 @@ class GoldLeaf_07 extends TransferModule { try { if (fileToDel.delete()){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + print("GL Handle 'Rename' command.", EMsgType.FAIL); return true; } return false; @@ -714,10 +718,10 @@ class GoldLeaf_07 extends TransferModule { } if (result){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Create' command.", EMsgType.FAIL); + print("GL Handle 'Create' command.", EMsgType.FAIL); return true; } - //logPrinter.print("GL Handle 'Create' command.", EMsgType.PASS); + //print("GL Handle 'Create' command.", EMsgType.PASS); return false; } } @@ -802,12 +806,12 @@ class GoldLeaf_07 extends TransferModule { "\n Received: " + bytesRead); // Let's tell as a command about our result. if (writeGL_PASS(longToArrLE(size))) { - logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); return true; } // Let's bypass bytes we read total if (writeToUsb(chunk)) { - logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); + print("GL Handle 'ReadFile' command", EMsgType.FAIL); return true; } return false; @@ -822,12 +826,12 @@ class GoldLeaf_07 extends TransferModule { return writeGL_FAIL("GL Handle 'ReadFile' command [CMD] Requested = "+size+" Read from file = "+bytesRead); // Let's tell as a command about our result. if (writeGL_PASS(longToArrLE(size))) { - logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); return true; } // Let's bypass bytes we read total if (writeToUsb(chunk)) { - logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); + print("GL Handle 'ReadFile' command", EMsgType.FAIL); return true; } return false; @@ -839,14 +843,14 @@ class GoldLeaf_07 extends TransferModule { } catch (NullPointerException ignored){} catch (IOException ioe_){ - logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); + print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); } try{ splitReader.close(); } catch (NullPointerException ignored){} catch (IOException ioe_){ - logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); + print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); } openReadFileNameAndPath = null; randAccessFile = null; @@ -888,7 +892,7 @@ class GoldLeaf_07 extends TransferModule { byte[] transferredData; if ((transferredData = readGL_file()) == null){ - logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); + print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); return true; } try{ @@ -899,7 +903,7 @@ class GoldLeaf_07 extends TransferModule { } // Report we're good if (writeGL_PASS()) { - logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); + print("GL Handle 'WriteFile' command", EMsgType.FAIL); return true; } return false; @@ -926,7 +930,7 @@ class GoldLeaf_07 extends TransferModule { command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 command.add(selectedFileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); + print("GL Handle 'SelectFile' command", EMsgType.FAIL); this.selectedFile = null; return true; } @@ -1006,13 +1010,13 @@ class GoldLeaf_07 extends TransferModule { closeOpenedReadFilesGl(); // Could be a problem if GL glitches and slow down process. Or if user has extra-slow SD card. TODO: refactor continue; default: - logPrinter.print("GL Data transfer issue [read]\n Returned: " + + print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return null; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return null; } private byte[] readGL_file(){ @@ -1035,13 +1039,13 @@ class GoldLeaf_07 extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [read]\n Returned: " + + print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return null; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return null; } /** @@ -1066,10 +1070,10 @@ class GoldLeaf_07 extends TransferModule { private boolean writeGL_FAIL(String reportToUImsg){ if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ - logPrinter.print(reportToUImsg, EMsgType.WARNING); + print(reportToUImsg, EMsgType.WARNING); return true; } - logPrinter.print(reportToUImsg, EMsgType.FAIL); + print(reportToUImsg, EMsgType.FAIL); return false; } /** @@ -1091,7 +1095,7 @@ class GoldLeaf_07 extends TransferModule { if (writeBufTransferred.get() == message.length) return false; else { - logPrinter.print("GL Data transfer issue [write]\n Requested: " + + print("GL Data transfer issue [write]\n Requested: " + message.length + "\n Transferred: " + writeBufTransferred.get(), EMsgType.FAIL); @@ -1100,13 +1104,13 @@ class GoldLeaf_07 extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [write]\n Returned: " + + print("GL Data transfer issue [write]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return true; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return true; } diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index 4e260cc..0a15656 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -103,7 +103,7 @@ class GoldLeaf_08 extends TransferModule { this.nspFilterForGl = nspFilter; - logPrinter.print("============= GoldLeaf v0.8 =============\n\t" + + print("============= GoldLeaf v0.8 =============\n\t" + "VIRT:/ equals files added into the application\n\t" + "HOME:/ equals " +System.getProperty("user.home"), EMsgType.INFO); @@ -273,7 +273,7 @@ class GoldLeaf_08 extends TransferModule { * */ private boolean startOrEndFile(){ if (writeGL_PASS()){ - logPrinter.print("GL Handle 'StartFile' command", EMsgType.FAIL); + print("GL Handle 'StartFile' command", EMsgType.FAIL); return true; } return false; @@ -288,7 +288,7 @@ class GoldLeaf_08 extends TransferModule { byte[] drivesCnt = intToArrLE(2); //2 // Write count of drives if (writeGL_PASS(drivesCnt)) { - logPrinter.print("GL Handle 'ListDrives' command", EMsgType.FAIL); + print("GL Handle 'ListDrives' command", EMsgType.FAIL); return true; } return false; @@ -344,7 +344,7 @@ class GoldLeaf_08 extends TransferModule { command.add(totalSize); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); + print("GL Handle 'GetDriveInfo' command", EMsgType.FAIL); return true; } @@ -360,7 +360,7 @@ class GoldLeaf_08 extends TransferModule { byte[] specialPathCnt = intToArrLE(0); // Write count of special paths if (writeGL_PASS(specialPathCnt)) { - logPrinter.print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); + print("GL Handle 'SpecialPathCount' command", EMsgType.FAIL); return true; } return false; @@ -382,13 +382,13 @@ class GoldLeaf_08 extends TransferModule { if (path.equals("VIRT:/")) { if (isGetDirectoryCount){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + 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); + print("GL Handle 'GetFileCount' command Count = "+nspMap.size(), EMsgType.FAIL); return true; } } @@ -429,7 +429,7 @@ class GoldLeaf_08 extends TransferModule { // If somehow there are no folders, let's say 0; if (filesOrDirs == null){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); + print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); return true; } return false; @@ -443,20 +443,20 @@ class GoldLeaf_08 extends TransferModule { 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); + print("GL Handle 'GetDirectoryOrFileCount' command", EMsgType.FAIL); return true; } } else if (path.startsWith("SPEC:/")){ if (isGetDirectoryCount){ // If dir request then 0 dirs if (writeGL_PASS()) { - logPrinter.print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); + print("GL Handle 'GetDirectoryCount' command", EMsgType.FAIL); return true; } } else if (selectedFile != null){ // Else it's file request, if we have selected then we will report 1. if (writeGL_PASS(intToArrLE(1))) { - logPrinter.print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); + print("GL Handle 'GetFileCount' command Count = 1", EMsgType.FAIL); return true; } } @@ -510,7 +510,7 @@ class GoldLeaf_08 extends TransferModule { // return proxyGetDirFile(true); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); + print("GL Handle 'GetDirectory' command.", EMsgType.FAIL); return true; } return false; @@ -567,7 +567,7 @@ class GoldLeaf_08 extends TransferModule { //if (proxyForGL) // TODO: NOTE: PROXY TAILS // return proxyGetDirFile(false); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -578,7 +578,7 @@ class GoldLeaf_08 extends TransferModule { command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 command.add(fileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -590,7 +590,7 @@ class GoldLeaf_08 extends TransferModule { command.add(intToArrLE(fileNameBytes.length / 2)); // since GL 0.7 command.add(fileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'GetFile' command.", EMsgType.FAIL); + print("GL Handle 'GetFile' command.", EMsgType.FAIL); return true; } return false; @@ -621,7 +621,7 @@ class GoldLeaf_08 extends TransferModule { command.add(longToArrLE(fileDirElement.length())); } if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -638,7 +638,7 @@ class GoldLeaf_08 extends TransferModule { command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG! if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -651,7 +651,7 @@ class GoldLeaf_08 extends TransferModule { command.add(GL_OBJ_TYPE_FILE); command.add(longToArrLE(selectedFile.length())); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); + print("GL Handle 'StatPath' command.", EMsgType.FAIL); return true; } return false; @@ -679,7 +679,7 @@ class GoldLeaf_08 extends TransferModule { try { if (currentFile.renameTo(newFile)){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + print("GL Handle 'Rename' command.", EMsgType.FAIL); return true; } return false; @@ -704,7 +704,7 @@ class GoldLeaf_08 extends TransferModule { try { if (fileToDel.delete()){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Rename' command.", EMsgType.FAIL); + print("GL Handle 'Rename' command.", EMsgType.FAIL); return true; } return false; @@ -742,10 +742,10 @@ class GoldLeaf_08 extends TransferModule { } if (result){ if (writeGL_PASS()) { - logPrinter.print("GL Handle 'Create' command.", EMsgType.FAIL); + print("GL Handle 'Create' command.", EMsgType.FAIL); return true; } - //logPrinter.print("GL Handle 'Create' command.", EMsgType.PASS); + //print("GL Handle 'Create' command.", EMsgType.PASS); return false; } } @@ -830,12 +830,12 @@ class GoldLeaf_08 extends TransferModule { "\n Received: " + bytesRead); // Let's tell as a command about our result. if (writeGL_PASS(longToArrLE(size))) { - logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); return true; } // Let's bypass bytes we read total if (writeToUsb(chunk)) { - logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); + print("GL Handle 'ReadFile' command", EMsgType.FAIL); return true; } return false; @@ -850,12 +850,12 @@ class GoldLeaf_08 extends TransferModule { return writeGL_FAIL("GL Handle 'ReadFile' command [CMD] Requested = "+size+" Read from file = "+bytesRead); // Let's tell as a command about our result. if (writeGL_PASS(longToArrLE(size))) { - logPrinter.print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); + print("GL Handle 'ReadFile' command [CMD]", EMsgType.FAIL); return true; } // Let's bypass bytes we read total if (writeToUsb(chunk)) { - logPrinter.print("GL Handle 'ReadFile' command", EMsgType.FAIL); + print("GL Handle 'ReadFile' command", EMsgType.FAIL); return true; } return false; @@ -867,14 +867,14 @@ class GoldLeaf_08 extends TransferModule { } catch (NullPointerException ignored){} catch (IOException ioe_){ - logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); + print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); } try{ splitReader.close(); } catch (NullPointerException ignored){} catch (IOException ioe_){ - logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); + print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioe_.getMessage(), EMsgType.WARNING); } openReadFileNameAndPath = null; randAccessFile = null; @@ -916,7 +916,7 @@ class GoldLeaf_08 extends TransferModule { byte[] transferredData; if ((transferredData = readGL_file()) == null){ - logPrinter.print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); + print("GL Handle 'WriteFile' command [1/1]", EMsgType.FAIL); return true; } try{ @@ -927,7 +927,7 @@ class GoldLeaf_08 extends TransferModule { } // Report we're good if (writeGL_PASS()) { - logPrinter.print("GL Handle 'WriteFile' command", EMsgType.FAIL); + print("GL Handle 'WriteFile' command", EMsgType.FAIL); return true; } return false; @@ -957,7 +957,7 @@ class GoldLeaf_08 extends TransferModule { command.add(intToArrLE(selectedFileNameBytes.length / 2)); // since GL 0.7 command.add(selectedFileNameBytes); if (writeGL_PASS(command)) { - logPrinter.print("GL Handle 'SelectFile' command", EMsgType.FAIL); + print("GL Handle 'SelectFile' command", EMsgType.FAIL); this.selectedFile = null; return true; } @@ -1033,13 +1033,13 @@ class GoldLeaf_08 extends TransferModule { closeOpenedReadFilesGl(); // Could be a problem if GL glitches and slow down process. Or if user has extra-slow SD card. TODO: refactor continue; default: - logPrinter.print("GL Data transfer issue [read]\n Returned: " + + print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return null; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return null; } private byte[] readGL_file(){ @@ -1060,13 +1060,13 @@ class GoldLeaf_08 extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [read]\n Returned: " + + print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return null; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return null; } /** @@ -1091,10 +1091,10 @@ class GoldLeaf_08 extends TransferModule { private boolean writeGL_FAIL(String reportToUImsg){ if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ - logPrinter.print(reportToUImsg, EMsgType.WARNING); + print(reportToUImsg, EMsgType.WARNING); return true; } - logPrinter.print(reportToUImsg, EMsgType.FAIL); + print(reportToUImsg, EMsgType.FAIL); return false; } /** @@ -1116,7 +1116,7 @@ class GoldLeaf_08 extends TransferModule { if (writeBufTransferred.get() == message.length) return false; else { - logPrinter.print("GL Data transfer issue [write]\n Requested: " + + print("GL Data transfer issue [write]\n Requested: " + message.length + "\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); return true; @@ -1124,13 +1124,13 @@ class GoldLeaf_08 extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - logPrinter.print("GL Data transfer issue [write]\n Returned: " + + print("GL Data transfer issue [write]\n Returned: " + UsbErrorCodes.getErrCode(result) + "\n GL Execution stopped", EMsgType.FAIL); return true; } } - logPrinter.print("GL Execution interrupted", EMsgType.INFO); + print("GL Execution interrupted", EMsgType.INFO); return true; } } diff --git a/src/main/java/nsusbloader/com/usb/TinFoil.java b/src/main/java/nsusbloader/com/usb/TinFoil.java index d033f08..33114bf 100644 --- a/src/main/java/nsusbloader/com/usb/TinFoil.java +++ b/src/main/java/nsusbloader/com/usb/TinFoil.java @@ -50,7 +50,7 @@ class TinFoil extends TransferModule { TinFoil(DeviceHandle handler, LinkedHashMap<String, File> nspMap, CancellableRunnable task, ILogPrinter logPrinter){ super(handler, nspMap, task, logPrinter); - logPrinter.print("============= Tinfoil =============", EMsgType.INFO); + print("============= Tinfoil =============", EMsgType.INFO); if (! sendListOfFiles()) return; @@ -69,25 +69,25 @@ class TinFoil extends TransferModule { byte[] padding = new byte[8]; if (writeUsb(TUL0)) { - logPrinter.print("TF Send list of files: handshake [1/4]", EMsgType.FAIL); + print("TF Send list of files: handshake [1/4]", EMsgType.FAIL); return false; } if (writeUsb(nspListNamesSize)) { // size of the list we can transfer - logPrinter.print("TF Send list of files: list length [2/4]", EMsgType.FAIL); + print("TF Send list of files: list length [2/4]", EMsgType.FAIL); return false; } if (writeUsb(padding)) { - logPrinter.print("TF Send list of files: padding [3/4]", EMsgType.FAIL); + print("TF Send list of files: padding [3/4]", EMsgType.FAIL); return false; } if (writeUsb(nspListNames)) { - logPrinter.print("TF Send list of files: list itself [4/4]", EMsgType.FAIL); + print("TF Send list of files: list itself [4/4]", EMsgType.FAIL); return false; } - logPrinter.print("TF Send list of files complete.", EMsgType.PASS); + print("TF Send list of files complete.", EMsgType.PASS); return true; } @@ -114,7 +114,7 @@ class TinFoil extends TransferModule { * After we sent commands to NS, this chain starts * */ private boolean proceedCommands(){ - logPrinter.print("TF Awaiting for NS commands.", EMsgType.INFO); + print("TF Awaiting for NS commands.", EMsgType.INFO); try{ byte[] deviceReply; byte command; @@ -127,18 +127,18 @@ class TinFoil extends TransferModule { switch (command){ case CMD_EXIT: - logPrinter.print("TF Transfer complete.", EMsgType.PASS); + print("TF Transfer complete.", EMsgType.PASS); return true; case CMD_FILE_RANGE_DEFAULT: case CMD_FILE_RANGE_ALTERNATIVE: - //logPrinter.print("TF Received 'FILE RANGE' command [0x0"+command+"].", EMsgType.PASS); + //print("TF Received 'FILE RANGE' command [0x0"+command+"].", EMsgType.PASS); if (fileRangeCmd()) return false; // catches exception } } } catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.INFO); + print(e.getMessage(), EMsgType.INFO); return false; } } @@ -169,7 +169,7 @@ class TinFoil extends TransferModule { String nspFileName = new String(receivedArray, StandardCharsets.UTF_8); - logPrinter.print(String.format("TF Reply to: %s" + + print(String.format("TF Reply to: %s" + "\n Offset: %-20d 0x%x" + "\n Size: %-20d 0x%x", nspFileName, @@ -188,28 +188,28 @@ class TinFoil extends TransferModule { else sendNormalFile(nspFile, size, offset); } catch (IOException ioe){ - logPrinter.print("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL); + print("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL); ioe.printStackTrace(); return true; } catch (ArithmeticException ae){ - logPrinter.print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):" + + print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):" + "\n "+ae.getMessage(), EMsgType.FAIL); ae.printStackTrace(); return true; } catch (NullPointerException npe){ - logPrinter.print("TF NullPointerException (in some moment application didn't find something. Something important.):" + + print("TF NullPointerException (in some moment application didn't find something. Something important.):" + "\n "+npe.getMessage(), EMsgType.FAIL); npe.printStackTrace(); return true; } catch (Exception defe){ - logPrinter.print(defe.getMessage(), EMsgType.FAIL); + print(defe.getMessage(), EMsgType.FAIL); return true; } return false; } - void sendSplitFile(File nspFile, long size, long offset) throws IOException, NullPointerException, ArithmeticException { + void sendSplitFile(File nspFile, long size, long offset) throws Exception { byte[] readBuffer; long currentOffset = 0; int chunk = 8388608; // = 8Mb; @@ -237,7 +237,7 @@ class TinFoil extends TransferModule { logPrinter.updateProgress(1.0); } - void sendNormalFile(File nspFile, long size, long offset) throws IOException, NullPointerException, ArithmeticException { + void sendNormalFile(File nspFile, long size, long offset) throws Exception { byte[] readBuffer; long currentOffset = 0; int chunk = 8388608; @@ -278,17 +278,17 @@ class TinFoil extends TransferModule { final byte[] twelveZeroBytes = new byte[12]; if (writeUsb(standardReplyBytes)){ // Send integer value of '1' in Little-endian format. - logPrinter.print("TF Sending response failed [1/3]", EMsgType.FAIL); + print("TF Sending response failed [1/3]", EMsgType.FAIL); return true; } if(writeUsb(sizeAsBytes)) { // Send EXACTLY what has been received - logPrinter.print("TF Sending response failed [2/3]", EMsgType.FAIL); + print("TF Sending response failed [2/3]", EMsgType.FAIL); return true; } if(writeUsb(twelveZeroBytes)) { // kinda another one padding - logPrinter.print("TF Sending response failed [3/3]", EMsgType.FAIL); + print("TF Sending response failed [3/3]", EMsgType.FAIL); return true; } return false; @@ -308,7 +308,7 @@ class TinFoil extends TransferModule { while (! task.isCancelled() ) { /* if (varVar != 0) - logPrinter.print("writeUsb() retry cnt: "+varVar, EMsgType.INFO); //NOTE: DEBUG + print("writeUsb() retry cnt: "+varVar, EMsgType.INFO); //NOTE: DEBUG varVar++; */ result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 5050); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01 @@ -317,7 +317,7 @@ class TinFoil extends TransferModule { case LibUsb.SUCCESS: if (writeBufTransferred.get() == message.length) return false; - logPrinter.print("TF Data transfer issue [write]" + + print("TF Data transfer issue [write]" + "\n Requested: "+message.length+ "\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); return true; @@ -326,13 +326,13 @@ class TinFoil extends TransferModule { //writeBufTransferred.clear(); // MUST BE HERE IF WE 'GET()' IT continue; default: - logPrinter.print("TF Data transfer issue [write]" + + print("TF Data transfer issue [write]" + "\n Returned: "+ UsbErrorCodes.getErrCode(result) + "\n (execution stopped)", EMsgType.FAIL); return true; } } - logPrinter.print("TF Execution interrupted", EMsgType.INFO); + print("TF Execution interrupted", EMsgType.INFO); return true; } /** diff --git a/src/main/java/nsusbloader/com/usb/TransferModule.java b/src/main/java/nsusbloader/com/usb/TransferModule.java index 4eebca6..e99a4a8 100644 --- a/src/main/java/nsusbloader/com/usb/TransferModule.java +++ b/src/main/java/nsusbloader/com/usb/TransferModule.java @@ -51,7 +51,7 @@ public abstract class TransferModule { File[] subFiles = f.listFiles((file, name) -> name.matches("[0-9]{2}")); if (subFiles == null || subFiles.length == 0) { - logPrinter.print("TransferModule: Exclude folder: " + f.getName(), EMsgType.WARNING); + print("TransferModule: Exclude folder: " + f.getName(), EMsgType.WARNING); return true; } @@ -59,7 +59,7 @@ public abstract class TransferModule { for (int i = subFiles.length - 2; i > 0 ; i--){ if (subFiles[i].length() != subFiles[i-1].length()) { - logPrinter.print("TransferModule: Exclude split file: "+f.getName()+ + print("TransferModule: Exclude split file: "+f.getName()+ "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); return true; } @@ -69,7 +69,7 @@ public abstract class TransferModule { long lastFileLength = subFiles[subFiles.length-1].length(); if (lastFileLength > firstFileLength){ - logPrinter.print("TransferModule: Exclude split file: "+f.getName()+ + print("TransferModule: Exclude split file: "+f.getName()+ "\n Chunk sizes of the split file are not the same, but has to be.", EMsgType.WARNING); return true; } @@ -77,4 +77,13 @@ public abstract class TransferModule { }); } public EFileStatus getStatus(){ return status; } + + void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } } diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index cf31dfd..3601848 100644 --- a/src/main/java/nsusbloader/com/usb/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -49,7 +49,7 @@ public class UsbCommunications extends CancellableRunnable { @Override public void run() { - logPrinter.print("\tStart", EMsgType.INFO); + print("\tStart", EMsgType.INFO); UsbConnect usbConnect = UsbConnect.connectHomebrewMode(logPrinter); @@ -87,7 +87,15 @@ public class UsbCommunications extends CancellableRunnable { */ private void close(EFileStatus status){ logPrinter.update(nspMap, status); - logPrinter.print("\tEnd", EMsgType.INFO); + print("\tEnd", EMsgType.INFO); logPrinter.close(); } + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/com/usb/UsbConnect.java b/src/main/java/nsusbloader/com/usb/UsbConnect.java index 91fe5d4..f4e5ad3 100644 --- a/src/main/java/nsusbloader/com/usb/UsbConnect.java +++ b/src/main/java/nsusbloader/com/usb/UsbConnect.java @@ -65,7 +65,10 @@ public class UsbConnect { usbConnect.connected = true; } catch (Exception e){ - logPrinter.print(e.getMessage(), EMsgType.FAIL); + try { + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } + catch (InterruptedException ignore){} usbConnect.close(); } @@ -89,7 +92,10 @@ public class UsbConnect { } catch (Exception e){ e.printStackTrace(); - logPrinter.print(e.getMessage(), EMsgType.FAIL); + try { + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } + catch (InterruptedException ignore){} usbConnect.close(); } return usbConnect; @@ -100,7 +106,7 @@ public class UsbConnect { private UsbConnect(ILogPrinter logPrinter){ this.logPrinter = logPrinter; this.connected = false; - }; + } private void createContextAndInitLibUSB() throws Exception{ // Creating Context required by libusb. Optional? Consider removing. @@ -178,7 +184,7 @@ public class UsbConnect { // Actually, there are no drivers in Linux kernel which uses this device. returningValue = LibUsb.setAutoDetachKernelDriver(handlerNS, true); if (returningValue != LibUsb.SUCCESS) - logPrinter.print("Skip kernel driver attach & detach ("+UsbErrorCodes.getErrCode(returningValue)+")", EMsgType.INFO); + print("Skip kernel driver attach & detach ("+UsbErrorCodes.getErrCode(returningValue)+")", EMsgType.INFO); } /* @@ -230,7 +236,7 @@ public class UsbConnect { returningValue = LibUsb.releaseInterface(handlerNS, DEFAULT_INTERFACE); if (returningValue != LibUsb.SUCCESS) { - logPrinter.print("Release interface failure: " + + print("Release interface failure: " + UsbErrorCodes.getErrCode(returningValue) + " (sometimes it's not an issue)", EMsgType.WARNING); } @@ -241,4 +247,13 @@ public class UsbConnect { if (contextNS != null) LibUsb.exit(contextNS); } + + private void print(String message, EMsgType type){ + try { + logPrinter.print(message, type); + } + catch (InterruptedException ie){ + ie.printStackTrace(); + } + } } diff --git a/src/main/resources/BlockListView.fxml b/src/main/resources/BlockListView.fxml new file mode 100644 index 0000000..00a70db --- /dev/null +++ b/src/main/resources/BlockListView.fxml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.AnchorPane?> + +<AnchorPane xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.BlockListViewController"> + <children> + <ListView fx:id="splitMergeListView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" /> + </children> +</AnchorPane> diff --git a/src/main/resources/SplitMergeTab.fxml b/src/main/resources/SplitMergeTab.fxml index 0032e02..2cedbcc 100644 --- a/src/main/resources/SplitMergeTab.fxml +++ b/src/main/resources/SplitMergeTab.fxml @@ -42,47 +42,42 @@ </GridPane> </children> </VBox> - <VBox fillWidth="false" spacing="5.0"> + <HBox spacing="15.0" VBox.vgrow="ALWAYS"> <children> - <RadioButton fx:id="splitRad" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_RadioBtn_Split"> - <toggleGroup> - <ToggleGroup fx:id="splitMergeTogGrp" /> - </toggleGroup> - </RadioButton> - <RadioButton fx:id="mergeRad" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_RadioBtn_Merge" toggleGroup="$splitMergeTogGrp" /> - </children> - <VBox.margin> - <Insets left="15.0" right="15.0" /> - </VBox.margin> - </VBox> - <VBox spacing="5.0"> - <children> - <HBox> - <children> - <Label fx:id="fileFolderLabelLbl" /> - <Label fx:id="fileFolderActualPathLbl" /> - </children> - </HBox> - <Button fx:id="selectFileFolderBtn" contentDisplay="TOP" mnemonicParsing="false" /> - </children> - <VBox.margin> - <Insets left="15.0" right="15.0" /> - </VBox.margin> - </VBox> - <VBox spacing="5.0"> - <children> - <HBox> + <fx:include fx:id="BlockListView" source="BlockListView.fxml" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS" /> + <VBox spacing="5.0"> <children> + <RadioButton fx:id="splitRad" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_RadioBtn_Split"> + <toggleGroup> + <ToggleGroup fx:id="splitMergeTogGrp" /> + </toggleGroup> + </RadioButton> + <RadioButton fx:id="mergeRad" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_RadioBtn_Merge" toggleGroup="$splitMergeTogGrp" /> + <Button fx:id="selectFileFolderBtn" contentDisplay="TOP" mnemonicParsing="false"> + <VBox.margin> + <Insets bottom="10.0" /> + </VBox.margin> + </Button> <Label text="%tabSplMrg_Lbl_SaveToLocation" /> - <Label fx:id="saveToPathLbl" /> + <Label fx:id="saveToPathLbl" maxWidth="200.0" textOverrun="CENTER_WORD_ELLIPSIS" /> + <Button fx:id="changeSaveToBtn" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_Btn_ChangeSaveToLocation" /> + <Pane VBox.vgrow="ALWAYS" /> + <VBox> + <children> + <HBox alignment="CENTER"> + <children> + <Button fx:id="convertBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabSplMrg_Btn_Convert" /> + </children> + </HBox> + </children> + </VBox> </children> - </HBox> - <Button fx:id="changeSaveToBtn" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_Btn_ChangeSaveToLocation" /> + </VBox> </children> <VBox.margin> <Insets left="15.0" right="15.0" /> </VBox.margin> - </VBox> + </HBox> <HBox alignment="CENTER"> <children> <Label fx:id="statusLbl" /> @@ -91,16 +86,6 @@ <Insets left="15.0" right="15.0" /> </VBox.margin> </HBox> - <Pane VBox.vgrow="ALWAYS" /> - <VBox> - <children> - <HBox alignment="CENTER"> - <children> - <Button fx:id="convertBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabSplMrg_Btn_Convert" /> - </children> - </HBox> - </children> - </VBox> <padding> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </padding> diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 5ee4192..e895189 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -205,6 +205,7 @@ .table-view .table-cell{ -fx-text-fill: #f7fafa; } + .table-row-cell, .table-row-cell:selected, .table-row-cell:filled:selected{ -fx-background-color: -fx-table-cell-border-color, #424242; -fx-background-insets: 0, 0 0 1 0; @@ -223,7 +224,35 @@ -fx-padding: 0.0em; /* 0 */ -fx-table-cell-border-color: #6d8484; } -// -========================== Context menu =====================- +/* -========================== ListView =====================- */ +.list-view { + -fx-background-color: #4f4f4f; + -fx-background-position: center; + -fx-background-repeat: no-repeat; + -fx-background-radius: 3; + -fx-border-color: #00ffc9; + -fx-border-radius: 3; + -fx-border-width: 2; +} + +.list-cell, .list-cell:selected, .list-cell:filled:selected{ + -fx-background-color: -fx-table-cell-border-color, #424242; + -fx-background-insets: 0, 0 0 1 0; + -fx-table-cell-border-color: #6d8484; +} +.list-cell:odd, .list-cell:odd:selected, .list-cell:odd:filled:selected{ + -fx-background-color: -fx-table-cell-border-color, #4f4f4f; + -fx-background-insets: 0, 0 0 1 0; + -fx-table-cell-border-color: #6d8484; +} + +.list-cell .text, .list-cell:odd .text{ + -fx-fill: #f7fafa; +} +.list-cell:filled:selected .text, .list-cell:odd:filled:selected .text{ + -fx-fill: #08f3ff; +} +/* -========================== Context menu =====================- */ .context-menu { -fx-background-color: #2d2d2d; -fx-cursor: hand; @@ -234,7 +263,7 @@ .context-menu .menu-item:focused .label { -fx-text-fill: white; } -// -========================== Text Field =====================- +/* -========================== Text Field =====================- */ .text-field { -fx-prompt-text-fill: #40596c; -fx-border-color: #289de8; diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index fde9c6a..cd2b17c 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -242,6 +242,35 @@ -fx-padding: 0.0em; /* 0 */ -fx-table-cell-border-color: #b0b0b0; } +/* -========================== ListView =====================- */ +.list-view { + -fx-background-color: #fefefe; + -fx-background-position: center; + -fx-background-repeat: no-repeat; + -fx-background-radius: 3; + -fx-border-color: #06b9bb; + -fx-border-radius: 3; + -fx-border-width: 2; +} + +.list-cell, .list-cell:selected, .list-cell:filled:selected{ + -fx-background-color: -fx-table-cell-border-color, #ebfffe; + -fx-background-insets: 0, 0 0 1 0; + -fx-table-cell-border-color: #b0b0b0; +} +.list-cell:odd, .list-cell:odd:selected, .list-cell:odd:filled:selected{ + -fx-background-color: -fx-table-cell-border-color, #fefefe; + -fx-background-insets: 0, 0 0 1 0; + -fx-table-cell-border-color: #b0b0b0; +} + +.list-cell .text, .list-cell:odd .text{ + -fx-fill: #2c2c2c; +} +.list-cell:filled:selected .text, .list-cell:odd:filled:selected .text{ + -fx-fill: #2c2c2c; + -fx-font-weight: bold +} /* -========================= Separator ===================- */ .separator *.line { -fx-border-style: solid; diff --git a/src/test/java/nsusbloader/com/usb/MergeTest.java b/src/test/java/nsusbloader/com/usb/MergeTest.java new file mode 100644 index 0000000..b6716ff --- /dev/null +++ b/src/test/java/nsusbloader/com/usb/MergeTest.java @@ -0,0 +1,86 @@ +package nsusbloader.com.usb; + +import nsusbloader.NSLMain; +import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class MergeTest { + private List<File> files; + @TempDir + File testFilesLocation; + @TempDir + File saveToPath; + + static Random random; + + @BeforeAll + static void init(){ + NSLMain.isCli = true; + MergeTest.random = new Random(); + } + + void makeSplittedFiles() throws Exception{ + String parentLocation = testFilesLocation.getAbsolutePath(); + this.files = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + files.add(createSplitFile(parentLocation, "TestFile4Merge_" + i)); + } + } + File createSplitFile(String parent, String name) throws Exception{ + Path file = Paths.get(parent, name); + File newlyCreatedSplitFile = Files.createDirectory(file).toFile(); + + Assertions.assertTrue(newlyCreatedSplitFile.exists()); + Assertions.assertTrue(newlyCreatedSplitFile.canRead()); + Assertions.assertTrue(newlyCreatedSplitFile.canWrite()); + + int chunksCount = random.nextInt(6 + 1) + 1; // At min = 1, max = 6 + populateSplittedFile(newlyCreatedSplitFile, chunksCount); + + return newlyCreatedSplitFile; + } + void populateSplittedFile(File splitFileContainer, int chunksCount) throws Exception{ + int chunkSize = random.nextInt(8192 + 1) + 8192; // At min = 8192, max = 8192*2 + + for (int i = 0; i < chunksCount; i++){ + File chunkFile = new File(splitFileContainer, String.format("%02d", i)); + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(chunkFile))){ + byte[] zero = new byte[chunkSize]; + bos.write(zero); + } + + Assertions.assertTrue(chunkFile.exists()); + Assertions.assertTrue(chunkFile.canRead()); + } + } + + @DisplayName("Test test-files location for merge") + @Test + void testTempLocation(){ + Assertions.assertTrue(testFilesLocation.isDirectory()); + } + +// @Disabled("Current test is not ready") + @DisplayName("Test merge functionality") + //@Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + @Test + void testMerge() throws Exception{ + makeSplittedFiles(); + SplitMergeTaskExecutor splitMergeTaskExecutor = new SplitMergeTaskExecutor(false, files, saveToPath.getAbsolutePath()); + Thread thread = new Thread(splitMergeTaskExecutor); + thread.setDaemon(true); + thread.start(); + thread.join(); + } +} diff --git a/src/test/java/nsusbloader/com/usb/SplitTest.java b/src/test/java/nsusbloader/com/usb/SplitTest.java new file mode 100644 index 0000000..bdd32f5 --- /dev/null +++ b/src/test/java/nsusbloader/com/usb/SplitTest.java @@ -0,0 +1,78 @@ +package nsusbloader.com.usb; + +import nsusbloader.NSLMain; +import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +class SplitTest { + private List<File> files; + @TempDir + File testFilesLocation; + @TempDir + File saveToPath; + + static Random random; + + @BeforeAll + static void init(){ + NSLMain.isCli = true; + SplitTest.random = new Random(); + } + + void makeRegularFiles() throws Exception { + String parentLocation = testFilesLocation.getAbsolutePath(); + Assertions.assertTrue(Files.exists(Paths.get(parentLocation))); + this.files = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + files.add(createRegularFile(parentLocation, "TestFile4Split_" + i)); + } + } + File createRegularFile(String parent, String name) throws Exception{ + Path file = Paths.get(parent, name); + File newlyCreatedFile = Files.createFile(file).toFile(); + + Assertions.assertTrue(newlyCreatedFile.exists()); + Assertions.assertTrue(newlyCreatedFile.canRead()); + Assertions.assertTrue(newlyCreatedFile.canWrite()); + + int randomValue = random.nextInt(8192 + 1) + 8192; // At min = 8192, max = 8192*2 + fulfillFile(newlyCreatedFile, randomValue); + + return newlyCreatedFile; + } + void fulfillFile(File file, int fileSize) throws Exception{ + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))){ + byte[] zero = new byte[fileSize]; + bos.write(zero); + } + } + + @DisplayName("Test test-files location for split") + @Test + void testTempLocation(){ + Assertions.assertTrue(testFilesLocation.isDirectory()); + } + + @DisplayName("Test split functionality") + //@Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + @Test + void testSplit() throws Exception{ + makeRegularFiles(); + SplitMergeTaskExecutor splitMergeTaskExecutor = new SplitMergeTaskExecutor(true, files, saveToPath.getAbsolutePath()); + Thread thread = new Thread(splitMergeTaskExecutor); + thread.setDaemon(true); + thread.start(); + thread.join(); + } +} From 2a3bdd949fc885fe7918c485d7d84f4d0ba5157d Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 9 Aug 2021 22:50:44 +0300 Subject: [PATCH 035/134] Solve - #44: move to JFX 16 --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 120c316..22da049 100644 --- a/pom.xml +++ b/pom.xml @@ -60,28 +60,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>11</version> + <version>16</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>11</version> + <version>16</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>11</version> + <version>16</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>11</version> + <version>16</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -89,28 +89,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>11</version> + <version>16</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>11</version> + <version>16</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>11</version> + <version>16</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>11</version> + <version>16</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -118,28 +118,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>11</version> + <version>16</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>11</version> + <version>16</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>11</version> + <version>16</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>11</version> + <version>16</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> From b02bc7ed2dfa243bacbc8a2ec6ddce8a7d813114 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 10 Aug 2021 17:00:33 +0300 Subject: [PATCH 036/134] Using idea from #90: Replace 'Tinfoil' by 'Awoo' everywhere on the front end. Also touched translation files (hope didn't broke it, but could be) Add 'Fusee Geelee' and 'RCM' labels used on 'RCM' tab to 'properties' files in case someone wants to translate it.. I don't think if someone should but there is an option. (note: CLI arguments remains the same while only description updated) --- README.md | 43 +++++++------- pom.xml | 4 +- src/main/java/nsusbloader/AppPreferences.java | 14 +++-- .../Controllers/GamesController.java | 41 +++++++------ .../nsusbloader/cli/CommandLineInterface.java | 4 +- .../java/nsusbloader/com/usb/TinFoil.java | 54 ++++++++--------- src/main/resources/RcmTab.fxml | 4 +- src/main/resources/SettingsBlockTinfoil.fxml | 2 +- src/main/resources/locale.properties | 11 ++-- src/main/resources/locale_ar_AR.properties | 1 + src/main/resources/locale_cs_CZ.properties | 5 +- src/main/resources/locale_de_DE.properties | 7 ++- src/main/resources/locale_en_US.properties | 10 ++-- src/main/resources/locale_es_ES.properties | 4 +- src/main/resources/locale_fr_FR.properties | 4 +- src/main/resources/locale_it_IT.properties | 4 +- src/main/resources/locale_ko_KR.properties | 5 +- src/main/resources/locale_pt_BR.properties | 4 +- src/main/resources/locale_ro_RO.properties | 4 +- src/main/resources/locale_ru_RU.properties | 9 ++- src/main/resources/locale_uk_UA.properties | 9 ++- src/main/resources/locale_vi_VN.properties | 4 +- src/main/resources/locale_zh_CN.properties | 58 +++++++++---------- src/main/resources/locale_zh_TW.properties | 4 +- 24 files changed, 164 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index 35e7358..a1560b0 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,12 @@ [Support author](#support-this-app) NS-USBloader is: -* A PC-side installer for **[Adubbz/TinFoil (v0.2.1)](https://github.com/Adubbz/Tinfoil/)**, **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** (USB and Network supported) and **[XorTroll/GoldLeaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. -Replacement for default **usb_install_pc.py**, **remote_install_pc.py**, **GoldTree**/**Quark**. +* A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/GoldLeaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. +Alternative to default **usb_install_pc.py**, **remote_install_pc.py**, **GoldTree**/**Quark**. * This application also could be used as RCM payload on Windows, MacOS and Linux (supported arch: x86, x86_64 and Raspberry Pi). -* And of course it's a tool for split files! -* And also for merging split-files into one :) +* It's a tool for creating split files! +* Also you can use it for merging split-files into one :) + [Click here for Android version ;)](https://github.com/developersu/ns-usbloader-mobile) @@ -49,6 +50,7 @@ Sometimes I add new posts about this project [on my home page](https://developer * Czech by [Spenaat](https://github.com/spenaat) * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234) * Arabic by [eslamabdel](https://github.com/eslamabdel) +* Romanian by [Călin Ilie](https://github.com/calini) ### System requirements @@ -67,11 +69,13 @@ JDK 11 for MacOS and Linux where '+' means 'any next NS-USBloader version'. -### Awoo Installer support +### Awoo Installer and compatible applications support -Awoo Installer uses the same command-set (or 'protocol') to TinFoil. So just select 'TinFoil' in case you're going to use Awoo. +Awoo Installer uses the same command-set (or 'protocol') to [Adubbz/Tinfoil](https://github.com/Adubbz/Tinfoil/). -Also, please go to 'Settings' tab of NS-USBloader after first installation and check 'Allow XCI / NSZ / XCZ files selection for TinFoil' option. This installer can install not only NSPs but a way more formats! +A lot of other forks/apps uses the same command-set. To stop speculating about the name it's now called 'Awoo'. It WAS called 'TinFoil' before. Not any more. + +Also, please go to 'Settings' tab of NS-USBloader after first installation and check 'Allow XCI / NSZ / XCZ files selection for Awoo' option. This installer can install not only NSPs but a way more formats! ### Usage ##### Linux: @@ -121,9 +125,9 @@ 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/)), GoldLeaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) or Awoo ([Huntereb](https://github.com/Huntereb/Awoo-Installer)) on your NS. +The first thing you should do it install Awoo ([Huntereb](https://github.com/Huntereb/Awoo-Installer)) or GoldLeaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) on your NS. -Take a look on app, find where is the option to install from USB and/or Network. Maybe [this article (about TinFoil)](https://developersu.blogspot.com/2019/02/ns-usbloader-en.html) will be helpful. +Take a look on app, find where is the option to install from USB and/or Network. Maybe (very old) [this article (about TinFoil)](https://developersu.blogspot.com/2019/02/ns-usbloader-en.html) will be helpful. #### In details @@ -137,7 +141,7 @@ Then you may drag-n-drop files (split-files aka folders) to application or use ' 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.~~ +There you can select checkbox for files that will be sent to application (AW/GL). ~~Since GoldLeaf v0.5 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. @@ -151,11 +155,11 @@ On this tab you can select payloader like Hekate or LockPick_RCM and send it to ##### 'Folder with arrows and zeroes' tab -On this tab you can split and merge files. Select 'Split' or 'Merge' and split (or merge). +On this tab you can split and merge files. Select 'Split' or 'Merge' and split (or merge). BTW Drag-n-drop supported. ##### 'Gears' 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. +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 Awoo's NET part is working interesting way. When you select in Awoo 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 Awoo "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: * Set 'Auto-check for updates' for checking for updates when application starts, or click button to verify if new version released immediately. @@ -175,10 +179,10 @@ To get help run ``$ java -jar ns-usbloader-4.0.jar --help`` -g,--goldleaf <...> Install via GoldLeaf mode. Check '-g help' for information. -h,--help Show this help -m,--merge <...> Merge files. Check '-m help' for information. - -n,--tfn <...> Install via Tinfoil/Awoo Network mode. Check '-n help' for information. + -n,--tfn <...> Install via Awoo Network mode. Check '-n help' for information. -r,--rcm <[PATH/]payload.bin> Send payload -s,--split <...> Split files. Check '-s help' for information. - -t,--tinfoil <FILE...> Install via Tinfoil/Awoo USB mode. + -t,--tinfoil <FILE...> Install via Awoo USB mode. -v,--version Show application version ``` @@ -214,23 +218,20 @@ $ java -jar ns-usbloader-4.0.jar -m /tmp/ ~/*.nsp ### 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. -Handling successful/failed installation is a purpose of the other side application: TinFoil or GoldLeaf. And they don't provide any feedback interfaces so I can't detect success/failure. +Handling successful/failed installation is a purpose of the other side application: Awoo/Awoo-like or GoldLeaf. And they don't provide any feedback interfaces so I can't detect success/failure. usb4java since NS-USBloader-v0.2.3 switched to 1.2.0 instead of 1.3.0. This should not impact anyone except users of macOS High Sierra (and Sierra, and El Capitan) where previous versions of NS-USBloader didn't work. Now builds with usb4java-1.2.0 marked as '-legacy' and builds with usb4java-1.3.0 doesn't have postfixes. ### 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 (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. 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/) -#### TODO (maybe): -- [x] [Android support](https://github.com/developersu/ns-usbloader-mobile) - ## Support this app - If you like this app, just give a star. If you want to make a donation*, please see below: @@ -239,9 +240,9 @@ If you want to make a donation*, please see below: <a href="https://paypal.me/developersu" title="PayPal"><img src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png" border="0" alt="PayPal Logo" /></a> -[Yandex.Money](https://money.yandex.ru/to/410014301951665) +[yoomoney](https://yoomoney.ru/to/410014301951665) -* Please note: this is non-commercial application. +*Please note: this is non-commercial application. Thanks diff --git a/pom.xml b/pom.xml index 22da049..036329b 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ <url>https://github.com/developersu/ns-usbloader/</url> <description> - NSP USB loader for TinFoil (USB and Network) and GoldLeaf + NSP USB loader for Awoo Installer and compatible (USB and Network) and GoldLeaf </description> <inceptionYear>2019</inceptionYear> <organization> @@ -257,7 +257,7 @@ <versionInfo> <fileVersion>1.0.0.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> - <fileDescription>TinFoil and GoldLeaf installer for your NS</fileDescription> + <fileDescription>Awoo and GoldLeaf installer for your NS</fileDescription> <copyright>GNU General Public License v3, 2019 ${project.organization.name}. Russia/LPR.</copyright> <productVersion>1.0.0.0</productVersion> <txtProductVersion>${project.version}</txtProductVersion> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index b5caac3..52a60fc 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -41,12 +41,14 @@ public class AppPreferences { theme = "/res/app_dark.css"; return theme; } - public String getProtocol(){ - String protocol = preferences.get("PROTOCOL", "TinFoil"); // Don't let user to change settings manually - if (!protocol.matches("(^TinFoil$)|(^GoldLeaf$)")) - protocol = "TinFoil"; - return protocol; + public int getProtocol(){ + int protocolIndex = preferences.getInt("protocol_index", 0); // Don't let user to change settings manually + if (protocolIndex < 0 || protocolIndex > 1) + protocolIndex = 0; + return protocolIndex; } + public void setProtocol(int protocolIndex){ preferences.putInt("protocol_index", protocolIndex); } + public String getNetUsb(){ String netUsb = preferences.get("NETUSB", "USB"); // Don't let user to change settings manually if (!netUsb.matches("(^USB$)|(^NET$)")) @@ -55,7 +57,7 @@ public class AppPreferences { } public void setTheme(String theme){ preferences.put("THEME", theme); } - public void setProtocol(String protocol){ preferences.put("PROTOCOL", protocol); } + public void setNetUsb(String netUsb){ preferences.put("NETUSB", netUsb); } public void setNsIp(String ip){preferences.put("NSIP", ip);} diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 3ccccd9..39bb64d 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -78,13 +78,13 @@ public class GamesController implements Initializable { this.resourceBundle = resourceBundle; AppPreferences preferences = AppPreferences.getInstance(); - ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("TinFoil", "GoldLeaf"); + ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("Awoo", "GoldLeaf"); choiceProtocol.setItems(choiceProtocolList); choiceProtocol.getSelectionModel().select(preferences.getProtocol()); choiceProtocol.setOnAction(e-> { - tableFilesListController.setNewProtocol(getSelectedProtocol()); - if (getSelectedProtocol().equals("GoldLeaf")) { + tableFilesListController.setNewProtocol(getSelectedProtocolByName()); + if (isGoldLeaf()) { choiceNetUsb.setDisable(true); choiceNetUsb.getSelectionModel().select("USB"); nsIpLbl.setVisible(false); @@ -100,12 +100,12 @@ public class GamesController implements Initializable { // Really bad disable-enable upload button function disableUploadStopBtn(tableFilesListController.isFilesForUploadListEmpty()); }); // Add listener to notify tableView controller - tableFilesListController.setNewProtocol(getSelectedProtocol()); // Notify tableView controller + tableFilesListController.setNewProtocol(getSelectedProtocolByName()); // Notify tableView controller ObservableList<String> choiceNetUsbList = FXCollections.observableArrayList("USB", "NET"); choiceNetUsb.setItems(choiceNetUsbList); choiceNetUsb.getSelectionModel().select(preferences.getNetUsb()); - if (getSelectedProtocol().equals("GoldLeaf")) { + if (isGoldLeaf()) { choiceNetUsb.setDisable(true); choiceNetUsb.getSelectionModel().select("USB"); } @@ -121,7 +121,7 @@ public class GamesController implements Initializable { }); // Set and configure NS IP field behavior nsIpTextField.setText(preferences.getNsIp()); - if (getSelectedProtocol().equals("TinFoil") && getSelectedNetUsb().equals("NET")){ + if (isTinfoil() && getSelectedNetUsb().equals("NET")){ nsIpLbl.setVisible(true); nsIpTextField.setVisible(true); } @@ -145,7 +145,7 @@ public class GamesController implements Initializable { selectSplitNspBtn.getStyleClass().add("buttonSelect"); uploadStopBtn.setOnAction(e-> uploadBtnAction()); - uploadStopBtn.setDisable(getSelectedProtocol().equals("TinFoil")); + uploadStopBtn.setDisable(isTinfoil()); this.btnUpStopImage = new Region(); btnUpStopImage.getStyleClass().add("regionUpload"); @@ -174,30 +174,33 @@ public class GamesController implements Initializable { AppPreferences.getInstance().setTheme(styleSheets.get(0)); } /** - * Get selected protocol (GL/TF) + * Get selected protocol index (GL/Awoo) * */ - String getSelectedProtocol(){ + private int getSelectedProtocolByIndex(){ + return choiceProtocol.getSelectionModel().getSelectedIndex(); + } + private String getSelectedProtocolByName(){ return choiceProtocol.getSelectionModel().getSelectedItem(); } /** * Get selected protocol (USB/NET) * */ - String getSelectedNetUsb(){ + private String getSelectedNetUsb(){ return choiceNetUsb.getSelectionModel().getSelectedItem(); } /** * Get NS IP address * */ - String getNsIp(){ + private String getNsIp(){ return nsIpTextField.getText(); } private boolean isGoldLeaf() { - return getSelectedProtocol().equals("GoldLeaf"); + return getSelectedProtocolByName().equals("GoldLeaf"); } private boolean isTinfoil() { - return getSelectedProtocol().equals("TinFoil"); + return getSelectedProtocolByName().equals("Awoo"); } private boolean isAllFiletypesAllowedForGL() { @@ -290,7 +293,7 @@ public class GamesController implements Initializable { * @param startFolder where to start * @param filesRegex for filenames */ - // TODO: Too sophisticated. Should be moved to simple class to keep things simplier + // TODO: Too sophisticated. Should be moved to simple class to keep things simpler private void collectFiles(List<File> storage, File startFolder, @@ -353,7 +356,7 @@ public class GamesController implements Initializable { TextArea logArea = MediatorControl.getInstance().getContoller().logArea; - if (getSelectedProtocol().equals("TinFoil") && tableFilesListController.getFilesForUpload() == null) { + if (isTinfoil() && tableFilesListController.getFilesForUpload() == null) { logArea.setText(resourceBundle.getString("tab3_Txt_NoFolderOrFileSelected")); return; } @@ -369,11 +372,11 @@ public class GamesController implements Initializable { SettingsController settings = MediatorControl.getInstance().getSettingsController(); // If USB selected - if (getSelectedProtocol().equals("GoldLeaf") ){ + if (isGoldLeaf()){ final SettingsBlockGoldleafController goldleafSettings = settings.getGoldleafSettings(); usbNetCommunications = new UsbCommunications(nspToUpload, "GoldLeaf" + goldleafSettings.getGlVer(), goldleafSettings.getNSPFileFilterForGL()); } - else if (( getSelectedProtocol().equals("TinFoil") && getSelectedNetUsb().equals("USB") )){ + else if (( isTinfoil() && getSelectedNetUsb().equals("USB") )){ usbNetCommunications = new UsbCommunications(nspToUpload, "TinFoil", false); } else { // NET INSTALL OVER TINFOIL @@ -477,7 +480,7 @@ public class GamesController implements Initializable { * Crunch. This function called from NSTableViewController * */ public void disableUploadStopBtn(boolean disable){ - if (getSelectedProtocol().equals("TinFoil")) + if (isTinfoil()) uploadStopBtn.setDisable(disable); else uploadStopBtn.setDisable(false); @@ -522,7 +525,7 @@ public class GamesController implements Initializable { public void updatePreferencesOnExit(){ AppPreferences preferences = AppPreferences.getInstance(); - preferences.setProtocol(getSelectedProtocol()); + preferences.setProtocol(getSelectedProtocolByIndex()); preferences.setRecent(getRecentPath()); preferences.setNetUsb(getSelectedNetUsb()); preferences.setNsIp(getNsIp()); diff --git a/src/main/java/nsusbloader/cli/CommandLineInterface.java b/src/main/java/nsusbloader/cli/CommandLineInterface.java index 21f9a06..bf18aee 100644 --- a/src/main/java/nsusbloader/cli/CommandLineInterface.java +++ b/src/main/java/nsusbloader/cli/CommandLineInterface.java @@ -136,14 +136,14 @@ public class CommandLineInterface { /* Tinfoil network mode options */ final Option tinfoilNetOption = Option.builder("n") .longOpt("tfn") - .desc("Install via Tinfoil/Awoo Network mode. Check '-n help' for information.") + .desc("Install via Awoo Network mode. Check '-n help' for information.") .hasArgs() .argName("...") .build(); /* Tinfoil/Awoo USB */ final Option tinfoilOption = Option.builder("t") .longOpt("tinfoil") - .desc("Install via Tinfoil/Awoo USB mode.") + .desc("Install via Awoo USB mode.") .hasArgs() .argName("FILE...") .build(); diff --git a/src/main/java/nsusbloader/com/usb/TinFoil.java b/src/main/java/nsusbloader/com/usb/TinFoil.java index 33114bf..d4aa9e4 100644 --- a/src/main/java/nsusbloader/com/usb/TinFoil.java +++ b/src/main/java/nsusbloader/com/usb/TinFoil.java @@ -50,7 +50,7 @@ class TinFoil extends TransferModule { TinFoil(DeviceHandle handler, LinkedHashMap<String, File> nspMap, CancellableRunnable task, ILogPrinter logPrinter){ super(handler, nspMap, task, logPrinter); - print("============= Tinfoil =============", EMsgType.INFO); + print("======== Awoo Installer and compatibles ========", EMsgType.INFO); if (! sendListOfFiles()) return; @@ -69,25 +69,25 @@ class TinFoil extends TransferModule { byte[] padding = new byte[8]; if (writeUsb(TUL0)) { - print("TF Send list of files: handshake [1/4]", EMsgType.FAIL); + print("Send list of files: handshake [1/4]", EMsgType.FAIL); return false; } if (writeUsb(nspListNamesSize)) { // size of the list we can transfer - print("TF Send list of files: list length [2/4]", EMsgType.FAIL); + print("Send list of files: list length [2/4]", EMsgType.FAIL); return false; } if (writeUsb(padding)) { - print("TF Send list of files: padding [3/4]", EMsgType.FAIL); + print("Send list of files: padding [3/4]", EMsgType.FAIL); return false; } if (writeUsb(nspListNames)) { - print("TF Send list of files: list itself [4/4]", EMsgType.FAIL); + print("Send list of files: list itself [4/4]", EMsgType.FAIL); return false; } - print("TF Send list of files complete.", EMsgType.PASS); + print("Send list of files complete.", EMsgType.PASS); return true; } @@ -114,7 +114,7 @@ class TinFoil extends TransferModule { * After we sent commands to NS, this chain starts * */ private boolean proceedCommands(){ - print("TF Awaiting for NS commands.", EMsgType.INFO); + print("Awaiting for NS commands.", EMsgType.INFO); try{ byte[] deviceReply; byte command; @@ -127,11 +127,11 @@ class TinFoil extends TransferModule { switch (command){ case CMD_EXIT: - print("TF Transfer complete.", EMsgType.PASS); + print("Transfer complete.", EMsgType.PASS); return true; case CMD_FILE_RANGE_DEFAULT: case CMD_FILE_RANGE_ALTERNATIVE: - //print("TF Received 'FILE RANGE' command [0x0"+command+"].", EMsgType.PASS); + //print("Received 'FILE RANGE' command [0x0"+command+"].", EMsgType.PASS); if (fileRangeCmd()) return false; // catches exception } @@ -169,7 +169,7 @@ class TinFoil extends TransferModule { String nspFileName = new String(receivedArray, StandardCharsets.UTF_8); - print(String.format("TF Reply to: %s" + + print(String.format("Reply to: %s" + "\n Offset: %-20d 0x%x" + "\n Size: %-20d 0x%x", nspFileName, @@ -188,16 +188,16 @@ class TinFoil extends TransferModule { else sendNormalFile(nspFile, size, offset); } catch (IOException ioe){ - print("TF IOException:\n "+ioe.getMessage(), EMsgType.FAIL); + print("IOException:\n "+ioe.getMessage(), EMsgType.FAIL); ioe.printStackTrace(); return true; } catch (ArithmeticException ae){ - print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):" + + print("ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):" + "\n "+ae.getMessage(), EMsgType.FAIL); ae.printStackTrace(); return true; } catch (NullPointerException npe){ - print("TF NullPointerException (in some moment application didn't find something. Something important.):" + + print("NullPointerException (in some moment application didn't find something. Something important.):" + "\n "+npe.getMessage(), EMsgType.FAIL); npe.printStackTrace(); return true; @@ -216,7 +216,7 @@ class TinFoil extends TransferModule { NSSplitReader nsSplitReader = new NSSplitReader(nspFile, size); if (nsSplitReader.seek(offset) != offset) - throw new IOException("TF Requested offset is out of file size. Nothing to transmit."); + throw new IOException("Requested offset is out of file size. Nothing to transmit."); while (currentOffset < size){ if ((currentOffset + chunk) >= size ) @@ -226,10 +226,10 @@ class TinFoil extends TransferModule { readBuffer = new byte[chunk]; // TODO: not perfect moment, consider refactoring. if (nsSplitReader.read(readBuffer) != chunk) - throw new IOException("TF Reading from stream suddenly ended."); + throw new IOException("Reading from stream suddenly ended."); if (writeUsb(readBuffer)) - throw new IOException("TF Failure during file transfer."); + throw new IOException("Failure during file transfer."); currentOffset += chunk; logPrinter.updateProgress((double)currentOffset / (double)size); } @@ -245,7 +245,7 @@ class TinFoil extends TransferModule { BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspFile)); if (bufferedInStream.skip(offset) != offset) - throw new IOException("TF Requested offset is out of file size. Nothing to transmit."); + throw new IOException("Requested offset is out of file size. Nothing to transmit."); while (currentOffset < size) { if ((currentOffset + chunk) >= size) @@ -255,10 +255,10 @@ class TinFoil extends TransferModule { readBuffer = new byte[chunk]; if (bufferedInStream.read(readBuffer) != chunk) - throw new IOException("TF Reading from stream suddenly ended."); + throw new IOException("Reading from stream suddenly ended."); if (writeUsb(readBuffer)) - throw new IOException("TF Failure during file transfer."); + throw new IOException("Failure during file transfer."); currentOffset += chunk; logPrinter.updateProgress((double)currentOffset / (double)size); } @@ -278,17 +278,17 @@ class TinFoil extends TransferModule { final byte[] twelveZeroBytes = new byte[12]; if (writeUsb(standardReplyBytes)){ // Send integer value of '1' in Little-endian format. - print("TF Sending response failed [1/3]", EMsgType.FAIL); + print("Sending response failed [1/3]", EMsgType.FAIL); return true; } if(writeUsb(sizeAsBytes)) { // Send EXACTLY what has been received - print("TF Sending response failed [2/3]", EMsgType.FAIL); + print("Sending response failed [2/3]", EMsgType.FAIL); return true; } if(writeUsb(twelveZeroBytes)) { // kinda another one padding - print("TF Sending response failed [3/3]", EMsgType.FAIL); + print("Sending response failed [3/3]", EMsgType.FAIL); return true; } return false; @@ -317,7 +317,7 @@ class TinFoil extends TransferModule { case LibUsb.SUCCESS: if (writeBufTransferred.get() == message.length) return false; - print("TF Data transfer issue [write]" + + print("Data transfer issue [write]" + "\n Requested: "+message.length+ "\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL); return true; @@ -326,13 +326,13 @@ class TinFoil extends TransferModule { //writeBufTransferred.clear(); // MUST BE HERE IF WE 'GET()' IT continue; default: - print("TF Data transfer issue [write]" + + print("Data transfer issue [write]" + "\n Returned: "+ UsbErrorCodes.getErrCode(result) + "\n (execution stopped)", EMsgType.FAIL); return true; } } - print("TF Execution interrupted", EMsgType.INFO); + print("Execution interrupted", EMsgType.INFO); return true; } /** @@ -357,11 +357,11 @@ class TinFoil extends TransferModule { case LibUsb.ERROR_TIMEOUT: continue; default: - throw new Exception("TF Data transfer issue [read]" + + throw new Exception("Data transfer issue [read]" + "\n Returned: " + UsbErrorCodes.getErrCode(result)+ "\n (execution stopped)"); } } - throw new InterruptedException("TF Execution interrupted"); + throw new InterruptedException("Execution interrupted"); } } diff --git a/src/main/resources/RcmTab.fxml b/src/main/resources/RcmTab.fxml index ea765c2..adb7750 100644 --- a/src/main/resources/RcmTab.fxml +++ b/src/main/resources/RcmTab.fxml @@ -21,7 +21,7 @@ <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #ff1515 40%, transparent 45%);" /> <HBox alignment="CENTER"> <children> - <Label text="Fusée Gelée RCM"> + <Label text="%tabRcm_Lbl_FuseeGelee"> <font> <Font name="System Bold" size="15.0" /> </font> @@ -43,7 +43,7 @@ </GridPane> <VBox spacing="8.0"> <children> - <Label text="Payload: " /> + <Label text="%tabRcm_Lbl_Payload" /> <HBox fx:id="plHbox1" alignment="CENTER_LEFT" onDragDropped="#handleDrop" onDragOver="#handleDragOver" spacing="5.0"> <children> <RadioButton fx:id="pldrRadio1" mnemonicParsing="false"> diff --git a/src/main/resources/SettingsBlockTinfoil.fxml b/src/main/resources/SettingsBlockTinfoil.fxml index 846e621..507bbff 100644 --- a/src/main/resources/SettingsBlockTinfoil.fxml +++ b/src/main/resources/SettingsBlockTinfoil.fxml @@ -9,7 +9,7 @@ <VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SettingsBlockTinfoilController"> <children> - <Label text="Tinfoil" /> + <Label text="%tab2_Lbl_AwooBlockTitle" /> <CheckBox mnemonicParsing="false" text="%tab2_Cb_AllowXciNszXcz" fx:id="xciNszXczSupportCB"> <VBox.margin> <Insets left="5.0" /> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index b629b93..8c96082 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -30,7 +30,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=Auto-detect IP tab2_Cb_RandSelectPort=Randomly get port tab2_Cb_DontServeRequests=Don't serve requests -tab2_Lbl_DontServeRequestsDesc=If selected, this computer won't reply to NSP files requests coming from NS (over the net) and use defined host settings to tell TinFoil where should it look for files. +tab2_Lbl_DontServeRequestsDesc=If selected, this computer won't reply to NSP files requests coming from NS (over the net) and use defined host settings to tell Awoo Installer (or compatible applications) where should it look for files. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Port set incorrectly! windowBodyErrorPort=Port can't be 0 or greater than 65535. @@ -40,8 +40,8 @@ windowTitleNewVersionNOTAval=No new versions available windowTitleNewVersionUnknown=Unable to check for new versions windowBodyNewVersionUnknown=Something went wrong\nMaybe internet unavailable, or GitHub is down windowBodyNewVersionNOTAval=You're using the latest version -tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Tinfoil -tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Tinfoil transfer protocol. Don't change if not sure. Enable for Awoo Installer. +tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Awoo +tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Awoo (aka Adubbz/TinFoil) transfer protocol. Don't change if not sure. Enable for Awoo Installer. tab2_Lbl_Language=Language windowBodyRestartToApplyLang=Please restart application to apply changes. btn_OpenSplitFile=Select split NSP @@ -74,4 +74,7 @@ windowBodyPleaseStopOtherProcessFirst=Please stop other active process before co tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. windowTitleAddingFiles=Searching for files... -windowBodyFilesScanned=Files scanned: %d\nWould be added: %d \ No newline at end of file +windowBodyFilesScanned=Files scanned: %d\nWould be added: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM \ No newline at end of file diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties index 1f5db48..2c99a15 100644 --- a/src/main/resources/locale_ar_AR.properties +++ b/src/main/resources/locale_ar_AR.properties @@ -69,3 +69,4 @@ btn_Close=\u0625\u063A\u0644\u0627\u0642 tab2_Cb_GlVersion=\u0625\u0635\u062F\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641" tab2_Cb_GLshowNspOnly=\u0627\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0625\u0645\u062A\u062F\u0627\u062F "\u0625\u0646 \u0625\u0633 \u0628\u064A" \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641". windowBodyPleaseStopOtherProcessFirst=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649 \u0642\u0628\u0644 \u0627\u0644\u0625\u0633\u062A\u0645\u0631\u0627\u0631. + diff --git a/src/main/resources/locale_cs_CZ.properties b/src/main/resources/locale_cs_CZ.properties index bdafeb4..c4148c9 100644 --- a/src/main/resources/locale_cs_CZ.properties +++ b/src/main/resources/locale_cs_CZ.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=Automaticky detekovat IP adresu tab2_Cb_RandSelectPort=Vygenerovat n\u00E1hodn\u00FD port tab2_Cb_DontServeRequests=Neodpov\u00EDdat na po\u017Eadavky -tab2_Lbl_DontServeRequestsDesc=Pokud toto zvol\u00EDte, tento po\u010D\u00EDta\u010D nebude reagovat na \u017E\u00E1dosti o NSP soubory ze strany Switche (v s\u00EDti) a zamez\u00ED TinFoilu prohled\u00E1vat m\u00EDsta se soubory. +tab2_Lbl_DontServeRequestsDesc=Pokud toto zvol\u00EDte, tento po\u010D\u00EDta\u010D nebude reagovat na \u017E\u00E1dosti o NSP soubory ze strany Switche (v s\u00EDti) a zamez\u00ED Awoo Installeru prohled\u00E1vat m\u00EDsta se soubory. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Nespr\u00E1vn\u00E9 nastaven\u00ED portu! windowBodyErrorPort=Port mus\u00ED b\u00FDt v rozmez\u00ED 1 a\u017E 65535. @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=Nov\u011Bj\u0161\u00ED verze nen\u00ED k dispozici windowTitleNewVersionUnknown=Nepoda\u0159ilo se zkontrolovat aktualizace windowBodyNewVersionUnknown=N\u011Bco se porouchalo\nMo\u017En\u00E1 nejste p\u0159ipojeni k internetu, nebo nefunguje GitHub windowBodyNewVersionNOTAval=Jste na nejnov\u011Bj\u0161\u00ED verzi -tab2_Cb_AllowXciNszXcz=Umo\u017Enit volbu XCI / NSZ / XCZ soubor\u016F pro Tinfoil +tab2_Cb_AllowXciNszXcz=Umo\u017Enit volbu XCI / NSZ / XCZ soubor\u016F pro Awoo tab2_Lbl_AllowXciNszXczDesc=Lze vyu\u017E\u00EDt v aplikac\u00EDch, kter\u00E9 podporuj\u00ED soubory typu XCI/NSZ/XCZ a pro p\u0159enos vyu\u017E\u00EDvaj\u00ED protokol Tinfoil. Nem\u011B\u0148te, jestli tomu nerozum\u00EDte. Aktivujte pro Awoo Installer. tab2_Lbl_Language=Jazyk windowBodyRestartToApplyLang=Pro aplikov\u00E1n\u00ED zm\u011Bn restartujte aplikaci. @@ -69,3 +69,4 @@ btn_Close=Zav\u0159\u00EDt tab2_Cb_GlVersion=GoldLeaf verze tab2_Cb_GLshowNspOnly=Uk\u00E1zat v GoldLeafu pouze *.nsp. windowBodyPleaseStopOtherProcessFirst=Pros\u00EDm, p\u0159ed pokra\u010Dov\u00E1n\u00EDm nejprve zru\u0161te aktivn\u00ED p\u0159enos. + diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index d57b730..f028cc3 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -30,7 +30,7 @@ tab2_Lbl_HostPort=Port tab2_Cb_AutoDetectIp=IP automatisch erkennen. tab2_Cb_RandSelectPort=Port zuf\u00E4llig erhalten. tab2_Cb_DontServeRequests=Anfragen nicht bedienen. -tab2_Lbl_DontServeRequestsDesc=Wenn ausgew\u00E4hlt, wird dieser Computer nicht auf NSP-Datei-Anfragen von NS (\u00FCber das Internet) reagieren und benutzerdefinierte Host-Einstellungen nutzen um Tinfoil mitzuteilen, wo es nach Dateien suchen soll. +tab2_Lbl_DontServeRequestsDesc=Wenn ausgew\u00E4hlt, wird dieser Computer nicht auf NSP-Datei-Anfragen von NS (\u00FCber das Internet) reagieren und benutzerdefinierte Host-Einstellungen nutzen um Awoo mitzuteilen, wo es nach Dateien suchen soll. tab2_Lbl_HostExtra=Extra windowTitleErrorPort=Inkorrekter Port! windowBodyErrorPort=Der Port darf nicht 0 oder gr\u00F6\u00DFer als 65535 sein. @@ -40,11 +40,12 @@ windowTitleNewVersionNOTAval=Keine neue Version verf\u00FCgbar windowTitleNewVersionUnknown=Nicht in der Lage nach Updates zu suchen windowBodyNewVersionUnknown=Etwas ist schiefgelaufen\nInternet vielleicht nicht verf\u00FCgbar, oder GitHub nicht verf\u00FCgbar windowBodyNewVersionNOTAval=Du benutzt die neueste Version -tab2_Cb_AllowXciNszXcz=Erlaube XCI- NSZ- XCZ-Dateien-Verwendung f\u00FCr Tinfoil -tab2_Lbl_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt Tinfoil Transfer Protocol. Nicht \u00E4ndern, wenn unsicher. +tab2_Cb_AllowXciNszXcz=Erlaube XCI- NSZ- XCZ-Dateien-Verwendung f\u00FCr Awoo +tab2_Lbl_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt z Transfer Protocol. Nicht \u00E4ndern, wenn unsicher. tab2_Lbl_Language=Sprache windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen. btn_OpenSplitFile=Split-NSP ausw\uFFFDhlen tab2_Cb_GLshowNspOnly=Nur *.nsp in GoldLeaf zeigen. btn_Cancel=Abbrechen + diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index b629b93..f1fd516 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -30,7 +30,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=Auto-detect IP tab2_Cb_RandSelectPort=Randomly get port tab2_Cb_DontServeRequests=Don't serve requests -tab2_Lbl_DontServeRequestsDesc=If selected, this computer won't reply to NSP files requests coming from NS (over the net) and use defined host settings to tell TinFoil where should it look for files. +tab2_Lbl_DontServeRequestsDesc=If selected, this computer won't reply to NSP files requests coming from NS (over the net) and use defined host settings to tell Awoo where should it look for files. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Port set incorrectly! windowBodyErrorPort=Port can't be 0 or greater than 65535. @@ -40,8 +40,8 @@ windowTitleNewVersionNOTAval=No new versions available windowTitleNewVersionUnknown=Unable to check for new versions windowBodyNewVersionUnknown=Something went wrong\nMaybe internet unavailable, or GitHub is down windowBodyNewVersionNOTAval=You're using the latest version -tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Tinfoil -tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Tinfoil transfer protocol. Don't change if not sure. Enable for Awoo Installer. +tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Awoo +tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Awoo (aka Adubbz/TinFoil) transfer protocol. Don't change if not sure. Enable for Awoo Installer. tab2_Lbl_Language=Language windowBodyRestartToApplyLang=Please restart application to apply changes. btn_OpenSplitFile=Select split NSP @@ -74,4 +74,6 @@ windowBodyPleaseStopOtherProcessFirst=Please stop other active process before co tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. windowTitleAddingFiles=Searching for files... -windowBodyFilesScanned=Files scanned: %d\nWould be added: %d \ No newline at end of file +windowBodyFilesScanned=Files scanned: %d\nWould be added: %d +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabRcm_Lbl_Payload=Payload: \ No newline at end of file diff --git a/src/main/resources/locale_es_ES.properties b/src/main/resources/locale_es_ES.properties index a2a3362..b535b7d 100644 --- a/src/main/resources/locale_es_ES.properties +++ b/src/main/resources/locale_es_ES.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=Puerto tab2_Cb_AutoDetectIp=Detectar IP autom\u00E1ticamente tab2_Cb_RandSelectPort=Obtener el puerto autom\u00E1ticamente tab2_Cb_DontServeRequests=No contestar solicitudes -tab2_Lbl_DontServeRequestsDesc=Si habilita esta opci\u00F3n, el ordenador no responder\u00E1 solicitudes de archivos NSP de la NS (en la red), y usar\u00E1 las configuraciones definidas por el host para indicar a Tinfoil donde se encuentran los archivos +tab2_Lbl_DontServeRequestsDesc=Si habilita esta opci\u00F3n, el ordenador no responder\u00E1 solicitudes de archivos NSP de la NS (en la red), y usar\u00E1 las configuraciones definidas por el host para indicar a Awoo donde se encuentran los archivos tab2_Lbl_HostExtra=Extra windowTitleErrorPort=Puerto asignado incorrectamente! windowBodyErrorPort=El puerto no puede ser 0 o mayor que 65535 @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=No hay actualizaciones disponibles windowTitleNewVersionUnknown=No fue posible encontrar actualizaciones windowBodyNewVersionUnknown=Algo fall\u00F3\nLa conexi\u00F3n a internet no funciona correctamente, o GitHub est\u00E1 ca\u00EDdo windowBodyNewVersionNOTAval=Est\u00E1s usando la \u00FAltima versi\u00F3n -tab2_Cb_AllowXciNszXcz=Permite la selecci\u00F3n de archivos XCI / NSZ / XCZ para Tinfoil +tab2_Cb_AllowXciNszXcz=Permite la selecci\u00F3n de archivos XCI / NSZ / XCZ para Awoo tab2_Lbl_AllowXciNszXczDesc=Usado por algunas aplicaciones de terceros que soportan XCI/NSZ/XCZ y que utilizan el protocolo de transferencia de Tinfoil. Si no est\u00E1 seguro no cambie la opci\u00F3n. tab2_Lbl_Language=Idioma windowBodyRestartToApplyLang=Por favor, reinicie el programa para aplicar los cambios. diff --git a/src/main/resources/locale_fr_FR.properties b/src/main/resources/locale_fr_FR.properties index 5dcc313..5fbb3e7 100644 --- a/src/main/resources/locale_fr_FR.properties +++ b/src/main/resources/locale_fr_FR.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=D\u00E9tection automatique d'IP tab2_Cb_RandSelectPort=Obtenir un port al\u00E9atoire tab2_Cb_DontServeRequests=Ne pas servir les demandes -tab2_Lbl_DontServeRequestsDesc=Si cette option est s\u00E9lectionn\u00E9e, cet ordinateur ne r\u00E9pond pas aux demandes de fichiers NSP provenant de NS (par le r\u00E9seau) et utilise les param\u00E8tres d\u2019h\u00F4te d\u00E9finis pour indiquer \u00E0 TinFoil o\u00F9 il doit rechercher les fichiers. +tab2_Lbl_DontServeRequestsDesc=Si cette option est s\u00E9lectionn\u00E9e, cet ordinateur ne r\u00E9pond pas aux demandes de fichiers NSP provenant de NS (par le r\u00E9seau) et utilise les param\u00E8tres d\u2019h\u00F4te d\u00E9finis pour indiquer \u00E0 Awoo o\u00F9 il doit rechercher les fichiers. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Port mal configur\u00E9! windowBodyErrorPort=V\u00E9rifiez que le port est sup\u00E9rieur \u00E0 0 et inf\u00E9rieur ou \u00E9gal \u00E0 65535. @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=Aucune nouvelle version disponible windowTitleNewVersionUnknown=Impossible de v\u00E9rifier les nouvelles versions windowBodyNewVersionNOTAval=Vous utilisez la derni\u00E8re version windowBodyNewVersionUnknown=Une erreur s'est produite\nPeut-\u00EAtre des probl\u00E8mes de connexion Internet ou GitHub est en panne -tab2_Cb_AllowXciNszXcz=Autoriser la s\u00E9lection de fichiers XCI / NSZ / XCZ pour TinFoil +tab2_Cb_AllowXciNszXcz=Autoriser la s\u00E9lection de fichiers XCI / NSZ / XCZ pour Awoo tab2_Lbl_AllowXciNszXczDesc=Utilis\u00E9 par certaines applications tierces prenant en charge XCI/NSZ/XCZ et utilisant le protocole de transfert TinFoil. Ne changez pas en cas de doute. tab2_Lbl_Language=La langue btn_Cancel=Annuler diff --git a/src/main/resources/locale_it_IT.properties b/src/main/resources/locale_it_IT.properties index 8180fb2..9443adf 100644 --- a/src/main/resources/locale_it_IT.properties +++ b/src/main/resources/locale_it_IT.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=porta tab2_Cb_AutoDetectIp=Auto-rileva IP tab2_Cb_RandSelectPort=Ottieni porta casualmente tab2_Cb_DontServeRequests=Non servire le richieste -tab2_Lbl_DontServeRequestsDesc=Se selezionato, questo computer non risponder\u00E0 alle richieste di file NSP in arrivo da NS (tramite rete) e user\u00E0 le impostazioni dell'host definite per dire a TinFoil dove cercare i file. +tab2_Lbl_DontServeRequestsDesc=Se selezionato, questo computer non risponder\u00E0 alle richieste di file NSP in arrivo da NS (tramite rete) e user\u00E0 le impostazioni dell'host definite per dire a Awoo dove cercare i file. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Porta impostata impropriamente! windowBodyErrorPort=La porta non pu\u00F2 essere 0 o maggiore di 65535. @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=Nessuna nuova versione disponibile windowTitleNewVersionUnknown=Impossibile cercare nuove versioni windowBodyNewVersionUnknown=Qualcosa \u00E8 andato storto\nForse internet non \u00E8 disponibile o GitHub ha problemi windowBodyNewVersionNOTAval=Stai usando la versione pi\u00F9 recente -tab2_Cb_AllowXciNszXcz=Consenti la selezione di file XCI / NSZ / XCZ per Tinfoil +tab2_Cb_AllowXciNszXcz=Consenti la selezione di file XCI / NSZ / XCZ per Awoo tab2_Lbl_AllowXciNszXczDesc=Usato dalle applicazioni che supportano XCI/NSZ/XCZ e usano il protocollo di trasferimento di Tinfoil. Non cambiarlo se non sei sicuro. Attivalo per Awoo Installer. tab2_Lbl_Language=Lingua windowBodyRestartToApplyLang=Riavvia l'applicazione per applicare le modifiche. diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index c447dd2..5872560 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -3,7 +3,6 @@ btn_OpenFolders=\uD30C\uC77C \uC120\uD0DD btn_Upload=NS\uC5D0 \uC5C5\uB85C\uB4DC tab3_Txt_EnteredAsMsg1=\uB2E4\uC74C\uACFC \uAC19\uC774 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4: tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD574 \uB8E8\uD2B8\uC774\uAC70\uB098 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. -tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uB8E8\uD2B8\uC774\uAC70\uB098 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD55C 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. tab3_Txt_FilesToUploadTitle=\uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C: tab3_Txt_GreetingsMessage=NS-USBloader\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4 tab3_Txt_NoFolderOrFileSelected=\uC120\uD0DD\uD55C \uD30C\uC77C \uC5C6\uC74C: \uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. @@ -30,7 +29,7 @@ tab2_Lbl_HostPort=\uD3EC\uD2B8 tab2_Cb_AutoDetectIp=IP \uC790\uB3D9\uAC10\uC9C0 tab2_Cb_RandSelectPort=\uBB34\uC791\uC704\uB85C \uD3EC\uD2B8 \uAC00\uC838\uC624\uAE30 tab2_Cb_DontServeRequests=\uC694\uCCAD\uC744 \uCC98\uB9AC\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624 -tab2_Lbl_DontServeRequestsDesc=\uC774 \uC635\uC158\uC744 \uC120\uD0DD\uD558\uBA74\uC774 \uCEF4\uD4E8\uD130\uB294 NS (\uB124\uD2B8\uC6CC\uD06C\uB97C \uD1B5\uD574)\uC5D0\uC11C \uC624\uB294 NSP \uD30C\uC77C \uC694\uCCAD\uC5D0 \uC751\uB2F5\uD558\uC9C0 \uC54A\uACE0 \uC815\uC758\uB41C \uD638\uC2A4\uD2B8 \uC124\uC815\uC744 \uC0AC\uC6A9\uD558\uC5EC TinFoil\uC5D0 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC704\uCE58\uB97C \uC54C\uB824\uC90D\uB2C8\uB2E4. +tab2_Lbl_DontServeRequestsDesc=\uC774 \uC635\uC158\uC744 \uC120\uD0DD\uD558\uBA74\uC774 \uCEF4\uD4E8\uD130\uB294 NS (\uB124\uD2B8\uC6CC\uD06C\uB97C \uD1B5\uD574)\uC5D0\uC11C \uC624\uB294 NSP \uD30C\uC77C \uC694\uCCAD\uC5D0 \uC751\uB2F5\uD558\uC9C0 \uC54A\uACE0 \uC815\uC758\uB41C \uD638\uC2A4\uD2B8 \uC124\uC815\uC744 \uC0AC\uC6A9\uD558\uC5EC Awoo\uC5D0 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC704\uCE58\uB97C \uC54C\uB824\uC90D\uB2C8\uB2E4. tab2_Lbl_HostExtra=\uCD94\uAC00 windowTitleErrorPort=\uD3EC\uD2B8\uAC00 \uC798\uBABB \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4! windowBodyErrorPort=\uD3EC\uD2B8\uB294 0\uC774\uAC70\uB098 65535\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. @@ -40,7 +39,7 @@ windowTitleNewVersionNOTAval=\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC0C8\uB85C\uC6B4 windowTitleNewVersionUnknown=\uC0C8\uB85C\uC6B4 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. windowBodyNewVersionUnknown=\uBB38\uC81C\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.\n\uC778\uD130\uB137\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uAC70\uB098 GitHub\uAC00 \uB2E4\uC6B4\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4. windowBodyNewVersionNOTAval=\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9\uD558\uACE0 \uC788\uC2B5\uB2C8\uB2E4. -tab2_Cb_AllowXciNszXcz=Tinfoil \uC6A9 XCI / NSZ / XCZ \uD30C\uC77C \uC120\uD0DD \uD5C8\uC6A9 +tab2_Cb_AllowXciNszXcz=Awoo \uC6A9 XCI / NSZ / XCZ \uD30C\uC77C \uC120\uD0DD \uD5C8\uC6A9 tab2_Lbl_AllowXciNszXczDesc=XCI / NSZ / XCZ\uB97C \uC9C0\uC6D0\uD558\uACE0 Tinfoil \uC804\uC1A1 \uD504\uB85C\uD1A0\uCF5C\uC744 \uD65C\uC6A9\uD558\uB294 \uC560\uD50C\uB9AC\uCF00\uC774\uC158\uC5D0\uC11C \uC0AC\uC6A9\uB429\uB2C8\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC740 \uACBD\uC6B0 \uBCC0\uACBD\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624. Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uC5D0 \uB300\uD574 \uD65C\uC131\uD654\uD569\uB2C8\uB2E4. tab2_Lbl_Language=\uC5B8\uC5B4 windowBodyRestartToApplyLang=\uBCC0\uACBD \uC0AC\uD56D\uC744 \uC801\uC6A9\uD558\uB824\uBA74 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC2ED\uC2DC\uC624. diff --git a/src/main/resources/locale_pt_BR.properties b/src/main/resources/locale_pt_BR.properties index f496294..551bad0 100644 --- a/src/main/resources/locale_pt_BR.properties +++ b/src/main/resources/locale_pt_BR.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=porta tab2_Cb_AutoDetectIp=Auto-detectar IP tab2_Cb_RandSelectPort=Usar porta aleat\u00F3ria tab2_Cb_DontServeRequests=n\u00E3o aceitar solicita\u00E7\u00F5es -tab2_Lbl_DontServeRequestsDesc=Se selecionado, Este computador n\u00E3o ir\u00E1 aceitar solicita\u00E7\u00F5es de arquivos .nsp vindos, atrav\u00E9s da rede, do seu switch. Isso ir\u00E1 usar as defini\u00E7\u00F5es do host para informar ao tinfoil onde qual caminho procurar pelos arquivos. +tab2_Lbl_DontServeRequestsDesc=Se selecionado, Este computador n\u00E3o ir\u00E1 aceitar solicita\u00E7\u00F5es de arquivos .nsp vindos, atrav\u00E9s da rede, do seu switch. Isso ir\u00E1 usar as defini\u00E7\u00F5es do host para informar ao awoo onde qual caminho procurar pelos arquivos. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Porta configurada incorretamente! windowBodyErrorPort=Porta n\u00E3o pode ser 0 or maior que 65535. @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=Nao h\u00E1 novas vers\u00F5es. windowTitleNewVersionUnknown=Nao conseguimos checar por novas atualiza\u00E7\u00F5es. windowBodyNewVersionUnknown=Algo deu errado...\nProblemas de conex\u00E3o ou com a p\u00E1gina do github, talvez? windowBodyNewVersionNOTAval=Voc\u00EA est\u00E1 na \u00FAltima vers\u00E3o! -tab2_Cb_AllowXciNszXcz=permitir arquivos XCI / NSZ / XCZ para o tinfoil +tab2_Cb_AllowXciNszXcz=permitir arquivos XCI / NSZ / XCZ para o awoo tab2_Lbl_AllowXciNszXczDesc=Usado por aplica\u00E7\u00F5es que suportam XCI/NSZ/XCZ e utiliza protocolos de transfer\u00EAncia do Tinfoil. N\u00E3o mude o que n\u00E3o tem certeza. Ative para uso com o Awoo-Installer. tab2_Lbl_Language=Idioma windowBodyRestartToApplyLang=Por favor, reinicie para aplicar as modifica\u00E7\u00F5es. diff --git a/src/main/resources/locale_ro_RO.properties b/src/main/resources/locale_ro_RO.properties index c32a837..07be038 100644 --- a/src/main/resources/locale_ro_RO.properties +++ b/src/main/resources/locale_ro_RO.properties @@ -30,7 +30,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=Auto-detecteaz\u0103 IP tab2_Cb_RandSelectPort=Alege portul aleatoriu tab2_Cb_DontServeRequests=Nu servii request-uri -tab2_Lbl_DontServeRequestsDesc=Dac\u0103\u00A0selectat, acest computer nu va r\u0103spunde la request-uri de fi\u0219iere NSP venite de la NS (pe re\u021Bea) \u0219i va folosi set\u0103rile de host definite pentru a \u00EEi comunica lui Tinfoil unde s\u0103 caute fi\u0219ierele. +tab2_Lbl_DontServeRequestsDesc=Dac\u0103\u00A0selectat, acest computer nu va r\u0103spunde la request-uri de fi\u0219iere NSP venite de la NS (pe re\u021Bea) \u0219i va folosi set\u0103rile de host definite pentru a \u00EEi comunica lui Awoo unde s\u0103 caute fi\u0219ierele. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Port setat incorect! windowBodyErrorPort=Portul nu poate fii 0 sau mai mare de 65535. @@ -40,7 +40,7 @@ windowTitleNewVersionNOTAval=Nu exist\u0103 actualiz\u0103ri disponibile windowTitleNewVersionUnknown=Nu pot verifica actualiz\u0103rile disponibile windowBodyNewVersionUnknown=A ap\u0103rut o problem\u0103.\n Poate nu e\u0219ti conectat la Internet, sau GitHub e mort. windowBodyNewVersionNOTAval=Folose\u0219ti cea mai nou\u0103 actualizare -tab2_Cb_AllowXciNszXcz=Adaug\u0103 fi\u0219iere XCI / NSZ / XCZ pentru selec\u021Bie \u00EEn Tinfoil +tab2_Cb_AllowXciNszXcz=Adaug\u0103 fi\u0219iere XCI / NSZ / XCZ pentru selec\u021Bie \u00EEn Awoo tab2_Lbl_AllowXciNszXczDesc=Folosit de aplica\u021Bii care suport\u0103 XCI/NSZ/XCZ \u0219i folosesc protocolul de transfer al lui Tinfoil. Nu schimba dac\u0103\u00A0nu e\u0219ti sigur. Bifeaz\u0103 pentru Awoo Installer. tab2_Lbl_Language=Limb\u0103 windowBodyRestartToApplyLang=Te rog restarteaz\u0103 aplica\u021Bia pentru a aplica set\u0103rile noi. diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 1dc4885..bfc8a03 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=\u043F\u043E\u0440\u0442 tab2_Cb_AutoDetectIp=\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u0442\u044C IP tab2_Cb_RandSelectPort=\u041E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u0442\u044C \u043F\u043E\u0440\u0442 \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u044B\u043C \u043E\u0431\u0440\u0430\u0437\u043E\u043C tab2_Cb_DontServeRequests=\u041D\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C \u0437\u0430\u043F\u0440\u043E\u0441\u044B -tab2_Lbl_DontServeRequestsDesc=\u0415\u0441\u043B\u0438 \u0432\u044B\u0431\u0440\u0430\u043D\u043E, \u0442\u043E\u0433\u0434\u0430 \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u043D\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u044B NSP \u0444\u0430\u0439\u043B\u043E\u0432. \u0411\u0443\u0434\u0443\u0442 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0445\u043E\u0441\u0442\u0430 \u0447\u0442\u043E\u0431\u044B \u0443\u043A\u0430\u0437\u0430\u0442\u044C TinFoil \u043E\u0442\u043A\u0443\u0434\u0430 \u0435\u043C\u0443 \u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B\u044B. +tab2_Lbl_DontServeRequestsDesc=\u0415\u0441\u043B\u0438 \u0432\u044B\u0431\u0440\u0430\u043D\u043E, \u0442\u043E\u0433\u0434\u0430 \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u043D\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u044B NSP \u0444\u0430\u0439\u043B\u043E\u0432. \u0411\u0443\u0434\u0443\u0442 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0445\u043E\u0441\u0442\u0430 \u0447\u0442\u043E\u0431\u044B \u0443\u043A\u0430\u0437\u0430\u0442\u044C Awoo Installer (\u0438\u043B\u0438 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u043E\u043C\u0443 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044E) \u043E\u0442\u043A\u0443\u0434\u0430 \u0435\u043C\u0443 \u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B\u044B. tab2_Lbl_HostExtra=\u044D\u043A\u0441\u0442\u0440\u0430 windowTitleErrorPort=\u041F\u043E\u0440\u0442 \u0443\u043A\u0430\u0437\u0430\u043D \u043D\u0435\u0432\u0435\u0440\u043D\u043E! windowBodyErrorPort=\u041F\u043E\u0440\u0442 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C 0 \u0438\u043B\u0438 \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0442\u044C 65535. @@ -38,8 +38,8 @@ windowTitleNewVersionNOTAval=\u041D\u0435\u0442 \u043D\u043E\u0432\u044B\u0445 \ windowTitleNewVersionUnknown=\u041D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u043B\u0438\u0447\u0438\u0435 \u043D\u043E\u0432\u044B\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 windowBodyNewVersionNOTAval=\u0412\u044B \u0443\u0436\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044E\u044E \u0432\u0435\u0440\u0441\u0438\u044E windowBodyNewVersionUnknown=\u0427\u0442\u043E-\u0442\u043E \u043F\u043E\u0448\u043B\u043E \u043D\u0435 \u0442\u0430\u043A.\n\u041C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043D\u0435\u0442 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442\u0430 \u0438\u043B\u0438 GitHub \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D. -tab2_Cb_AllowXciNszXcz=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440 XCI, NSZ \u0438 XCZ \u0444\u0430\u0439\u043B\u043E\u0432 \u0434\u043B\u044F Tinfoil -tab2_Lbl_AllowXciNszXczDesc=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \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, NSZ, XCZ \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. \u0412\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u043F\u0440\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0438 Awoo Installer. +tab2_Cb_AllowXciNszXcz=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440 XCI, NSZ \u0438 XCZ \u0444\u0430\u0439\u043B\u043E\u0432 \u0434\u043B\u044F Awoo +tab2_Lbl_AllowXciNszXczDesc=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0441\u044F \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, NSZ, XCZ \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 Awoo (\u0442.\u043D. \u0441\u0442\u0430\u0440\u044B\u0439 Tinfoil). \u041D\u0435 \u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0435\u0441\u043B\u0438 \u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D\u044B. \u0412\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u043F\u0440\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0438 Awoo Installer. tab2_Lbl_Language=\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. tab2_Cb_GLshowNspOnly=\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. @@ -73,4 +73,7 @@ tab2_Cb_foldersSelectorForRomsDesc=\u041C\u0435\u043D\u044F\u0435\u0442 \u043F\u tab2_Cb_foldersSelectorForRoms=\u0412\u044B\u0431\u0438\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 \u0441 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0432\u043C\u0435\u0441\u0442\u043E \u0432\u044B\u0431\u043E\u0440\u0430 \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u043E\u0447\u043A\u0435. windowTitleAddingFiles=\u0418\u0449\u0435\u043C \u0444\u0430\u0439\u043B\u044B... windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u043E\u0432 \u043F\u0440\u043E\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u043E: %d\n\u0418\u0437 \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u0431\u0443\u0434\u0435\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer \u0438 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u044B\u0435 +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index 0152896..7e88bb5 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=\u043F\u043E\u0440\u0442 tab2_Cb_AutoDetectIp=\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E \u0432\u0438\u0437\u043D\u0430\u0447\u0430\u0442\u0438 IP tab2_Cb_RandSelectPort=\u0412\u0438\u0437\u043D\u0430\u0447\u0430\u0442\u0438 \u043F\u043E\u0440\u0442 \u0432\u0438\u043F\u0430\u0434\u043A\u043E\u0432\u0438\u043C \u0447\u0438\u043D\u043E\u043C tab2_Cb_DontServeRequests=\u041D\u0435 \u043E\u0431\u0440\u043E\u0431\u043B\u044F\u0442\u0438 \u0437\u0430\u043F\u0438\u0442\u0438 -tab2_Lbl_DontServeRequestsDesc=\u042F\u043A\u0449\u043E \u0432\u0438\u0431\u0440\u0430\u043D\u043E, \u0442\u043E\u0434\u0456 \u0446\u0435\u0439 \u043A\u043E\u043C\u043F'\u044E\u0442\u0435\u0440 \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0442\u0438\u043C\u0435 \u043D\u0430 \u0437\u0430\u043F\u0438\u0442\u0438 NSP \u0444\u0430\u0439\u043B\u0456\u0432. \u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0432\u0430\u0442\u0438\u043C\u0443\u0442\u044C\u0441\u044F \u043D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F \u0445\u043E\u0441\u0442\u0430 \u0434\u043B\u044F \u0432\u043A\u0430\u0437\u0430\u043D\u043D\u044F TinFoil \u043C\u0456\u0441\u0446\u044F \u0437\u0432\u0456\u0434\u043A\u0438 \u0444\u0430\u0439\u043B\u0438 \u043C\u0430\u044E\u0442\u044C \u0431\u0440\u0430\u0442\u0438\u0441\u044F. +tab2_Lbl_DontServeRequestsDesc=\u042F\u043A\u0449\u043E \u0432\u0438\u0431\u0440\u0430\u043D\u043E, \u0442\u043E\u0434\u0456 \u0446\u0435\u0439 \u043A\u043E\u043C\u043F'\u044E\u0442\u0435\u0440 \u043D\u0435 \u0432\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u0430\u0442\u0438\u043C\u0435 \u043D\u0430 \u0437\u0430\u043F\u0438\u0442\u0438 NSP \u0444\u0430\u0439\u043B\u0456\u0432. \u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0432\u0430\u0442\u0438\u043C\u0443\u0442\u044C\u0441\u044F \u043D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F \u0445\u043E\u0441\u0442\u0430 \u0434\u043B\u044F \u0432\u043A\u0430\u0437\u0430\u043D\u043D\u044F Awoo Installer (\u0430\u0431\u043E \u0441\u0443\u043C\u0456\u0441\u043D\u043E\u043C\u0443 \u0434\u043E\u0434\u0430\u0442\u043A\u0443) \u043C\u0456\u0441\u0446\u044F \u0437\u0432\u0456\u0434\u043A\u0438 \u0444\u0430\u0439\u043B\u0438 \u043C\u0430\u044E\u0442\u044C \u0431\u0440\u0430\u0442\u0438\u0441\u044F. tab2_Lbl_HostExtra=\u0435\u043A\u0441\u0442\u0440\u0430 windowTitleErrorPort=\u041D\u0435\u0432\u0456\u0440\u043D\u043E \u0432\u0438\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u043F\u043E\u0440\u0442! windowBodyErrorPort=\u041F\u043E\u0440\u0442 \u043D\u0435 \u043C\u043E\u0436\u0435 \u0431\u0443\u0442\u0438 0 \u0430\u0431\u043E \u043F\u0440\u0438\u0432\u0438\u0449\u0443\u0432\u0430\u0442\u0438 65535. @@ -38,8 +38,8 @@ windowTitleNewVersionNOTAval=\u041D\u0435\u043C\u0430\u0454 \u043D\u043E\u0432\u windowTitleNewVersionUnknown=\u041D\u0435\u043C\u043E\u0436\u043B\u0438\u0432\u043E \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043D\u0430\u044F\u0432\u043D\u0456\u0441\u0442\u044C \u043D\u043E\u0432\u0438\u0445 \u0432\u0435\u0440\u0441\u0456\u0439 windowBodyNewVersionNOTAval=\u0412\u0438 \u0432\u0436\u0435 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u0435 \u043E\u0441\u0442\u0430\u043D\u043D\u044E \u0432\u0435\u0440\u0441\u0456\u044E windowBodyNewVersionUnknown=\u0429\u043E\u0441\u044C \u043F\u0456\u0448\u043B\u043E \u043D\u0435 \u0442\u0430\u043A.\n\u041C\u043E\u0436\u043B\u0438\u0432\u043E, \u0456\u043D\u0442\u0435\u0440\u043D\u0435\u0442 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439, \u0430\u0431\u043E GitHub \u043D\u0435 \u043F\u0440\u0430\u0446\u044E\u0454. -tab2_Cb_AllowXciNszXcz=\u0414\u043E\u0437\u0432\u043E\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0456\u0440 XCI, NSZ \u0442\u0430 XCZ \u0444\u0430\u0439\u043B\u0456\u0432 \u0434\u043B\u044F \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u043D\u043D\u044F \u0443 Tinfoil -tab2_Lbl_AllowXciNszXczDesc=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0434\u043E\u0434\u0430\u0442\u043A\u0430\u043C\u0438, \u0449\u043E \u043C\u0430\u044E\u0442\u044C \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u043A\u0443 XCI, NSZ, XCZ \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. \u041D\u0435 \u0437\u043C\u0456\u043D\u044E\u0439\u0442\u0435, \u044F\u043A\u0449\u043E \u043D\u0435 \u0432\u043F\u0435\u0432\u043D\u0435\u043D\u0456. \u0423\u0432\u0456\u043C\u043A\u043D\u0456\u0442\u044C \u044F\u043A\u0449\u043E \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u0435 Awoo Installer +tab2_Cb_AllowXciNszXcz=\u0414\u043E\u0437\u0432\u043E\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0456\u0440 XCI, NSZ \u0442\u0430 XCZ \u0444\u0430\u0439\u043B\u0456\u0432 \u0434\u043B\u044F \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u043D\u043D\u044F \u0443 Awoo +tab2_Lbl_AllowXciNszXczDesc=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0434\u043E\u0434\u0430\u0442\u043A\u0430\u043C\u0438, \u0449\u043E \u043C\u0430\u044E\u0442\u044C \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u043A\u0443 XCI, NSZ, XCZ \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 Awoo (\u0442.\u0437. \u0441\u0442\u0430\u0440\u0438\u0439 Tinfoil). \u041D\u0435 \u0437\u043C\u0456\u043D\u044E\u0439\u0442\u0435, \u044F\u043A\u0449\u043E \u043D\u0435 \u0432\u043F\u0435\u0432\u043D\u0435\u043D\u0456. \u0423\u0432\u0456\u043C\u043A\u043D\u0456\u0442\u044C \u044F\u043A\u0449\u043E \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u0435 Awoo Installer tab2_Lbl_Language=\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. tab2_Cb_GLshowNspOnly=\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. @@ -73,3 +73,6 @@ tab2_Cb_foldersSelectorForRomsDesc=\u0417\u043C\u0456\u043D\u044E\u0454 \u043F\u tab2_Cb_foldersSelectorForRoms=\u0412\u0438\u0431\u0438\u0440\u0430\u0442\u0438 \u043F\u0430\u043F\u043A\u0443 \u0437 ROM \u0444\u0430\u0439\u043B\u0430\u043C\u0438 \u0437\u0430\u043C\u0456\u0441\u0442\u044C \u0432\u0438\u0431\u043E\u0440\u0443 \u0444\u0430\u0439\u043B\u0456\u0432 \u043F\u043E\u043E\u0434\u0438\u043D\u0446\u0456. windowTitleAddingFiles=\u0428\u0443\u043A\u0430\u0454\u043C\u043E \u0444\u0430\u0439\u043B\u0438... windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u0456\u0432 \u043F\u0440\u043E\u0441\u043A\u0430\u043D\u043E\u0432\u0430\u043D\u043E: %d\n\u0417 \u044F\u043A\u0438\u0445 \u0431\u0443\u0434\u0435 \u0434\u043E\u0434\u0430\u043D\u043E: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer \u0442\u0430 \u0441\u0443\u043C\u0456\u0441\u043D\u0456 +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM diff --git a/src/main/resources/locale_vi_VN.properties b/src/main/resources/locale_vi_VN.properties index 46f1642..cccefce 100644 --- a/src/main/resources/locale_vi_VN.properties +++ b/src/main/resources/locale_vi_VN.properties @@ -15,7 +15,7 @@ tab1_table_Lbl_FileName=T\u00EAn t\u1EADp tin tab1_table_Lbl_Size=K\u00EDch th\u01B0\u1EDBc tab1_table_Lbl_Status=Tr\u1EA1ng th\u00E1i tab1_table_Lbl_Upload=T\u1EA3i l\u00EAn? -tab2_Cb_AllowXciNszXcz=Cho ph\u00E9p ch\u1ECDn c\u00E1c t\u1EADp tin XCI \u0111\u1ED1i v\u1EDBi TinFoil +tab2_Cb_AllowXciNszXcz=Cho ph\u00E9p ch\u1ECDn c\u00E1c t\u1EADp tin XCI \u0111\u1ED1i v\u1EDBi Awoo tab2_Cb_AutoCheckForUpdates=T\u1EF1 \u0111\u1ED9ng ki\u1EC3m tra c\u00E1c b\u1EA3n c\u1EADp nh\u1EADt tab2_Cb_AutoDetectIp=T\u1EF1 ph\u00E1t hi\u1EC7n IP tab2_Cb_DontServeRequests=Kh\u00F4ng ph\u1EE5c v\u1EE5 c\u00E1c y\u00EAu c\u1EA7u @@ -25,7 +25,7 @@ tab2_Cb_RandSelectPort=L\u1EA5y c\u1ED5ng ng\u1EABu nhi\u00EAn tab2_Cb_ValidateNSHostName=Lu\u00F4n x\u00E1c th\u1EF1c \u0111\u1EA7u v\u00E0o IP NS. tab2_Lbl_AllowXciNszXczDesc=\u0110\u01B0\u1EE3c s\u1EED d\u1EE5ng b\u1EDFi c\u00E1c \u1EE9ng d\u1EE5ng b\u00EAn th\u1EE9 ba h\u1ED7 tr\u1EE3 XCI/NSZ/XCZ v\u00E0 v\u1EADn d\u1EE5ng giao th\u1EE9c truy\u1EC1n t\u1EA3i TinFoil. \u0110\u1EEBng thay \u0111\u1ED5i n\u1EBFu b\u1EA1n kh\u00F4ng ch\u1EAFc. tab2_Lbl_ApplicationSettings=Thi\u1EBFt l\u1EADp ch\u00EDnh -tab2_Lbl_DontServeRequestsDesc=N\u1EBFu ch\u1ECDn, m\u00E1y t\u00EDnh n\u00E0y s\u1EBD kh\u00F4ng h\u1ED3i \u0111\u00E1p \u0111\u1EBFn c\u00E1c y\u00EAu c\u1EA7u t\u1EADp tin NSP \u0111\u1EBFn t\u1EEB NS (qua m\u1EA1ng) v\u00E0 s\u1EED d\u1EE5ng thi\u1EBFt l\u1EADp c\u1ED5ng \u0111\u00E3 \u0111\u1ECBnh tr\u01B0\u1EDBc \u0111\u1EC3 b\u00E1o cho TinFoil n\u01A1i n\u00F3 s\u1EBD t\u00ECm t\u1EADp tin. +tab2_Lbl_DontServeRequestsDesc=N\u1EBFu ch\u1ECDn, m\u00E1y t\u00EDnh n\u00E0y s\u1EBD kh\u00F4ng h\u1ED3i \u0111\u00E1p \u0111\u1EBFn c\u00E1c y\u00EAu c\u1EA7u t\u1EADp tin NSP \u0111\u1EBFn t\u1EEB NS (qua m\u1EA1ng) v\u00E0 s\u1EED d\u1EE5ng thi\u1EBFt l\u1EADp c\u1ED5ng \u0111\u00E3 \u0111\u1ECBnh tr\u01B0\u1EDBc \u0111\u1EC3 b\u00E1o cho Awoo n\u01A1i n\u00F3 s\u1EBD t\u00ECm t\u1EADp tin. tab2_Lbl_HostExtra=m\u1EDF r\u1ED9ng tab2_Lbl_HostIP=IP M\u00E1y ch\u1EE7 tab2_Lbl_HostPort=c\u1ED5ng diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 90a4cf4..a789f16 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=\u81EA\u52A8\u63A2\u6D4B IP tab2_Cb_RandSelectPort=\u968F\u673A\u9009\u62E9\u7AEF\u53E3 tab2_Cb_DontServeRequests=\u4E0D\u54CD\u5E94\u8BF7\u6C42 -tab2_Lbl_DontServeRequestsDesc=\u5982\u679C\u9009\u62E9\uFF0C\u8FD9\u53F0\u7535\u8111\u5C06\u4E0D\u4F1A\u54CD\u5E94NS\u901A\u8FC7\u7F51\u7EDC\u53D1\u9001\u7684NSP\u6587\u4EF6\u8BF7\u6C42\uFF0C\u8F6C\u800C\u4F7F\u7528\u9884\u8BBE\u7684\u914D\u7F6E\u544A\u77E5TinFoil\u6587\u4EF6\u7684\u83B7\u53D6\u5730\u5740\u3002 +tab2_Lbl_DontServeRequestsDesc=\u5982\u679C\u9009\u62E9\uFF0C\u8FD9\u53F0\u7535\u8111\u5C06\u4E0D\u4F1A\u54CD\u5E94NS\u901A\u8FC7\u7F51\u7EDC\u53D1\u9001\u7684NSP\u6587\u4EF6\u8BF7\u6C42\uFF0C\u8F6C\u800C\u4F7F\u7528\u9884\u8BBE\u7684\u914D\u7F6E\u544A\u77E5Awoo\u6587\u4EF6\u7684\u83B7\u53D6\u5730\u5740\u3002 tab2_Lbl_HostExtra=extra windowTitleErrorPort=\u7AEF\u53E3\u8BBE\u7F6E\u4E0D\u6B63\u786E\uFF01 windowBodyErrorPort=\u7AEF\u53E3\u4E0D\u80FD\u7B49\u4E8E0\u6216\u8005\u5927\u4E8E65535. @@ -38,34 +38,34 @@ windowTitleNewVersionNOTAval=\u6CA1\u6709\u53EF\u7528\u7684\u65B0\u7248\u672C windowTitleNewVersionUnknown=\u65E0\u6CD5\u68C0\u67E5\u65B0\u7248\u672C windowBodyNewVersionUnknown=\u51FA\u9519\u4E86\n\u53EF\u80FD\u662F\u7F51\u7EDC\u95EE\u9898\uFF0C\u6216\u8005Github\u670D\u52A1\u4E0D\u53EF\u7528 windowBodyNewVersionNOTAval=\u4F60\u6B63\u5728\u4F7F\u7528\u6700\u65B0\u7248 -tab2_Cb_AllowXciNszXcz=TinFoil\u6A21\u5F0F\u5141\u8BB8\u9009\u62E9XCI\u6587\u4EF6 +tab2_Cb_AllowXciNszXcz=Awoo\u6A21\u5F0F\u5141\u8BB8\u9009\u62E9XCI\u6587\u4EF6 tab2_Lbl_AllowXciNszXczDesc=\u7528\u4E8E\u4E00\u4E9B\u652F\u6301XCI/NSZ/XCZ\u548CTinfoil\u4F20\u8F93\u534F\u8BAE\u7684\u7B2C\u4E09\u65B9\u5E94\u7528\u3002\u5982\u679C\u4E0D\u6E05\u695A\u4E0D\u8981\u4FEE\u6539\u3002 tab2_Lbl_Language=\u8BED\u8A00 windowBodyRestartToApplyLang=\u8BF7\u91CD\u542F\u5E94\u7528\u4EE5\u5E94\u7528\u66F4\u6539\u3002 -btn_OpenSplitFile=\u9009\u62e9\u5206\u5272\u7684\u004e\u0053\u0050 -tab2_Lbl_ApplicationSettings=\u4e3b\u8981\u8bbe\u5b9a -tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272\u0026\u5408\u5e76\u6587\u4ef6\u5de5\u5177 -tabSplMrg_RadioBtn_Split=\u5206\u5272\u0026\u5408\u5e76\u6587\u4ef6\u5de5\u5177 -tabSplMrg_RadioBtn_Merge=\u5408\u5e76 -tabSplMrg_Txt_File=\u6587\u4ef6\u003a -tabSplMrg_Txt_Folder=\u5206\u5272\u6587\u4ef6\u0028\u6587\u4ef6\u5939\u0029\u003a\u0020 -tabSplMrg_Btn_SelectFile=\u9009\u62e9\u6587\u4ef6 -tabSplMrg_Btn_SelectFolder=\u9009\u62e9\u6587\u4ef6\u5939 -tabSplMrg_Lbl_SaveToLocation=\u8def\u5f84\u003a\u0020 -tabSplMrg_Btn_ChangeSaveToLocation=\u53d8\u66f4 -tabSplMrg_Btn_Convert=\u8f6c\u6362 -windowTitleError=\u9519\u8bef -windowBodyPleaseFinishTransfersFirst=\u5f53\u7a0b\u5e8f\u6b63\u5728\u5904\u7406\u0055\u0053\u0042\u002f\u004e\u0065\u0074\u0077\u006f\u0072\u006b\u5b89\u88c5\u65f6\u65e0\u6cd5\u540c\u65f6\u6267\u884c\u5206\u5272\u002f\u5408\u5e76\u6587\u4ef6\u002e\u0020\u5982\u9700\u7ee7\u7eed\u002c\u5fc5\u987b\u4e2d\u65ad\u76ee\u524d\u7684\u4f20\u8f93\u002e -done_txt=\u5b8c\u6210\u0021 -failure_txt=\u5931\u8d25 -btn_Select=\u9009\u62e9 -btn_InjectPayloader=\u6ce8\u5165payload -tabNXDT_Btn_Start=\u5f00\u59cb\u0021 -tab2_Btn_InstallDrivers=\u4e0b\u8f7d\u5e76\u5b89\u88c5\u9a71\u52a8\u7a0b\u5e8f -windowTitleDownloadDrivers=\u4e0b\u8f7d\u5e76\u5b89\u88c5\u9a71\u52a8\u7a0b\u5e8f -windowBodyDownloadDrivers=\u6b63\u5728\u4e0b\u8f7d\u9a71\u52a8\u7a0b\u5e8f (libusbK v3.0.7.0)... -btn_Cancel=\u53d6\u6d88 -btn_Close=\u5173\u95ed -tab2_Cb_GlVersion=GoldLeaf\u7248\u672c -tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5185\u4ec5\u663e\u793a*.nsp\u6587\u4ef6 -windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u6267\u884c\u76ee\u524d\u7684\u64cd\u4f5c\u7a0b\u5e8f\u002c\u8bf7\u5148\u505c\u6b62\u5176\u4ed6\u6b63\u5728\u5904\u7406\u7684\u7a0b\u5e8f\u002e +btn_OpenSplitFile=\u9009\u62E9\u5206\u5272\u7684NSP +tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8BBE\u5B9A +tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 +tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 +tabSplMrg_RadioBtn_Merge=\u5408\u5E76 +tabSplMrg_Txt_File=\u6587\u4EF6: +tabSplMrg_Txt_Folder=\u5206\u5272\u6587\u4EF6(\u6587\u4EF6\u5939):\u0020 +tabSplMrg_Btn_SelectFile=\u9009\u62E9\u6587\u4EF6 +tabSplMrg_Btn_SelectFolder=\u9009\u62E9\u6587\u4EF6\u5939 +tabSplMrg_Lbl_SaveToLocation=\u8DEF\u5F84:\u0020 +tabSplMrg_Btn_ChangeSaveToLocation=\u53D8\u66F4 +tabSplMrg_Btn_Convert=\u8F6C\u6362 +windowTitleError=\u9519\u8BEF +windowBodyPleaseFinishTransfersFirst=\u5F53\u7A0B\u5E8F\u6B63\u5728\u5904\u7406USB/Network\u5B89\u88C5\u65F6\u65E0\u6CD5\u540C\u65F6\u6267\u884C\u5206\u5272/\u5408\u5E76\u6587\u4EF6.\u0020\u5982\u9700\u7EE7\u7EED,\u5FC5\u987B\u4E2D\u65AD\u76EE\u524D\u7684\u4F20\u8F93. +done_txt=\u5B8C\u6210! +failure_txt=\u5931\u8D25 +btn_Select=\u9009\u62E9 +btn_InjectPayloader=\u6CE8\u5165payload +tabNXDT_Btn_Start=\u5F00\u59CB! +tab2_Btn_InstallDrivers=\u4E0B\u8F7D\u5E76\u5B89\u88C5\u9A71\u52A8\u7A0B\u5E8F +windowTitleDownloadDrivers=\u4E0B\u8F7D\u5E76\u5B89\u88C5\u9A71\u52A8\u7A0B\u5E8F +windowBodyDownloadDrivers=\u6B63\u5728\u4E0B\u8F7D\u9A71\u52A8\u7A0B\u5E8F (libusbK v3.0.7.0)... +btn_Cancel=\u53D6\u6D88 +btn_Close=\u5173\u95ED +tab2_Cb_GlVersion=GoldLeaf\u7248\u672C +tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5185\u4EC5\u663E\u793A*.nsp\u6587\u4EF6 +windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u6267\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8BF7\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u5904\u7406\u7684\u7A0B\u5E8F. diff --git a/src/main/resources/locale_zh_TW.properties b/src/main/resources/locale_zh_TW.properties index 7802b5f..6788727 100644 --- a/src/main/resources/locale_zh_TW.properties +++ b/src/main/resources/locale_zh_TW.properties @@ -28,7 +28,7 @@ tab2_Lbl_HostPort=\u9023\u63A5\u57E0 tab2_Cb_AutoDetectIp=\u81EA\u52D5\u5075\u6E2CIP tab2_Cb_RandSelectPort=\u96A8\u6A5F\u9078\u53D6\u9023\u63A5\u57E0 tab2_Cb_DontServeRequests=\u4E0D\u56DE\u61C9NS\u8ACB\u6C42 -tab2_Lbl_DontServeRequestsDesc=\u555F\u7528\u6B64\u8A2D\u5B9A\u5F8C,\u6B64\u96FB\u8166\u5C07\u4E0D\u6703\u56DE\u61C9\u4F86\u81EANS(\u900F\u904E\u7DB2\u8DEF)\u7684NSP\u6A94\u6848\u8ACB\u6C42,\u4E26\u8B93TinFoil\u5C0B\u627E\u6A94\u6848\u4F86\u6E90\u6642\u4F7F\u7528\u9810\u8A2D\u7684\u4E3B\u6A5F\u8A2D\u5B9A. +tab2_Lbl_DontServeRequestsDesc=\u555F\u7528\u6B64\u8A2D\u5B9A\u5F8C,\u6B64\u96FB\u8166\u5C07\u4E0D\u6703\u56DE\u61C9\u4F86\u81EANS(\u900F\u904E\u7DB2\u8DEF)\u7684NSP\u6A94\u6848\u8ACB\u6C42,\u4E26\u8B93Awoo\u5C0B\u627E\u6A94\u6848\u4F86\u6E90\u6642\u4F7F\u7528\u9810\u8A2D\u7684\u4E3B\u6A5F\u8A2D\u5B9A. tab2_Lbl_HostExtra=\u5176\u4ED6 windowTitleErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u4E0D\u6B63\u78BA! windowBodyErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u503C\u5FC5\u9808\u4ECB\u65BC0\u523065535\u4E4B\u9593. @@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=\u76EE\u524D\u6C92\u6709\u53EF\u66F4\u65B0\u7684\u7 windowTitleNewVersionUnknown=\u76EE\u524D\u7121\u6CD5\u6AA2\u67E5\u6709\u7121\u53EF\u66F4\u65B0\u7248\u672C windowBodyNewVersionUnknown=\u767C\u751F\u932F\u8AA4\n\u53EF\u80FD\u7DB2\u8DEF\u4E0D\u53EF\u7528\u6216\u8A0A\u865F\u4E0D\u8DB3,\u6216\u8005\u7121\u6CD5\u9023\u4E0AGitHub\u5B98\u7DB2\u4F3A\u670D\u5668 windowBodyNewVersionNOTAval=\u76EE\u524D\u4F7F\u7528\u7684\u7A0B\u5F0F\u5DF2\u662F\u6700\u65B0\u7248\u672C -tab2_Cb_AllowXciNszXcz=\u5141\u8A31Tinfoil\u6A21\u5F0F\u6642\u9078\u53D6XCI / NSZ / XCZ \u6A94\u6848\u683C\u5F0F +tab2_Cb_AllowXciNszXcz=\u5141\u8A31Awoo\u6A21\u5F0F\u6642\u9078\u53D6XCI / NSZ / XCZ \u6A94\u6848\u683C\u5F0F tab2_Lbl_AllowXciNszXczDesc=\u6B64\u8A2D\u5B9A\u5C08\u70BA\u652F\u63F4XCI/NSZ/XCZ\u6A94\u6848\u683C\u5F0F\u8207Tinfoil\u50B3\u8F38\u5354\u8B70\u7684\u7B2C\u4E09\u65B9\u7A0B\u5F0F\u4F7F\u7528. \u5982\u4E0D\u78BA\u5B9A,\u8ACB\u52FF\u8B8A\u66F4\u6B64\u9805\u8A2D\u5B9A. \u4F7F\u7528Awoo Installer\u8ACB\u555F\u7528\u6B64\u8A2D\u5B9A. tab2_Lbl_Language=\u4ECB\u9762\u8A9E\u7CFB windowBodyRestartToApplyLang=\u8ACB\u91CD\u65B0\u555F\u52D5\u7A0B\u5F0F\u4EE5\u5957\u7528\u8B8A\u66F4\u7684\u8A2D\u5B9A. From 892e919d1816d7710fe40b88b08edf423474ea2e Mon Sep 17 00:00:00 2001 From: unbranched <39440265+unbranched@users.noreply.github.com> Date: Sat, 21 Aug 2021 11:03:05 +0000 Subject: [PATCH 037/134] Italian translation update --- src/main/resources/locale_it_IT.properties | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/resources/locale_it_IT.properties b/src/main/resources/locale_it_IT.properties index 9443adf..8aaad2c 100644 --- a/src/main/resources/locale_it_IT.properties +++ b/src/main/resources/locale_it_IT.properties @@ -1,5 +1,7 @@ btn_OpenFile=Seleziona file +btn_OpenFolders=Seleziona cartella btn_Upload=Invia a NS +btn_OpenFolders_tooltip=Seleziona una cartella da scansionare.\nQuesta cartella e tutte le sue sottocartelle verranno scansionate.\nTutti i file validi verranno aggiunti alla lista. tab3_Txt_EnteredAsMsg1=Sei entrato come: tab3_Txt_EnteredAsMsg2=Devi essere root o avere configurato le regole 'udev' per questo utente, per evitare eventuali problemi. tab3_Txt_FilesToUploadTitle=File da inviare: @@ -23,12 +25,12 @@ tab1_Lbl_NSIP=IP NS: tab2_Cb_ValidateNSHostName=Convalida sempre l'input dell'IP di NS. windowBodyBadIp=Sei sicuro di aver inserito correttamente l'indirizzo IP di NS? windowTitleBadIp=Indirizzo IP di NS probabilmente sbagliato -tab2_Cb_ExpertMode=Modalit\u00E0 esperto +tab2_Cb_ExpertMode=Modalit\u00E0 esperto (configurazione della rete) tab2_Lbl_HostPort=porta tab2_Cb_AutoDetectIp=Auto-rileva IP tab2_Cb_RandSelectPort=Ottieni porta casualmente tab2_Cb_DontServeRequests=Non servire le richieste -tab2_Lbl_DontServeRequestsDesc=Se selezionato, questo computer non risponder\u00E0 alle richieste di file NSP in arrivo da NS (tramite rete) e user\u00E0 le impostazioni dell'host definite per dire a Awoo dove cercare i file. +tab2_Lbl_DontServeRequestsDesc=Se selezionato, questo computer non risponder\u00E0 alle richieste di file NSP in arrivo da NS (tramite rete) e user\u00E0 le impostazioni dell'host definite per dire ad Awoo Installer (o alle applicazioni compatibili) dove cercare i file. tab2_Lbl_HostExtra=extra windowTitleErrorPort=Porta impostata impropriamente! windowBodyErrorPort=La porta non pu\u00F2 essere 0 o maggiore di 65535. @@ -36,16 +38,15 @@ tab2_Cb_AutoCheckForUpdates=Controllo automatico degli aggiornamenti windowTitleNewVersionAval=Nuova versione disponibile windowTitleNewVersionNOTAval=Nessuna nuova versione disponibile windowTitleNewVersionUnknown=Impossibile cercare nuove versioni -windowBodyNewVersionUnknown=Qualcosa \u00E8 andato storto\nForse internet non \u00E8 disponibile o GitHub ha problemi +windowBodyNewVersionUnknown=Qualcosa \u00E8 andato storto\nForse internet non \u00E8 disponibile o GitHub \u00E8 irraggiungibile windowBodyNewVersionNOTAval=Stai usando la versione pi\u00F9 recente tab2_Cb_AllowXciNszXcz=Consenti la selezione di file XCI / NSZ / XCZ per Awoo -tab2_Lbl_AllowXciNszXczDesc=Usato dalle applicazioni che supportano XCI/NSZ/XCZ e usano il protocollo di trasferimento di Tinfoil. Non cambiarlo se non sei sicuro. Attivalo per Awoo Installer. +tab2_Lbl_AllowXciNszXczDesc=Usato dalle applicazioni che supportano XCI/NSZ/XCZ ed utilizza il protocollo di trasferimento di Awoo (o Adubbz/TinFoil). Non cambiarlo se non sei sicuro. Attivalo per Awoo Installer. tab2_Lbl_Language=Lingua windowBodyRestartToApplyLang=Riavvia l'applicazione per applicare le modifiche. -tab2_Cb_GLshowNspOnly=Mostra solo *.nsp in GoldLeaf. btn_OpenSplitFile=Seleziona NSP troncato tab2_Lbl_ApplicationSettings=Impostazioni principali -tabSplMrg_Lbl_SplitNMergeTitle=Strumento tronca e unisci +tabSplMrg_Lbl_SplitNMergeTitle=Strumento tronca e unisci file tabSplMrg_RadioBtn_Split=Tronca tabSplMrg_RadioBtn_Merge=Unisci tabSplMrg_Txt_File=File: @@ -61,6 +62,19 @@ done_txt=Fatto! failure_txt=Fallito btn_Select=Seleziona btn_InjectPayloader=Inietta payload +tabNXDT_Btn_Start=Avvia! +tab2_Btn_InstallDrivers=Scarica e installa i driver +windowTitleDownloadDrivers=Scarica e installa i driver +windowBodyDownloadDrivers=Scaricamento dei driver (libusbK v3.0.7.0)... btn_Cancel=Annulla +btn_Close=Chiudi tab2_Cb_GlVersion=Versione di GoldLeaf - +tab2_Cb_GLshowNspOnly=Mostra solo *.nsp in GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Ferma l'altro processo attivo prima di continuare. +tab2_Cb_foldersSelectorForRoms=Seleziona una cartella con file ROM invece di scegliere le ROM individualmente. +tab2_Cb_foldersSelectorForRomsDesc=Cambia il comportamento del pulsante 'Seleziona file' nella scheda 'Giochi': invece di selezionare file ROM uno ad uno, puoi scegliere una cartella per aggiungere tutti i file supportati in una volta sola. +windowTitleAddingFiles=Ricerca di file... +windowBodyFilesScanned=File scansionati: %d\nVerranno aggiunti: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer e compatibili +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 3f2e986aed794e4ec478ad28ecb3fb9dcec91068 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 25 Aug 2021 07:03:17 +0300 Subject: [PATCH 038/134] Validated using newest GoldLeaf v0.9 and works fine. Updated text, application preferences record, fxml, readme. --- README.md | 2 +- src/main/java/nsusbloader/AppPreferences.java | 10 ++++------ .../Controllers/SettingsBlockGoldleafController.java | 2 +- src/main/java/nsusbloader/com/usb/GoldLeaf_08.java | 2 +- .../java/nsusbloader/com/usb/UsbCommunications.java | 2 +- src/main/resources/SettingsBlockGoldleaf.fxml | 8 +++----- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a1560b0..6935324 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ JDK 11 for MacOS and Linux | v0.6 | none | | v0.6.1 | v0.6 | | v0.7 - 0.7.3 | v0.7+ | -| v0.8 | v1.0+ | +| v0.8 - 0.9 | v1.0+ | where '+' means 'any next NS-USBloader version'. diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 52a60fc..18afe80 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -27,7 +27,7 @@ public class AppPreferences { private final Preferences preferences; private final Locale locale; - public static final String[] goldleafSupportedVersions = {"v0.5", "v0.7.x", "v0.8"}; + public static final String[] goldleafSupportedVersions = {"v0.5", "v0.7.x", "v0.8-0.9"}; private AppPreferences(){ this.preferences = Preferences.userRoot().node("NS-USBloader"); @@ -113,12 +113,10 @@ public class AppPreferences { public boolean getNspFileFilterGL(){return preferences.getBoolean("GL_NSP_FILTER", false); } public void setNspFileFilterGL(boolean prop){preferences.putBoolean("GL_NSP_FILTER", prop);} - public String getGlVersion(){ - int recentGlVersionIndex = goldleafSupportedVersions.length - 1; - String recentGlVersion = goldleafSupportedVersions[recentGlVersionIndex]; - return preferences.get("gl_version", recentGlVersion); + public int getGlVersion(){ + return preferences.getInt("gl_ver", goldleafSupportedVersions.length - 1); } - public void setGlVersion(String version){ preferences.put("gl_version", version);} + public void setGlVersion(int version){ preferences.putInt("gl_ver", version);} public double getSceneWidth(){ return preferences.getDouble("WIND_WIDTH", 850.0); } public void setSceneWidth(double value){ preferences.putDouble("WIND_WIDTH", value); } diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java index b5ba33d..8be5568 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java @@ -53,6 +53,6 @@ public class SettingsBlockGoldleafController implements Initializable { final AppPreferences preferences = AppPreferences.getInstance(); preferences.setNspFileFilterGL(getNSPFileFilterForGL()); - preferences.setGlVersion(getGlVer()); + preferences.setGlVersion(glVersionChoiceBox.getSelectionModel().getSelectedIndex()); } } diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index 0a15656..f8bf657 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -103,7 +103,7 @@ class GoldLeaf_08 extends TransferModule { this.nspFilterForGl = nspFilter; - print("============= GoldLeaf v0.8 =============\n\t" + + print("=========== GoldLeaf v0.8-0.9 ===========\n\t" + "VIRT:/ equals files added into the application\n\t" + "HOME:/ equals " +System.getProperty("user.home"), EMsgType.INFO); diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index 3601848..595ede2 100644 --- a/src/main/java/nsusbloader/com/usb/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -66,7 +66,7 @@ public class UsbCommunications extends CancellableRunnable { case "TinFoil": module = new TinFoil(handler, nspMap, this, logPrinter); break; - case "GoldLeafv0.8": + case "GoldLeafv0.8-0.9": module = new GoldLeaf_08(handler, nspMap, this, logPrinter, nspFilterForGl); break; case "GoldLeafv0.7.x": diff --git a/src/main/resources/SettingsBlockGoldleaf.fxml b/src/main/resources/SettingsBlockGoldleaf.fxml index 9c84293..f30cc9e 100644 --- a/src/main/resources/SettingsBlockGoldleaf.fxml +++ b/src/main/resources/SettingsBlockGoldleaf.fxml @@ -7,9 +7,7 @@ <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> - -<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" - fx:controller="nsusbloader.Controllers.SettingsBlockGoldleafController"> +<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SettingsBlockGoldleafController"> <children> <Label text="GoldLeaf" /> <CheckBox fx:id="nspFilesFilterForGLCB" mnemonicParsing="false" text="%tab2_Cb_GLshowNspOnly"> @@ -19,11 +17,11 @@ <HBox alignment="CENTER_LEFT" spacing="5.0"> <children> <Label text="%tab2_Cb_GlVersion" /> - <ChoiceBox fx:id="glVersionChoiceBox" prefWidth="75.0" /> + <ChoiceBox fx:id="glVersionChoiceBox" prefWidth="120.0" /> </children> <VBox.margin> <Insets left="5.0" /> </VBox.margin> </HBox> </children> -</VBox> \ No newline at end of file +</VBox> From 747a745bbdcf957a2ffdc1c6b27cd4769e06384e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 26 Aug 2021 16:16:04 +0300 Subject: [PATCH 039/134] Update screenshots, small UI corrections. --- README.md | 5 ++--- screenshots/3.png | Bin 31987 -> 44358 bytes screenshots/4.png | Bin 41977 -> 68621 bytes .../Controllers/SplitMergeController.java | 4 +++- src/main/resources/SplitMergeTab.fxml | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6935324..d58ce3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # NS-USBloader -   - +    [Support author](#support-this-app) NS-USBloader is: @@ -47,7 +46,7 @@ Sometimes I add new posts about this project [on my home page](https://developer * Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [exiori](https://github.com/exiori) * German by [Swarsele](https://github.com/Swarsele) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) -* Czech by [Spenaat](https://github.com/spenaat) +* Czech by [Spenaat](https://github.com/spenaat)r * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234) * Arabic by [eslamabdel](https://github.com/eslamabdel) * Romanian by [Călin Ilie](https://github.com/calini) diff --git a/screenshots/3.png b/screenshots/3.png index a8849843b6c9fc83f4a8bf2f6a5a2b47ff76b832..35e6dc0f36f2f95e134a3025d502a3ca5db99412 100644 GIT binary patch literal 44358 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 literal 31987 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 RT3hl78mhV~<w{T9{2yBltcw5u diff --git a/screenshots/4.png b/screenshots/4.png index 525944f7fd13d0ec09ef2e3977fd2fbe2492f30f..1e1fac6480cd6a254229d07a99b2b94d5ba11604 100644 GIT binary patch literal 68621 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 A2LJ#7 literal 41977 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%0DpDho2~t@MjEviA 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 VvHbU73HWWD^(mAE+5Ezd{{UOJR@eXl diff --git a/src/main/java/nsusbloader/Controllers/SplitMergeController.java b/src/main/java/nsusbloader/Controllers/SplitMergeController.java index b7a6be9..57de95d 100644 --- a/src/main/java/nsusbloader/Controllers/SplitMergeController.java +++ b/src/main/java/nsusbloader/Controllers/SplitMergeController.java @@ -94,7 +94,9 @@ public class SplitMergeController implements Initializable { else mergeRad.fire(); - saveToPathLbl.setText(AppPreferences.getInstance().getSplitMergeRecent()); + String previouslyUsedSaveToPath = AppPreferences.getInstance().getSplitMergeRecent(); + + saveToPathLbl.setText(FilesHelper.getRealFolder(previouslyUsedSaveToPath)); changeSaveToBtn.setOnAction((actionEvent -> { DirectoryChooser directoryChooser = new DirectoryChooser(); diff --git a/src/main/resources/SplitMergeTab.fxml b/src/main/resources/SplitMergeTab.fxml index 2cedbcc..6301946 100644 --- a/src/main/resources/SplitMergeTab.fxml +++ b/src/main/resources/SplitMergeTab.fxml @@ -59,7 +59,7 @@ </VBox.margin> </Button> <Label text="%tabSplMrg_Lbl_SaveToLocation" /> - <Label fx:id="saveToPathLbl" maxWidth="200.0" textOverrun="CENTER_WORD_ELLIPSIS" /> + <Label fx:id="saveToPathLbl" maxWidth="-Infinity" prefWidth="175.0" textOverrun="CENTER_WORD_ELLIPSIS" /> <Button fx:id="changeSaveToBtn" contentDisplay="TOP" mnemonicParsing="false" text="%tabSplMrg_Btn_ChangeSaveToLocation" /> <Pane VBox.vgrow="ALWAYS" /> <VBox> From 8af1aa485e9b01a6721e429c318bc6320567a281 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 27 Aug 2021 18:35:53 +0300 Subject: [PATCH 040/134] Fix #102 --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d58ce3c..1793f0d 100644 --- a/README.md +++ b/README.md @@ -86,18 +86,16 @@ Also, please go to 'Settings' tab of NS-USBloader after first installation and c 3. Optional: add user to 'udev' rules to use NS not-from-root-account ``` root # vim /etc/udev/rules.d/99-NS.rules -SUBSYSTEM=="usb", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="3000", GROUP="plugdev" +SUBSYSTEM=="usb", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="3000", MODE="0666" root # udevadm control --reload-rules && udevadm trigger ``` 4. For RCM part ``` root # vim /etc/udev/rules.d/99-NS-RCM.rules -SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", GROUP="plugdev" +SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", MODE="0666" root # udevadm control --reload-rules && udevadm trigger ``` -Please note: you may have to change 'plugdev' group from example above to the different one. It depends on you linux distro. - ##### Raspberry Pi 1. Install JDK: `sudo apt install default-jdk` From 7d8b32dc7c6e4fe5380d1b5118f2ec210bca3a73 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 28 Aug 2021 09:22:02 +0800 Subject: [PATCH 041/134] Add and Update simChinese list by FFT9 --- src/main/resources/locale_zh_CN.properties | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index a789f16..c6f4581 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -1,5 +1,7 @@ btn_OpenFile=\u9009\u62E9.NSP\u6587\u4EF6 +btn_OpenFolders=\u9009\u62e9\u76ee\u5f55 btn_Upload=\u4E0A\u4F20\u5230NS +btn_OpenFolders_tooltip=\u6b63\u5728\u626b\u63cf\u9009\u4e2d\u7684\u76ee\u5f55.\n\u6240\u6709\u7684\u76ee\u5f55\u626b\u63cf\u5b8c\u6210.\n\u7b26\u5408\u7684\u6587\u4ef6\u5df2\u7ecf\u6dfb\u52a0\u5230\u5217\u8868\u4e2d. tab3_Txt_EnteredAsMsg1=\u4F60\u6B63\u5728\u4F7F\u7528: tab3_Txt_EnteredAsMsg2=\u4F60\u5E94\u8BE5\u4F7F\u7528root\u8D26\u53F7\u6216\u8005\u4E3A\u5F53\u524D\u7528\u6237\u914D\u7F6E'udev'\u89C4\u5219\u6765\u907F\u514D\u53EF\u80FD\u7684\u95EE\u9898\u3002 tab3_Txt_FilesToUploadTitle=\u8981\u4E0A\u4F20\u7684\u6587\u4EF6: @@ -69,3 +71,10 @@ btn_Close=\u5173\u95ED tab2_Cb_GlVersion=GoldLeaf\u7248\u672C tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5185\u4EC5\u663E\u793A*.nsp\u6587\u4EF6 windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u6267\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8BF7\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u5904\u7406\u7684\u7A0B\u5E8F. +tab2_Cb_foldersSelectorForRoms=\u9009\u4e2d\u540e\u5207\u6362\u6210\u9009\u62e9\u76ee\u5f55\u6a21\u5f0f. +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +windowTitleAddingFiles=\u641c\u7d22\u6587\u4ef6\u4e2d... +windowBodyFilesScanned=\u626b\u63cf\u6587\u4ef6: %25d\n\u88ab\u6dfb\u52a0: %25d +tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From b5a6972671b7ec357f0fed32f09417b3223252a1 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 28 Aug 2021 09:25:10 +0800 Subject: [PATCH 042/134] Update Chinese (Simplified) by FFT9 --- src/main/resources/locale_zh_CN.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index c6f4581..1f7e645 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -75,6 +75,6 @@ tab2_Cb_foldersSelectorForRoms=\u9009\u4e2d\u540e\u5207\u6362\u6210\u9009\u62e9\ tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. windowTitleAddingFiles=\u641c\u7d22\u6587\u4ef6\u4e2d... windowBodyFilesScanned=\u626b\u63cf\u6587\u4ef6: %25d\n\u88ab\u6dfb\u52a0: %25d -tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +tab2_Lbl_AwooBlockTitle=awoo installer \u5b8c\u6210 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 66f4c22417dcc62652ec3968bef99cb9538a15f4 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 28 Aug 2021 11:18:08 +0800 Subject: [PATCH 043/134] =?UTF-8?q?Update=20Chinese(Traditional=EF=BC=89by?= =?UTF-8?q?=20FFT9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/locale_zh_TW.properties | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locale_zh_TW.properties b/src/main/resources/locale_zh_TW.properties index 6788727..96e99c8 100644 --- a/src/main/resources/locale_zh_TW.properties +++ b/src/main/resources/locale_zh_TW.properties @@ -1,5 +1,7 @@ btn_OpenFile=\u9078\u64C7\u6A94\u6848 +btn_OpenFolders=\u9009\u62e9\u8def\u5f84 btn_Upload=\u4E0A\u50B3\u81F3NS +btn_OpenFolders_tooltip=\u6383\u63cf\u9078\u4e2d\u7684\u8def\u5f91.\n\u6240\u6709\u7684\u8def\u5f91\u6383\u63cf\u5b8c\u6210.\n\u5339\u914d\u7684\u6a94\u6848\u5df2\u6dfb\u52a0\u5230\u5217\u8868. tab3_Txt_EnteredAsMsg1=\u76EE\u524D\u767B\u5165\u7684\u4F7F\u7528\u8005: tab3_Txt_EnteredAsMsg2=\u57F7\u884C\u6B64\u64CD\u4F5C\u6642\u4F60\u5FC5\u9808\u64C1\u6709\u6700\u9AD8\u5B58\u53D6\u6B0A\u9650,\u6216\u8005\u5DF2\u914D\u7F6E\u4E86'udev'\u898F\u5247,\u4EE5\u78BA\u4FDD\u4E0D\u6703\u51FA\u73FE\u4EFB\u4F55\u554F\u984C. tab3_Txt_FilesToUploadTitle=\u4E0A\u50B3\u7684\u6A94\u6848: @@ -68,4 +70,11 @@ btn_Cancel=\u53D6\u6D88 btn_Close=\u95DC\u9589 tab2_Cb_GlVersion=GoldLeaf\u7248\u672C tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5167\u50C5\u986F\u793A*.nsp\u6A94\u6848 -windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57F7\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8ACB\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u8655\u7406\u7684\u7A0B\u5E8F. \ No newline at end of file +windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57F7\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8ACB\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u8655\u7406\u7684\u7A0B\u5E8F. +tab2_Cb_foldersSelectorForRoms=\u9078\u4e2d\u5f8c\u5207\u63db\u70ba\u9078\u64c7\u8def\u5f91\u529f\u80fd +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +windowTitleAddingFiles=\u6aa2\u7d22\u6a94\u6848\u4e2d... +windowBodyFilesScanned=\u6a94\u6848\u6383\u63cf\u5230: %25d\n\u88ab\u6dfb\u52a0: %25d +tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From c5e8dc67adfc28f8ce0d737f6d5993d8b17e50e8 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 28 Aug 2021 11:24:50 +0800 Subject: [PATCH 044/134] Update chinese translation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1793f0d..e345d8c 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,11 @@ Sometimes I add new posts about this project [on my home page](https://developer * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) * Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), Kuziel Alejandro -* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [exiori](https://github.com/exiori) +* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group](https://www.xxgame.net) * German by [Swarsele](https://github.com/Swarsele) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) * Czech by [Spenaat](https://github.com/spenaat)r -* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234) +* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group](https://www.xxgame.net) * Arabic by [eslamabdel](https://github.com/eslamabdel) * Romanian by [Călin Ilie](https://github.com/calini) From 3fde307ea173b417cce5a0cf5d4204618a36b0e3 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 28 Aug 2021 11:27:45 +0800 Subject: [PATCH 045/134] Update chinese translation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e345d8c..d489a77 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,11 @@ Sometimes I add new posts about this project [on my home page](https://developer * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) * Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), Kuziel Alejandro -* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group](https://www.xxgame.net) +* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](https://www.xxgame.net) +* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](https://www.xxgame.net) * German by [Swarsele](https://github.com/Swarsele) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) -* Czech by [Spenaat](https://github.com/spenaat)r -* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group](https://www.xxgame.net) +* Czech by [Spenaat](https://github.com/spenaat) * Arabic by [eslamabdel](https://github.com/eslamabdel) * Romanian by [Călin Ilie](https://github.com/calini) From ff77c117ade0d254e4fec972ce981b84713b7007 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 5 Sep 2021 02:57:08 +0300 Subject: [PATCH 046/134] Add libusb4java.dylib that @agungrbudiman gave me. --- pom.xml | 2 +- .../usb4java/darwin-aarch64/libusb4java.dylib | Bin 0 -> 113232 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/main/resources/org/usb4java/darwin-aarch64/libusb4java.dylib diff --git a/pom.xml b/pom.xml index 036329b..64967bd 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>5.1-SNAPSHOT</version> + <version>5.2-SNAPSHOT</version> <url>https://github.com/developersu/ns-usbloader/</url> <description> diff --git a/src/main/resources/org/usb4java/darwin-aarch64/libusb4java.dylib b/src/main/resources/org/usb4java/darwin-aarch64/libusb4java.dylib new file mode 100644 index 0000000000000000000000000000000000000000..75a1fa8c000e46647506e58cc5d2b4b9904f3431 GIT binary patch literal 113232 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 literal 0 HcmV?d00001 From 2ee03629805524a6d20d8fa2b410ac998e07ccfa Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 16 Sep 2021 00:51:02 +0300 Subject: [PATCH 047/134] Move to OpenJFX 17 --- README.md | 11 ++++++++--- pom.xml | 24 ++++++++++++------------ src/main/java/nsusbloader/NSLMain.java | 5 +++-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d489a77..0e518dd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Sometimes I add new posts about this project [on my home page](https://developer * [Pablo Curiel (DarkMatterCore)](https://github.com/DarkMatterCore) * [wolfposd](https://github.com/wolfposd) - +* [agungrbudiman](https://github.com/agungrbudiman) * French by [Stephane Meden (JackFromNice)](https://github.com/JackFromNice) * Italian by [unbranched](https://github.com/unbranched) @@ -74,8 +74,6 @@ Awoo Installer uses the same command-set (or 'protocol') to [Adubbz/Tinfoil](htt A lot of other forks/apps uses the same command-set. To stop speculating about the name it's now called 'Awoo'. It WAS called 'TinFoil' before. Not any more. -Also, please go to 'Settings' tab of NS-USBloader after first installation and check 'Allow XCI / NSZ / XCZ files selection for Awoo' option. This installer can install not only NSPs but a way more formats! - ### Usage ##### Linux: @@ -112,6 +110,13 @@ Set 'Security & Privacy' settings if needed. *Please note: JDK 11 is recommended for using on MacOS. There are few really weird issues already reported from JDK 14 users on Mac.* +##### macOS on Apple Silicon (ARM) + +1. Some users [tested](https://github.com/developersu/ns-usbloader/issues/91) this application with [Zulu-JDK with FX support](https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx) +2. Try OpenJDK 17 that has m1 support. + +You should try it too while we're waiting for platform support at OpenJDK/Oracle JDKs. + ##### Windows: * [Download and install Java JRE](http://java.com/download/) (8u60 or higher) diff --git a/pom.xml b/pom.xml index 64967bd..1465a80 100644 --- a/pom.xml +++ b/pom.xml @@ -60,28 +60,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>16</version> + <version>17</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>16</version> + <version>17</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>16</version> + <version>17</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>16</version> + <version>17</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -89,28 +89,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>16</version> + <version>17</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>16</version> + <version>17</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>16</version> + <version>17</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>16</version> + <version>17</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -118,28 +118,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>16</version> + <version>17</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>16</version> + <version>17</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>16</version> + <version>17</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>16</version> + <version>17</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 6c26e1d..c133f72 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v5.1"; + public static final String appVersion = "v5.2"; public static boolean isCli; @Override @@ -67,7 +67,8 @@ public class NSLMain extends Application { primaryStage.setOnCloseRequest(e->{ if (MediatorControl.getInstance().getTransferActive()) - if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit"))) + if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), + rb.getString("windowBodyConfirmExit"))) e.consume(); }); From 9301f4da8e50ecd19b8089025fd55eb8a67a6107 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 16 Sep 2021 00:54:42 +0300 Subject: [PATCH 048/134] update README --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0e518dd..fe47e88 100644 --- a/README.md +++ b/README.md @@ -108,14 +108,12 @@ Double-click on downloaded .jar file. Follow instructions. Or see 'Linux' sectio Set 'Security & Privacy' settings if needed. -*Please note: JDK 11 is recommended for using on MacOS. There are few really weird issues already reported from JDK 14 users on Mac.* +*Please note: JDK 11 is recommended for using on MacOS (EXCEPT APPLE SILICONE). There are few really weird issues already reported from JDK 14 users on Mac.* ##### macOS on Apple Silicon (ARM) -1. Some users [tested](https://github.com/developersu/ns-usbloader/issues/91) this application with [Zulu-JDK with FX support](https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx) -2. Try OpenJDK 17 that has m1 support. - -You should try it too while we're waiting for platform support at OpenJDK/Oracle JDKs. +* Some users [tested](https://github.com/developersu/ns-usbloader/issues/91) this application with [Zulu-JDK with FX support](https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx). Try it! +* OpenJDK 17 also should be a working solution. ##### Windows: From f0714ef961dcc34502aa7e623fa1f3a4bc737e2b Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 16 Sep 2021 01:00:05 +0300 Subject: [PATCH 049/134] And I forgot to update screenshot. Here. --- screenshots/1.png | Bin 52410 -> 54793 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/1.png b/screenshots/1.png index dfa03c67a7d9010a9c934e358354663c61f2fbc5..6225c735bfa3c66fe63d62eb87892987168524f4 100644 GIT binary patch literal 54793 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 literal 52410 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 H9PqyY6;o{Q From eb72fd17b61030847a39627883a13ce0433647e5 Mon Sep 17 00:00:00 2001 From: qazrfv1234 <30403706+qazrfv1234@users.noreply.github.com> Date: Thu, 16 Sep 2021 21:46:44 +0800 Subject: [PATCH 050/134] Update locale_zh_TW.properties --- src/main/resources/locale_zh_TW.properties | 146 ++++++++++----------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/src/main/resources/locale_zh_TW.properties b/src/main/resources/locale_zh_TW.properties index 96e99c8..739bedd 100644 --- a/src/main/resources/locale_zh_TW.properties +++ b/src/main/resources/locale_zh_TW.properties @@ -1,80 +1,80 @@ -btn_OpenFile=\u9078\u64C7\u6A94\u6848 -btn_OpenFolders=\u9009\u62e9\u8def\u5f84 -btn_Upload=\u4E0A\u50B3\u81F3NS -btn_OpenFolders_tooltip=\u6383\u63cf\u9078\u4e2d\u7684\u8def\u5f91.\n\u6240\u6709\u7684\u8def\u5f91\u6383\u63cf\u5b8c\u6210.\n\u5339\u914d\u7684\u6a94\u6848\u5df2\u6dfb\u52a0\u5230\u5217\u8868. -tab3_Txt_EnteredAsMsg1=\u76EE\u524D\u767B\u5165\u7684\u4F7F\u7528\u8005: -tab3_Txt_EnteredAsMsg2=\u57F7\u884C\u6B64\u64CD\u4F5C\u6642\u4F60\u5FC5\u9808\u64C1\u6709\u6700\u9AD8\u5B58\u53D6\u6B0A\u9650,\u6216\u8005\u5DF2\u914D\u7F6E\u4E86'udev'\u898F\u5247,\u4EE5\u78BA\u4FDD\u4E0D\u6703\u51FA\u73FE\u4EFB\u4F55\u554F\u984C. -tab3_Txt_FilesToUploadTitle=\u4E0A\u50B3\u7684\u6A94\u6848: -tab3_Txt_GreetingsMessage=\u6B61\u8FCE\u4F7F\u7528NS-USBloader -tab3_Txt_NoFolderOrFileSelected=\u5C1A\u672A\u9078\u53D6\u6A94\u6848: \u4E0A\u50B3\u4F47\u5217\u6C92\u6709\u9805\u76EE -windowBodyConfirmExit=\u6B64\u6642\u9000\u51FA\u7A0B\u5F0F\u5C07\u6703\u4E2D\u65B7\u6B63\u5728\u50B3\u8F38\u7684\u8CC7\u6599.\n\u9019\u9805\u64CD\u4F5C\u53EF\u80FD\u6703\u9020\u6210\u4EE4\u4EBA\u5F8C\u6094\u7684\u640D\u5BB3.\n\u662F\u5426\u78BA\u8A8D\u8981\u4E2D\u65B7\u50B3\u8F38\u4E26\u4E14\u7D50\u675F\u7A0B\u5F0F? -windowTitleConfirmExit=\u4E0D,\u8ACB\u52FF\u9000\u51FA! -btn_Stop=\u4E2D\u65B7\u64CD\u4F5C +btn_OpenFile=\u9078\u64c7\u6a94\u6848 +btn_OpenFolders=\u9078\u64c7\u8cc7\u6599\u593e +btn_Upload=\u4e0a\u50b3\u81f3NS +btn_OpenFolders_tooltip=\u9078\u64c7\u8981\u6aa2\u67e5\u7684\u8cc7\u6599\u593e.\n\u6703\u6aa2\u67e5\u8cc7\u6599\u593e\u8207\u6240\u6709\u5b50\u76ee\u9304.\n\u6240\u6709\u7b26\u5408\u7684\u6a94\u6848\u90fd\u6703\u52a0\u5165\u4f47\u5217. +tab3_Txt_EnteredAsMsg1=\u76ee\u524d\u767b\u5165\u7684\u4f7f\u7528\u8005: +tab3_Txt_EnteredAsMsg2=\u57f7\u884c\u6b64\u64cd\u4f5c\u6642\u4f60\u5fc5\u9808\u64c1\u6709\u7ba1\u7406\u54e1\u6b0a\u9650,\u6216\u6b64\u767b\u5165\u7684\u4f7f\u7528\u8005\u5e33\u6236\u5df2\u914d\u7f6e'udev'\u898f\u5247,\u4ee5\u907f\u514d\u767c\u751f\u554f\u984c. +tab3_Txt_FilesToUploadTitle=\u4e0a\u50b3\u7684\u6a94\u6848: +tab3_Txt_GreetingsMessage=\u6b61\u8fce\u4f7f\u7528NS-USBloader +tab3_Txt_NoFolderOrFileSelected=\u5c1a\u672a\u9078\u53d6\u6a94\u6848: \u4e0a\u50b3\u4f47\u5217\u6c92\u6709\u9805\u76ee +windowBodyConfirmExit=\u6b64\u6642\u9000\u51fa\u7a0b\u5f0f\u5c07\u6703\u4e2d\u65b7\u6b63\u5728\u50b3\u8f38\u7684\u8cc7\u6599.\n\u518d\u6b21\u50b3\u8f38\u8cc7\u6599\u5c07\u91cd\u8907\u4f54\u7528\u6642\u9593.\n\u662f\u5426\u78ba\u8a8d\u8981\u4e2d\u65b7\u50b3\u8f38\u4e26\u4e14\u7d50\u675f\u7a0b\u5f0f? +windowTitleConfirmExit=\u4e0d,\u8acb\u52ff\u9000\u51fa! +btn_Stop=\u4e2d\u65b7\u64cd\u4f5c tab3_Txt_GreetingsMessage2=--\n\ Source: https://github.com/developersu/ns-usbloader/\n\ Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=\u72C0\u614B -tab1_table_Lbl_FileName=\u6A94\u6848\u540D\u7A31 -tab1_table_Lbl_Size=\u6A94\u6848\u5927\u5C0F -tab1_table_Lbl_Upload=\u4E0A\u50B3? -tab1_table_contextMenu_Btn_BtnDelete=\u79FB\u9664 -tab1_table_contextMenu_Btn_DeleteAll=\u5168\u90E8\u79FB\u9664 -tab2_Lbl_HostIP=\u96FB\u8166IP +tab1_table_Lbl_Status=\u72c0\u614b +tab1_table_Lbl_FileName=\u6a94\u6848\u540d\u7a31 +tab1_table_Lbl_Size=\u6a94\u6848\u5927\u5c0f +tab1_table_Lbl_Upload=\u4e0a\u50b3? +tab1_table_contextMenu_Btn_BtnDelete=\u79fb\u9664 +tab1_table_contextMenu_Btn_DeleteAll=\u5168\u90e8\u79fb\u9664 +tab2_Lbl_HostIP=\u96fb\u8166IP tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=\u6BCF\u6B21\u8F38\u5165\u5F8C\u90FD\u9A57\u8B49NS IP -windowBodyBadIp=\u4F60\u6240\u8F38\u5165\u7684NS IP\u4F4D\u5740\u662F\u5426\u6B63\u78BA? -windowTitleBadIp=NS IP\u4F4D\u5740\u4E0D\u6B63\u78BA -tab2_Cb_ExpertMode=\u5C08\u5BB6\u6A21\u5F0F (NET \u5B89\u88DD) -tab2_Lbl_HostPort=\u9023\u63A5\u57E0 -tab2_Cb_AutoDetectIp=\u81EA\u52D5\u5075\u6E2CIP -tab2_Cb_RandSelectPort=\u96A8\u6A5F\u9078\u53D6\u9023\u63A5\u57E0 -tab2_Cb_DontServeRequests=\u4E0D\u56DE\u61C9NS\u8ACB\u6C42 -tab2_Lbl_DontServeRequestsDesc=\u555F\u7528\u6B64\u8A2D\u5B9A\u5F8C,\u6B64\u96FB\u8166\u5C07\u4E0D\u6703\u56DE\u61C9\u4F86\u81EANS(\u900F\u904E\u7DB2\u8DEF)\u7684NSP\u6A94\u6848\u8ACB\u6C42,\u4E26\u8B93Awoo\u5C0B\u627E\u6A94\u6848\u4F86\u6E90\u6642\u4F7F\u7528\u9810\u8A2D\u7684\u4E3B\u6A5F\u8A2D\u5B9A. -tab2_Lbl_HostExtra=\u5176\u4ED6 -windowTitleErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u4E0D\u6B63\u78BA! -windowBodyErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u503C\u5FC5\u9808\u4ECB\u65BC0\u523065535\u4E4B\u9593. -tab2_Cb_AutoCheckForUpdates=\u81EA\u52D5\u6AA2\u67E5\u66F4\u65B0 -windowTitleNewVersionAval=\u6709\u53EF\u66F4\u65B0\u7684\u7248\u672C -windowTitleNewVersionNOTAval=\u76EE\u524D\u6C92\u6709\u53EF\u66F4\u65B0\u7684\u7248\u672C -windowTitleNewVersionUnknown=\u76EE\u524D\u7121\u6CD5\u6AA2\u67E5\u6709\u7121\u53EF\u66F4\u65B0\u7248\u672C -windowBodyNewVersionUnknown=\u767C\u751F\u932F\u8AA4\n\u53EF\u80FD\u7DB2\u8DEF\u4E0D\u53EF\u7528\u6216\u8A0A\u865F\u4E0D\u8DB3,\u6216\u8005\u7121\u6CD5\u9023\u4E0AGitHub\u5B98\u7DB2\u4F3A\u670D\u5668 -windowBodyNewVersionNOTAval=\u76EE\u524D\u4F7F\u7528\u7684\u7A0B\u5F0F\u5DF2\u662F\u6700\u65B0\u7248\u672C -tab2_Cb_AllowXciNszXcz=\u5141\u8A31Awoo\u6A21\u5F0F\u6642\u9078\u53D6XCI / NSZ / XCZ \u6A94\u6848\u683C\u5F0F -tab2_Lbl_AllowXciNszXczDesc=\u6B64\u8A2D\u5B9A\u5C08\u70BA\u652F\u63F4XCI/NSZ/XCZ\u6A94\u6848\u683C\u5F0F\u8207Tinfoil\u50B3\u8F38\u5354\u8B70\u7684\u7B2C\u4E09\u65B9\u7A0B\u5F0F\u4F7F\u7528. \u5982\u4E0D\u78BA\u5B9A,\u8ACB\u52FF\u8B8A\u66F4\u6B64\u9805\u8A2D\u5B9A. \u4F7F\u7528Awoo Installer\u8ACB\u555F\u7528\u6B64\u8A2D\u5B9A. -tab2_Lbl_Language=\u4ECB\u9762\u8A9E\u7CFB -windowBodyRestartToApplyLang=\u8ACB\u91CD\u65B0\u555F\u52D5\u7A0B\u5F0F\u4EE5\u5957\u7528\u8B8A\u66F4\u7684\u8A2D\u5B9A. -btn_OpenSplitFile=\u9078\u64C7\u5206\u5272\u7684NSP -tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8A2D\u5B9A -tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 -tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 -tabSplMrg_RadioBtn_Merge=\u5408\u4F75 -tabSplMrg_Txt_File=\u6A94\u6848: -tabSplMrg_Txt_Folder=\u5206\u5272\u6A94\u6848(\u8CC7\u6599\u593E): -tabSplMrg_Btn_SelectFile=\u9078\u64C7\u6A94\u6848 -tabSplMrg_Btn_SelectFolder=\u9078\u64C7\u8CC7\u6599\u593E -tabSplMrg_Lbl_SaveToLocation=\u5132\u5B58\u8DEF\u5F91: -tabSplMrg_Btn_ChangeSaveToLocation=\u8B8A\u66F4 -tabSplMrg_Btn_Convert=\u8F49\u6A94 -windowTitleError=\u932F\u8AA4 -windowBodyPleaseFinishTransfersFirst=\u7576\u7A0B\u5F0F\u6B63\u5728\u8655\u7406USB/Network\u5B89\u88DD\u6642\u7121\u6CD5\u540C\u6642\u57F7\u884C\u5206\u5272/\u5408\u4F75\u6A94\u6848. \u5982\u9700\u7E7C\u7E8C,\u5FC5\u9808\u4E2D\u65B7\u76EE\u524D\u7684\u50B3\u8F38. -done_txt=\u5B8C\u6210! +tab2_Cb_ValidateNSHostName=\u6bcf\u6b21\u8f38\u5165\u5f8c\u90fd\u9a57\u8b49NS IP +windowBodyBadIp=\u4f60\u6240\u8f38\u5165\u7684NS IP\u4f4d\u5740\u662f\u5426\u6b63\u78ba? +windowTitleBadIp=NS IP\u4f4d\u5740\u4e0d\u6b63\u78ba +tab2_Cb_ExpertMode=\u5c08\u5bb6\u6a21\u5f0f (NET \u5b89\u88dd) +tab2_Lbl_HostPort=\u9023\u63a5\u57e0 +tab2_Cb_AutoDetectIp=\u81ea\u52d5\u5075\u6e2cIP +tab2_Cb_RandSelectPort=\u96a8\u6a5f\u9078\u53d6\u9023\u63a5\u57e0 +tab2_Cb_DontServeRequests=\u4e0d\u56de\u61c9NS\u8acb\u6c42 +tab2_Lbl_DontServeRequestsDesc=\u555f\u7528\u6b64\u8a2d\u5b9a\u5f8c,\u6b64\u96fb\u8166\u5c07\u4e0d\u6703\u56de\u61c9\u4f86\u81eaNS(\u900f\u904e\u7db2\u8def)\u7684NSP\u6a94\u6848\u8acb\u6c42,\u4e26\u8b93Awoo(\u6216\u5176\u4ed6\u540c\u985e\u578b\u7a0b\u5f0f)\u5c0b\u627e\u6a94\u6848\u4f86\u6e90\u6642\u4f7f\u7528\u9810\u8a2d\u7684\u4e3b\u6a5f\u8a2d\u5b9a. +tab2_Lbl_HostExtra=\u5176\u4ed6 +windowTitleErrorPort=\u9023\u63a5\u57e0\u8a2d\u5b9a\u4e0d\u6b63\u78ba! +windowBodyErrorPort=\u9023\u63a5\u57e0\u8a2d\u5b9a\u503c\u5fc5\u9808\u4ecb\u65bc0\u523065535\u4e4b\u9593. +tab2_Cb_AutoCheckForUpdates=\u81ea\u52d5\u6aa2\u67e5\u66f4\u65b0 +windowTitleNewVersionAval=\u6709\u53ef\u66f4\u65b0\u7684\u7248\u672c +windowTitleNewVersionNOTAval=\u76ee\u524d\u6c92\u6709\u53ef\u66f4\u65b0\u7684\u7248\u672c +windowTitleNewVersionUnknown=\u76ee\u524d\u7121\u6cd5\u6aa2\u67e5\u6709\u7121\u53ef\u66f4\u65b0\u7248\u672c +windowBodyNewVersionUnknown=\u767c\u751f\u932f\u8aa4\n\u53ef\u80fd\u7db2\u8def\u4e0d\u53ef\u7528\u6216\u8a0a\u865f\u4e0d\u8db3,\u6216\u8005\u7121\u6cd5\u9023\u4e0aGitHub\u5b98\u7db2\u4f3a\u670d\u5668 +windowBodyNewVersionNOTAval=\u76ee\u524d\u4f7f\u7528\u7684\u7a0b\u5f0f\u5df2\u662f\u6700\u65b0\u7248\u672c +tab2_Cb_AllowXciNszXcz=\u5141\u8a31Awoo\u6a21\u5f0f\u6642\u9078\u53d6XCI / NSZ / XCZ \u6a94\u6848\u683c\u5f0f +tab2_Lbl_AllowXciNszXczDesc=\u6b64\u8a2d\u5b9a\u5c08\u70ba\u652f\u63f4XCI/NSZ/XCZ\u6a94\u6848\u683c\u5f0f\u8207Tinfoil\u50b3\u8f38\u5354\u8b70\u7684\u7b2c\u4e09\u65b9\u7a0b\u5f0f\u4f7f\u7528. \u5982\u4e0d\u78ba\u5b9a,\u8acb\u52ff\u8b8a\u66f4\u6b64\u9805\u8a2d\u5b9a. \u4f7f\u7528Awoo Installer\u8acb\u555f\u7528\u6b64\u8a2d\u5b9a. +tab2_Lbl_Language=\u4ecb\u9762\u8a9e\u7cfb +windowBodyRestartToApplyLang=\u8acb\u91cd\u65b0\u555f\u52d5\u7a0b\u5f0f\u4ee5\u5957\u7528\u8b8a\u66f4\u7684\u8a2d\u5b9a. +btn_OpenSplitFile=\u9078\u64c7\u5206\u5272\u7684NSP +tab2_Lbl_ApplicationSettings=\u4e3b\u8981\u8a2d\u5b9a +tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u4f75\u6a94\u6848\u5de5\u5177 +tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u4f75\u6a94\u6848\u5de5\u5177 +tabSplMrg_RadioBtn_Merge=\u5408\u4f75 +tabSplMrg_Txt_File=\u6a94\u6848: +tabSplMrg_Txt_Folder=\u5206\u5272\u6a94\u6848(\u8cc7\u6599\u593e): +tabSplMrg_Btn_SelectFile=\u9078\u64c7\u6a94\u6848 +tabSplMrg_Btn_SelectFolder=\u9078\u64c7\u8cc7\u6599\u593e +tabSplMrg_Lbl_SaveToLocation=\u5132\u5b58\u8def\u5f91: +tabSplMrg_Btn_ChangeSaveToLocation=\u8b8a\u66f4 +tabSplMrg_Btn_Convert=\u8f49\u6a94 +windowTitleError=\u932f\u8aa4 +windowBodyPleaseFinishTransfersFirst=\u7576\u7a0b\u5f0f\u6b63\u5728\u8655\u7406USB/\u7db2\u8def\u5b89\u88dd\u6642\u7121\u6cd5\u540c\u6642\u57f7\u884c\u5206\u5272/\u5408\u4f75\u6a94\u6848. \u5982\u9700\u7e7c\u7e8c,\u5fc5\u9808\u4e2d\u65b7\u76ee\u524d\u7684\u50b3\u8f38. +done_txt=\u5b8c\u6210! failure_txt=\u5931\u6557 -btn_Select=\u9078\u64C7 -btn_InjectPayloader=\u6CE8\u5165payload -tabNXDT_Btn_Start=\u958B\u59CB! -tab2_Btn_InstallDrivers=\u4E0B\u8F09\u4E26\u5B89\u88DD\u9A45\u52D5\u7A0B\u5F0F -windowTitleDownloadDrivers=\u4E0B\u8F09\u4E26\u5B89\u88DD\u9A45\u52D5\u7A0B\u5F0F -windowBodyDownloadDrivers=\u6B63\u5728\u4E0B\u8F09\u9A45\u52D5\u7A0B\u5F0F (libusbK v3.0.7.0)... -btn_Cancel=\u53D6\u6D88 -btn_Close=\u95DC\u9589 -tab2_Cb_GlVersion=GoldLeaf\u7248\u672C -tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5167\u50C5\u986F\u793A*.nsp\u6A94\u6848 -windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57F7\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8ACB\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u8655\u7406\u7684\u7A0B\u5E8F. -tab2_Cb_foldersSelectorForRoms=\u9078\u4e2d\u5f8c\u5207\u63db\u70ba\u9078\u64c7\u8def\u5f91\u529f\u80fd -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. -windowTitleAddingFiles=\u6aa2\u7d22\u6a94\u6848\u4e2d... -windowBodyFilesScanned=\u6a94\u6848\u6383\u63cf\u5230: %25d\n\u88ab\u6dfb\u52a0: %25d -tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +btn_Select=\u9078\u64c7 +btn_InjectPayloader=\u6ce8\u5165payload +tabNXDT_Btn_Start=\u958b\u59cb! +tab2_Btn_InstallDrivers=\u4e0b\u8f09\u4e26\u5b89\u88dd\u9a45\u52d5\u7a0b\u5f0f +windowTitleDownloadDrivers=\u4e0b\u8f09\u4e26\u5b89\u88dd\u9a45\u52d5\u7a0b\u5f0f +windowBodyDownloadDrivers=\u6b63\u5728\u4e0b\u8f09\u9a45\u52d5\u7a0b\u5f0f (libusbK v3.0.7.0)... +btn_Cancel=\u53d6\u6d88 +btn_Close=\u95dc\u9589 +tab2_Cb_GlVersion=GoldLeaf\u7248\u672c +tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5167\u50c5\u986f\u793a*.nsp\u6a94\u6848 +windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57f7\u884c\u76ee\u524d\u7684\u64cd\u4f5c\u7a0b\u5e8f,\u8acb\u5148\u505c\u6b62\u5176\u4ed6\u6b63\u5728\u8655\u7406\u7684\u7a0b\u5e8f. +tab2_Cb_foldersSelectorForRoms=\u9078\u64c7\u6a94\u6848\u6642\u7531\u539f\u5148\u500b\u5225\u9078\u53d6\u8b8a\u66f4\u70ba\u6307\u5b9a\u8cc7\u6599\u593e\u5167\u7684\u6240\u6709\u7b26\u5408\u6a94\u6848. +tab2_Cb_foldersSelectorForRomsDesc=\u555f\u7528\u6b64\u8a2d\u5b9a\u5f8c,\u5c07\u8b8a\u66f4'\u904a\u6232\u63a7\u5236\u5668'\u5206\u9801\u4e0b\u65b9\u7684'\u9078\u64c7\u6a94\u6848'\u6309\u9215\u529f\u80fd: \u7531\u539f\u5148\u7684\u500b\u5225\u6a94\u6848\u9032\u884c\u9010\u4e00\u9078\u53d6\uff0c\u53d6\u4ee3\u70ba\u4e00\u6b21\u52a0\u5165\u6307\u5b9a\u8cc7\u6599\u593e\u5167\u7b26\u5408\u7684\u6240\u6709\u6a94\u6848. +windowTitleAddingFiles=\u6b63\u5728\u52a0\u5165\u4f47\u5217... +windowBodyFilesScanned=\u6a94\u6848\u5df2\u6383\u63cf:%25d\n \u5df2\u52a0\u5165: %25d +tab2_Lbl_AwooBlockTitle=Awoo Installer \u8207\u540c\u985e\u578b\u7a0b\u5f0f tabRcm_Lbl_Payload=Payload: -tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabRcm_Lbl_FuseeGelee=Fus\u00e9e Gel\u00e9e RCM From 8547eb0e4e3ac4415e1bf3f36bb13b5fd645352e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 21 Sep 2021 01:42:13 +0300 Subject: [PATCH 051/134] Polish README.md --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe47e88..78a22fe 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # NS-USBloader     -[Support author](#support-this-app) + +[Support author link](#support-this-app) NS-USBloader is: * A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/GoldLeaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. Alternative to default **usb_install_pc.py**, **remote_install_pc.py**, **GoldTree**/**Quark**. -* This application also could be used as RCM payload on Windows, MacOS and Linux (supported arch: x86, x86_64 and Raspberry Pi). +* RCM payload tool that works on Windows, macOS (Intel and Apple Silicon) and Linux (x86, amd64 and Raspberry Pi ARM). * It's a tool for creating split files! * Also you can use it for merging split-files into one :) @@ -108,12 +109,12 @@ Double-click on downloaded .jar file. Follow instructions. Or see 'Linux' sectio Set 'Security & Privacy' settings if needed. -*Please note: JDK 11 is recommended for using on MacOS (EXCEPT APPLE SILICONE). There are few really weird issues already reported from JDK 14 users on Mac.* +*Please note: JDK 11 is recommended for using on MacOS (EXCEPT APPLE SILICON). There are few really weird issues already reported from JDK 14 users on Mac.* ##### macOS on Apple Silicon (ARM) * Some users [tested](https://github.com/developersu/ns-usbloader/issues/91) this application with [Zulu-JDK with FX support](https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx). Try it! -* OpenJDK 17 also should be a working solution. +* OpenJDK 17 also should be a working solution. [Tell us if it works for you!]((https://github.com/developersu/ns-usbloader/issues/91)) ##### Windows: @@ -220,7 +221,14 @@ $ java -jar ns-usbloader-4.0.jar -m /tmp/ ~/*.nsp '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. Handling successful/failed installation is a purpose of the other side application: Awoo/Awoo-like or GoldLeaf. And they don't provide any feedback interfaces so I can't detect success/failure. -usb4java since NS-USBloader-v0.2.3 switched to 1.2.0 instead of 1.3.0. This should not impact anyone except users of macOS High Sierra (and Sierra, and El Capitan) where previous versions of NS-USBloader didn't work. Now builds with usb4java-1.2.0 marked as '-legacy' and builds with usb4java-1.3.0 doesn't have postfixes. +#### What is this '-legacy' jar?! + +**JAR with NO postfixes** recommended for Windows users, Linux users and MacOS users who're using Mojave or later versions. + +**JAR with '-legacy' postfix** is for MacOS users who're still using OS X releases before (!) Mojave. +(It also works for Linux and for Windows but sometimes it doesn't work for Windows and I don't know why). + +We have this situation because of weird behaviour inside usb4java library used in this application for USB interactions. In '-legacy' it's v1.2.0 and in 'normal' it's v1.3.0 ### Translators! From 5b67821d3b5e54e52ccdaed0f9f48e23303e4c52 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Wed, 22 Sep 2021 10:03:30 +0800 Subject: [PATCH 052/134] Create locale_zh_CN.properties Update a line --- src/main/resources/locale_zh_CN.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 1f7e645..5262f68 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -71,8 +71,8 @@ btn_Close=\u5173\u95ED tab2_Cb_GlVersion=GoldLeaf\u7248\u672C tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5185\u4EC5\u663E\u793A*.nsp\u6587\u4EF6 windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u6267\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8BF7\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u5904\u7406\u7684\u7A0B\u5E8F. -tab2_Cb_foldersSelectorForRoms=\u9009\u4e2d\u540e\u5207\u6362\u6210\u9009\u62e9\u76ee\u5f55\u6a21\u5f0f. -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +tab2_Cb_foldersSelectorForRoms=\u9009\u4e2d\u540e\u5207\u6362\u6210\u9009\u62e9\u76ee\u5f55\u6a21\u5f0f +tab2_Cb_foldersSelectorForRomsDesc=\u542f\u7528\u6b64\u529f\u80fd\u540e\uff0c\u5c06\u6539\u53d8\u201c\u6e38\u620f\u6807\u7b7e\u201d\u4e2d\u7684\u201c\u9009\u62e9\u6587\u4ef6\u201d\u6309\u952e\u529f\u80fd\uff1a\u4ece\u9009\u62e9\u4e00\u4e2a\u6587\u4ef6\u66ff\u6362\u4e3a\u9009\u62e9\u4e00\u4e2a\u76ee\u5f55\u3002 windowTitleAddingFiles=\u641c\u7d22\u6587\u4ef6\u4e2d... windowBodyFilesScanned=\u626b\u63cf\u6587\u4ef6: %25d\n\u88ab\u6dfb\u52a0: %25d tab2_Lbl_AwooBlockTitle=awoo installer \u5b8c\u6210 From ac3c0a36982b7b32444063f6a240b0759cdfc390 Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Sat, 13 Nov 2021 10:11:32 +0900 Subject: [PATCH 053/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 5872560..7de4802 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -1,10 +1,11 @@ btn_OpenFile=\uD30C\uC77C \uC120\uD0DD btn_OpenFolders=\uD30C\uC77C \uC120\uD0DD btn_Upload=NS\uC5D0 \uC5C5\uB85C\uB4DC +btn_OpenFolders_tooltip=\uC2A4\uCE94\uD560 \uD3F4\uB354\uB97C \uC120\uD0DD\uD569\uB2C8\uB2E4.\n\uC774 \uD3F4\uB354\uC640 \uBAA8\uB4E0 \uD558\uC704 \uD3F4\uB354\uAC00 \uAC80\uC0C9\uB429\uB2C8\uB2E4.\n\uC77C\uCE58\uD558\uB294 \uBAA8\uB4E0 \uD30C\uC77C\uC774 \uBAA9\uB85D\uC5D0 \uCD94\uAC00\uB429\uB2C8\uB2E4. tab3_Txt_EnteredAsMsg1=\uB2E4\uC74C\uACFC \uAC19\uC774 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4: tab3_Txt_EnteredAsMsg2=\uBB38\uC81C\uB97C \uBC29\uC9C0\uD558\uB824\uBA74 \uC774 \uC0AC\uC6A9\uC790\uC5D0 \uB300\uD574 \uB8E8\uD2B8\uC774\uAC70\uB098 'udev'\uADDC\uCE59\uC744 \uAD6C\uC131\uD574\uC57C \uD569\uB2C8\uB2E4. tab3_Txt_FilesToUploadTitle=\uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C: -tab3_Txt_GreetingsMessage=NS-USBloader\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4 +tab3_Txt_GreetingsMessage=NS-USB\uB85C\uB354\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4. tab3_Txt_NoFolderOrFileSelected=\uC120\uD0DD\uD55C \uD30C\uC77C \uC5C6\uC74C: \uC5C5\uB85C\uB4DC \uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. windowBodyConfirmExit=\uB370\uC774\uD130 \uC804\uC1A1\uC774 \uC9C4\uD589 \uC911\uC774\uBA70 \uC774 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2EB\uC73C\uBA74 \uC911\uB2E8\uB429\uB2C8\uB2E4.\n\uC9C0\uAE08 \uD560 \uC218 \uC788\uB294 \uAC83\uC740 \uB354 \uB098\uC05C \uC77C\uC785\uB2C8\uB2E4.\n\uD504\uB85C\uC138\uC2A4\uB97C \uC911\uB2E8\uD558\uACE0 \uC885\uB8CC\uD558\uACA0\uC2B5\uB2C8\uAE4C? windowTitleConfirmExit=\uC544\uB2C8\uC624, \uC774\uAC83\uC740 \uD558\uC9C0\uB9C8\uC138\uC694! @@ -74,3 +75,6 @@ tab2_Cb_foldersSelectorForRoms=ROM\uC744 \uAC1C\uBCC4\uC801\uC73C\uB85C \uC120\u tab2_Cb_foldersSelectorForRomsDesc='\uAC8C\uC784' \uD0ED\uC5D0\uC11C '\uD30C\uC77C \uC120\uD0DD' \uBC84\uD2BC \uB3D9\uC791 \uBCC0\uACBD: ROM \uD30C\uC77C\uC744 \uD558\uB098\uC529 \uC120\uD0DD\uD558\uB294 \uB300\uC2E0 \uD3F4\uB354\uB97C \uC120\uD0DD\uD558\uC5EC \uC9C0\uC6D0\uB418\uB294 \uBAA8\uB4E0 \uD30C\uC77C\uC744 \uD55C \uBC88\uC5D0 \uCD94\uAC00 \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. windowTitleAddingFiles=\uD30C\uC77C \uAC80\uC0C9 \uC911... windowBodyFilesScanned=\uC2A4\uCE94 \uB41C \uD30C\uC77C: %d\n\uCD94\uAC00 \uB420 \uAC83: %d +tab2_Lbl_AwooBlockTitle=Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uACFC \uD638\uD658\uC131 +tabRcm_Lbl_Payload=\uD398\uC774\uB85C\uB4DC: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 5f03939b91c178974e4809e9b9b139971d4b5864 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 1 Mar 2022 16:06:55 +0300 Subject: [PATCH 054/134] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78a22fe..f484972 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@     +## Mirror: [source code](https://git.redrise.ru/desu/ns-usbloader) | [builds](https://redrise.ru/jen/blue/organizations/jenkins/ns-usbloader/activity) + [Support author link](#support-this-app) NS-USBloader is: @@ -60,12 +62,13 @@ JDK 11 for MacOS and Linux ### Supported GoldLeaf versions | GoldLeaf version | NS-USBloader version | -| ---------------- | -------------------- | +|------------------|----------------------| | v0.5 | v0.4 - v0.5.2, v0.8+ | | v0.6 | none | | v0.6.1 | v0.6 | | v0.7 - 0.7.3 | v0.7+ | | v0.8 - 0.9 | v1.0+ | +| v0.10 | not supported; TBD | where '+' means 'any next NS-USBloader version'. @@ -95,6 +98,8 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", MODE="0666" root # udevadm control --reload-rules && udevadm trigger ``` +5. For HiDPI use scaling like `java -Dglass.gtk.uiScale=150% -jar application.jar` + ##### Raspberry Pi 1. Install JDK: `sudo apt install default-jdk` @@ -119,7 +124,7 @@ Set 'Security & Privacy' settings if needed. ##### Windows: * [Download and install Java JRE](http://java.com/download/) (8u60 or higher) -* Get this application (JAR file) and double-click on on it (alternatively open 'cmd', go to place where jar located and execute via `java -jar thisAppName.jar`) +* Get this application (JAR file) and double-click on it (alternatively open 'cmd', go to place where jar located and execute via `java -jar thisAppName.jar`) * Once application opens click on 'Gear' icon. * Click 'Download and install drivers' * Install drivers From 0b8092077a867c360a1a7117b5b8f49f48a1eda4 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 15 Mar 2022 03:01:51 +0300 Subject: [PATCH 055/134] Remove paypal and liberapay out of the 'donations' section. I don't know if I can tell names of the people who decided to support this project by donations, but anyway: Dear ap*** R** and Ku**** A********, I highly appreciate your participation and want to assure you that all funding this project received will go to one of the local charity funds. Thank you both! --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f484972..205dba0 100644 --- a/README.md +++ b/README.md @@ -249,10 +249,6 @@ If you like this app, just give a star. If you want to make a donation*, please see below: -<a href="https://liberapay.com/developersu/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a> - -<a href="https://paypal.me/developersu" title="PayPal"><img src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png" border="0" alt="PayPal Logo" /></a> - [yoomoney](https://yoomoney.ru/to/410014301951665) *Please note: this is non-commercial application. From 5ed2167e9d7aa7bf19d61a7e260ba5458ac3f6e8 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 26 Jul 2022 18:25:26 +0300 Subject: [PATCH 056/134] Add Goldleaf v0.10 support. Remove jenkins malware, add Drone, update readme, increment version. Shifting code to the safer place. Update dependencies. Fix #94: Just doing as @Fatih120 said Fix #124: Implement updated Spanish translation by @Uzi-Oni --- .drone.yml | 41 + Jenkinsfile | 31 - README.md | 33 +- pom.xml | 50 +- src/main/java/nsusbloader/AppPreferences.java | 2 +- src/main/java/nsusbloader/NSLMain.java | 3 +- .../nsusbloader/com/usb/GoldLeaf_010.java | 1130 +++++++++++++++++ .../java/nsusbloader/com/usb/TinFoil.java | 2 +- .../com/usb/UsbCommunications.java | 11 +- src/main/resources-filtered/app.properties | 1 + src/main/resources/locale.properties | 7 +- src/main/resources/locale_ar_AR.properties | 6 +- src/main/resources/locale_cs_CZ.properties | 6 +- src/main/resources/locale_de_DE.properties | 6 +- src/main/resources/locale_en_US.properties | 7 +- src/main/resources/locale_es_ES.properties | 106 +- src/main/resources/locale_fr_FR.properties | 6 +- src/main/resources/locale_it_IT.properties | 6 +- src/main/resources/locale_ko_KR.properties | 6 +- src/main/resources/locale_pt_BR.properties | 6 +- src/main/resources/locale_ro_RO.properties | 7 +- src/main/resources/locale_ru_RU.properties | 6 +- src/main/resources/locale_uk_UA.properties | 6 +- src/main/resources/locale_vi_VN.properties | 7 +- src/main/resources/locale_zh_CN.properties | 20 +- src/main/resources/locale_zh_TW.properties | 152 +-- 26 files changed, 1447 insertions(+), 217 deletions(-) create mode 100644 .drone.yml delete mode 100644 Jenkinsfile create mode 100644 src/main/java/nsusbloader/com/usb/GoldLeaf_010.java create mode 100644 src/main/resources-filtered/app.properties diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..9dce40f --- /dev/null +++ b/.drone.yml @@ -0,0 +1,41 @@ +kind: pipeline +type: docker +name: default + +steps: + - name: test + image: maven:3-jdk-11 + commands: + - mvn -B -DskipTests clean package + - mvn test -B + volumes: + - name: m2 + path: /root/.m2 + + - name: archive-standard-artifact + image: alpine:latest + commands: + - mkdir -p /builds/ns-usbloader + - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + volumes: + - name: builds + path: /builds + + - name: make-store-legacy-artifact + image: maven:3-jdk-11 + commands: + - sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml + - sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-legacy-${project.version}-${maven.build.timestamp}<\/finalName>/g' pom.xml + - mvn -B -DskipTests clean package + - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + volumes: + - name: m2 + path: /root/.m2 + +volumes: + - name: m2 + host: + path: /home/docker/drone/files/m2 + - name: builds + host: + path: /home/www/builds \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 6809359..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,31 +0,0 @@ -pipeline { - agent { - docker { - image 'maven:3-jdk-11' - args '-v /home/docker/jenkins/files/m2:/root/.m2' - } - } - - stages { - stage('Build') { - steps { - sh 'mvn -B -DskipTests clean package' - } - post { - success { - archiveArtifacts artifacts: 'target/*.jar, target/*.exe' - } - } - } - stage('Test') { - steps { - sh 'mvn test' - } - post { - always { - junit 'target/surefire-reports/*.xml' - } - } - } - } -} \ No newline at end of file diff --git a/README.md b/README.md index 205dba0..8f85571 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # NS-USBloader -    - -## Mirror: [source code](https://git.redrise.ru/desu/ns-usbloader) | [builds](https://redrise.ru/jen/blue/organizations/jenkins/ns-usbloader/activity) - -[Support author link](#support-this-app) +   [](https://ci.redrise.ru/desu/ns-usbloader) NS-USBloader is: * A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/GoldLeaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. @@ -17,8 +13,14 @@ Alternative to default **usb_install_pc.py**, **remote_install_pc.py**, **GoldTr [Click here for Android version ;)](https://github.com/developersu/ns-usbloader-mobile) With GUI and cookies. Works on Windows, macOS and Linux. +### Let's stay in touch: +#### [→ Independent source code storage](https://git.redrise.ru/desu/ns-usbloader) +#### [→ Mirror, issues tracker, place to send PRs](https://github.com/developersu/ns-usbloader) +#### [→ Nightly builds](https://redrise.ru/builds/ns-usbloader/) -Sometimes I add new posts about this project [on my home page](https://developersu.blogspot.com/search/label/NS-USBloader). +[Support/Donate](#support-this-app) + +Sometimes I add new posts about this project [on my blog page](https://developersu.blogspot.com/search/label/NS-USBloader).  <img src="screenshots/2.png" alt="screenshot" width="250"/> <img src="screenshots/3.png" alt="screenshot" width="250"/> @@ -45,7 +47,7 @@ Sometimes I add new posts about this project [on my home page](https://developer * Italian by [unbranched](https://github.com/unbranched) * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) -* Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), Kuziel Alejandro +* Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), [Kuziel Alejandro](https://github.com/Uzi-Oni) * Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](https://www.xxgame.net) * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](https://www.xxgame.net) * German by [Swarsele](https://github.com/Swarsele) @@ -68,7 +70,7 @@ JDK 11 for MacOS and Linux | v0.6.1 | v0.6 | | v0.7 - 0.7.3 | v0.7+ | | v0.8 - 0.9 | v1.0+ | -| v0.10 | not supported; TBD | +| v0.10 | v6.0 | where '+' means 'any next NS-USBloader version'. @@ -245,14 +247,19 @@ To convert files of any locale to readable format (and vise-versa) you can use t ## Support this app -If you like this app, just give a star. +If you like this app, just give a star (@ GitHub). -If you want to make a donation*, please see below: +This is non-commercial project. -[yoomoney](https://yoomoney.ru/to/410014301951665) +Nevertheless, I'll be more than happy if you find a chance to make a donation for charity to people I trust: -*Please note: this is non-commercial application. +* BTC → 1YoBdyiL4TTVsCWmJ93TkfU2a1s9UfJcY +* LTC → MEXnCLjwvaAZpaoJ1J4biF4DpoAx86gCkf +* ETH → 0x82Ab0ddE183C12cAa6eD61DF3671675C4bdC42fc +* DOGE → DFfVjsbcs9VfV9EQTSFF3xJVbjZSbmctP3 +* DOT → 13javzN4ixHPmBfR1oZHjAecydvWbEuqRtBWuxrZyKvkUrg3 +* USDT (TRC20) → THhhs883gH1AcvmNb2EVfhR7QNkWnoa1vN -Thanks +Thanks! Appreciate assistance and support of both [Vitaliy](https://github.com/SebastianUA) and [Konstantin](https://github.com/konstantin-kelemen). Without you all this magic would not have happened. diff --git a/pom.xml b/pom.xml index 1465a80..6d40b70 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,11 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>5.2-SNAPSHOT</version> + <version>6.0</version> - <url>https://github.com/developersu/ns-usbloader/</url> + <url>https://redrise.ru</url> <description> - NSP USB loader for Awoo Installer and compatible (USB and Network) and GoldLeaf + NS multi-tool </description> <inceptionYear>2019</inceptionYear> <organization> @@ -42,6 +42,7 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format> </properties> <issueManagement> @@ -54,7 +55,7 @@ <dependency> <groupId>commons-cli</groupId> <artifactId>commons-cli</artifactId> - <version>1.4</version> + <version>1.5.0</version> <scope>compile</scope> </dependency> <dependency> @@ -67,21 +68,21 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -89,28 +90,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -118,28 +119,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> @@ -154,23 +155,35 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> - <version>5.5.2</version> + <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> - <version>5.5.2</version> + <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> - <version>5.5.2</version> + <version>5.8.2</version> <scope>test</scope> </dependency> </dependencies> <build> + <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + </resource> + <resource> + <directory>src/main/resources-filtered</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> <!-- Junit5 --> <plugin> @@ -217,6 +230,7 @@ <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> + <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> <execution> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 18afe80..7fb69dd 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -27,7 +27,7 @@ public class AppPreferences { private final Preferences preferences; private final Locale locale; - public static final String[] goldleafSupportedVersions = {"v0.5", "v0.7.x", "v0.8-0.9"}; + public static final String[] goldleafSupportedVersions = {"v0.5", "v0.7.x", "v0.8-0.9", "v0.10"}; private AppPreferences(){ this.preferences = Preferences.userRoot().node("NS-USBloader"); diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index c133f72..aa03af7 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -32,7 +32,7 @@ import java.util.ResourceBundle; public class NSLMain extends Application { - public static final String appVersion = "v5.2"; + public static String appVersion; public static boolean isCli; @Override @@ -41,6 +41,7 @@ public class NSLMain extends Application { Locale userLocale = AppPreferences.getInstance().getLocale(); ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale); + NSLMain.appVersion = ResourceBundle.getBundle("app").getString("_version"); loader.setResources(rb); Parent root = loader.load(); diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java new file mode 100644 index 0000000..a5669d2 --- /dev/null +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java @@ -0,0 +1,1130 @@ +/* + Copyright 2019-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. +*/ +package nsusbloader.com.usb; + +import javafx.application.Platform; +import javafx.stage.FileChooser; +import nsusbloader.MediatorControl; +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.com.helpers.NSSplitReader; +import org.usb4java.DeviceHandle; +import org.usb4java.LibUsb; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +/** + * GoldLeaf 0.8 processing + */ +class GoldLeaf_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<String, BufferedOutputStream> writeFilesMap; + private long virtDriveSize; + private HashMap<String, Long> splitFileSize; + + private final boolean isWindows; + private final String homePath; + // For using in CMD_SelectFile with SPEC:/ prefix + private File selectedFile; + + private final CancellableRunnable task; + + GoldLeaf_010(DeviceHandle handler, + LinkedHashMap<String, File> nspMap, + CancellableRunnable task, + ILogPrinter logPrinter, + boolean nspFilter) + { + super(handler, nspMap, task, logPrinter); + + this.task = task; + + final byte CMD_GetDriveCount = 1; + final byte CMD_GetDriveInfo = 2; + final byte CMD_StatPath = 3; + final byte CMD_GetFileCount = 4; + final byte CMD_GetFile = 5; + final byte CMD_GetDirectoryCount = 6; + final byte CMD_GetDirectory = 7; + final byte CMD_StartFile = 8; // 1 -open read RAF; 2 open write RAF; 3 open write RAF and seek to EOF (???). + final byte CMD_ReadFile = 9; + final byte CMD_WriteFile = 10; + final byte CMD_EndFile = 11; // 1 - closed read RAF; 2 close write RAF. + final byte CMD_Create = 12; + final byte CMD_Delete = 13; + final byte CMD_Rename = 14; + final byte CMD_GetSpecialPathCount = 15; + final byte CMD_GetSpecialPath = 16; + final byte CMD_SelectFile = 17; + + final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; + + this.nspFilterForGl = nspFilter; + + 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<byte[]> command = new LinkedList<>(); + command.add(driveLabelLen); + command.add(driveLabel); + command.add(driveLetterLen); + command.add(driveLetter); + command.add(totalFreeSpace); + command.add(totalSize); + + if (writeGL_PASS(command)) { + 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<byte[]> 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<byte[]> command = new LinkedList<>(); + + if (dirName.startsWith("HOME:/")) { + dirName = updateHomePath(dirName); + + if (dirName.equals(recentPath) && recentFiles != null && recentFiles.length != 0){ + byte[] fileNameBytes = recentFiles[subDirNo].getBytes(StandardCharsets.UTF_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<byte[]> 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.getInstance().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<byte[]> 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<byte[]> messages){ + ByteBuffer writeBuffer = ByteBuffer.allocate(4096); + writeBuffer.put(CMD_GLCO_SUCCESS); + for (byte[] arr : messages) + writeBuffer.put(arr); + return writeToUsb(writeBuffer.array()); + } + + private boolean writeGL_FAIL(String reportToUImsg){ + if (writeToUsb(Arrays.copyOf(CMD_GLCO_FAILURE, 4096))){ + 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/TinFoil.java b/src/main/java/nsusbloader/com/usb/TinFoil.java index d4aa9e4..4b342ab 100644 --- a/src/main/java/nsusbloader/com/usb/TinFoil.java +++ b/src/main/java/nsusbloader/com/usb/TinFoil.java @@ -197,7 +197,7 @@ class TinFoil extends TransferModule { ae.printStackTrace(); return true; } catch (NullPointerException npe){ - print("NullPointerException (in some moment application didn't find something. Something important.):" + + print("Application didn't find something important. Make sure you have enough space on medium!" + "\n "+npe.getMessage(), EMsgType.FAIL); npe.printStackTrace(); return true; diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index 595ede2..1222a55 100644 --- a/src/main/java/nsusbloader/com/usb/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -49,7 +49,7 @@ public class UsbCommunications extends CancellableRunnable { @Override public void run() { - print("\tStart", EMsgType.INFO); + print("\tStart"); UsbConnect usbConnect = UsbConnect.connectHomebrewMode(logPrinter); @@ -66,6 +66,9 @@ public class UsbCommunications extends CancellableRunnable { case "TinFoil": module = new TinFoil(handler, nspMap, this, logPrinter); break; + case "GoldLeafv0.10": + module = new GoldLeaf_010(handler, nspMap, this, logPrinter, nspFilterForGl); + break; case "GoldLeafv0.8-0.9": module = new GoldLeaf_08(handler, nspMap, this, logPrinter, nspFilterForGl); break; @@ -87,12 +90,12 @@ public class UsbCommunications extends CancellableRunnable { */ private void close(EFileStatus status){ logPrinter.update(nspMap, status); - print("\tEnd", EMsgType.INFO); + print("\tEnd"); logPrinter.close(); } - private void print(String message, EMsgType type){ + private void print(String message){ try { - logPrinter.print(message, type); + logPrinter.print(message, EMsgType.INFO); } catch (InterruptedException ie){ ie.printStackTrace(); diff --git a/src/main/resources-filtered/app.properties b/src/main/resources-filtered/app.properties new file mode 100644 index 0000000..fc2f1ed --- /dev/null +++ b/src/main/resources-filtered/app.properties @@ -0,0 +1 @@ +_version=v${project.version} \ No newline at end of file diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index 8c96082..bfb2ae7 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=Data transfer is in progress and closing this application windowTitleConfirmExit=No, don't do this! btn_Stop=Interrupt tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status tab1_table_Lbl_FileName=File name @@ -77,4 +78,4 @@ windowTitleAddingFiles=Searching for files... windowBodyFilesScanned=Files scanned: %d\nWould be added: %d tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible tabRcm_Lbl_Payload=Payload: -tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM \ No newline at end of file +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties index 2c99a15..1fb7fc0 100644 --- a/src/main/resources/locale_ar_AR.properties +++ b/src/main/resources/locale_ar_AR.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=\u062C\u0627\u0631\u064A \u0646\u0642\u0644 \u0627\u0644\u windowTitleConfirmExit=\u0644\u0627 , \u0644\u0627 \u062A\u0641\u0639\u0644 \u0630\u0644\u0643! btn_Stop=\u0625\u064A\u0642\u0627\u0641 tab3_Txt_GreetingsMessage2=--\n\ -\u0627\u0644\u0645\u0635\u062F\u0631: https://github.com/developersu/ns-usbloader/\n\ -\u0627\u0644\u0645\u0648\u0642\u0639: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +\u0627\u0644\u0645\u0635\u062F\u0631: https://git.redrise.ru/desu/ns-usbloader/\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +\u0627\u0644\u0645\u0648\u0642\u0639: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=\u0627\u0644\u062D\u0627\u0644\u0629 tab1_table_Lbl_FileName=\u0627\u0633\u0645 \u0627\u0644\u0645\u0644\u0641 @@ -70,3 +71,4 @@ tab2_Cb_GlVersion=\u0625\u0635\u062F\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\ tab2_Cb_GLshowNspOnly=\u0627\u0639\u0631\u0636 \u0641\u0642\u0637 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0625\u0645\u062A\u062F\u0627\u062F "\u0625\u0646 \u0625\u0633 \u0628\u064A" \u0641\u064A \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u0640 "\u062C\u0648\u0644\u062F \u0644\u064A\u0641". windowBodyPleaseStopOtherProcessFirst=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0642\u0645 \u0628\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649 \u0642\u0628\u0644 \u0627\u0644\u0625\u0633\u062A\u0645\u0631\u0627\u0631. + diff --git a/src/main/resources/locale_cs_CZ.properties b/src/main/resources/locale_cs_CZ.properties index c4148c9..03ebe60 100644 --- a/src/main/resources/locale_cs_CZ.properties +++ b/src/main/resources/locale_cs_CZ.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=Pr\u00E1v\u011B prob\u00EDh\u00E1 p\u0159enos dat a zav\u0 windowTitleConfirmExit=Ne, te\u010F nechci odej\u00EDt! btn_Stop=P\u0159eru\u0161it tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Stav tab1_table_Lbl_FileName=N\u00E1zev souboru @@ -70,3 +71,4 @@ tab2_Cb_GlVersion=GoldLeaf verze tab2_Cb_GLshowNspOnly=Uk\u00E1zat v GoldLeafu pouze *.nsp. windowBodyPleaseStopOtherProcessFirst=Pros\u00EDm, p\u0159ed pokra\u010Dov\u00E1n\u00EDm nejprve zru\u0161te aktivn\u00ED p\u0159enos. + diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index f028cc3..2e10d92 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=Der Datentransfer ist noch nicht abgeschlossen; das Schlie windowTitleConfirmExit=Nein, mach das nicht! btn_Stop=Unterbrechen tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status tab1_table_Lbl_FileName=Dateiname @@ -49,3 +50,4 @@ tab2_Cb_GLshowNspOnly=Nur *.nsp in GoldLeaf zeigen. btn_Cancel=Abbrechen + diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index f1fd516..62027f7 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=Data transfer is in progress and closing this application windowTitleConfirmExit=No, don't do this! btn_Stop=Interrupt tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status tab1_table_Lbl_FileName=File name @@ -76,4 +77,4 @@ tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'G windowTitleAddingFiles=Searching for files... windowBodyFilesScanned=Files scanned: %d\nWould be added: %d tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM -tabRcm_Lbl_Payload=Payload: \ No newline at end of file +tabRcm_Lbl_Payload=Payload: diff --git a/src/main/resources/locale_es_ES.properties b/src/main/resources/locale_es_ES.properties index b535b7d..f447ef1 100644 --- a/src/main/resources/locale_es_ES.properties +++ b/src/main/resources/locale_es_ES.properties @@ -1,47 +1,81 @@ -btn_OpenFile=Seleccionar los archivos +btn_OpenFile=Seleccionar archivos +btn_OpenFolders=Seleccionar carpeta btn_Upload=Enviar a NS -tab3_Txt_EnteredAsMsg1=Est\u00E1 conectado como: -tab3_Txt_EnteredAsMsg2=Deber\u00EDa ser root o haber configurado las reglas 'udev' de este usuario para evitar problemas. -tab3_Txt_FilesToUploadTitle=Archivos a subir: +btn_OpenFolders_tooltip=Select a folder to be scanned.\nThis folder and all of its subfolders will be scanned.\nAll matching files will be added to the list. +tab3_Txt_EnteredAsMsg1=Est\u00E1s conectado como: +tab3_Txt_EnteredAsMsg2=Debes tener permisos root o haber configurado las reglas 'udev' de este usuario para evitar problemas. +tab3_Txt_FilesToUploadTitle=Archivos a enviar: tab3_Txt_GreetingsMessage=Bienvenido a NS-USBloader -tab3_Txt_NoFolderOrFileSelected=No ha seleccionado ning\u00FAn archivo: Nada se subir\u00E1. -windowBodyConfirmExit=Transferencia de datos en progreso, cerrar la aplicaci\u00F3n lo interrumpir\u00E1.\nNo se recomienda.\nInterrumpir proceso y salir? -windowTitleConfirmExit=No, no haga esto! +tab3_Txt_NoFolderOrFileSelected=Ning\u00FAn archivo seleccionado: No se enviar\u00E1 nada. +windowBodyConfirmExit=Se est\u00E1n transfiriendo archivos y cerrar la aplicaci\u00F3n lo interrumpir\u00E1.\nNo debes hacer esto.\n\u00BFInterrumpir el proceso y salir? +windowTitleConfirmExit=\u00A1No hagas esto! btn_Stop=Interrumpir tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=Estado +tab1_table_Lbl_Status=Estatus tab1_table_Lbl_FileName=Nombre del archivo tab1_table_Lbl_Size=Tama\u00F1o -tab1_table_Lbl_Upload=Enviar? -tab1_table_contextMenu_Btn_BtnDelete=Eliminar -tab1_table_contextMenu_Btn_DeleteAll=Eliminar todo +tab1_table_Lbl_Upload=\u00BFEnviar? +tab1_table_contextMenu_Btn_BtnDelete=Quitar +tab1_table_contextMenu_Btn_DeleteAll=Quitar todos tab2_Lbl_HostIP=IP del host -tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=Validar siempre la entrada de IP de la NS. -windowBodyBadIp=Est\u00E1 seguro de que ha introducido la IP de la NS correctamente? -windowTitleBadIp=Es posible que la direcci\u00F3n IP de la NS sea incorrecta -tab2_Cb_ExpertMode=Modo Experto -tab2_Lbl_HostPort=Puerto -tab2_Cb_AutoDetectIp=Detectar IP autom\u00E1ticamente -tab2_Cb_RandSelectPort=Obtener el puerto autom\u00E1ticamente -tab2_Cb_DontServeRequests=No contestar solicitudes -tab2_Lbl_DontServeRequestsDesc=Si habilita esta opci\u00F3n, el ordenador no responder\u00E1 solicitudes de archivos NSP de la NS (en la red), y usar\u00E1 las configuraciones definidas por el host para indicar a Awoo donde se encuentran los archivos -tab2_Lbl_HostExtra=Extra -windowTitleErrorPort=Puerto asignado incorrectamente! -windowBodyErrorPort=El puerto no puede ser 0 o mayor que 65535 -tab2_Cb_AutoCheckForUpdates=Comprobar actualizaciones autom\u00E1ticamente -windowTitleNewVersionAval=Actualizaci\u00F3n disponible -windowTitleNewVersionNOTAval=No hay actualizaciones disponibles -windowTitleNewVersionUnknown=No fue posible encontrar actualizaciones -windowBodyNewVersionUnknown=Algo fall\u00F3\nLa conexi\u00F3n a internet no funciona correctamente, o GitHub est\u00E1 ca\u00EDdo +tab1_Lbl_NSIP=IP del NS: +tab2_Cb_ValidateNSHostName=Siempre verifica el IP del NS. +windowBodyBadIp=\u00BFEst\u00E1s seguro que la direcci\u00F3n IP ingresada es la del NS? +windowTitleBadIp=La IP del NS parece ser incorrecta. +tab2_Cb_ExpertMode=Modo experto (NET setup) +tab2_Lbl_HostPort=puerto +tab2_Cb_AutoDetectIp=Detectar autom\u00E1ticamente la IP +tab2_Cb_RandSelectPort=Obtener puerto aleatoriamente +tab2_Cb_DontServeRequests=No responder solicitudes +tab2_Lbl_DontServeRequestsDesc=Si se activa, esta computadora no responder\u00E1 a las solicitudes de archivos NSP provenientes del NS (en la red) y se usar\u00E1n las configuraciones definidas por el host para decirle a Awoo Installer (o aplicaciones compatibles) donde buscar por los archivos. +tab2_Lbl_HostExtra=extra +windowTitleErrorPort=\u00A1Puerto incorrecto! +windowBodyErrorPort=\u00A1Puerto incorrecto! +tab2_Cb_AutoCheckForUpdates=Buscar actualizaciones autom\u00E1ticamente +windowTitleNewVersionAval=Nueva versi\u00F3n disponible +windowTitleNewVersionNOTAval=No hay ninguna nueva versi\u00F3n disponible +windowTitleNewVersionUnknown=No se pueden buscar actualizaciones +windowBodyNewVersionUnknown=Algo sali\u00F3 mal\nQuiz\u00E1s no tienes conexi\u00F3n a internet, o GitHub est\u00E1 caido windowBodyNewVersionNOTAval=Est\u00E1s usando la \u00FAltima versi\u00F3n -tab2_Cb_AllowXciNszXcz=Permite la selecci\u00F3n de archivos XCI / NSZ / XCZ para Awoo -tab2_Lbl_AllowXciNszXczDesc=Usado por algunas aplicaciones de terceros que soportan XCI/NSZ/XCZ y que utilizan el protocolo de transferencia de Tinfoil. Si no est\u00E1 seguro no cambie la opci\u00F3n. +tab2_Cb_AllowXciNszXcz=Permitir seleccionar archivos XCI / NSZ / XCZ en Awoo +tab2_Lbl_AllowXciNszXczDesc=Usado por aplicaciones que soportan XCI/NSZ/XCZ y utilizan el protocolo de transferencia Awoo (Adubbz/TinFoil). No modificar si no est\u00E1s seguro. Habilitar para Awoo Installer. tab2_Lbl_Language=Idioma -windowBodyRestartToApplyLang=Por favor, reinicie el programa para aplicar los cambios. -tab2_Cb_GLshowNspOnly=Mostrar solo *.nsp en GoldLeaf. +windowBodyRestartToApplyLang=Reinicia la aplicaci\u00F3n para aplicar los cambios. +btn_OpenSplitFile=Seleccionar NSP en partes +tab2_Lbl_ApplicationSettings=Ajustes principales +tabSplMrg_Lbl_SplitNMergeTitle=Herramienta para dividir y fusionar archivos +tabSplMrg_RadioBtn_Split=Dividir +tabSplMrg_RadioBtn_Merge=Fusionar +tabSplMrg_Txt_File=Archivo: +tabSplMrg_Txt_Folder=Archivo dividido (carpeta): +tabSplMrg_Btn_SelectFile=Seleccionar archivo +tabSplMrg_Btn_SelectFolder=Seleccionar carpeta +tabSplMrg_Lbl_SaveToLocation=Guardar en: +tabSplMrg_Btn_ChangeSaveToLocation=Cambiar +tabSplMrg_Btn_Convert=Convertir +windowTitleError=Error +windowBodyPleaseFinishTransfersFirst=No se pueden dividir/fusionar archivos mientras se est\u00E1 realizando otro proceso. Por favor, cancela las operaciones de transferencia activas primero. +done_txt=\u00A1Listo! +failure_txt=Error +btn_Select=Seleccionar +btn_InjectPayloader=Injectar payload +tabNXDT_Btn_Start=\u00A1Iniciar! +tab2_Btn_InstallDrivers=Descargar e instalar controladores +windowTitleDownloadDrivers=Descargar e instalar controladores +windowBodyDownloadDrivers=Descargando controladores (libusbK v3.0.7.0)... btn_Cancel=Cancelar - +btn_Close=Cerrar +tab2_Cb_GlVersion=Versi\u00F3n de GoldLeaf +tab2_Cb_GLshowNspOnly=Mostrar solo *.nsp en GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Det\u00E9n los dem\u00E1s procesos activos antes de continuar. +tab2_Cb_foldersSelectorForRoms=Seleccionar una carpeta con archivos ROM en lugar de seleccionarlos individualmente. +tab2_Cb_foldersSelectorForRomsDesc=Cambia el comportamiento del bot\u00F3n de 'Seleccionar archivos' en la pesta\u00F1a 'Juegos': en lugar de seleccionar archivos ROM uno por uno puedes seleccionar una carpeta para instalarlos todos a la vez. +windowTitleAddingFiles=Buscando archivos... +windowBodyFilesScanned=Archivos encontrados: %d\nSe a\u00F1adir\u00E1n: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer y compatibles +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM diff --git a/src/main/resources/locale_fr_FR.properties b/src/main/resources/locale_fr_FR.properties index 5fbb3e7..08048b8 100644 --- a/src/main/resources/locale_fr_FR.properties +++ b/src/main/resources/locale_fr_FR.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=Le transfert de donn\u00E9es est en cours et la fermeture windowTitleConfirmExit=Non, ne faites pas \u00E7a! btn_Stop=Interrompre tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Upload=Envoyer ? tab1_table_Lbl_Size=Taille @@ -44,3 +45,4 @@ tab2_Lbl_Language=La langue btn_Cancel=Annuler + diff --git a/src/main/resources/locale_it_IT.properties b/src/main/resources/locale_it_IT.properties index 8aaad2c..103b64d 100644 --- a/src/main/resources/locale_it_IT.properties +++ b/src/main/resources/locale_it_IT.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=Il trasferimento dei dati \u00E8 in corso e la chiusura de windowTitleConfirmExit=No, non farlo! btn_Stop=Interrompi tab3_Txt_GreetingsMessage2=--\n\ -Sorgenti: https://github.com/developersu/ns-usbloader/\n\ -Sito: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Sorgenti: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Sito: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Stato tab1_table_Lbl_FileName=Nome file @@ -78,3 +79,4 @@ windowBodyFilesScanned=File scansionati: %d\nVerranno aggiunti: %d tab2_Lbl_AwooBlockTitle=Awoo Installer e compatibili tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 7de4802..8d11a70 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=\uB370\uC774\uD130 \uC804\uC1A1\uC774 \uC9C4\uD589 \uC911\ windowTitleConfirmExit=\uC544\uB2C8\uC624, \uC774\uAC83\uC740 \uD558\uC9C0\uB9C8\uC138\uC694! btn_Stop=\uC911\uB2E8 tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=\uC0C1\uD0DC tab1_table_Lbl_FileName=\uD30C\uC77C \uC774\uB984 @@ -78,3 +79,4 @@ windowBodyFilesScanned=\uC2A4\uCE94 \uB41C \uD30C\uC77C: %d\n\uCD94\uAC00 \uB420 tab2_Lbl_AwooBlockTitle=Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uACFC \uD638\uD658\uC131 tabRcm_Lbl_Payload=\uD398\uC774\uB85C\uB4DC: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_pt_BR.properties b/src/main/resources/locale_pt_BR.properties index 551bad0..5b6f5ba 100644 --- a/src/main/resources/locale_pt_BR.properties +++ b/src/main/resources/locale_pt_BR.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=Transfer\u00EAncia de dados em progresso: Fechar ir\u00E1 windowTitleConfirmExit=N\u00E3o, n\u00E3o fa\u00E7a isso! btn_Stop=Interromper! tab3_Txt_GreetingsMessage2=--\n\ -Fonte: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Fonte: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status tab1_table_Lbl_FileName=Nome do Arquivo @@ -69,3 +70,4 @@ btn_Close=Fechar tab2_Cb_GlVersion=Vers\u00E3o do GoldLeaf tab2_Cb_GLshowNspOnly=Mostrar apenas *.nsp no GoldLeaf. windowBodyPleaseStopOtherProcessFirst=Por favor, pare outros processos ativos antes de prosseguir + diff --git a/src/main/resources/locale_ro_RO.properties b/src/main/resources/locale_ro_RO.properties index 07be038..6929a64 100644 --- a/src/main/resources/locale_ro_RO.properties +++ b/src/main/resources/locale_ro_RO.properties @@ -11,8 +11,9 @@ windowBodyConfirmExit=Transferul de date este \u00EEn desf\u0103\u0219urare \u02 windowTitleConfirmExit=Nu, nu face asta! btn_Stop=\u00CEntrerupere tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=Status tab1_table_Lbl_FileName=Numele fi\u0219ierului @@ -74,4 +75,4 @@ windowBodyPleaseStopOtherProcessFirst=Te rog opre\u0219te toate cel\u0103lalte p tab2_Cb_foldersSelectorForRoms=Selecteaz\u0103 directorul cu fi\u0219iere ROM \u00EEn loc s\u0103 selectezi fi\u0219iere ROM individual. tab2_Cb_foldersSelectorForRomsDesc=Face ca 'Selecteaz\u0103 fi\u0219ierele' \u00EEn tab-ul 'Games' s\u0103 selecteze toate fi\u0219ierele de-o dat\u0103\u00A0\u00EEn loc de a selecta fi\u0219iere ROM unul c\u00E2te unul. windowTitleAddingFiles=Caut fi\u0219iere... -windowBodyFilesScanned=Fi\u0219iere scanate: %d\nVor fi ad\u0103ugate: %d \ No newline at end of file +windowBodyFilesScanned=Fi\u0219iere scanate: %d\nVor fi ad\u0103ugate: %d diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index bfc8a03..6620554 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=\u0421\u0435\u0439\u0447\u0430\u0441 \u043F\u0440\u043E\u0 windowTitleConfirmExit=\u041D\u0435\u0442, \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0441\u044C! btn_Stop=\u041F\u0440\u0435\u0440\u0432\u0430\u0442\u044C tab3_Txt_GreetingsMessage2=--\n\ -\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\ -\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://git.redrise.ru/desu/ns-usbloader\n\ +\u0417\u0435\u0440\u043A\u0430\u043B\u043E: https://github.com/developersu/ns-usbloader/\n\ +\u0421\u0430\u0439\u0442: https://redrise.ru\n\ \u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su] tab1_table_Lbl_Upload=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044C? tab1_table_Lbl_Size=\u0420\u0430\u0437\u043C\u0435\u0440 @@ -77,3 +78,4 @@ tab2_Lbl_AwooBlockTitle=Awoo Installer \u0438 \u0441\u043E\u0432\u043C\u0435\u04 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index 7e88bb5..aad36fc 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -9,8 +9,9 @@ windowBodyConfirmExit=\u0417\u0430\u0440\u0430\u0437 \u0432\u0456\u0434\u0431\u0 windowTitleConfirmExit=\u041D\u0456, \u0437\u0443\u043F\u0438\u043D\u0438\u0441\u044C! btn_Stop=\u041F\u0435\u0440\u0435\u0440\u0432\u0430\u0442\u0438 tab3_Txt_GreetingsMessage2=--\n\ -\u0421\u0438\u0440\u0446\u0435\u0432\u0438\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\ -\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +\u0421\u0438\u0440\u0446\u0435\u0432\u0438\u0439 \u043A\u043E\u0434: https://git.redrise.ru/desu/ns-usbloader\n\ +\u0414\u0437\u0435\u0440\u043A\u0430\u043B\u043E: https://github.com/developersu/ns-usbloader/\n\ +\u0421\u0430\u0439\u0442: https://redrise.ru\n\ \u0418\u0441\u0430\u0454\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u043E [developer.su] tab1_table_Lbl_Status=\u0421\u0442\u0430\u043D tab1_table_Lbl_FileName=\u0406\u043C'\u044F \u0444\u0430\u0439\u043B\u0443 @@ -76,3 +77,4 @@ windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u0456\u0432 \u043F\u0440\u043E\u tab2_Lbl_AwooBlockTitle=Awoo Installer \u0442\u0430 \u0441\u0443\u043C\u0456\u0441\u043D\u0456 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_vi_VN.properties b/src/main/resources/locale_vi_VN.properties index cccefce..a8db7dc 100644 --- a/src/main/resources/locale_vi_VN.properties +++ b/src/main/resources/locale_vi_VN.properties @@ -34,7 +34,11 @@ tab3_Txt_EnteredAsMsg1=B\u1EA1n \u0111\u00E3 truy nh\u1EADp d\u01B0\u1EDBi d\u1E tab3_Txt_EnteredAsMsg2=B\u1EA1n n\u00EAn c\u00F3 quy\u1EC1n root ho\u1EB7c c\u1EA5u h\u00ECnh quy\u1EC1n 'udev' cho ng\u01B0\u1EDDi d\u00F9ng n\u00E0y \u0111\u1EC3 tr\u00E1nh c\u00E1c v\u1EA5n \u0111\u1EC1 ph\u00E1t sinh. tab3_Txt_FilesToUploadTitle=T\u1EADp tin s\u1EBD t\u1EA3i l\u00EAn\: tab3_Txt_GreetingsMessage=Ch\u00E0o m\u1EEBng \u0111\u1EBFn NS-USBloader -tab3_Txt_GreetingsMessage2=--\nNgu\u1ED3n\: https\://github.com/developersu/ns-usbloader/\nTrang\: https\://developersu.blogspot.com/search/label/NS-USBloader\nDmitry Isaenko [developer.su] +tab3_Txt_GreetingsMessage2=--\n\ +Ngu\u1ED3n: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Trang: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] tab3_Txt_NoFolderOrFileSelected=Kh\u00F4ng c\u00F3 t\u1EADp tin n\u00E0o \u0111\u01B0\u1EE3c ch\u1ECDn\: kh\u00F4ng c\u00F3 g\u00EC \u0111\u1EC3 t\u1EA3i l\u00EAn. tabSplMrg_Btn_ChangeSaveToLocation=Thay \u0111\u1ED5i tabSplMrg_Btn_Convert=Chuy\u1EC3n \u0111\u1ED5i @@ -62,3 +66,4 @@ windowTitleNewVersionNOTAval=Ch\u01B0a c\u00F3 phi\u00EAn b\u1EA3n m\u1EDBi windowTitleNewVersionUnknown=Kh\u00F4ng th\u1EC3 ki\u1EC3m tra phi\u00EAn b\u1EA3n m\u1EDBi btn_Cancel=H\u1EE7y B\u1ECF + diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 5262f68..8039b0a 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -1,7 +1,7 @@ btn_OpenFile=\u9009\u62E9.NSP\u6587\u4EF6 -btn_OpenFolders=\u9009\u62e9\u76ee\u5f55 +btn_OpenFolders=\u9009\u62E9\u76EE\u5F55 btn_Upload=\u4E0A\u4F20\u5230NS -btn_OpenFolders_tooltip=\u6b63\u5728\u626b\u63cf\u9009\u4e2d\u7684\u76ee\u5f55.\n\u6240\u6709\u7684\u76ee\u5f55\u626b\u63cf\u5b8c\u6210.\n\u7b26\u5408\u7684\u6587\u4ef6\u5df2\u7ecf\u6dfb\u52a0\u5230\u5217\u8868\u4e2d. +btn_OpenFolders_tooltip=\u6B63\u5728\u626B\u63CF\u9009\u4E2D\u7684\u76EE\u5F55.\n\u6240\u6709\u7684\u76EE\u5F55\u626B\u63CF\u5B8C\u6210.\n\u7B26\u5408\u7684\u6587\u4EF6\u5DF2\u7ECF\u6DFB\u52A0\u5230\u5217\u8868\u4E2D. tab3_Txt_EnteredAsMsg1=\u4F60\u6B63\u5728\u4F7F\u7528: tab3_Txt_EnteredAsMsg2=\u4F60\u5E94\u8BE5\u4F7F\u7528root\u8D26\u53F7\u6216\u8005\u4E3A\u5F53\u524D\u7528\u6237\u914D\u7F6E'udev'\u89C4\u5219\u6765\u907F\u514D\u53EF\u80FD\u7684\u95EE\u9898\u3002 tab3_Txt_FilesToUploadTitle=\u8981\u4E0A\u4F20\u7684\u6587\u4EF6: @@ -11,8 +11,9 @@ windowBodyConfirmExit=\u6570\u636E\u6B63\u5728\u4F20\u8F93\u4E2D\uFF0C\u5173\u95 windowTitleConfirmExit=\u4E0D, \u4E0D\u8FDB\u884C\u8FD9\u9879\u64CD\u4F5C\uFF01 btn_Stop=\u4E2D\u65AD tab3_Txt_GreetingsMessage2=--\n\ -\u6E90\u4EE3\u7801: https://github.com/developersu/ns-usbloader/\n\ -\u7F51\u7AD9: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +\u6E90\u4EE3\u7801: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +\u7F51\u7AD9: https://redrise.ru\n\ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=\u72B6\u6001 tab1_table_Lbl_FileName=\u6587\u4EF6\u540D @@ -71,10 +72,11 @@ btn_Close=\u5173\u95ED tab2_Cb_GlVersion=GoldLeaf\u7248\u672C tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5185\u4EC5\u663E\u793A*.nsp\u6587\u4EF6 windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u6267\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8BF7\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u5904\u7406\u7684\u7A0B\u5E8F. -tab2_Cb_foldersSelectorForRoms=\u9009\u4e2d\u540e\u5207\u6362\u6210\u9009\u62e9\u76ee\u5f55\u6a21\u5f0f -tab2_Cb_foldersSelectorForRomsDesc=\u542f\u7528\u6b64\u529f\u80fd\u540e\uff0c\u5c06\u6539\u53d8\u201c\u6e38\u620f\u6807\u7b7e\u201d\u4e2d\u7684\u201c\u9009\u62e9\u6587\u4ef6\u201d\u6309\u952e\u529f\u80fd\uff1a\u4ece\u9009\u62e9\u4e00\u4e2a\u6587\u4ef6\u66ff\u6362\u4e3a\u9009\u62e9\u4e00\u4e2a\u76ee\u5f55\u3002 -windowTitleAddingFiles=\u641c\u7d22\u6587\u4ef6\u4e2d... -windowBodyFilesScanned=\u626b\u63cf\u6587\u4ef6: %25d\n\u88ab\u6dfb\u52a0: %25d -tab2_Lbl_AwooBlockTitle=awoo installer \u5b8c\u6210 +tab2_Cb_foldersSelectorForRoms=\u9009\u4E2D\u540E\u5207\u6362\u6210\u9009\u62E9\u76EE\u5F55\u6A21\u5F0F +tab2_Cb_foldersSelectorForRomsDesc=\u542F\u7528\u6B64\u529F\u80FD\u540E\uFF0C\u5C06\u6539\u53D8\u201C\u6E38\u620F\u6807\u7B7E\u201D\u4E2D\u7684\u201C\u9009\u62E9\u6587\u4EF6\u201D\u6309\u952E\u529F\u80FD\uFF1A\u4ECE\u9009\u62E9\u4E00\u4E2A\u6587\u4EF6\u66FF\u6362\u4E3A\u9009\u62E9\u4E00\u4E2A\u76EE\u5F55\u3002 +windowTitleAddingFiles=\u641C\u7D22\u6587\u4EF6\u4E2D... +windowBodyFilesScanned=\u626B\u63CF\u6587\u4EF6: %25d\n\u88AB\u6DFB\u52A0: %25d +tab2_Lbl_AwooBlockTitle=awoo installer \u5B8C\u6210 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_zh_TW.properties b/src/main/resources/locale_zh_TW.properties index 739bedd..615b87f 100644 --- a/src/main/resources/locale_zh_TW.properties +++ b/src/main/resources/locale_zh_TW.properties @@ -1,80 +1,82 @@ -btn_OpenFile=\u9078\u64c7\u6a94\u6848 -btn_OpenFolders=\u9078\u64c7\u8cc7\u6599\u593e -btn_Upload=\u4e0a\u50b3\u81f3NS -btn_OpenFolders_tooltip=\u9078\u64c7\u8981\u6aa2\u67e5\u7684\u8cc7\u6599\u593e.\n\u6703\u6aa2\u67e5\u8cc7\u6599\u593e\u8207\u6240\u6709\u5b50\u76ee\u9304.\n\u6240\u6709\u7b26\u5408\u7684\u6a94\u6848\u90fd\u6703\u52a0\u5165\u4f47\u5217. -tab3_Txt_EnteredAsMsg1=\u76ee\u524d\u767b\u5165\u7684\u4f7f\u7528\u8005: -tab3_Txt_EnteredAsMsg2=\u57f7\u884c\u6b64\u64cd\u4f5c\u6642\u4f60\u5fc5\u9808\u64c1\u6709\u7ba1\u7406\u54e1\u6b0a\u9650,\u6216\u6b64\u767b\u5165\u7684\u4f7f\u7528\u8005\u5e33\u6236\u5df2\u914d\u7f6e'udev'\u898f\u5247,\u4ee5\u907f\u514d\u767c\u751f\u554f\u984c. -tab3_Txt_FilesToUploadTitle=\u4e0a\u50b3\u7684\u6a94\u6848: -tab3_Txt_GreetingsMessage=\u6b61\u8fce\u4f7f\u7528NS-USBloader -tab3_Txt_NoFolderOrFileSelected=\u5c1a\u672a\u9078\u53d6\u6a94\u6848: \u4e0a\u50b3\u4f47\u5217\u6c92\u6709\u9805\u76ee -windowBodyConfirmExit=\u6b64\u6642\u9000\u51fa\u7a0b\u5f0f\u5c07\u6703\u4e2d\u65b7\u6b63\u5728\u50b3\u8f38\u7684\u8cc7\u6599.\n\u518d\u6b21\u50b3\u8f38\u8cc7\u6599\u5c07\u91cd\u8907\u4f54\u7528\u6642\u9593.\n\u662f\u5426\u78ba\u8a8d\u8981\u4e2d\u65b7\u50b3\u8f38\u4e26\u4e14\u7d50\u675f\u7a0b\u5f0f? -windowTitleConfirmExit=\u4e0d,\u8acb\u52ff\u9000\u51fa! -btn_Stop=\u4e2d\u65b7\u64cd\u4f5c +btn_OpenFile=\u9078\u64C7\u6A94\u6848 +btn_OpenFolders=\u9078\u64C7\u8CC7\u6599\u593E +btn_Upload=\u4E0A\u50B3\u81F3NS +btn_OpenFolders_tooltip=\u9078\u64C7\u8981\u6AA2\u67E5\u7684\u8CC7\u6599\u593E.\n\u6703\u6AA2\u67E5\u8CC7\u6599\u593E\u8207\u6240\u6709\u5B50\u76EE\u9304.\n\u6240\u6709\u7B26\u5408\u7684\u6A94\u6848\u90FD\u6703\u52A0\u5165\u4F47\u5217. +tab3_Txt_EnteredAsMsg1=\u76EE\u524D\u767B\u5165\u7684\u4F7F\u7528\u8005: +tab3_Txt_EnteredAsMsg2=\u57F7\u884C\u6B64\u64CD\u4F5C\u6642\u4F60\u5FC5\u9808\u64C1\u6709\u7BA1\u7406\u54E1\u6B0A\u9650,\u6216\u6B64\u767B\u5165\u7684\u4F7F\u7528\u8005\u5E33\u6236\u5DF2\u914D\u7F6E'udev'\u898F\u5247,\u4EE5\u907F\u514D\u767C\u751F\u554F\u984C. +tab3_Txt_FilesToUploadTitle=\u4E0A\u50B3\u7684\u6A94\u6848: +tab3_Txt_GreetingsMessage=\u6B61\u8FCE\u4F7F\u7528NS-USBloader +tab3_Txt_NoFolderOrFileSelected=\u5C1A\u672A\u9078\u53D6\u6A94\u6848: \u4E0A\u50B3\u4F47\u5217\u6C92\u6709\u9805\u76EE +windowBodyConfirmExit=\u6B64\u6642\u9000\u51FA\u7A0B\u5F0F\u5C07\u6703\u4E2D\u65B7\u6B63\u5728\u50B3\u8F38\u7684\u8CC7\u6599.\n\u518D\u6B21\u50B3\u8F38\u8CC7\u6599\u5C07\u91CD\u8907\u4F54\u7528\u6642\u9593.\n\u662F\u5426\u78BA\u8A8D\u8981\u4E2D\u65B7\u50B3\u8F38\u4E26\u4E14\u7D50\u675F\u7A0B\u5F0F? +windowTitleConfirmExit=\u4E0D,\u8ACB\u52FF\u9000\u51FA! +btn_Stop=\u4E2D\u65B7\u64CD\u4F5C tab3_Txt_GreetingsMessage2=--\n\ -Source: https://github.com/developersu/ns-usbloader/\n\ -Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=\u72c0\u614b -tab1_table_Lbl_FileName=\u6a94\u6848\u540d\u7a31 -tab1_table_Lbl_Size=\u6a94\u6848\u5927\u5c0f -tab1_table_Lbl_Upload=\u4e0a\u50b3? -tab1_table_contextMenu_Btn_BtnDelete=\u79fb\u9664 -tab1_table_contextMenu_Btn_DeleteAll=\u5168\u90e8\u79fb\u9664 -tab2_Lbl_HostIP=\u96fb\u8166IP +tab1_table_Lbl_Status=\u72C0\u614B +tab1_table_Lbl_FileName=\u6A94\u6848\u540D\u7A31 +tab1_table_Lbl_Size=\u6A94\u6848\u5927\u5C0F +tab1_table_Lbl_Upload=\u4E0A\u50B3? +tab1_table_contextMenu_Btn_BtnDelete=\u79FB\u9664 +tab1_table_contextMenu_Btn_DeleteAll=\u5168\u90E8\u79FB\u9664 +tab2_Lbl_HostIP=\u96FB\u8166IP tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=\u6bcf\u6b21\u8f38\u5165\u5f8c\u90fd\u9a57\u8b49NS IP -windowBodyBadIp=\u4f60\u6240\u8f38\u5165\u7684NS IP\u4f4d\u5740\u662f\u5426\u6b63\u78ba? -windowTitleBadIp=NS IP\u4f4d\u5740\u4e0d\u6b63\u78ba -tab2_Cb_ExpertMode=\u5c08\u5bb6\u6a21\u5f0f (NET \u5b89\u88dd) -tab2_Lbl_HostPort=\u9023\u63a5\u57e0 -tab2_Cb_AutoDetectIp=\u81ea\u52d5\u5075\u6e2cIP -tab2_Cb_RandSelectPort=\u96a8\u6a5f\u9078\u53d6\u9023\u63a5\u57e0 -tab2_Cb_DontServeRequests=\u4e0d\u56de\u61c9NS\u8acb\u6c42 -tab2_Lbl_DontServeRequestsDesc=\u555f\u7528\u6b64\u8a2d\u5b9a\u5f8c,\u6b64\u96fb\u8166\u5c07\u4e0d\u6703\u56de\u61c9\u4f86\u81eaNS(\u900f\u904e\u7db2\u8def)\u7684NSP\u6a94\u6848\u8acb\u6c42,\u4e26\u8b93Awoo(\u6216\u5176\u4ed6\u540c\u985e\u578b\u7a0b\u5f0f)\u5c0b\u627e\u6a94\u6848\u4f86\u6e90\u6642\u4f7f\u7528\u9810\u8a2d\u7684\u4e3b\u6a5f\u8a2d\u5b9a. -tab2_Lbl_HostExtra=\u5176\u4ed6 -windowTitleErrorPort=\u9023\u63a5\u57e0\u8a2d\u5b9a\u4e0d\u6b63\u78ba! -windowBodyErrorPort=\u9023\u63a5\u57e0\u8a2d\u5b9a\u503c\u5fc5\u9808\u4ecb\u65bc0\u523065535\u4e4b\u9593. -tab2_Cb_AutoCheckForUpdates=\u81ea\u52d5\u6aa2\u67e5\u66f4\u65b0 -windowTitleNewVersionAval=\u6709\u53ef\u66f4\u65b0\u7684\u7248\u672c -windowTitleNewVersionNOTAval=\u76ee\u524d\u6c92\u6709\u53ef\u66f4\u65b0\u7684\u7248\u672c -windowTitleNewVersionUnknown=\u76ee\u524d\u7121\u6cd5\u6aa2\u67e5\u6709\u7121\u53ef\u66f4\u65b0\u7248\u672c -windowBodyNewVersionUnknown=\u767c\u751f\u932f\u8aa4\n\u53ef\u80fd\u7db2\u8def\u4e0d\u53ef\u7528\u6216\u8a0a\u865f\u4e0d\u8db3,\u6216\u8005\u7121\u6cd5\u9023\u4e0aGitHub\u5b98\u7db2\u4f3a\u670d\u5668 -windowBodyNewVersionNOTAval=\u76ee\u524d\u4f7f\u7528\u7684\u7a0b\u5f0f\u5df2\u662f\u6700\u65b0\u7248\u672c -tab2_Cb_AllowXciNszXcz=\u5141\u8a31Awoo\u6a21\u5f0f\u6642\u9078\u53d6XCI / NSZ / XCZ \u6a94\u6848\u683c\u5f0f -tab2_Lbl_AllowXciNszXczDesc=\u6b64\u8a2d\u5b9a\u5c08\u70ba\u652f\u63f4XCI/NSZ/XCZ\u6a94\u6848\u683c\u5f0f\u8207Tinfoil\u50b3\u8f38\u5354\u8b70\u7684\u7b2c\u4e09\u65b9\u7a0b\u5f0f\u4f7f\u7528. \u5982\u4e0d\u78ba\u5b9a,\u8acb\u52ff\u8b8a\u66f4\u6b64\u9805\u8a2d\u5b9a. \u4f7f\u7528Awoo Installer\u8acb\u555f\u7528\u6b64\u8a2d\u5b9a. -tab2_Lbl_Language=\u4ecb\u9762\u8a9e\u7cfb -windowBodyRestartToApplyLang=\u8acb\u91cd\u65b0\u555f\u52d5\u7a0b\u5f0f\u4ee5\u5957\u7528\u8b8a\u66f4\u7684\u8a2d\u5b9a. -btn_OpenSplitFile=\u9078\u64c7\u5206\u5272\u7684NSP -tab2_Lbl_ApplicationSettings=\u4e3b\u8981\u8a2d\u5b9a -tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u4f75\u6a94\u6848\u5de5\u5177 -tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u4f75\u6a94\u6848\u5de5\u5177 -tabSplMrg_RadioBtn_Merge=\u5408\u4f75 -tabSplMrg_Txt_File=\u6a94\u6848: -tabSplMrg_Txt_Folder=\u5206\u5272\u6a94\u6848(\u8cc7\u6599\u593e): -tabSplMrg_Btn_SelectFile=\u9078\u64c7\u6a94\u6848 -tabSplMrg_Btn_SelectFolder=\u9078\u64c7\u8cc7\u6599\u593e -tabSplMrg_Lbl_SaveToLocation=\u5132\u5b58\u8def\u5f91: -tabSplMrg_Btn_ChangeSaveToLocation=\u8b8a\u66f4 -tabSplMrg_Btn_Convert=\u8f49\u6a94 -windowTitleError=\u932f\u8aa4 -windowBodyPleaseFinishTransfersFirst=\u7576\u7a0b\u5f0f\u6b63\u5728\u8655\u7406USB/\u7db2\u8def\u5b89\u88dd\u6642\u7121\u6cd5\u540c\u6642\u57f7\u884c\u5206\u5272/\u5408\u4f75\u6a94\u6848. \u5982\u9700\u7e7c\u7e8c,\u5fc5\u9808\u4e2d\u65b7\u76ee\u524d\u7684\u50b3\u8f38. -done_txt=\u5b8c\u6210! +tab2_Cb_ValidateNSHostName=\u6BCF\u6B21\u8F38\u5165\u5F8C\u90FD\u9A57\u8B49NS IP +windowBodyBadIp=\u4F60\u6240\u8F38\u5165\u7684NS IP\u4F4D\u5740\u662F\u5426\u6B63\u78BA? +windowTitleBadIp=NS IP\u4F4D\u5740\u4E0D\u6B63\u78BA +tab2_Cb_ExpertMode=\u5C08\u5BB6\u6A21\u5F0F (NET \u5B89\u88DD) +tab2_Lbl_HostPort=\u9023\u63A5\u57E0 +tab2_Cb_AutoDetectIp=\u81EA\u52D5\u5075\u6E2CIP +tab2_Cb_RandSelectPort=\u96A8\u6A5F\u9078\u53D6\u9023\u63A5\u57E0 +tab2_Cb_DontServeRequests=\u4E0D\u56DE\u61C9NS\u8ACB\u6C42 +tab2_Lbl_DontServeRequestsDesc=\u555F\u7528\u6B64\u8A2D\u5B9A\u5F8C,\u6B64\u96FB\u8166\u5C07\u4E0D\u6703\u56DE\u61C9\u4F86\u81EANS(\u900F\u904E\u7DB2\u8DEF)\u7684NSP\u6A94\u6848\u8ACB\u6C42,\u4E26\u8B93Awoo(\u6216\u5176\u4ED6\u540C\u985E\u578B\u7A0B\u5F0F)\u5C0B\u627E\u6A94\u6848\u4F86\u6E90\u6642\u4F7F\u7528\u9810\u8A2D\u7684\u4E3B\u6A5F\u8A2D\u5B9A. +tab2_Lbl_HostExtra=\u5176\u4ED6 +windowTitleErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u4E0D\u6B63\u78BA! +windowBodyErrorPort=\u9023\u63A5\u57E0\u8A2D\u5B9A\u503C\u5FC5\u9808\u4ECB\u65BC0\u523065535\u4E4B\u9593. +tab2_Cb_AutoCheckForUpdates=\u81EA\u52D5\u6AA2\u67E5\u66F4\u65B0 +windowTitleNewVersionAval=\u6709\u53EF\u66F4\u65B0\u7684\u7248\u672C +windowTitleNewVersionNOTAval=\u76EE\u524D\u6C92\u6709\u53EF\u66F4\u65B0\u7684\u7248\u672C +windowTitleNewVersionUnknown=\u76EE\u524D\u7121\u6CD5\u6AA2\u67E5\u6709\u7121\u53EF\u66F4\u65B0\u7248\u672C +windowBodyNewVersionUnknown=\u767C\u751F\u932F\u8AA4\n\u53EF\u80FD\u7DB2\u8DEF\u4E0D\u53EF\u7528\u6216\u8A0A\u865F\u4E0D\u8DB3,\u6216\u8005\u7121\u6CD5\u9023\u4E0AGitHub\u5B98\u7DB2\u4F3A\u670D\u5668 +windowBodyNewVersionNOTAval=\u76EE\u524D\u4F7F\u7528\u7684\u7A0B\u5F0F\u5DF2\u662F\u6700\u65B0\u7248\u672C +tab2_Cb_AllowXciNszXcz=\u5141\u8A31Awoo\u6A21\u5F0F\u6642\u9078\u53D6XCI / NSZ / XCZ \u6A94\u6848\u683C\u5F0F +tab2_Lbl_AllowXciNszXczDesc=\u6B64\u8A2D\u5B9A\u5C08\u70BA\u652F\u63F4XCI/NSZ/XCZ\u6A94\u6848\u683C\u5F0F\u8207Tinfoil\u50B3\u8F38\u5354\u8B70\u7684\u7B2C\u4E09\u65B9\u7A0B\u5F0F\u4F7F\u7528. \u5982\u4E0D\u78BA\u5B9A,\u8ACB\u52FF\u8B8A\u66F4\u6B64\u9805\u8A2D\u5B9A. \u4F7F\u7528Awoo Installer\u8ACB\u555F\u7528\u6B64\u8A2D\u5B9A. +tab2_Lbl_Language=\u4ECB\u9762\u8A9E\u7CFB +windowBodyRestartToApplyLang=\u8ACB\u91CD\u65B0\u555F\u52D5\u7A0B\u5F0F\u4EE5\u5957\u7528\u8B8A\u66F4\u7684\u8A2D\u5B9A. +btn_OpenSplitFile=\u9078\u64C7\u5206\u5272\u7684NSP +tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8A2D\u5B9A +tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 +tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 +tabSplMrg_RadioBtn_Merge=\u5408\u4F75 +tabSplMrg_Txt_File=\u6A94\u6848: +tabSplMrg_Txt_Folder=\u5206\u5272\u6A94\u6848(\u8CC7\u6599\u593E): +tabSplMrg_Btn_SelectFile=\u9078\u64C7\u6A94\u6848 +tabSplMrg_Btn_SelectFolder=\u9078\u64C7\u8CC7\u6599\u593E +tabSplMrg_Lbl_SaveToLocation=\u5132\u5B58\u8DEF\u5F91: +tabSplMrg_Btn_ChangeSaveToLocation=\u8B8A\u66F4 +tabSplMrg_Btn_Convert=\u8F49\u6A94 +windowTitleError=\u932F\u8AA4 +windowBodyPleaseFinishTransfersFirst=\u7576\u7A0B\u5F0F\u6B63\u5728\u8655\u7406USB/\u7DB2\u8DEF\u5B89\u88DD\u6642\u7121\u6CD5\u540C\u6642\u57F7\u884C\u5206\u5272/\u5408\u4F75\u6A94\u6848. \u5982\u9700\u7E7C\u7E8C,\u5FC5\u9808\u4E2D\u65B7\u76EE\u524D\u7684\u50B3\u8F38. +done_txt=\u5B8C\u6210! failure_txt=\u5931\u6557 -btn_Select=\u9078\u64c7 -btn_InjectPayloader=\u6ce8\u5165payload -tabNXDT_Btn_Start=\u958b\u59cb! -tab2_Btn_InstallDrivers=\u4e0b\u8f09\u4e26\u5b89\u88dd\u9a45\u52d5\u7a0b\u5f0f -windowTitleDownloadDrivers=\u4e0b\u8f09\u4e26\u5b89\u88dd\u9a45\u52d5\u7a0b\u5f0f -windowBodyDownloadDrivers=\u6b63\u5728\u4e0b\u8f09\u9a45\u52d5\u7a0b\u5f0f (libusbK v3.0.7.0)... -btn_Cancel=\u53d6\u6d88 -btn_Close=\u95dc\u9589 -tab2_Cb_GlVersion=GoldLeaf\u7248\u672c -tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5167\u50c5\u986f\u793a*.nsp\u6a94\u6848 -windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57f7\u884c\u76ee\u524d\u7684\u64cd\u4f5c\u7a0b\u5e8f,\u8acb\u5148\u505c\u6b62\u5176\u4ed6\u6b63\u5728\u8655\u7406\u7684\u7a0b\u5e8f. -tab2_Cb_foldersSelectorForRoms=\u9078\u64c7\u6a94\u6848\u6642\u7531\u539f\u5148\u500b\u5225\u9078\u53d6\u8b8a\u66f4\u70ba\u6307\u5b9a\u8cc7\u6599\u593e\u5167\u7684\u6240\u6709\u7b26\u5408\u6a94\u6848. -tab2_Cb_foldersSelectorForRomsDesc=\u555f\u7528\u6b64\u8a2d\u5b9a\u5f8c,\u5c07\u8b8a\u66f4'\u904a\u6232\u63a7\u5236\u5668'\u5206\u9801\u4e0b\u65b9\u7684'\u9078\u64c7\u6a94\u6848'\u6309\u9215\u529f\u80fd: \u7531\u539f\u5148\u7684\u500b\u5225\u6a94\u6848\u9032\u884c\u9010\u4e00\u9078\u53d6\uff0c\u53d6\u4ee3\u70ba\u4e00\u6b21\u52a0\u5165\u6307\u5b9a\u8cc7\u6599\u593e\u5167\u7b26\u5408\u7684\u6240\u6709\u6a94\u6848. -windowTitleAddingFiles=\u6b63\u5728\u52a0\u5165\u4f47\u5217... -windowBodyFilesScanned=\u6a94\u6848\u5df2\u6383\u63cf:%25d\n \u5df2\u52a0\u5165: %25d -tab2_Lbl_AwooBlockTitle=Awoo Installer \u8207\u540c\u985e\u578b\u7a0b\u5f0f +btn_Select=\u9078\u64C7 +btn_InjectPayloader=\u6CE8\u5165payload +tabNXDT_Btn_Start=\u958B\u59CB! +tab2_Btn_InstallDrivers=\u4E0B\u8F09\u4E26\u5B89\u88DD\u9A45\u52D5\u7A0B\u5F0F +windowTitleDownloadDrivers=\u4E0B\u8F09\u4E26\u5B89\u88DD\u9A45\u52D5\u7A0B\u5F0F +windowBodyDownloadDrivers=\u6B63\u5728\u4E0B\u8F09\u9A45\u52D5\u7A0B\u5F0F (libusbK v3.0.7.0)... +btn_Cancel=\u53D6\u6D88 +btn_Close=\u95DC\u9589 +tab2_Cb_GlVersion=GoldLeaf\u7248\u672C +tab2_Cb_GLshowNspOnly=\u5728GoldLeaf\u5167\u50C5\u986F\u793A*.nsp\u6A94\u6848 +windowBodyPleaseStopOtherProcessFirst=\u5982\u8981\u57F7\u884C\u76EE\u524D\u7684\u64CD\u4F5C\u7A0B\u5E8F,\u8ACB\u5148\u505C\u6B62\u5176\u4ED6\u6B63\u5728\u8655\u7406\u7684\u7A0B\u5E8F. +tab2_Cb_foldersSelectorForRoms=\u9078\u64C7\u6A94\u6848\u6642\u7531\u539F\u5148\u500B\u5225\u9078\u53D6\u8B8A\u66F4\u70BA\u6307\u5B9A\u8CC7\u6599\u593E\u5167\u7684\u6240\u6709\u7B26\u5408\u6A94\u6848. +tab2_Cb_foldersSelectorForRomsDesc=\u555F\u7528\u6B64\u8A2D\u5B9A\u5F8C,\u5C07\u8B8A\u66F4'\u904A\u6232\u63A7\u5236\u5668'\u5206\u9801\u4E0B\u65B9\u7684'\u9078\u64C7\u6A94\u6848'\u6309\u9215\u529F\u80FD: \u7531\u539F\u5148\u7684\u500B\u5225\u6A94\u6848\u9032\u884C\u9010\u4E00\u9078\u53D6\uFF0C\u53D6\u4EE3\u70BA\u4E00\u6B21\u52A0\u5165\u6307\u5B9A\u8CC7\u6599\u593E\u5167\u7B26\u5408\u7684\u6240\u6709\u6A94\u6848. +windowTitleAddingFiles=\u6B63\u5728\u52A0\u5165\u4F47\u5217... +windowBodyFilesScanned=\u6A94\u6848\u5DF2\u6383\u63CF:%25d\n \u5DF2\u52A0\u5165: %25d +tab2_Lbl_AwooBlockTitle=Awoo Installer \u8207\u540C\u985E\u578B\u7A0B\u5F0F tabRcm_Lbl_Payload=Payload: -tabRcm_Lbl_FuseeGelee=Fus\u00e9e Gel\u00e9e RCM +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + From eefaa70f95c6dcf7c9db5d6177ff75e669e1689b Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 26 Jul 2022 20:16:01 +0300 Subject: [PATCH 057/134] fix yaml --- .drone.yml | 33 +++++++++++++++++---------------- .make_legacy | 2 ++ 2 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 .make_legacy diff --git a/.drone.yml b/.drone.yml index 9dce40f..db885a8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,31 +6,32 @@ steps: - name: test image: maven:3-jdk-11 commands: - - mvn -B -DskipTests clean package - - mvn test -B + - mvn -B -DskipTests clean package + - mvn test -B volumes: - - name: m2 - path: /root/.m2 + - name: m2 + path: /root/.m2 - name: archive-standard-artifact image: alpine:latest commands: - - mkdir -p /builds/ns-usbloader - - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + - mkdir -p /builds/ns-usbloader + - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: - - name: builds - path: /builds + - name: builds + path: /builds - - name: make-store-legacy-artifact + - name: emerge-legacy-artifact image: maven:3-jdk-11 commands: - - sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml - - sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-legacy-${project.version}-${maven.build.timestamp}<\/finalName>/g' pom.xml - - mvn -B -DskipTests clean package - - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + - . ./.make_legacy + - mvn -B -DskipTests clean package + - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: - - name: m2 - path: /root/.m2 + - name: m2 + path: /root/.m2 + - name: builds + path: /builds volumes: - name: m2 @@ -38,4 +39,4 @@ volumes: path: /home/docker/drone/files/m2 - name: builds host: - path: /home/www/builds \ No newline at end of file + path: /home/www/builds diff --git a/.make_legacy b/.make_legacy new file mode 100644 index 0000000..051c7f5 --- /dev/null +++ b/.make_legacy @@ -0,0 +1,2 @@ +sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml +sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-legacy-${project.version}-${maven.build.timestamp}<\/finalName>/g' pom.xml From bc2cf44a9761fe0d90a74f0d99287ca6501b240c Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 2 Aug 2022 21:46:36 +0300 Subject: [PATCH 058/134] Update build hacks --- .make_legacy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.make_legacy b/.make_legacy index 051c7f5..a7c3a6f 100644 --- a/.make_legacy +++ b/.make_legacy @@ -1,2 +1,2 @@ sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml -sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-legacy-${project.version}-${maven.build.timestamp}<\/finalName>/g' pom.xml +sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}<\/finalName>/g' pom.xml From d08993be171f22b9360fa9b90e74f4a9e5742462 Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Fri, 5 Aug 2022 22:36:58 +0900 Subject: [PATCH 059/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 8d11a70..c491ead 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -18,7 +18,7 @@ Dmitry Isaenko [developer.su] tab1_table_Lbl_Status=\uC0C1\uD0DC tab1_table_Lbl_FileName=\uD30C\uC77C \uC774\uB984 tab1_table_Lbl_Size=\uD06C\uAE30 -tab1_table_Lbl_Upload=\uC5C5\uB85C\uB4DC\uD569\uB2C8\uAE4C? +tab1_table_Lbl_Upload=\uC5C5\uB85C\uB4DC? tab1_table_contextMenu_Btn_BtnDelete=\uC0AD\uC81C tab1_table_contextMenu_Btn_DeleteAll=\uBAA8\uB450 \uC0AD\uC81C tab2_Lbl_HostIP=\uD638\uC2A4\uD2B8 IP From 38af160ae7abd41605df641dbffda0d7fa0464f0 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sat, 13 Aug 2022 17:07:17 +0300 Subject: [PATCH 060/134] small readme adjustments --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8f85571..ce06d7b 100644 --- a/README.md +++ b/README.md @@ -253,12 +253,17 @@ This is non-commercial project. Nevertheless, I'll be more than happy if you find a chance to make a donation for charity to people I trust: -* BTC → 1YoBdyiL4TTVsCWmJ93TkfU2a1s9UfJcY -* LTC → MEXnCLjwvaAZpaoJ1J4biF4DpoAx86gCkf +* BTC → bc1q67j4yjmt67mes0hv03fyydjejmw6ahw0v932su * ETH → 0x82Ab0ddE183C12cAa6eD61DF3671675C4bdC42fc -* DOGE → DFfVjsbcs9VfV9EQTSFF3xJVbjZSbmctP3 -* DOT → 13javzN4ixHPmBfR1oZHjAecydvWbEuqRtBWuxrZyKvkUrg3 -* USDT (TRC20) → THhhs883gH1AcvmNb2EVfhR7QNkWnoa1vN +* DOGE → D8o42b952yjEWZ5Ajq4ZtjGvx1kR7DyDHm +* DOT → 1511KQqm6Mme5Za4rtgsSdzCyuEzQ6CC1a6XLxQJXXW3GWpo +* LTC → LRFXTN4rTEiQ3RxzssMFC5S1WMRd1raN2Q +* LUNA → terra1n0sfmljpuu87h7lrn9nzxadfa3qxthlmvtq5lf +* TRX → TU3AyFkF12Jg1yApcXGz91R8xKvXV1EzkW +* ETC → 0x78A946fC9708c024298c9a4f8961A49B7a830d53 +* USDT (TRC20) → TU3AyFkF12Jg1yApcXGz91R8xKvXV1EzkW +* USDT (ERC20) → 0x82Ab0ddE183C12cAa6eD61DF3671675C4bdC42fc +* XRP → r91kfRiRsvnDp7evHNmXkrL3yAL7eakWk1 Thanks! From 182c5293b5ac30135798ea9296610e259f4eb412 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 22 Aug 2022 20:26:10 +0300 Subject: [PATCH 061/134] Add Swedish translation kindly prepared by @yeager --- README.md | 1 + src/main/resources/locale_sv_SE.properties | 81 ++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/resources/locale_sv_SE.properties diff --git a/README.md b/README.md index ce06d7b..10b60bf 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Czech by [Spenaat](https://github.com/spenaat) * Arabic by [eslamabdel](https://github.com/eslamabdel) * Romanian by [Călin Ilie](https://github.com/calini) +* Swedish by [Daniel Nylander](https://github.com/yeager) - (coming soon) ### System requirements diff --git a/src/main/resources/locale_sv_SE.properties b/src/main/resources/locale_sv_SE.properties new file mode 100644 index 0000000..03718c7 --- /dev/null +++ b/src/main/resources/locale_sv_SE.properties @@ -0,0 +1,81 @@ +btn_OpenFile=V\u00E4lj filer +btn_OpenFolders=V\u00E4lj mapp +btn_Upload=Skicka upp till NS +btn_OpenFolders_tooltip=V\u00E4lj en mapp att skanna.\nDenna mapp och alla dess undermappar kommer att skannas.\nAlla matchande filer kommer att l\u00E4ggas till i listan. +tab3_Txt_EnteredAsMsg1=Du har startat som: +tab3_Txt_EnteredAsMsg2=Du b\u00F6r vara root eller ha konfigurerat 'udev'-regler f\u00F6r denna anv\u00E4ndare f\u00F6r att undvika problem. +tab3_Txt_FilesToUploadTitle=Filer att skicka upp: +tab3_Txt_GreetingsMessage=V\u00E4lkommen till NS-USBloader +tab3_Txt_NoFolderOrFileSelected=Inga filer markerade: ingenting att skicka upp. +windowBodyConfirmExit=Data\u00F6verf\u00F6ring p\u00E5g\u00E5r och st\u00E4nger du detta program kommer den avbrytas.\nDet \u00E4r det v\u00E4rsta du kan g\u00F6ra nu.\nAvbryta processen och avsluta? +windowTitleConfirmExit=Nej, g\u00F6r inte detta! +btn_Stop=Avbryt +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=Status +tab1_table_Lbl_FileName=Filnamn +tab1_table_Lbl_Size=Storlek +tab1_table_Lbl_Upload=Skicka upp? +tab1_table_contextMenu_Btn_BtnDelete=Ta bort +tab1_table_contextMenu_Btn_DeleteAll=Ta bort alla +tab2_Lbl_HostIP=V\u00E4rdens IP +tab1_Lbl_NSIP=NS IP: +tab2_Cb_ValidateNSHostName=Validera alltid NS IP-inmatning. +windowBodyBadIp=\u00C4r du s\u00E4ker p\u00E5 att du har angivet NS IP-adress korrekt? +windowTitleBadIp=IP-adress f\u00F6r NS \u00E4r antagligen felaktig +tab2_Cb_ExpertMode=Expertl\u00E4ge (NET setup) +tab2_Lbl_HostPort=port +tab2_Cb_AutoDetectIp=Autodetektera IP +tab2_Cb_RandSelectPort=Slumpm\u00E4ssig port +tab2_Cb_DontServeRequests=Hantera inte beg\u00E4ran +tab2_Lbl_DontServeRequestsDesc=Om vald, kommer denna dator inte att svara p\u00E5 beg\u00E4ran av NSP-filer som kommer fr\u00E5n NS (\u00F6ver n\u00E4tet) och anv\u00E4nder definierade v\u00E4rdinst\u00E4llningar f\u00F6r att instruera Awoo Installer (eller kompatibla program) var den ska leta efter filer. +tab2_Lbl_HostExtra=extra +windowTitleErrorPort=Port inst\u00E4lld felaktigt! +windowBodyErrorPort=Port kan inte vara 0 eller h\u00F6gre \u00E4n 65535. +tab2_Cb_AutoCheckForUpdates=Automatisk uppdateringskontroll +windowTitleNewVersionAval=Ny version tillg\u00E4nglig +windowTitleNewVersionNOTAval=Inga nya versioner tillg\u00E4ngliga +windowTitleNewVersionUnknown=Kunde inte leta efter nya versioner +windowBodyNewVersionUnknown=N\u00E5gonting gick fel\nKanske inte kan n\u00E5 internet, eller GitHub \u00E4r nere +windowBodyNewVersionNOTAval=Du anv\u00E4nder den senaste versionen +tab2_Cb_AllowXciNszXcz=Till\u00E5t val av XCI / NSZ / XCZ-filer f\u00F6r Awoo +tab2_Lbl_AllowXciNszXczDesc=Anv\u00E4nds av program som har st\u00F6d f\u00F6r XCI/NSZ/XCZ och anv\u00E4nder Awoo (aka Adubbz/TinFoil) \u00F6verf\u00F6ringsprotokoll. \u00C4ndra inte om du \u00E4r os\u00E4ker. Aktivera f\u00F6r Awoo Installer. +tab2_Lbl_Language=Spr\u00E5k +windowBodyRestartToApplyLang=Starta om programmet f\u00F6r att verkst\u00E4lla \u00E4ndringar. +btn_OpenSplitFile=V\u00E4lj delad NSP +tab2_Lbl_ApplicationSettings=Huvudinst\u00E4llningar +tabSplMrg_Lbl_SplitNMergeTitle=Verktyg f\u00F6r att dela upp och sl\u00E5 ihop filer +tabSplMrg_RadioBtn_Split=Dela upp +tabSplMrg_RadioBtn_Merge=Sl\u00E5 ihop +tabSplMrg_Txt_File=Fil: +tabSplMrg_Txt_Folder=Dela upp fil (mapp): +tabSplMrg_Btn_SelectFile=V\u00E4lj fil +tabSplMrg_Btn_SelectFolder=V\u00E4lj mapp +tabSplMrg_Lbl_SaveToLocation=Spara till: +tabSplMrg_Btn_ChangeSaveToLocation=\u00C4ndra +tabSplMrg_Btn_Convert=Konvertera +windowTitleError=Fel +windowBodyPleaseFinishTransfersFirst=Kunde inte dela upp/sl\u00E5 ihop filer n\u00E4r programmets USB/n\u00E4tverksprocess \u00E4r aktiv. Avbryt f\u00F6rst aktiva \u00F6verf\u00F6ringar. +done_txt=F\u00E4rdig! +failure_txt=Misslyckades +btn_Select=V\u00E4lj +btn_InjectPayloader=Injicera payload +tabNXDT_Btn_Start=Starta! +tab2_Btn_InstallDrivers=H\u00E4mta och installera drivrutiner +windowTitleDownloadDrivers=H\u00E4mta och installera drivrutiner +windowBodyDownloadDrivers=H\u00E4mtar drivrutiner (libusbK v3.0.7.0)... +btn_Cancel=Avbryt +btn_Close=St\u00E4ng +tab2_Cb_GlVersion=GoldLeaf version +tab2_Cb_GLshowNspOnly=Visa endast *.nsp i GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Stoppa andra aktiva processer innan du forts\u00E4tter. +tab2_Cb_foldersSelectorForRoms=V\u00E4lj mapp med ROM-filer ist\u00E4llet f\u00F6r att v\u00E4lja enstaka ROM-filer. +tab2_Cb_foldersSelectorForRomsDesc=\u00C4ndrar 'V\u00E4lj filer'-knappens beteende p\u00E5 'Spel'-fliken: ist\u00E4llet f\u00F6r att v\u00E4lja enstaka ROM-filer s\u00E5 kan du v\u00E4lja mapp f\u00F6r att l\u00E4gga till alla filer som st\u00F6ds p\u00E5 en g\u00E5ng. +windowTitleAddingFiles=Letar efter filer... +windowBodyFilesScanned=Filer genoms\u00F6kta: %d\nKommer att l\u00E4ggas till: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer och kompatibla +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM \ No newline at end of file From 51a3b1e7a13240d4a5f7530221e48922d86bc3b8 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 25 Sep 2022 00:28:58 +0300 Subject: [PATCH 062/134] Update pom --- pom.xml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 6d40b70..41d434a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>6.0</version> + <version>6.1</version> <url>https://redrise.ru</url> <description> @@ -61,7 +61,7 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>17</version> + <version>18.0.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -155,19 +155,19 @@ <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> - <version>5.8.2</version> + <version>5.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> - <version>5.8.2</version> + <version>5.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> - <version>5.8.2</version> + <version>5.9.0</version> <scope>test</scope> </dependency> </dependencies> @@ -207,6 +207,7 @@ </plugin> <!-- Don't generate default JAR without dependencies --> <plugin> + <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <executions> From c9dbb37c14affc7ac5332d288a2aa79787784d2d Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Thu, 6 Oct 2022 17:06:49 +0900 Subject: [PATCH 063/134] Create locale_ja_JP.properties --- src/main/resources/locale_ja_JP.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/locale_ja_JP.properties diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/locale_ja_JP.properties @@ -0,0 +1 @@ + From 2edea43d6331a8604e66af69fbd0b3191981ebb8 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Thu, 6 Oct 2022 17:13:01 +0900 Subject: [PATCH 064/134] Create locale_ja_ryu.properties --- src/main/resources/locale_ja_ryu.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/locale_ja_ryu.properties diff --git a/src/main/resources/locale_ja_ryu.properties b/src/main/resources/locale_ja_ryu.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/locale_ja_ryu.properties @@ -0,0 +1 @@ + From 6244fd974a98bc642dad07b557f18b6ca31c37a0 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Fri, 7 Oct 2022 01:35:21 +0900 Subject: [PATCH 065/134] Update locale_ja_JP.properties --- src/main/resources/locale_ja_JP.properties | 82 +++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties index 8b13789..c14a3d4 100644 --- a/src/main/resources/locale_ja_JP.properties +++ b/src/main/resources/locale_ja_JP.properties @@ -1 +1,81 @@ - +btn_OpenFile=ファイルを選択 +btn_OpenFolders=フォルダーを選択 +btn_Upload=NSにアップロード +btn_OpenFolders_tooltip=スキャンするフォルダを選択してください。\nこのフォルダとそのすべてのサブフォルダがスキャンされます。\n一致するすべてのファイルがリストに追加されます。 +tab3_Txt_EnteredAsMsg1=あなたは次のように入力されました: +tab3_Txt_EnteredAsMsg2=問題を回避するには、root になるか、このユーザーに「udev」ルールを設定する必要があります。 +tab3_Txt_FilesToUploadTitle=アップロードするファイル: +tab3_Txt_GreetingsMessage=NS-USBloader へようこそ +tab3_Txt_NoFolderOrFileSelected=ファイルが選択されていません: アップロードするものがありません。 +windowBodyConfirmExit=データ転送が進行中です。このアプリケーションを閉じると中断されます。\n最悪の事態です。\n処理を中断して終了しますか? +windowTitleConfirmExit=いいえ、これをしないでください ! +btn_Stop=割り込み +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=ステータス +tab1_table_Lbl_FileName=ファイル名 +tab1_table_Lbl_Size=サイズ +tab1_table_Lbl_Upload=アップロード? +tab1_table_contextMenu_Btn_BtnDelete=削除する +tab1_table_contextMenu_Btn_DeleteAll=すべて削除する +tab2_Lbl_HostIP=ホスト IP +tab1_Lbl_NSIP=NS IP: +tab2_Cb_ValidateNSHostName=NS IP 入力を常に検証します。 +windowBodyBadIp=NS IP アドレスを正しく入力しましたか? +windowTitleBadIp=NSのIPアドレスが間違っている可能性があります +tab2_Cb_ExpertMode=エキスパートモード (NET セットアップ) +tab2_Lbl_HostPort=ポート +tab2_Cb_AutoDetectIp=IPの自動検出 +tab2_Cb_RandSelectPort=ランダムにポートを取得 +tab2_Cb_DontServeRequests=リクエストを処理しない +tab2_Lbl_DontServeRequestsDesc=選択すると、このコンピュータは NS (ネット経由) からの NSP ファイル要求に応答せず、定義されたホスト設定を使用して Awoo インストーラ (または互換アプリケーション) にファイルを探す場所を伝えます。 +tab2_Lbl_HostExtra=エキストラ +windowTitleErrorPort=ポートが正しく設定されていません! +windowBodyErrorPort=ポートを 0 または 65535 より大きくすることはできません。 +tab2_Cb_AutoCheckForUpdates=アップデートの自動チェック +windowTitleNewVersionAval=新しいバージョンが利用可能です +windowTitleNewVersionNOTAval=新しいバージョンはありません +windowTitleNewVersionUnknown=新しいバージョンを確認できません +windowBodyNewVersionUnknown=問題が発生しました\nインターネットが利用できないか、GitHubがダウンしている可能性があります +windowBodyNewVersionNOTAval=最新バージョンを使用しています +tab2_Cb_AllowXciNszXcz=Awoo の XCI / NSZ / XCZ ファイルの選択を許可 +tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ をサポートし、Awoo (別名 Adubbz/TinFoil) 転送プロトコルを利用するアプリケーションで使用されます。 よくわからない場合は変更しないでください。 Awoo インストーラーを有効にします。 +tab2_Lbl_Language=言語 +windowBodyRestartToApplyLang=変更を適用するにはアプリケーションを再起動してください。 +btn_OpenSplitFile=スプリットNSPを選択 +tab2_Lbl_ApplicationSettings=主な設定 +tabSplMrg_Lbl_SplitNMergeTitle=ファイルの分割と結合ツール +tabSplMrg_RadioBtn_Split=ファイルの分割と結合ツール +tabSplMrg_RadioBtn_Merge=マージ +tabSplMrg_Txt_File=ファイル: +tabSplMrg_Txt_Folder=分割ファイル (フォルダー): +tabSplMrg_Btn_SelectFile=ファイルを選ぶ +tabSplMrg_Btn_SelectFolder=フォルダーを選択 +tabSplMrg_Lbl_SaveToLocation=に保存: +tabSplMrg_Btn_ChangeSaveToLocation=変化する +tabSplMrg_Btn_Convert=Convert +windowTitleError=Error +windowBodyPleaseFinishTransfersFirst=Unable to split/merge files when application USB/Network process active. Please interrupt active transfers first. +done_txt=Done! +failure_txt=Failed +btn_Select=Select +btn_InjectPayloader=Inject payload +tabNXDT_Btn_Start=Start! +tab2_Btn_InstallDrivers=Download and install drivers +windowTitleDownloadDrivers=Download and install drivers +windowBodyDownloadDrivers=Downloading drivers (libusbK v3.0.7.0)... +btn_Cancel=Cancel +btn_Close=Close +tab2_Cb_GlVersion=GoldLeaf version +tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. +windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. +tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. +tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. +windowTitleAddingFiles=Searching for files... +windowBodyFilesScanned=Files scanned: %d\nWould be added: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From cc0912837331ff1651374e7cba8fa0544afaa930 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Sat, 8 Oct 2022 01:02:16 +0900 Subject: [PATCH 066/134] Update locale_ja_JP.properties --- src/main/resources/locale_ja_JP.properties | 44 +++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties index c14a3d4..efda9c7 100644 --- a/src/main/resources/locale_ja_JP.properties +++ b/src/main/resources/locale_ja_JP.properties @@ -56,26 +56,26 @@ tabSplMrg_Btn_SelectFile=ファイルを選ぶ tabSplMrg_Btn_SelectFolder=フォルダーを選択 tabSplMrg_Lbl_SaveToLocation=に保存: tabSplMrg_Btn_ChangeSaveToLocation=変化する -tabSplMrg_Btn_Convert=Convert -windowTitleError=Error -windowBodyPleaseFinishTransfersFirst=Unable to split/merge files when application USB/Network process active. Please interrupt active transfers first. -done_txt=Done! -failure_txt=Failed -btn_Select=Select -btn_InjectPayloader=Inject payload -tabNXDT_Btn_Start=Start! -tab2_Btn_InstallDrivers=Download and install drivers -windowTitleDownloadDrivers=Download and install drivers -windowBodyDownloadDrivers=Downloading drivers (libusbK v3.0.7.0)... -btn_Cancel=Cancel -btn_Close=Close -tab2_Cb_GlVersion=GoldLeaf version -tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. -tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. -windowTitleAddingFiles=Searching for files... -windowBodyFilesScanned=Files scanned: %d\nWould be added: %d -tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible -tabRcm_Lbl_Payload=Payload: +tabSplMrg_Btn_Convert=変換 +windowTitleError=エラー +windowBodyPleaseFinishTransfersFirst=アプリケーション USB/ネットワーク プロセスがアクティブな場合、ファイルを分割/マージできません。 最初にアクティブな転送を中断してください。 +done_txt=終わり! +failure_txt=失敗 +btn_Select=選択する +btn_InjectPayloader=ペイロードを挿入する +tabNXDT_Btn_Start=始める! +tab2_Btn_InstallDrivers=ドライバーのダウンロードとインストール +windowTitleDownloadDrivers=ドライバーのダウンロードとインストール +windowBodyDownloadDrivers=ドライバーをダウンロードしています (libusbK v3.0.7.0)... +btn_Cancel=キャンセル +btn_Close=閉じる +tab2_Cb_GlVersion=Gold Leafバージョン +tab2_Cb_GLshowNspOnly=GoldLeaf で *.nsp のみを表示します。 +windowBodyPleaseStopOtherProcessFirst=続行する前に、他のアクティブなプロセスを停止してください。 +tab2_Cb_foldersSelectorForRoms=ROM を個別に選択するのではなく、ROM ファイルのあるフォルダを選択します。 +tab2_Cb_foldersSelectorForRomsDesc=[ゲーム] タブの [ファイルを選択] ボタンの動作を変更: ROM ファイルを 1 つずつ選択する代わりに、フォルダーを選択して、サポートされているすべてのファイルを一度に追加できます。 +windowTitleAddingFiles=ファイルを検索しています... +windowBodyFilesScanned=スキャンされたファイル: %d\n追加予定: %d +tab2_Lbl_AwooBlockTitle=Awooインストーラーと互換性 +tabRcm_Lbl_Payload=ペイロード: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From b75421bc9e9927dfcc97d85b84c525439c201785 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Sat, 8 Oct 2022 01:35:32 +0900 Subject: [PATCH 067/134] Update locale_ja_ryu.properties --- src/main/resources/locale_ja_ryu.properties | 82 ++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locale_ja_ryu.properties b/src/main/resources/locale_ja_ryu.properties index 8b13789..6f78f20 100644 --- a/src/main/resources/locale_ja_ryu.properties +++ b/src/main/resources/locale_ja_ryu.properties @@ -1 +1,81 @@ - +btn_OpenFile=ファイル選択 +btn_OpenFolders=フォルダー選択 +btn_Upload=NSんかいアップロード +btn_OpenFolders_tooltip=スキャンするフォルダ選択しくぃみそーれー。\nくぬフォルダとぅうぬまじりぬサブフォルダぬスキャンさりやびーん。\n一致するまじりぬファイルぬリストんかいいりしーらりやびーん。 +tab3_Txt_EnteredAsMsg1=うんじょー次ぬぐとぅ入力さりやびたん: +tab3_Txt_EnteredAsMsg2=問題回避すんがー、root ないが、くぬユーザーんかい「udev」ルール設定するいりゆーぬあいびーん。 +tab3_Txt_FilesToUploadTitle=アップロードするファイル: +tab3_Txt_GreetingsMessage=NS-USBloader んかいめんそーれー +tab3_Txt_NoFolderOrFileSelected=ファイルぬ選択さりやびらん: アップロードすしがあいびらん。 +windowBodyConfirmExit=データ転送ぬ進行中やいびーん。くぬアプリケーションくーいんでぃ中断さりやびーん。\n最悪ぬ事態やいびーん。\n処理中断し終了さびーが? +windowTitleConfirmExit=うぅーうぅー、くりさんぐーとぅーくぃみそーれー ! +btn_Stop=割り込み +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=ステータス +tab1_table_Lbl_FileName=ファイル名 +tab1_table_Lbl_Size=サイズ +tab1_table_Lbl_Upload=アップロード? +tab1_table_contextMenu_Btn_BtnDelete=削除すん +tab1_table_contextMenu_Btn_DeleteAll=まじり削除すん +tab2_Lbl_HostIP=ホスト IP +tab1_Lbl_NSIP=NS IP: +tab2_Cb_ValidateNSHostName=NS IP 入力常に検証さびーん。 +windowBodyBadIp=NS IP アドレス正しく入力さびたが? +windowTitleBadIp=NSぬIPアドレスぬばっぺーとーる可能性があいびーん +tab2_Cb_ExpertMode=エキスパートモード (NET セットアップ) +tab2_Lbl_HostPort=ポート +tab2_Cb_AutoDetectIp=IPぬ自動検出 +tab2_Cb_RandSelectPort=ランダムんかいポート取得 +tab2_Cb_DontServeRequests=リクエスト処理さん +tab2_Lbl_DontServeRequestsDesc=選択しーねー、くぬコンピューター NS (ネット経由) からぬ NSP ファイル要求んかい応答さじ、定義さったるホスト設定使用し Awoo インストーラ (あらんでぃ互換アプリケーション) んかいファイルかめーいるばすちてーやびーん。 +tab2_Lbl_HostExtra=エキストラ +windowTitleErrorPort=ポートぬ正しく設定さりやびらん! +windowBodyErrorPort=ポート 0 あらんでぃ 65535 ゆりまぎくすしぇーなやびらん。 +tab2_Cb_AutoCheckForUpdates=アップデートぬ自動チェック +windowTitleNewVersionAval=みーさるバージョンぬ利用可能やいびーん +windowTitleNewVersionNOTAval=みーさるバージョンーあいびらん +windowTitleNewVersionUnknown=みーさるバージョン確認なやびらん +windowBodyNewVersionUnknown=問題が発生さびたん\nインターネットぬ利用ならんが、GitHubがダウンそーる可能性があいびーん +windowBodyNewVersionNOTAval=最新バージョン使用そーいびーん +tab2_Cb_AllowXciNszXcz=Awoo ぬ XCI / NSZ / XCZ ファイルぬ選択許可 +tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ サポートしー、Awoo (別名 Adubbz/TinFoil) 転送プロトコル利用するアプリケーションっし使用さりやびーん。 ゆーわからんばーや変更さんぐーとぅーくぃみそーれー。 Awoo インストーラー有効なさびーん。 +tab2_Lbl_Language=言語 +windowBodyRestartToApplyLang=変更適用すんがーアプリケーション再起動しくぃみそーれー。 +btn_OpenSplitFile=スプリットNSP選択 +tab2_Lbl_ApplicationSettings=主な設定 +tabSplMrg_Lbl_SplitNMergeTitle=ファイルぬ分割とぅ結合ツール +tabSplMrg_RadioBtn_Split=ファイルの分割と結合ツール +tabSplMrg_RadioBtn_Merge=マージ +tabSplMrg_Txt_File=ファイル: +tabSplMrg_Txt_Folder=分割ファイル (フォルダー): +tabSplMrg_Btn_SelectFile=ファイルいらぶん +tabSplMrg_Btn_SelectFolder=フォルダー選択 +tabSplMrg_Lbl_SaveToLocation=んかい保存: +tabSplMrg_Btn_ChangeSaveToLocation=変わいん +tabSplMrg_Btn_Convert=変換 +windowTitleError=エラー +windowBodyPleaseFinishTransfersFirst=アプリケーション USB/ネットワーク プロセスぬアクティブなばー、ファイル分割/マージなやびらん。 最初んかいアクティブな転送中断しくぃみそーれー。 +done_txt=うわい! +failure_txt=失敗 +btn_Select=選択すん +btn_InjectPayloader=ペイロード挿入すん +tabNXDT_Btn_Start=始みーん! +tab2_Btn_InstallDrivers=ドライバーぬダウンロードとぅインストール +windowTitleDownloadDrivers=ドライバーぬダウンロードとぅインストール +windowBodyDownloadDrivers=ドライバーダウンロードそーいびーん (libusbK v3.0.7.0)... +btn_Cancel=キャンセル +btn_Close=くーいん +tab2_Cb_GlVersion=Gold Leafバージョン +tab2_Cb_GLshowNspOnly=GoldLeaf でぃ *.nsp ぬみ表示さびーん。 +windowBodyPleaseStopOtherProcessFirst=続行する前に、他ぬアクティブなプロセス停止しくぃみそーれー。 +tab2_Cb_foldersSelectorForRoms=ROM 個別に選択するぬでーなく、ROM ファイルぬあるフォルダ選択さびーん。 +tab2_Cb_foldersSelectorForRomsDesc=[ゲーム] タブぬ [ファイル選択] ボタンぬ動作変更: ROM ファイル 1 ちなー選択する代わりんかい、フォルダー選択し、サポートさりとーるまじりぬファイル一度んかい追加なやびーん。 +windowTitleAddingFiles=ファイル検索そーいびーん... +windowBodyFilesScanned=スキャンさったるファイル: %d\n追加予定: %d +tab2_Lbl_AwooBlockTitle=Awooインストーラーとぅ互換性 +tabRcm_Lbl_Payload=ペイロード: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 89dd6fdbe0bcd4a2861ffe043600e33e3e20d3a4 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Sat, 8 Oct 2022 02:53:51 +0900 Subject: [PATCH 068/134] Update locale_ja_JP.properties --- src/main/resources/locale_ja_JP.properties | 148 ++++++++++----------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties index efda9c7..5f617b0 100644 --- a/src/main/resources/locale_ja_JP.properties +++ b/src/main/resources/locale_ja_JP.properties @@ -1,81 +1,81 @@ -btn_OpenFile=ファイルを選択 -btn_OpenFolders=フォルダーを選択 -btn_Upload=NSにアップロード -btn_OpenFolders_tooltip=スキャンするフォルダを選択してください。\nこのフォルダとそのすべてのサブフォルダがスキャンされます。\n一致するすべてのファイルがリストに追加されます。 -tab3_Txt_EnteredAsMsg1=あなたは次のように入力されました: -tab3_Txt_EnteredAsMsg2=問題を回避するには、root になるか、このユーザーに「udev」ルールを設定する必要があります。 -tab3_Txt_FilesToUploadTitle=アップロードするファイル: -tab3_Txt_GreetingsMessage=NS-USBloader へようこそ -tab3_Txt_NoFolderOrFileSelected=ファイルが選択されていません: アップロードするものがありません。 -windowBodyConfirmExit=データ転送が進行中です。このアプリケーションを閉じると中断されます。\n最悪の事態です。\n処理を中断して終了しますか? -windowTitleConfirmExit=いいえ、これをしないでください ! -btn_Stop=割り込み +btn_OpenFile=\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E +btn_OpenFolders=\u30D5\u30A9\u30EB\u30C0\u30FC\u3092\u9078\u629E +btn_Upload=NS\u306B\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9 +btn_OpenFolders_tooltip=\u30B9\u30AD\u30E3\u30F3\u3059\u308B\u30D5\u30A9\u30EB\u30C0\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u3068\u305D\u306E\u3059\u3079\u3066\u306E\u30B5\u30D6\u30D5\u30A9\u30EB\u30C0\u304C\u30B9\u30AD\u30E3\u30F3\u3055\u308C\u307E\u3059\u3002\n\u4E00\u81F4\u3059\u308B\u3059\u3079\u3066\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u30EA\u30B9\u30C8\u306B\u8FFD\u52A0\u3055\u308C\u307E\u3059\u3002 +tab3_Txt_EnteredAsMsg1=\u3042\u306A\u305F\u306F\u6B21\u306E\u3088\u3046\u306B\u5165\u529B\u3055\u308C\u307E\u3057\u305F: +tab3_Txt_EnteredAsMsg2=\u554F\u984C\u3092\u56DE\u907F\u3059\u308B\u306B\u306F\u3001root \u306B\u306A\u308B\u304B\u3001\u3053\u306E\u30E6\u30FC\u30B6\u30FC\u306B\u300Cudev\u300D\u30EB\u30FC\u30EB\u3092\u8A2D\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002 +tab3_Txt_FilesToUploadTitle=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3059\u308B\u30D5\u30A1\u30A4\u30EB: +tab3_Txt_GreetingsMessage=NS-USBloader\u3078\u3088\u3046\u3053\u305D +tab3_Txt_NoFolderOrFileSelected=\u30D5\u30A1\u30A4\u30EB\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u307E\u305B\u3093: \u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093\u3002 +windowBodyConfirmExit=\u30C7\u30FC\u30BF\u8EE2\u9001\u304C\u9032\u884C\u4E2D\u3067\u3059\u3002\u3053\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u9589\u3058\u308B\u3068\u4E2D\u65AD\u3055\u308C\u307E\u3059\u3002\n\u6700\u60AA\u306E\u4E8B\u614B\u3067\u3059\u3002\n\u51E6\u7406\u3092\u4E2D\u65AD\u3057\u3066\u7D42\u4E86\u3057\u307E\u3059\u304B? +windowTitleConfirmExit=\u3044\u3044\u3048\u3001\u3053\u308C\u3092\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\uFF01 +btn_Stop=\u5272\u308A\u8FBC\u307F tab3_Txt_GreetingsMessage2=--\n\ Source: https://git.redrise.ru/desu/ns-usbloader\n\ Mirror: https://github.com/developersu/ns-usbloader/\n\ Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=ステータス -tab1_table_Lbl_FileName=ファイル名 -tab1_table_Lbl_Size=サイズ -tab1_table_Lbl_Upload=アップロード? -tab1_table_contextMenu_Btn_BtnDelete=削除する -tab1_table_contextMenu_Btn_DeleteAll=すべて削除する -tab2_Lbl_HostIP=ホスト IP +tab1_table_Lbl_Status=\u30B9\u30C6\u30FC\u30BF\u30B9 +tab1_table_Lbl_FileName=\u30D5\u30A1\u30A4\u30EB\u540D +tab1_table_Lbl_Size=\u30B5\u30A4\u30BA +tab1_table_Lbl_Upload=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\uFF1F +tab1_table_contextMenu_Btn_BtnDelete=\u524A\u9664\u3059\u308B +tab1_table_contextMenu_Btn_DeleteAll=\u3059\u3079\u3066\u524A\u9664\u3059\u308B +tab2_Lbl_HostIP=\u30DB\u30B9\u30C8 IP tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=NS IP 入力を常に検証します。 -windowBodyBadIp=NS IP アドレスを正しく入力しましたか? -windowTitleBadIp=NSのIPアドレスが間違っている可能性があります -tab2_Cb_ExpertMode=エキスパートモード (NET セットアップ) -tab2_Lbl_HostPort=ポート -tab2_Cb_AutoDetectIp=IPの自動検出 -tab2_Cb_RandSelectPort=ランダムにポートを取得 -tab2_Cb_DontServeRequests=リクエストを処理しない -tab2_Lbl_DontServeRequestsDesc=選択すると、このコンピュータは NS (ネット経由) からの NSP ファイル要求に応答せず、定義されたホスト設定を使用して Awoo インストーラ (または互換アプリケーション) にファイルを探す場所を伝えます。 -tab2_Lbl_HostExtra=エキストラ -windowTitleErrorPort=ポートが正しく設定されていません! -windowBodyErrorPort=ポートを 0 または 65535 より大きくすることはできません。 -tab2_Cb_AutoCheckForUpdates=アップデートの自動チェック -windowTitleNewVersionAval=新しいバージョンが利用可能です -windowTitleNewVersionNOTAval=新しいバージョンはありません -windowTitleNewVersionUnknown=新しいバージョンを確認できません -windowBodyNewVersionUnknown=問題が発生しました\nインターネットが利用できないか、GitHubがダウンしている可能性があります -windowBodyNewVersionNOTAval=最新バージョンを使用しています -tab2_Cb_AllowXciNszXcz=Awoo の XCI / NSZ / XCZ ファイルの選択を許可 -tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ をサポートし、Awoo (別名 Adubbz/TinFoil) 転送プロトコルを利用するアプリケーションで使用されます。 よくわからない場合は変更しないでください。 Awoo インストーラーを有効にします。 -tab2_Lbl_Language=言語 -windowBodyRestartToApplyLang=変更を適用するにはアプリケーションを再起動してください。 -btn_OpenSplitFile=スプリットNSPを選択 -tab2_Lbl_ApplicationSettings=主な設定 -tabSplMrg_Lbl_SplitNMergeTitle=ファイルの分割と結合ツール -tabSplMrg_RadioBtn_Split=ファイルの分割と結合ツール -tabSplMrg_RadioBtn_Merge=マージ -tabSplMrg_Txt_File=ファイル: -tabSplMrg_Txt_Folder=分割ファイル (フォルダー): -tabSplMrg_Btn_SelectFile=ファイルを選ぶ -tabSplMrg_Btn_SelectFolder=フォルダーを選択 -tabSplMrg_Lbl_SaveToLocation=に保存: -tabSplMrg_Btn_ChangeSaveToLocation=変化する -tabSplMrg_Btn_Convert=変換 -windowTitleError=エラー -windowBodyPleaseFinishTransfersFirst=アプリケーション USB/ネットワーク プロセスがアクティブな場合、ファイルを分割/マージできません。 最初にアクティブな転送を中断してください。 -done_txt=終わり! -failure_txt=失敗 -btn_Select=選択する -btn_InjectPayloader=ペイロードを挿入する -tabNXDT_Btn_Start=始める! -tab2_Btn_InstallDrivers=ドライバーのダウンロードとインストール -windowTitleDownloadDrivers=ドライバーのダウンロードとインストール -windowBodyDownloadDrivers=ドライバーをダウンロードしています (libusbK v3.0.7.0)... -btn_Cancel=キャンセル -btn_Close=閉じる -tab2_Cb_GlVersion=Gold Leafバージョン -tab2_Cb_GLshowNspOnly=GoldLeaf で *.nsp のみを表示します。 -windowBodyPleaseStopOtherProcessFirst=続行する前に、他のアクティブなプロセスを停止してください。 -tab2_Cb_foldersSelectorForRoms=ROM を個別に選択するのではなく、ROM ファイルのあるフォルダを選択します。 -tab2_Cb_foldersSelectorForRomsDesc=[ゲーム] タブの [ファイルを選択] ボタンの動作を変更: ROM ファイルを 1 つずつ選択する代わりに、フォルダーを選択して、サポートされているすべてのファイルを一度に追加できます。 -windowTitleAddingFiles=ファイルを検索しています... -windowBodyFilesScanned=スキャンされたファイル: %d\n追加予定: %d -tab2_Lbl_AwooBlockTitle=Awooインストーラーと互換性 -tabRcm_Lbl_Payload=ペイロード: +tab2_Cb_ValidateNSHostName=NS IP \u5165\u529B\u3092\u5E38\u306B\u691C\u8A3C\u3057\u307E\u3059\u3002 +windowBodyBadIp=NS IP \u30A2\u30C9\u30EC\u30B9\u3092\u6B63\u3057\u304F\u5165\u529B\u3057\u307E\u3057\u305F\u304B? +windowTitleBadIp=NS\u306EIP\u30A2\u30C9\u30EC\u30B9\u304C\u9593\u9055\u3063\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059 +tab2_Cb_ExpertMode=\u30A8\u30AD\u30B9\u30D1\u30FC\u30C8\u30E2\u30FC\u30C9 (NET \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7) +tab2_Lbl_HostPort=\u30DD\u30FC\u30C8 +tab2_Cb_AutoDetectIp=IP\u306E\u81EA\u52D5\u691C\u51FA +tab2_Cb_RandSelectPort=\u30E9\u30F3\u30C0\u30E0\u306B\u30DD\u30FC\u30C8\u3092\u53D6\u5F97 +tab2_Cb_DontServeRequests=\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u51E6\u7406\u3057\u306A\u3044 +tab2_Lbl_DontServeRequestsDesc=\u9078\u629E\u3059\u308B\u3068\u3001\u3053\u306E\u30B3\u30F3\u30D4\u30E5\u30FC\u30BF\u306F NS (\u30CD\u30C3\u30C8\u7D4C\u7531) \u304B\u3089\u306E NSP \u30D5\u30A1\u30A4\u30EB\u8981\u6C42\u306B\u5FDC\u7B54\u305B\u305A\u3001\u5B9A\u7FA9\u3055\u308C\u305F\u30DB\u30B9\u30C8\u8A2D\u5B9A\u3092\u4F7F\u7528\u3057\u3066 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9 (\u307E\u305F\u306F\u4E92\u63DB\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3) \u306B\u30D5\u30A1\u30A4\u30EB\u3092\u63A2\u3059\u5834\u6240\u3092\u4F1D\u3048\u307E\u3059\u3002 +tab2_Lbl_HostExtra=\u30A8\u30AD\u30B9\u30C8\u30E9 +windowTitleErrorPort=\u30DD\u30FC\u30C8\u304C\u6B63\u3057\u304F\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093! +windowBodyErrorPort=\u30DD\u30FC\u30C8\u3092 0 \u307E\u305F\u306F 65535 \u3088\u308A\u5927\u304D\u304F\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002 +tab2_Cb_AutoCheckForUpdates=\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u306E\u81EA\u52D5\u30C1\u30A7\u30C3\u30AF +windowTitleNewVersionAval=\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3\u304C\u5229\u7528\u53EF\u80FD\u3067\u3059 +windowTitleNewVersionNOTAval=\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093 +windowTitleNewVersionUnknown=\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093 +windowBodyNewVersionUnknown=\u554F\u984C\u304C\u767A\u751F\u3057\u307E\u3057\u305F\n\u30A4\u30F3\u30BF\u30FC\u30CD\u30C3\u30C8\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u3001GitHub\u304C\u30C0\u30A6\u30F3\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059 +windowBodyNewVersionNOTAval=\u6700\u65B0\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059 +tab2_Cb_AllowXciNszXcz=Awoo \u306E XCI / NSZ / XCZ \u30D5\u30A1\u30A4\u30EB\u306E\u9078\u629E\u3092\u8A31\u53EF +tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ \u3092\u30B5\u30DD\u30FC\u30C8\u3057\u3001Awoo (\u5225\u540D Adubbz/TinFoil) \u8EE2\u9001\u30D7\u30ED\u30C8\u30B3\u30EB\u3092\u5229\u7528\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3067\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 \u3088\u304F\u308F\u304B\u3089\u306A\u3044\u5834\u5408\u306F\u5909\u66F4\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3092\u6709\u52B9\u306B\u3057\u307E\u3059\u3002 +tab2_Lbl_Language=\u8A00\u8A9E +windowBodyRestartToApplyLang=\u5909\u66F4\u3092\u9069\u7528\u3059\u308B\u306B\u306F\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u518D\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8NSP\u3092\u9078\u629E +tab2_Lbl_ApplicationSettings=\u4E3B\u306A\u8A2D\u5B9A +tabSplMrg_Lbl_SplitNMergeTitle=\u30D5\u30A1\u30A4\u30EB\u306E\u5206\u5272\u3068\u7D50\u5408\u30C4\u30FC\u30EB +tabSplMrg_RadioBtn_Split=\u30D5\u30A1\u30A4\u30EB\u306E\u5206\u5272\u3068\u7D50\u5408\u30C4\u30FC\u30EB +tabSplMrg_RadioBtn_Merge=\u30DE\u30FC\u30B8 +tabSplMrg_Txt_File=\u30D5\u30A1\u30A4\u30EB\uFF1A +tabSplMrg_Txt_Folder=\u5206\u5272\u30D5\u30A1\u30A4\u30EB (\u30D5\u30A9\u30EB\u30C0\u30FC): +tabSplMrg_Btn_SelectFile=\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u3076 +tabSplMrg_Btn_SelectFolder=\u30D5\u30A9\u30EB\u30C0\u30FC\u3092\u9078\u629E +tabSplMrg_Lbl_SaveToLocation=\u306B\u4FDD\u5B58\uFF1A +tabSplMrg_Btn_ChangeSaveToLocation=\u5909\u5316\u3059\u308B +tabSplMrg_Btn_Convert=\u5909\u63DB +windowTitleError=\u30A8\u30E9\u30FC +windowBodyPleaseFinishTransfersFirst=\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3 USB/\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF \u30D7\u30ED\u30BB\u30B9\u304C\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u5834\u5408\u3001\u30D5\u30A1\u30A4\u30EB\u3092\u5206\u5272/\u30DE\u30FC\u30B8\u3067\u304D\u307E\u305B\u3093\u3002 \u6700\u521D\u306B\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u8EE2\u9001\u3092\u4E2D\u65AD\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +done_txt=\u7D42\u308F\u308A\uFF01 +failure_txt=\u5931\u6557 +btn_Select=\u9078\u629E\u3059\u308B +btn_InjectPayloader=\u30DA\u30A4\u30ED\u30FC\u30C9\u3092\u633F\u5165\u3059\u308B +tabNXDT_Btn_Start=\u59CB\u3081\u308B\uFF01 +tab2_Btn_InstallDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3068\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB +windowTitleDownloadDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3068\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB +windowBodyDownloadDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u3066\u3044\u307E\u3059 (libusbK v3.0.7.0)... +btn_Cancel=\u30AD\u30E3\u30F3\u30BB\u30EB +btn_Close=\u9589\u3058\u308B +tab2_Cb_GlVersion=GoldLeaf\u30D0\u30FC\u30B8\u30E7\u30F3 +tab2_Cb_GLshowNspOnly=GoldLeaf\u3067 *.nsp \u306E\u307F\u3092\u8868\u793A\u3057\u307E\u3059\u3002 +windowBodyPleaseStopOtherProcessFirst=\u7D9A\u884C\u3059\u308B\u524D\u306B\u3001\u4ED6\u306E\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u30D7\u30ED\u30BB\u30B9\u3092\u505C\u6B62\u3057\u3066\u304F\u3060\u3055\u3044\u3002 +tab2_Cb_foldersSelectorForRoms=ROM \u3092\u500B\u5225\u306B\u9078\u629E\u3059\u308B\u306E\u3067\u306F\u306A\u304F\u3001ROM \u30D5\u30A1\u30A4\u30EB\u306E\u3042\u308B\u30D5\u30A9\u30EB\u30C0\u3092\u9078\u629E\u3057\u307E\u3059\u3002 +tab2_Cb_foldersSelectorForRomsDesc=[\u30B2\u30FC\u30E0] \u30BF\u30D6\u306E [\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E] \u30DC\u30BF\u30F3\u306E\u52D5\u4F5C\u3092\u5909\u66F4: ROM \u30D5\u30A1\u30A4\u30EB\u3092 1 \u3064\u305A\u3064\u9078\u629E\u3059\u308B\u4EE3\u308F\u308A\u306B\u3001\u30D5\u30A9\u30EB\u30C0\u30FC\u3092\u9078\u629E\u3057\u3066\u3001\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B\u3059\u3079\u3066\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u4E00\u5EA6\u306B\u8FFD\u52A0\u3067\u304D\u307E\u3059\u3002 +windowTitleAddingFiles=\u30D5\u30A1\u30A4\u30EB\u3092\u691C\u7D22\u3057\u3066\u3044\u307E\u3059... +windowBodyFilesScanned=\u30B9\u30AD\u30E3\u30F3\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB: %d\n\u8FFD\u52A0\u4E88\u5B9A: %d +tab2_Lbl_AwooBlockTitle=Awoo\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3068\u4E92\u63DB\u6027 +tabRcm_Lbl_Payload=\u30DA\u30A4\u30ED\u30FC\u30C9\uFF1A tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 9b02750dd3c95d651b1c13b478ed482d86ec9114 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Sat, 8 Oct 2022 03:10:04 +0900 Subject: [PATCH 069/134] Update locale_ja_ryu.properties --- src/main/resources/locale_ja_ryu.properties | 90 ++++++++++----------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/main/resources/locale_ja_ryu.properties b/src/main/resources/locale_ja_ryu.properties index 6f78f20..75a7999 100644 --- a/src/main/resources/locale_ja_ryu.properties +++ b/src/main/resources/locale_ja_ryu.properties @@ -1,53 +1,53 @@ -btn_OpenFile=ファイル選択 -btn_OpenFolders=フォルダー選択 -btn_Upload=NSんかいアップロード -btn_OpenFolders_tooltip=スキャンするフォルダ選択しくぃみそーれー。\nくぬフォルダとぅうぬまじりぬサブフォルダぬスキャンさりやびーん。\n一致するまじりぬファイルぬリストんかいいりしーらりやびーん。 -tab3_Txt_EnteredAsMsg1=うんじょー次ぬぐとぅ入力さりやびたん: -tab3_Txt_EnteredAsMsg2=問題回避すんがー、root ないが、くぬユーザーんかい「udev」ルール設定するいりゆーぬあいびーん。 -tab3_Txt_FilesToUploadTitle=アップロードするファイル: -tab3_Txt_GreetingsMessage=NS-USBloader んかいめんそーれー -tab3_Txt_NoFolderOrFileSelected=ファイルぬ選択さりやびらん: アップロードすしがあいびらん。 -windowBodyConfirmExit=データ転送ぬ進行中やいびーん。くぬアプリケーションくーいんでぃ中断さりやびーん。\n最悪ぬ事態やいびーん。\n処理中断し終了さびーが? -windowTitleConfirmExit=うぅーうぅー、くりさんぐーとぅーくぃみそーれー ! -btn_Stop=割り込み +btn_OpenFile=\u30D5\u30A1\u30A4\u30EB\u9078\u629E +btn_OpenFolders=\u30D5\u30A9\u30EB\u30C0\u30FC\u9078\u629E +btn_Upload=NS\u3093\u304B\u3044\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9 +btn_OpenFolders_tooltip=\u30B9\u30AD\u30E3\u30F3\u3059\u308B\u30D5\u30A9\u30EB\u30C0\u9078\u629E\u3057\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002\n\u304F\u306C\u30D5\u30A9\u30EB\u30C0\u3068\u3045\u3046\u306C\u307E\u3058\u308A\u306C\u30B5\u30D6\u30D5\u30A9\u30EB\u30C0\u306C\u30B9\u30AD\u30E3\u30F3\u3055\u308A\u3084\u3073\u30FC\u3093\u3002\n\u4E00\u81F4\u3059\u308B\u307E\u3058\u308A\u306C\u30D5\u30A1\u30A4\u30EB\u306C\u30EA\u30B9\u30C8\u3093\u304B\u3044\u3044\u308A\u3057\u30FC\u3089\u308A\u3084\u3073\u30FC\u3093\u3002 +tab3_Txt_EnteredAsMsg1=\u3046\u3093\u3058\u3087\u30FC\u6B21\u306C\u3050\u3068\u3045\u5165\u529B\u3055\u308A\u3084\u3073\u305F\u3093: +tab3_Txt_EnteredAsMsg2=\u554F\u984C\u56DE\u907F\u3059\u3093\u304C\u30FC\u3001root \u306A\u3044\u304C\u3001\u304F\u306C\u30E6\u30FC\u30B6\u30FC\u3093\u304B\u3044\u300Cudev\u300D\u30EB\u30FC\u30EB\u8A2D\u5B9A\u3059\u308B\u3044\u308A\u3086\u30FC\u306C\u3042\u3044\u3073\u30FC\u3093\u3002 +tab3_Txt_FilesToUploadTitle=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3059\u308B\u30D5\u30A1\u30A4\u30EB: +tab3_Txt_GreetingsMessage=NS-USBloader\u3093\u304B\u3044\u3081\u3093\u305D\u30FC\u308C\u30FC +tab3_Txt_NoFolderOrFileSelected=\u30D5\u30A1\u30A4\u30EB\u306C\u9078\u629E\u3055\u308A\u3084\u3073\u3089\u3093: \u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3059\u3057\u304C\u3042\u3044\u3073\u3089\u3093\u3002 +windowBodyConfirmExit=\u30C7\u30FC\u30BF\u8EE2\u9001\u306C\u9032\u884C\u4E2D\u3084\u3044\u3073\u30FC\u3093\u3002\u304F\u306C\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u304F\u30FC\u3044\u3093\u3067\u3043\u4E2D\u65AD\u3055\u308A\u3084\u3073\u30FC\u3093\u3002\n\u6700\u60AA\u306C\u4E8B\u614B\u3084\u3044\u3073\u30FC\u3093\u3002\n\u51E6\u7406\u4E2D\u65AD\u3057\u7D42\u4E86\u3055\u3073\u30FC\u304C? +windowTitleConfirmExit=\u3046\u3045\u30FC\u3046\u3045\u30FC\u3001\u304F\u308A\u3055\u3093\u3050\u30FC\u3068\u3045\u30FC\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\uFF01 +btn_Stop=\u5272\u308A\u8FBC\u307F tab3_Txt_GreetingsMessage2=--\n\ Source: https://git.redrise.ru/desu/ns-usbloader\n\ Mirror: https://github.com/developersu/ns-usbloader/\n\ Site: https://redrise.ru\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=ステータス -tab1_table_Lbl_FileName=ファイル名 -tab1_table_Lbl_Size=サイズ -tab1_table_Lbl_Upload=アップロード? -tab1_table_contextMenu_Btn_BtnDelete=削除すん -tab1_table_contextMenu_Btn_DeleteAll=まじり削除すん -tab2_Lbl_HostIP=ホスト IP +tab1_table_Lbl_Status=\u30B9\u30C6\u30FC\u30BF\u30B9 +tab1_table_Lbl_FileName=\u30D5\u30A1\u30A4\u30EB\u540D +tab1_table_Lbl_Size=\u30B5\u30A4\u30BA +tab1_table_Lbl_Upload=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\uFF1F +tab1_table_contextMenu_Btn_BtnDelete=\u524A\u9664\u3059\u3093 +tab1_table_contextMenu_Btn_DeleteAll=\u307E\u3058\u308A\u524A\u9664\u3059\u3093 +tab2_Lbl_HostIP=\u30DB\u30B9\u30C8 IP tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=NS IP 入力常に検証さびーん。 -windowBodyBadIp=NS IP アドレス正しく入力さびたが? -windowTitleBadIp=NSぬIPアドレスぬばっぺーとーる可能性があいびーん -tab2_Cb_ExpertMode=エキスパートモード (NET セットアップ) -tab2_Lbl_HostPort=ポート -tab2_Cb_AutoDetectIp=IPぬ自動検出 -tab2_Cb_RandSelectPort=ランダムんかいポート取得 -tab2_Cb_DontServeRequests=リクエスト処理さん -tab2_Lbl_DontServeRequestsDesc=選択しーねー、くぬコンピューター NS (ネット経由) からぬ NSP ファイル要求んかい応答さじ、定義さったるホスト設定使用し Awoo インストーラ (あらんでぃ互換アプリケーション) んかいファイルかめーいるばすちてーやびーん。 -tab2_Lbl_HostExtra=エキストラ -windowTitleErrorPort=ポートぬ正しく設定さりやびらん! -windowBodyErrorPort=ポート 0 あらんでぃ 65535 ゆりまぎくすしぇーなやびらん。 -tab2_Cb_AutoCheckForUpdates=アップデートぬ自動チェック -windowTitleNewVersionAval=みーさるバージョンぬ利用可能やいびーん -windowTitleNewVersionNOTAval=みーさるバージョンーあいびらん -windowTitleNewVersionUnknown=みーさるバージョン確認なやびらん -windowBodyNewVersionUnknown=問題が発生さびたん\nインターネットぬ利用ならんが、GitHubがダウンそーる可能性があいびーん -windowBodyNewVersionNOTAval=最新バージョン使用そーいびーん -tab2_Cb_AllowXciNszXcz=Awoo ぬ XCI / NSZ / XCZ ファイルぬ選択許可 -tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ サポートしー、Awoo (別名 Adubbz/TinFoil) 転送プロトコル利用するアプリケーションっし使用さりやびーん。 ゆーわからんばーや変更さんぐーとぅーくぃみそーれー。 Awoo インストーラー有効なさびーん。 -tab2_Lbl_Language=言語 -windowBodyRestartToApplyLang=変更適用すんがーアプリケーション再起動しくぃみそーれー。 -btn_OpenSplitFile=スプリットNSP選択 -tab2_Lbl_ApplicationSettings=主な設定 -tabSplMrg_Lbl_SplitNMergeTitle=ファイルぬ分割とぅ結合ツール +tab2_Cb_ValidateNSHostName=NS IP \u5165\u529B\u5E38\u306B\u691C\u8A3C\u3055\u3073\u30FC\u3093\u3002 +windowBodyBadIp=NS IP \u30A2\u30C9\u30EC\u30B9\u6B63\u3057\u304F\u5165\u529B\u3055\u3073\u305F\u304C? +windowTitleBadIp=NS\u306CIP\u30A2\u30C9\u30EC\u30B9\u306C\u3070\u3063\u307A\u30FC\u3068\u30FC\u308B\u53EF\u80FD\u6027\u304C\u3042\u3044\u3073\u30FC\u3093 +tab2_Cb_ExpertMode=\u30A8\u30AD\u30B9\u30D1\u30FC\u30C8\u30E2\u30FC\u30C9 (NET \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7) +tab2_Lbl_HostPort=\u30DD\u30FC\u30C8 +tab2_Cb_AutoDetectIp=IP\u306C\u81EA\u52D5\u691C\u51FA +tab2_Cb_RandSelectPort=\u30E9\u30F3\u30C0\u30E0\u3093\u304B\u3044\u30DD\u30FC\u30C8\u53D6\u5F97 +tab2_Cb_DontServeRequests=\u30EA\u30AF\u30A8\u30B9\u30C8\u51E6\u7406\u3055\u3093 +tab2_Lbl_DontServeRequestsDesc=\u9078\u629E\u3057\u30FC\u306D\u30FC\u3001\u304F\u306C\u30B3\u30F3\u30D4\u30E5\u30FC\u30BF\u30FC NS (\u30CD\u30C3\u30C8\u7D4C\u7531) \u304B\u3089\u306C NSP \u30D5\u30A1\u30A4\u30EB\u8981\u6C42\u3093\u304B\u3044\u5FDC\u7B54\u3055\u3058\u3001\u5B9A\u7FA9\u3055\u3063\u305F\u308B\u30DB\u30B9\u30C8\u8A2D\u5B9A\u4F7F\u7528\u3057 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9 (\u3042\u3089\u3093\u3067\u3043\u4E92\u63DB\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3) \u3093\u304B\u3044\u30D5\u30A1\u30A4\u30EB\u304B\u3081\u30FC\u3044\u308B\u3070\u3059\u3061\u3066\u30FC\u3084\u3073\u30FC\u3093\u3002 +tab2_Lbl_HostExtra=\u30A8\u30AD\u30B9\u30C8\u30E9 +windowTitleErrorPort=\u30DD\u30FC\u30C8\u306C\u6B63\u3057\u304F\u8A2D\u5B9A\u3055\u308A\u3084\u3073\u3089\u3093! +windowBodyErrorPort=\u30DD\u30FC\u30C8 0 \u3042\u3089\u3093\u3067\u3043 65535 \u3086\u308A\u307E\u304E\u304F\u3059\u3057\u3047\u30FC\u306A\u3084\u3073\u3089\u3093\u3002 +tab2_Cb_AutoCheckForUpdates=\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u306C\u81EA\u52D5\u30C1\u30A7\u30C3\u30AF +windowTitleNewVersionAval=\u307F\u30FC\u3055\u308B\u30D0\u30FC\u30B8\u30E7\u30F3\u306C\u5229\u7528\u53EF\u80FD\u3084\u3044\u3073\u30FC\u3093 +windowTitleNewVersionNOTAval=\u307F\u30FC\u3055\u308B\u30D0\u30FC\u30B8\u30E7\u30F3\u30FC\u3042\u3044\u3073\u3089\u3093 +windowTitleNewVersionUnknown=\u307F\u30FC\u3055\u308B\u30D0\u30FC\u30B8\u30E7\u30F3\u78BA\u8A8D\u306A\u3084\u3073\u3089\u3093 +windowBodyNewVersionUnknown=\u554F\u984C\u304C\u767A\u751F\u3055\u3073\u305F\u3093\n\u30A4\u30F3\u30BF\u30FC\u30CD\u30C3\u30C8\u306C\u5229\u7528\u306A\u3089\u3093\u304C\u3001GitHub\u304C\u30C0\u30A6\u30F3\u305D\u30FC\u308B\u53EF\u80FD\u6027\u304C\u3042\u3044\u3073\u30FC\u3093 +windowBodyNewVersionNOTAval=\u6700\u65B0\u30D0\u30FC\u30B8\u30E7\u30F3\u4F7F\u7528\u305D\u30FC\u3044\u3073\u30FC\u3093 +tab2_Cb_AllowXciNszXcz=Awoo \u306C XCI / NSZ / XCZ \u30D5\u30A1\u30A4\u30EB\u306C\u9078\u629E\u8A31\u53EF +tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ \u30B5\u30DD\u30FC\u30C8\u3057\u30FC\u3001Awoo (\u5225\u540D Adubbz/TinFoil) \u8EE2\u9001\u30D7\u30ED\u30C8\u30B3\u30EB\u5229\u7528\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3063\u3057\u4F7F\u7528\u3055\u308A\u3084\u3073\u30FC\u3093\u3002 \u3086\u30FC\u308F\u304B\u3089\u3093\u3070\u30FC\u3084\u5909\u66F4\u3055\u3093\u3050\u30FC\u3068\u3045\u30FC\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u6709\u52B9\u306A\u3055\u3073\u30FC\u3093\u3002 +tab2_Lbl_Language=\u8A00\u8A9E +windowBodyRestartToApplyLang=\u5909\u66F4\u9069\u7528\u3059\u3093\u304C\u30FC\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u518D\u8D77\u52D5\u3057\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 +btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8NSP\u9078\u629E +tab2_Lbl_ApplicationSettings=\u4E3B\u306A\u8A2D\u5B9A +tabSplMrg_Lbl_SplitNMergeTitle=\u30D5\u30A1\u30A4\u30EB\u306C\u5206\u5272\u3068\u3045\u7D50\u5408\u30C4\u30FC\u30EB tabSplMrg_RadioBtn_Split=ファイルの分割と結合ツール tabSplMrg_RadioBtn_Merge=マージ tabSplMrg_Txt_File=ファイル: @@ -70,7 +70,7 @@ windowBodyDownloadDrivers=ドライバーダウンロードそーいびーん (l btn_Cancel=キャンセル btn_Close=くーいん tab2_Cb_GlVersion=Gold Leafバージョン -tab2_Cb_GLshowNspOnly=GoldLeaf でぃ *.nsp ぬみ表示さびーん。 +tab2_Cb_GLshowNspOnly=GoldLeafでぃ *.nsp ぬみ表示さびーん。 windowBodyPleaseStopOtherProcessFirst=続行する前に、他ぬアクティブなプロセス停止しくぃみそーれー。 tab2_Cb_foldersSelectorForRoms=ROM 個別に選択するぬでーなく、ROM ファイルぬあるフォルダ選択さびーん。 tab2_Cb_foldersSelectorForRomsDesc=[ゲーム] タブぬ [ファイル選択] ボタンぬ動作変更: ROM ファイル 1 ちなー選択する代わりんかい、フォルダー選択し、サポートさりとーるまじりぬファイル一度んかい追加なやびーん。 From 610d04c9bd950a033caf269abf82c94e961c0be6 Mon Sep 17 00:00:00 2001 From: kuragehime <kuragehime641@gmail.com> Date: Sat, 8 Oct 2022 03:24:08 +0900 Subject: [PATCH 070/134] Update locale_ja_ryu.properties --- src/main/resources/locale_ja_ryu.properties | 60 ++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main/resources/locale_ja_ryu.properties b/src/main/resources/locale_ja_ryu.properties index 75a7999..69972da 100644 --- a/src/main/resources/locale_ja_ryu.properties +++ b/src/main/resources/locale_ja_ryu.properties @@ -48,34 +48,34 @@ windowBodyRestartToApplyLang=\u5909\u66F4\u9069\u7528\u3059\u3093\u304C\u30FC\u3 btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8NSP\u9078\u629E tab2_Lbl_ApplicationSettings=\u4E3B\u306A\u8A2D\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u30D5\u30A1\u30A4\u30EB\u306C\u5206\u5272\u3068\u3045\u7D50\u5408\u30C4\u30FC\u30EB -tabSplMrg_RadioBtn_Split=ファイルの分割と結合ツール -tabSplMrg_RadioBtn_Merge=マージ -tabSplMrg_Txt_File=ファイル: -tabSplMrg_Txt_Folder=分割ファイル (フォルダー): -tabSplMrg_Btn_SelectFile=ファイルいらぶん -tabSplMrg_Btn_SelectFolder=フォルダー選択 -tabSplMrg_Lbl_SaveToLocation=んかい保存: -tabSplMrg_Btn_ChangeSaveToLocation=変わいん -tabSplMrg_Btn_Convert=変換 -windowTitleError=エラー -windowBodyPleaseFinishTransfersFirst=アプリケーション USB/ネットワーク プロセスぬアクティブなばー、ファイル分割/マージなやびらん。 最初んかいアクティブな転送中断しくぃみそーれー。 -done_txt=うわい! -failure_txt=失敗 -btn_Select=選択すん -btn_InjectPayloader=ペイロード挿入すん -tabNXDT_Btn_Start=始みーん! -tab2_Btn_InstallDrivers=ドライバーぬダウンロードとぅインストール -windowTitleDownloadDrivers=ドライバーぬダウンロードとぅインストール -windowBodyDownloadDrivers=ドライバーダウンロードそーいびーん (libusbK v3.0.7.0)... -btn_Cancel=キャンセル -btn_Close=くーいん -tab2_Cb_GlVersion=Gold Leafバージョン -tab2_Cb_GLshowNspOnly=GoldLeafでぃ *.nsp ぬみ表示さびーん。 -windowBodyPleaseStopOtherProcessFirst=続行する前に、他ぬアクティブなプロセス停止しくぃみそーれー。 -tab2_Cb_foldersSelectorForRoms=ROM 個別に選択するぬでーなく、ROM ファイルぬあるフォルダ選択さびーん。 -tab2_Cb_foldersSelectorForRomsDesc=[ゲーム] タブぬ [ファイル選択] ボタンぬ動作変更: ROM ファイル 1 ちなー選択する代わりんかい、フォルダー選択し、サポートさりとーるまじりぬファイル一度んかい追加なやびーん。 -windowTitleAddingFiles=ファイル検索そーいびーん... -windowBodyFilesScanned=スキャンさったるファイル: %d\n追加予定: %d -tab2_Lbl_AwooBlockTitle=Awooインストーラーとぅ互換性 -tabRcm_Lbl_Payload=ペイロード: +tabSplMrg_RadioBtn_Split=\u30D5\u30A1\u30A4\u30EB\u306C\u5206\u5272\u3068\u3045\u7D50\u5408\u30C4\u30FC\u30EB +tabSplMrg_RadioBtn_Merge=\u30DE\u30FC\u30B8 +tabSplMrg_Txt_File=\u30D5\u30A1\u30A4\u30EB\uFF1A +tabSplMrg_Txt_Folder=\u5206\u5272\u30D5\u30A1\u30A4\u30EB (\u30D5\u30A9\u30EB\u30C0\u30FC): +tabSplMrg_Btn_SelectFile=\u30D5\u30A1\u30A4\u30EB\u3044\u3089\u3076\u3093 +tabSplMrg_Btn_SelectFolder=\u30D5\u30A9\u30EB\u30C0\u30FC\u9078\u629E +tabSplMrg_Lbl_SaveToLocation=\u3093\u304B\u3044\u4FDD\u5B58\uFF1A +tabSplMrg_Btn_ChangeSaveToLocation=\u5909\u308F\u3044\u3093 +tabSplMrg_Btn_Convert=\u5909\u63DB +windowTitleError=\u30A8\u30E9\u30FC +windowBodyPleaseFinishTransfersFirst=\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3 USB/\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF \u30D7\u30ED\u30BB\u30B9\u306C\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u3070\u30FC\u3001\u30D5\u30A1\u30A4\u30EB\u5206\u5272/\u30DE\u30FC\u30B8\u306A\u3084\u3073\u3089\u3093\u3002 \u6700\u521D\u3093\u304B\u3044\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u8EE2\u9001\u4E2D\u65AD\u3057\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 +done_txt=\u3046\u308F\u3044\uFF01 +failure_txt=\u5931\u6557 +btn_Select=\u9078\u629E\u3059\u3093 +btn_InjectPayloader=\u30DA\u30A4\u30ED\u30FC\u30C9\u633F\u5165\u3059\u3093 +tabNXDT_Btn_Start=\u59CB\u307F\u30FC\u3093\uFF01 +tab2_Btn_InstallDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u306C\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3068\u3045\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB +windowTitleDownloadDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u306C\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3068\u3045\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB +windowBodyDownloadDrivers=\u30C9\u30E9\u30A4\u30D0\u30FC\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u305D\u30FC\u3044\u3073\u30FC\u3093 (libusbK v3.0.7.0)... +btn_Cancel=\u30AD\u30E3\u30F3\u30BB\u30EB +btn_Close=\u304F\u30FC\u3044\u3093 +tab2_Cb_GlVersion=Gold Leaf\u30D0\u30FC\u30B8\u30E7\u30F3 +tab2_Cb_GLshowNspOnly=GoldLeaf\u3067\u3043 *.nsp \u306C\u307F\u8868\u793A\u3055\u3073\u30FC\u3093\u3002 +windowBodyPleaseStopOtherProcessFirst=\u884C\u3059\u308B\u524D\u306B\u3001\u4ED6\u306C\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u30D7\u30ED\u30BB\u30B9\u505C\u6B62\u3057\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 +tab2_Cb_foldersSelectorForRoms=ROM \u500B\u5225\u306B\u9078\u629E\u3059\u308B\u306C\u3067\u30FC\u306A\u304F\u3001ROM \u30D5\u30A1\u30A4\u30EB\u306C\u3042\u308B\u30D5\u30A9\u30EB\u30C0\u9078\u629E\u3055\u3073\u30FC\u3093\u3002 +tab2_Cb_foldersSelectorForRomsDesc=[\u30B2\u30FC\u30E0] \u30BF\u30D6\u306C [\u30D5\u30A1\u30A4\u30EB\u9078\u629E] \u30DC\u30BF\u30F3\u306C\u52D5\u4F5C\u5909\u66F4: ROM \u30D5\u30A1\u30A4\u30EB 1 \u3061\u306A\u30FC\u9078\u629E\u3059\u308B\u4EE3\u308F\u308A\u3093\u304B\u3044\u3001\u30D5\u30A9\u30EB\u30C0\u30FC\u9078\u629E\u3057\u3001\u30B5\u30DD\u30FC\u30C8\u3055\u308A\u3068\u30FC\u308B\u307E\u3058\u308A\u306C\u30D5\u30A1\u30A4\u30EB\u4E00\u5EA6\u3093\u304B\u3044\u8FFD\u52A0\u306A\u3084\u3073\u30FC\u3093\u3002 +windowTitleAddingFiles=\u30D5\u30A1\u30A4\u30EB\u691C\u7D22\u305D\u30FC\u3044\u3073\u30FC\u3093... +windowBodyFilesScanned=\u30B9\u30AD\u30E3\u30F3\u3055\u3063\u305F\u308B\u30D5\u30A1\u30A4\u30EB: %d\n\u8FFD\u52A0\u4E88\u5B9A: %d +tab2_Lbl_AwooBlockTitle=Awoo\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3068\u3045\u4E92\u63DB\u6027 +tabRcm_Lbl_Payload=\u30DA\u30A4\u30ED\u30FC\u30C9\uFF1A tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM From 8b23b8967b6b0acda105ae0c13461db49517dd2e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 11 Oct 2022 17:04:55 +0300 Subject: [PATCH 071/134] Correct locales logic to get some kind of ISO 639-3 support, update pom, make ja_ryu appear (unfortunately as 'Japanese' language and not as 'Central Okinawan') --- README.md | 6 ++++-- pom.xml | 2 +- src/main/java/nsusbloader/AppPreferences.java | 2 +- .../java/nsusbloader/UI/LocaleHolder.java | 20 +++++++++---------- ...yu.properties => locale_ja_RYU.properties} | 0 5 files changed, 15 insertions(+), 15 deletions(-) rename src/main/resources/{locale_ja_ryu.properties => locale_ja_RYU.properties} (100%) diff --git a/README.md b/README.md index 10b60bf..1ebcd95 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,9 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Czech by [Spenaat](https://github.com/spenaat) * Arabic by [eslamabdel](https://github.com/eslamabdel) * Romanian by [Călin Ilie](https://github.com/calini) -* Swedish by [Daniel Nylander](https://github.com/yeager) - (coming soon) +* Swedish by [Daniel Nylander](https://github.com/yeager) +* Japanese by [kuragehime](https://github.com/kuragehimekurara1) +* Ryukyuan languages by [kuragehime](https://github.com/kuragehimekurara1) ### System requirements @@ -71,7 +73,7 @@ JDK 11 for MacOS and Linux | v0.6.1 | v0.6 | | v0.7 - 0.7.3 | v0.7+ | | v0.8 - 0.9 | v1.0+ | -| v0.10 | v6.0 | +| v0.10 | v6.0+ | where '+' means 'any next NS-USBloader version'. diff --git a/pom.xml b/pom.xml index 41d434a..b3c4607 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>6.1</version> + <version>6.2</version> <url>https://redrise.ru</url> <description> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 7fb69dd..363f9ba 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -32,7 +32,7 @@ public class AppPreferences { private AppPreferences(){ this.preferences = Preferences.userRoot().node("NS-USBloader"); String localeCode = preferences.get("locale", Locale.getDefault().toString()); - this.locale = new Locale(localeCode.substring(0, 2), localeCode.substring(3, 5)); + this.locale = new Locale(localeCode.substring(0, 2), localeCode.substring(3)); } public String getTheme(){ diff --git a/src/main/java/nsusbloader/UI/LocaleHolder.java b/src/main/java/nsusbloader/UI/LocaleHolder.java index 5095ded..f1b2319 100644 --- a/src/main/java/nsusbloader/UI/LocaleHolder.java +++ b/src/main/java/nsusbloader/UI/LocaleHolder.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2022 Dmitry Isaenko This file is part of NS-USBloader. @@ -26,16 +26,14 @@ public class LocaleHolder { private final String localeCode; private final String languageName; - public LocaleHolder(Locale locale){ - this.locale = locale; - this.localeCode = locale.toString(); - this.languageName = locale.getDisplayLanguage(locale) + " (" + locale + ")"; - } - public LocaleHolder(String localeFileName) { - String country = localeFileName.substring(7, 9); - String language = localeFileName.substring(10, 12); - this.locale = new Locale(country, language); + String language = localeFileName.substring(7, 9); + String country; + if (localeFileName.length() > 23) // ISO 639-3 not supported by Java + country = localeFileName.substring(10, localeFileName.indexOf('.')); + else // ISO 639-1 + country = localeFileName.substring(10, 12); + this.locale = new Locale(language, country); this.localeCode = locale.toString(); this.languageName = locale.getDisplayLanguage(locale) + " (" + locale + ")"; } @@ -47,7 +45,7 @@ public class LocaleHolder { public String getLocaleCode(){ return localeCode; - }; + } public Locale getLocale() { return locale; diff --git a/src/main/resources/locale_ja_ryu.properties b/src/main/resources/locale_ja_RYU.properties similarity index 100% rename from src/main/resources/locale_ja_ryu.properties rename to src/main/resources/locale_ja_RYU.properties From 55df39923f15e0267fdcf58df1a6c199228946f2 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 23 Dec 2022 07:08:22 +0300 Subject: [PATCH 072/134] Correct readme, update few icons, add freedesktop entry and svg icon. Could be useful for packagers. --- .drone.yml | 2 + .make_legacy | 1 + README.md | 28 +- misc/freedesktop_entry/ns-usbloader.desktop | 9 + misc/freedesktop_entry/ns-usbloader.svg | 118 ++++ pom.xml | 78 ++- screenshots/ApplicationLogo.svg | 234 +++----- src/main/java/nsusbloader/AppPreferences.java | 8 + .../Controllers/NSLMainController.java | 13 +- .../Controllers/PatchesController.java | 225 ++++++++ .../java/nsusbloader/MediatorControl.java | 12 +- .../ModelControllers/MessagesConsumer.java | 2 + .../nsusbloader/NSLDataTypes/EModule.java | 3 +- src/main/java/nsusbloader/NSLMain.java | 9 +- .../Utilities/patches/es/BinToAsmPrinter.java | 526 ++++++++++++++++++ .../Utilities/patches/es/EsNcaSearchTask.java | 50 ++ .../Utilities/patches/es/EsPatch.java | 174 ++++++ .../Utilities/patches/es/EsPatchMaker.java | 187 +++++++ .../Utilities/patches/es/SimplyFind.java | 161 ++++++ .../patches/es/finders/HeuristicEs1.java | 109 ++++ .../patches/es/finders/HeuristicEs2.java | 168 ++++++ .../patches/es/finders/HeuristicEs3.java | 151 +++++ .../patches/es/finders/HeuristicEsWizard.java | 127 +++++ .../patches/es/finders/IHeuristicEs.java | 47 ++ .../nsusbloader/cli/CommandLineInterface.java | 17 +- src/main/resources/NSLMain.fxml | 10 +- src/main/resources/PatchesTab.fxml | 122 ++++ src/main/resources/locale.properties | 11 + src/main/resources/locale_ru_RU.properties | 11 + src/main/resources/locale_uk_UA.properties | 11 + src/main/resources/res/app_dark.css | 7 + src/main/resources/res/app_light.css | 7 + 32 files changed, 2433 insertions(+), 205 deletions(-) create mode 100755 misc/freedesktop_entry/ns-usbloader.desktop create mode 100644 misc/freedesktop_entry/ns-usbloader.svg create mode 100644 src/main/java/nsusbloader/Controllers/PatchesController.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java create mode 100644 src/main/resources/PatchesTab.fxml diff --git a/.drone.yml b/.drone.yml index db885a8..7d3a283 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,6 +17,7 @@ steps: commands: - mkdir -p /builds/ns-usbloader - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + - cp target/ns-usbloader-*exe /builds/ns-usbloader/ volumes: - name: builds path: /builds @@ -27,6 +28,7 @@ steps: - . ./.make_legacy - mvn -B -DskipTests clean package - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + - cp target/ns-usbloader-*exe /builds/ns-usbloader/ volumes: - name: m2 path: /root/.m2 diff --git a/.make_legacy b/.make_legacy index a7c3a6f..f0f75ee 100644 --- a/.make_legacy +++ b/.make_legacy @@ -1,2 +1,3 @@ sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}<\/finalName>/g' pom.xml +sed -z -i -e 's/<outfile>target\/NS-USBloader-${project.version}-${maven.build.timestamp}.exe<\/outfile>/<outfile>target\/NS-USBloader-${project.version}-legacy-${maven.build.timestamp}.exe<\/outfile>/g' pom.xml diff --git a/README.md b/README.md index 1ebcd95..3bfb3f1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# NS-USBloader +<h1 align="center"><img src="screenshots/ApplicationLogo.svg" alt="NS-USBloader" width="450px"/></h1>    [](https://ci.redrise.ru/desu/ns-usbloader) NS-USBloader is: -* A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/GoldLeaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. +* A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/Goldleaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. Alternative to default **usb_install_pc.py**, **remote_install_pc.py**, **GoldTree**/**Quark**. * RCM payload tool that works on Windows, macOS (Intel and Apple Silicon) and Linux (x86, amd64 and Raspberry Pi ARM). * It's a tool for creating split files! @@ -59,14 +59,16 @@ 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) +* angelodalzotto makes packages in AUR + ### System requirements JRE/JDK 8u60 or higher for Windows JDK 11 for MacOS and Linux -### Supported GoldLeaf versions -| GoldLeaf version | NS-USBloader version | +### Supported Goldleaf versions +| Goldleaf version | NS-USBloader version | |------------------|----------------------| | v0.5 | v0.4 - v0.5.2, v0.8+ | | v0.6 | none | @@ -136,7 +138,7 @@ Set 'Security & Privacy' settings if needed. #### And how to use it? -The first thing you should do it install Awoo ([Huntereb](https://github.com/Huntereb/Awoo-Installer)) or GoldLeaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) on your NS. +The first thing you should do it install Awoo ([Huntereb](https://github.com/Huntereb/Awoo-Installer)) or Goldleaf ([XorTroll](https://github.com/XorTroll/Goldleaf)) on your NS. Take a look on app, find where is the option to install from USB and/or Network. Maybe (very old) [this article (about TinFoil)](https://developersu.blogspot.com/2019/02/ns-usbloader-en.html) will be helpful. @@ -146,19 +148,19 @@ There are three tabs. First one is main. ##### 'Gamepad' tab. -At the top of you selecting from drop-down application and protocol that you're going to use. For GoldLeaf only USB is available. Lamp icon stands for switching themes (light or dark). +At the top of you selecting from drop-down application and protocol that you're going to use. For Goldleaf only USB is available. Lamp icon stands for switching themes (light or dark). Then you may drag-n-drop files (split-files aka folders) to application or use 'Select NSP files' button. Multiple selection for files available. Click it again and select files from another folder it you want, it will be added into the table. Table. -There you can select checkbox for files that will be sent to application (AW/GL). ~~Since GoldLeaf v0.5 allow you only one file transmission per time, only one file is available for selection.~~ +There you can select checkbox for files that will be sent to application (AW/GL). ~~Since Goldleaf v0.5 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. +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. +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. ##### 'RCM' tab @@ -174,7 +176,7 @@ Here you can configure settings for network file transmission. Usually you shoul 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. +* Set 'Show only *.nsp in Goldleaf' to filter all files displayed at HOME:/ drive. So only NSP files will appear. ##### 'Dialog with three dots' tab. @@ -187,7 +189,7 @@ To get help run ``$ java -jar ns-usbloader-4.0.jar --help`` ``` -c,--clean Remove/reset settings and exit - -g,--goldleaf <...> Install via GoldLeaf mode. Check '-g help' for information. + -g,--Goldleaf <...> Install via Goldleaf mode. Check '-g help' for information. -h,--help Show this help -m,--merge <...> Merge files. Check '-m help' for information. -n,--tfn <...> Install via Awoo Network mode. Check '-n help' for information. @@ -218,7 +220,7 @@ Send RCM payload: $ java -jar ns-usbloader-4.0.jar -r C:\Users\Superhero\hekate.bin Send files to Awoo Installer via Net-install: $ java -jar ns-usbloader-4.0.jar -n nsip=192.168.0.1 ./file.nsz ./file.nsp ~/*.xci -Send files to GoldLeaf v0.8: +Send files to Goldleaf v0.8: $ java -jar ns-usbloader-4.0.jar -g ver=v0.8 ./* Split files: $ java -jar ns-usbloader-4.0.jar -s /tmp/ ~/*.nsp @@ -229,7 +231,7 @@ $ java -jar ns-usbloader-4.0.jar -m /tmp/ ~/*.nsp ### 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. -Handling successful/failed installation is a purpose of the other side application: Awoo/Awoo-like or GoldLeaf. And they don't provide any feedback interfaces so I can't detect success/failure. +Handling successful/failed installation is a purpose of the other side application: Awoo/Awoo-like or Goldleaf. And they don't provide any feedback interfaces so I can't detect success/failure. #### What is this '-legacy' jar?! diff --git a/misc/freedesktop_entry/ns-usbloader.desktop b/misc/freedesktop_entry/ns-usbloader.desktop new file mode 100755 index 0000000..d402210 --- /dev/null +++ b/misc/freedesktop_entry/ns-usbloader.desktop @@ -0,0 +1,9 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Type=Application +Name=NS-USBloader +Exec=ns-usbloader +Comment=NS multi tool +Terminal=false +Icon=ns-usbloader.svg +Categories=Game; diff --git a/misc/freedesktop_entry/ns-usbloader.svg b/misc/freedesktop_entry/ns-usbloader.svg new file mode 100644 index 0000000..75a45a7 --- /dev/null +++ b/misc/freedesktop_entry/ns-usbloader.svg @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="45mm" + height="45mm" + viewBox="0 0 45.000001 45" + version="1.1" + id="svg8" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + sodipodi:docname="ns-usbloader.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="5.6568543" + inkscape:cx="206.82873" + inkscape:cy="90.421279" + inkscape:document-units="mm" + inkscape:current-layer="svg8" + showgrid="false" + units="mm" + inkscape:window-width="3754" + inkscape:window-height="2127" + inkscape:window-x="1166" + inkscape:window-y="0" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:document-rotation="0" + inkscape:showpageshadow="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1"> + <inkscape:grid + type="xygrid" + id="grid829" + originx="-40.993993" + originy="-37.999754" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="g851" + inkscape:label="mainGRP" + transform="translate(-40.993968,-26.999903)"> + <path + id="path847" + transform="scale(0.26458333)" + d="m 188.97656,102.04688 c -18.97637,0 -34.01562,18.70089 -34.01562,30.23632 -0.0219,42.2441 -0.0386,69.16374 0,109.60742 0,11.85827 15.03925,30.23633 34.01562,30.23633 h 48.94141 V 102.04688 Z" + style="fill:#00c8fc;fill-opacity:1;stroke-width:1" + inkscape:connector-curvature="0" + inkscape:label="con" /> + <path + id="rect823" + d="m 64.973294,26.999903 v 45 h 21.02 v -45 z" + style="fill:#ec0000;fill-opacity:1;stroke-width:0.116442" + inkscape:connector-curvature="0" + inkscape:label="big" /> + </g> + <path + id="path862" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.392613" + d="m 11.547015,28.4105 a 2.1593723,2.1593723 0 0 0 -2.2000565,2.117691 2.1593723,2.1593723 0 0 0 2.1176905,2.200059 2.1593723,2.1593723 0 0 0 2.200555,-2.1167 2.1593723,2.1593723 0 0 0 -2.1167,-2.20105 l -0.04267,2.158874 z m 3.840427,-3.937182 a 2.1593723,2.1593723 0 0 0 -2.199561,2.117693 2.1593723,2.1593723 0 0 0 2.117195,2.200057 2.1593723,2.1593723 0 0 0 2.200555,-2.117195 2.1593723,2.1593723 0 0 0 -2.1167,-2.200555 l -0.04217,2.158876 z m -7.6809635,0 a 2.1593722,2.1593722 0 0 0 -2.200057,2.117693 2.1593722,2.1593722 0 0 0 2.117691,2.200057 2.1593722,2.1593722 0 0 0 2.200554,-2.117195 2.1593722,2.1593722 0 0 0 -2.117195,-2.200555 l -0.04218,2.158876 z m 3.8405365,-3.938173 a 2.1593723,2.1593723 0 0 0 -2.2000565,2.117691 2.1593723,2.1593723 0 0 0 2.1176905,2.200059 2.1593723,2.1593723 0 0 0 2.200555,-2.1167 2.1593723,2.1593723 0 0 0 -2.1167,-2.20105 l -0.04267,2.158874 z" + inkscape:label="small_c" + inkscape:connector-curvature="0" /> + <g + inkscape:label="slogan" + id="layer1" + transform="translate(-40.993968,-213.9999)" + inkscape:groupmode="layer"> + <g + aria-label="everywhere" + transform="matrix(0.26458333,0,0,0.26458333,-24.077084,181.70833)" + style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:35.0667px;line-height:1.25;font-family:Raleway;-inkscape-font-specification:'Raleway, Light';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none" + id="flowRoot1022" + inkscape:label="flowRoot1022" + inkscape:groupmode="layer" /> + </g> + <path + id="path853" + style="fill:#ffffff;fill-opacity:1;stroke-width:0.272727" + d="m 11.506852,6.300096 a 4.5000001,4.5000001 0 0 1 4.49918,4.500616 4.5000001,4.5000001 0 0 1 -4.500411,4.499384 4.5000001,4.5000001 0 0 1 -4.4995895,-4.500205 4.5000001,4.5000001 0 0 1 4.5000005,-4.499795" + inkscape:label="big_c" + inkscape:connector-curvature="0" /> + <path + id="path1334" + d="m 35.091899,7.050931 -1.966235,3.404775 h 1.402615 v 17.402765 l -3.579993,-3.388515 c -0.231149,-0.288377 -0.393301,-0.665687 -0.402284,-1.053816 0,-1.570143 -3.97e-4,-2.502567 -9.76e-4,-2.845788 0.662828,-0.232646 1.141253,-0.857572 1.141253,-1.600489 0,-0.939842 -0.76265,-1.702559 -1.702831,-1.702559 -0.940589,0 -1.702967,0.76265 -1.702967,1.702559 0,0.742917 0.478154,1.367843 1.140438,1.600489 l -4.76e-4,2.812447 c 0,0.762241 0.418205,1.560957 0.90847,2.069322 -0.01456,-0.01388 -0.03007,-0.02831 2.83e-4,8.15e-4 0.01211,0.01075 3.797875,3.595101 3.797875,3.595101 0.230809,0.287762 0.39194,0.664868 0.401196,1.052727 v 1.968677 c -1.30057,0.260954 -2.28042,1.409421 -2.28042,2.787065 0,1.570687 1.273194,2.843881 2.843405,2.843881 1.570686,0 2.843946,-1.273194 2.843946,-2.843881 0,-1.377917 -0.980663,-2.526381 -2.282303,-2.787335 v -1.934186 c 0,-0.005 2.59e-4,-0.0099 0,-0.01498 v -4.278078 c 0.0098,-0.38711 0.171203,-0.763741 0.402285,-1.051231 0,0 3.785488,-3.583532 3.797733,-3.594625 0.03043,-0.02872 0.01454,-0.01444 2.59e-4,-2.58e-4 0.490197,-0.508364 0.908134,-1.307418 0.908134,-2.069798 l -3.97e-4,-2.710376 h 1.141588 v -3.405646 h -3.405338 v 3.405663 h 1.140097 c 0,0 -0.0012,0.713861 -0.0012,2.743583 -0.0089,0.388196 -0.170862,0.765917 -0.402012,1.054155 l -3.580815,3.389238 V 10.455706 h 1.404858 z" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke-width:0.0680452" + inkscape:label="usb_logo" /> + <path + style="fill:#000000;fill-opacity:1;stroke-width:0.264583" + d="m 21.967046,0 c -0.0065,-1.9994448e-6 -0.01188,0.0054 -0.01188,0.01188 v 44.976581 c 0,0.0065 0.0054,0.01188 0.01188,0.01188 h 2.000395 c 0.0065,0 0.01188,-0.0054 0.01188,-0.01188 V 0.011885 c 0,-0.0065 -0.0054,-0.011879999445 -0.01188,-0.011879999445 z" + id="rect1136" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csssssssc" /> +</svg> diff --git a/pom.xml b/pom.xml index b3c4607..346cec4 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ <url>https://redrise.ru</url> <description> - NS multi-tool + NS multi tool </description> <inceptionYear>2019</inceptionYear> <organization> @@ -61,28 +61,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -90,28 +90,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -119,31 +119,60 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>18.0.1</version> + <version>19</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> + + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>19</version> + <classifier>mac-aarch64</classifier> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>19</version> + <classifier>mac-aarch64</classifier> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-fxml</artifactId> + <version>19</version> + <classifier>mac-aarch64</classifier> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>19</version> + <classifier>mac-aarch64</classifier> + <scope>compile</scope> + </dependency> <!-- https://mvnrepository.com/artifact/org.usb4java/usb4java --> <dependency> <groupId>org.usb4java</groupId> @@ -170,6 +199,13 @@ <version>5.9.0</version> <scope>test</scope> </dependency> + <!-- picked from local repo --> + <dependency> + <groupId>ru.redrise</groupId> + <artifactId>libKonogonka</artifactId> + <version>0.1-SNAPSHOT</version> + <scope>compile</scope> + </dependency> </dependencies> <build> <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName> @@ -243,10 +279,10 @@ </execution> </executions> </plugin> - <!-- Launch4j <plugin> + <!-- https://mvnrepository.com/artifact/com.akathist.maven.plugins.launch4j/launch4j-maven-plugin --> <groupId>com.akathist.maven.plugins.launch4j</groupId> - <version>1.7.25</version> + <version>2.2.0</version> <artifactId>launch4j-maven-plugin</artifactId> <executions> <execution> @@ -258,8 +294,8 @@ <configuration> <headerType>gui</headerType> <icon>appicon.ico</icon> - <outfile>target/NS-USBloader-${project.version}.exe</outfile> - <jar>target/ns-usbloader-${project.version}-jar-with-dependencies.jar</jar> + <outfile>target/NS-USBloader-${project.version}-${maven.build.timestamp}.exe</outfile> + <jar>target/${project.artifactId}-${project.version}-${maven.build.timestamp}.jar</jar> <errTitle>NS-USBloader</errTitle> <classPath> <mainClass>nsusbloader.Main</mainClass> @@ -267,13 +303,14 @@ <preCp>anything</preCp> </classPath> <jre> - <minVersion>1.8</minVersion> + <minVersion>1.8.0</minVersion> + <path>%JAVA_HOME%;%PATH%</path> </jre> <versionInfo> <fileVersion>1.0.0.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> - <fileDescription>Awoo and GoldLeaf installer for your NS</fileDescription> - <copyright>GNU General Public License v3, 2019 ${project.organization.name}. Russia/LPR.</copyright> + <fileDescription>NS multi tool</fileDescription> + <copyright>GNU General Public License v3, 2019 ${project.organization.name}, Russia.</copyright> <productVersion>1.0.0.0</productVersion> <txtProductVersion>${project.version}</txtProductVersion> <companyName>${project.organization.name}</companyName> @@ -285,7 +322,6 @@ </execution> </executions> </plugin> - --> </plugins> </build> </project> \ No newline at end of file diff --git a/screenshots/ApplicationLogo.svg b/screenshots/ApplicationLogo.svg index 222de82..852045a 100644 --- a/screenshots/ApplicationLogo.svg +++ b/screenshots/ApplicationLogo.svg @@ -1,19 +1,19 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="132.50603mm" - height="58.000351mm" - viewBox="0 0 132.50603 58.000351" + height="45.000351mm" + viewBox="0 0 132.50603 45.000351" version="1.1" id="svg8" - inkscape:version="1.0 (4035a4fb49, 2020-05-01)" - sodipodi:docname="Application Logo.svg"> + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + sodipodi:docname="Application Logo.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> <defs id="defs2" /> <sodipodi:namedview @@ -23,28 +23,31 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1.4142136" - inkscape:cx="383.67648" - inkscape:cy="100.29922" + inkscape:zoom="4.0000001" + inkscape:cx="233.49999" + inkscape:cy="127.875" inkscape:document-units="mm" - inkscape:current-layer="svg8" + inkscape:current-layer="flowRoot1022" showgrid="false" units="mm" - inkscape:window-width="1860" - inkscape:window-height="1058" - inkscape:window-x="1980" + inkscape:window-width="3754" + inkscape:window-height="2127" + inkscape:window-x="1166" inkscape:window-y="0" inkscape:window-maximized="1" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - inkscape:document-rotation="0"> + inkscape:document-rotation="0" + inkscape:showpageshadow="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1"> <inkscape:grid type="xygrid" id="grid829" - originx="-40.993969" - originy="-37.999746" /> + originx="-40.99399" + originy="-37.999752" /> </sodipodi:namedview> <metadata id="metadata5"> @@ -54,7 +57,6 @@ <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> </cc:Work> </rdf:RDF> </metadata> @@ -114,145 +116,65 @@ inkscape:connector-curvature="0" style="fill:#ffffff;stroke-width:0.0680452" inkscape:label="usb_logo" /> - <text - xml:space="preserve" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.43163px;line-height:1.25;font-family:Play;-inkscape-font-specification:'Play, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.357908" - x="47.959785" - y="17.188828" - id="text982" + <g + aria-label="NS-USBloader" transform="scale(0.97866107,1.0218042)" - inkscape:label="ns-usbloader"><tspan - sodipodi:role="line" - id="tspan980" - x="47.959785" - y="17.188828" - style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3619px;font-family:Play;-inkscape-font-specification:'Play, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;stroke-width:0.357908">NS-USBloader</tspan></text> + id="text982" + style="font-size:1.43163px;line-height:1.25;font-family:Play;-inkscape-font-specification:'Play, Normal';letter-spacing:0px;word-spacing:0px;fill:#ffffff;stroke-width:0.357908" + inkscape:label="ns-usbloader"> + <path + d="M 56.49804,17.188828 H 54.934697 L 50.217947,9.9065918 V 17.188828 H 49.082185 V 8.5169542 h 1.563342 l 4.716751,7.1619788 V 8.5169542 h 1.135762 z" + style="font-size:13.3619px" + id="path880" /> + <path + d="m 64.488451,14.249209 v 0.62801 q 0,1.389637 -0.788353,1.924113 -0.788352,0.521115 -2.458589,0.521115 -1.068952,0 -2.645656,-0.213791 v -1.082314 q 1.576704,0.334048 2.725827,0.334048 0.921971,0 1.403,-0.200429 0.49439,-0.21379 0.49439,-0.8418 v -1.122399 q 0,-0.547838 -0.320686,-0.77499 -0.307323,-0.240515 -1.122399,-0.240515 H 60.76048 q -1.309466,0 -1.83058,-0.521114 -0.507752,-0.534476 -0.507752,-1.563342 v -0.694819 q 0,-1.0288662 0.721542,-1.5232565 0.734905,-0.4943903 2.685742,-0.4943903 0.935333,0 2.231438,0.1469809 v 0.9754187 q -1.483171,-0.2271523 -2.324971,-0.2271523 -1.242657,0 -1.656876,0.2538761 -0.400857,0.2405142 -0.400857,0.8284374 v 0.988781 q 0,0.454305 0.320686,0.668095 0.320686,0.200429 1.135762,0.200429 h 1.042228 q 1.349552,0 1.83058,0.49439 0.481029,0.481028 0.481029,1.536618 z" + style="font-size:13.3619px" + id="path882" /> + <path + d="m 69.472442,13.848352 h -3.714608 v -0.962056 h 3.714608 z" + style="font-size:13.3619px" + id="path884" /> + <path + d="m 77.903799,8.5169542 v 6.4537978 q 0,2.351695 -3.393923,2.351695 -0.681456,0 -1.215932,-0.09353 -0.534476,-0.08017 -1.055591,-0.307323 -0.521114,-0.240515 -0.815076,-0.734905 -0.293961,-0.49439 -0.293961,-1.215933 V 8.5169542 h 1.242656 V 14.95739 q 0,0.855162 0.574562,1.175847 0.574562,0.320686 1.590066,0.320686 1.015505,0 1.563343,-0.320686 0.561199,-0.320685 0.561199,-1.175847 V 8.5169542 Z" + style="font-size:13.3619px" + id="path886" /> + <path + d="m 85.907587,14.249209 v 0.62801 q 0,1.389637 -0.788352,1.924113 -0.788353,0.521115 -2.45859,0.521115 -1.068952,0 -2.645656,-0.213791 v -1.082314 q 1.576704,0.334048 2.725827,0.334048 0.921971,0 1.403,-0.200429 0.49439,-0.21379 0.49439,-0.8418 v -1.122399 q 0,-0.547838 -0.320686,-0.77499 -0.307323,-0.240515 -1.122399,-0.240515 h -1.015505 q -1.309466,0 -1.83058,-0.521114 -0.507752,-0.534476 -0.507752,-1.563342 v -0.694819 q 0,-1.0288662 0.721542,-1.5232565 0.734905,-0.4943903 2.685742,-0.4943903 0.935333,0 2.231438,0.1469809 v 0.9754187 q -1.483171,-0.2271523 -2.324971,-0.2271523 -1.242657,0 -1.656875,0.2538761 -0.400857,0.2405142 -0.400857,0.8284374 v 0.988781 q 0,0.454305 0.320685,0.668095 0.320686,0.200429 1.135762,0.200429 h 1.042228 q 1.349552,0 1.83058,0.49439 0.481029,0.481028 0.481029,1.536618 z" + style="font-size:13.3619px" + id="path888" /> + <path + d="m 94.031625,14.008695 v 1.456447 q 0,0.948695 -0.721543,1.33619 -0.721542,0.387496 -2.351694,0.387496 H 87.764894 V 8.5169542 h 3.099961 q 1.122399,0 1.750409,0.2271523 0.641371,0.2271523 0.828437,0.5611998 0.200429,0.3340475 0.200429,0.8551617 v 1.162485 q 0,0.534476 -0.307324,0.815076 -0.293962,0.2806 -0.881885,0.414219 v 0.05345 q 0.694819,0.120257 1.135761,0.481028 0.440943,0.360771 0.440943,0.921971 z m -1.603428,-2.725827 v -1.015505 q 0,-0.4943902 -0.360771,-0.6948187 -0.360772,-0.2004285 -1.202571,-0.2004285 h -1.803857 v 2.9396182 h 1.843942 q 0.828438,0 1.175848,-0.240514 0.347409,-0.253876 0.347409,-0.788352 z m 0.307324,4.128827 v -1.309466 q 0,-0.34741 -0.146981,-0.547838 -0.133619,-0.213791 -0.440943,-0.293962 -0.293962,-0.09353 -0.534476,-0.106895 -0.227152,-0.02672 -0.654733,-0.02672 h -1.89739 v 3.16677 h 1.89739 q 0.975419,0 1.376276,-0.200428 0.400857,-0.200429 0.400857,-0.681457 z" + style="font-size:13.3619px" + id="path890" /> + <path + d="M 97.07815,17.188828 H 95.875579 V 7.8354973 h 1.202571 z" + style="font-size:13.3619px" + id="path892" /> + <path + d="m 104.8815,12.899658 v 2.244799 q 0,0.601285 -0.14698,1.015504 -0.14698,0.414219 -0.37413,0.641371 -0.22715,0.227153 -0.65474,0.34741 -0.41421,0.120257 -0.80171,0.146981 -0.37413,0.02672 -1.00214,0.02672 -0.80172,0 -1.25602,-0.05345 -0.44094,-0.05345 -0.895248,-0.253876 -0.440942,-0.213791 -0.641371,-0.668095 -0.187067,-0.454305 -0.187067,-1.202571 v -2.244799 q 0,-0.641372 0.146981,-1.082314 0.160343,-0.440943 0.374134,-0.681457 0.227152,-0.240515 0.654731,-0.360772 0.42758,-0.133619 0.78835,-0.160342 0.37414,-0.02672 1.01551,-0.02672 0.64137,0 1.00214,0.02672 0.37413,0.02672 0.80171,0.160342 0.42759,0.120257 0.64138,0.360772 0.22715,0.240514 0.37413,0.681457 0.16034,0.440942 0.16034,1.082314 z m -1.22929,2.418503 v -2.618932 q 0,-0.895247 -0.36077,-1.109038 -0.36078,-0.21379 -1.38964,-0.21379 -1.02887,0 -1.38964,0.21379 -0.36077,0.213791 -0.36077,1.109038 v 2.618932 q 0,0.427581 0.0935,0.681457 0.0935,0.253876 0.34741,0.374134 0.25388,0.106895 0.52112,0.133619 0.26723,0.02672 0.78835,0.02672 0.52111,0 0.78835,-0.02672 0.26724,-0.02672 0.52111,-0.133619 0.25388,-0.120258 0.34741,-0.374134 0.0935,-0.253876 0.0935,-0.681457 z" + style="font-size:13.3619px" + id="path894" /> + <path + d="m 111.85643,17.188828 h -1.16249 V 16.57418 q -0.14698,0.748267 -1.95084,0.748267 -1.33619,0 -1.8573,-0.400857 -0.50775,-0.414219 -0.50775,-1.656876 0,-1.082314 0.48103,-1.483171 0.48103,-0.414219 1.8573,-0.414219 h 1.93748 v -1.028866 q 0,-0.534476 -0.33405,-0.721543 -0.32069,-0.187067 -1.2293,-0.187067 -0.98878,0 -2.21807,0.160343 v -0.881885 q 1.32283,-0.120257 2.39178,-0.120257 1.63015,0 2.11118,0.400857 0.48103,0.387495 0.48103,1.630152 z m -1.20257,-1.309467 V 14.11559 h -1.85731 q -0.82844,0 -1.04223,0.213791 -0.20042,0.21379 -0.20042,0.975419 0,0.681456 0.21379,0.962056 0.22715,0.267238 0.93533,0.267238 h 0.24051 q 0.34741,0 0.57456,-0.01336 0.24052,-0.01336 0.53448,-0.06681 0.29396,-0.06681 0.44094,-0.200429 0.16035,-0.14698 0.16035,-0.374134 z" + style="font-size:13.3619px" + id="path896" /> + <path + d="m 119.59297,17.188828 h -1.17585 v -0.761629 q 0,0.34741 -0.58792,0.62801 -0.57456,0.267238 -1.5767,0.267238 -1.403,0 -1.97757,-0.440943 -0.57456,-0.440943 -0.57456,-1.61679 v -2.846085 q 0,-0.975419 0.60129,-1.402999 0.60128,-0.427581 2.01765,-0.427581 0.76162,0 1.40299,0.200428 0.65474,0.200429 0.6681,0.62801 h 0.0267 q -0.0267,-1.095676 -0.0267,-1.6435142 V 7.8354973 h 1.20257 z m -1.20257,-1.696962 v -3.193494 q 0,-0.868524 -1.77713,-0.868524 -1.06895,0 -1.403,0.160343 -0.32069,0.160343 -0.32069,0.708181 v 3.193494 q 0,0.547838 0.36077,0.77499 0.37414,0.227153 1.25602,0.227153 0.98878,0 1.42973,-0.227153 0.4543,-0.227152 0.4543,-0.77499 z" + style="font-size:13.3619px" + id="path898" /> + <path + d="m 126.88855,14.222486 h -4.34261 v 0.975418 q 0,0.681457 0.48103,0.975419 0.48102,0.2806 1.42972,0.2806 0.92197,0 2.17799,-0.320686 v 0.921972 q -1.20257,0.267238 -2.33833,0.267238 -2.96635,0 -2.96635,-1.977562 v -2.552123 q 0,-1.189209 0.68146,-1.696961 0.68146,-0.507752 2.17799,-0.507752 1.403,0 2.04437,0.49439 0.65473,0.481028 0.65473,1.710323 z m -1.20257,-0.788352 v -1.015505 q 0,-0.587924 -0.36077,-0.801714 -0.36077,-0.227152 -1.20257,-0.227152 -0.80171,0 -1.18921,0.21379 -0.38749,0.213791 -0.38749,0.815076 v 1.015505 z" + style="font-size:13.3619px" + id="path900" /> + <path + d="m 132.20659,11.616915 h -0.6681 q -0.82844,0 -1.22929,0.21379 -0.3875,0.213791 -0.3875,0.801715 v 4.556408 h -1.20257 v -6.46716 h 1.13576 v 1.05559 q 0,-0.467667 0.57456,-0.828438 0.58793,-0.360771 1.18921,-0.360771 h 0.58793 z" + style="font-size:13.3619px" + id="path902" /> + </g> <path style="fill:#000000;fill-opacity:1;stroke-width:0.264583" d="m 21.967046,0 c -0.0065,-1.9994448e-6 -0.01188,0.0054 -0.01188,0.01188 v 44.976581 c 0,0.0065 0.0054,0.01188 0.01188,0.01188 h 2.000395 c 0.0065,0 0.01188,-0.0054 0.01188,-0.01188 V 0.011885 c 0,-0.0065 -0.0054,-0.011879999445 -0.01188,-0.011879999445 z" id="rect1136" inkscape:connector-curvature="0" sodipodi:nodetypes="csssssssc" /> - <g - inkscape:label="CREATIVE_COMMONS_SHIELD" - transform="matrix(0.1713381,0,0,0.17131892,77.526533,36.479842)" - id="g287" - inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-sa.png" - inkscape:export-xdpi="300.23013" - inkscape:export-ydpi="300.23013"> - <path - id="path3817_2_" - nodetypes="ccccccc" - d="m 182.23532,75.39014 114.06396,0.20312 c 1.59375,0 3.01758,-0.23682 3.01758,3.18018 l -0.13965,37.56689 H 179.3569 V 78.63379 c 0,-1.68457 0.16309,-3.24365 2.87842,-3.24365 z" - style="fill:#aab2ab" /> - <g - id="g5908_2_" - transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)"> - <path - id="path5906_2_" - cx="296.35416" - ry="22.939548" - cy="264.3577" - type="arc" - rx="22.939548" - d="m 187.20944,-55.6792 c 0.006,8.68024 -7.02786,15.72095 -15.7081,15.72708 -8.68021,0.005 -15.72205,-7.02786 -15.72708,-15.70804 0,-0.0067 0,-0.01233 0,-0.01904 -0.005,-8.68134 7.02783,-15.72205 15.70807,-15.72711 8.68134,-0.0056 15.72208,7.02789 15.72711,15.70807 0,0.0056 0,0.01233 0,0.01904 z" - style="fill:#ffffff" /> - <g - id="g5706_2_" - transform="translate(-289.6157,99.0653)"> - <path - id="path5708_2_" - d="m 473.88455,-167.54724 c 3.48541,3.48596 5.22839,7.75391 5.22839,12.80273 0,5.04938 -1.7128,9.27148 -5.13834,12.66736 -3.63531,3.5766 -7.93179,5.36432 -12.88947,5.36432 -4.89777,0 -9.11987,-1.77261 -12.6651,-5.31955 -3.54584,-3.54581 -5.31845,-7.78299 -5.31845,-12.71213 0,-4.92859 1.77261,-9.19598 5.31845,-12.80273 3.4552,-3.48651 7.67725,-5.22894 12.6651,-5.22894 5.04829,0 9.31401,1.74243 12.79942,5.22894 z m -23.11798,2.34485 c -2.94675,2.97638 -4.41956,6.46289 -4.41956,10.46234 0,3.99835 1.45828,7.4552 4.37424,10.37067 2.91653,2.9165 6.38849,4.37476 10.41705,4.37476 4.02853,0 7.53018,-1.47281 10.50656,-4.41901 2.8259,-2.73584 4.23941,-6.17706 4.23941,-10.32642 0,-4.11804 -1.43646,-7.61292 -4.30768,-10.48474 -2.87064,-2.87067 -6.34988,-4.30652 -10.43829,-4.30652 -4.08837,0 -7.54638,1.44318 -10.37173,4.32892 z m 7.75449,8.70312 c -0.45032,-0.98163 -1.12433,-1.47223 -2.02325,-1.47223 -1.58914,0 -2.38342,1.06952 -2.38342,3.2085 0,2.13959 0.79428,3.20911 2.38342,3.20911 1.04938,0 1.79895,-0.5213 2.24866,-1.56512 l 2.20276,1.17303 c -1.04993,1.86548 -2.62506,2.79901 -4.72549,2.79901 -1.6199,0 -2.91763,-0.4967 -3.89206,-1.48956 -0.97607,-0.99341 -1.46274,-2.36273 -1.46274,-4.10797 0,-1.71558 0.50229,-3.07709 1.50748,-4.08563 1.00519,-1.00793 2.25705,-1.51251 3.75781,-1.51251 2.22012,0 3.80984,0.87488 4.77081,2.62286 z m 10.36334,0 c -0.45087,-0.98163 -1.11148,-1.47223 -1.98239,-1.47223 -1.62106,0 -2.43213,1.06952 -2.43213,3.2085 0,2.13959 0.81107,3.20911 2.43213,3.20911 1.05103,0 1.78717,-0.5213 2.20724,-1.56512 l 2.25201,1.17303 c -1.04825,1.86548 -2.62119,2.79901 -4.71768,2.79901 -1.61771,0 -2.91263,-0.4967 -3.88647,-1.48956 -0.97217,-0.99341 -1.45938,-2.36273 -1.45938,-4.10797 0,-1.71558 0.49448,-3.07709 1.48288,-4.08563 0.98782,-1.00793 2.24527,-1.51251 3.77347,-1.51251 2.21619,0 3.80368,0.87488 4.76132,2.62286 z" /> - </g> - </g> - <path - d="M 297.29639,74.91064 H 181.06688 c -1.24658,0 -2.26074,1.01465 -2.26074,2.26123 v 39.49561 c 0,0.28174 0.22852,0.51074 0.51025,0.51074 h 119.73 c 0.28174,0 0.51074,-0.229 0.51074,-0.51074 v -39.4956 c 0,-1.24659 -1.01416,-2.26124 -2.26074,-2.26124 z m -116.22951,1.02149 h 116.22951 c 0.68359,0 1.23926,0.55615 1.23926,1.23975 0,0 0,15.91943 0,27.41846 H 215.4619 c -3.04492,5.50537 -8.91113,9.24365 -15.64355,9.24365 -6.73535,0 -12.6001,-3.73486 -15.64355,-9.24365 h -4.34814 c 0,-11.49902 0,-27.41846 0,-27.41846 -2e-5,-0.6836 0.55663,-1.23975 1.24022,-1.23975 z" - id="path294" /> - <g - enable-background="new " - id="g296"> - <path - d="m 265.60986,112.8833 c 0.0801,0.15576 0.1875,0.28174 0.32129,0.37842 0.13379,0.0962 0.29004,0.16797 0.46973,0.21436 0.18066,0.0469 0.36719,0.0703 0.55957,0.0703 0.12988,0 0.26953,-0.0107 0.41895,-0.0327 0.14844,-0.0215 0.28809,-0.064 0.41895,-0.12598 0.12988,-0.062 0.23926,-0.14795 0.3252,-0.25684 0.0879,-0.10889 0.13086,-0.24707 0.13086,-0.41553 0,-0.18018 -0.0576,-0.32617 -0.17285,-0.43848 -0.11426,-0.1123 -0.26562,-0.20508 -0.45215,-0.28027 -0.18555,-0.0742 -0.39746,-0.13965 -0.63281,-0.1958 -0.23633,-0.0562 -0.47559,-0.11816 -0.71777,-0.18701 -0.24902,-0.062 -0.49121,-0.13818 -0.72754,-0.22852 -0.23535,-0.0898 -0.44727,-0.20703 -0.63379,-0.3501 -0.18652,-0.14307 -0.33691,-0.32178 -0.45215,-0.53662 -0.11426,-0.21484 -0.17188,-0.47461 -0.17188,-0.7793 0,-0.34277 0.0732,-0.63965 0.21875,-0.8916 0.14648,-0.25195 0.33789,-0.46191 0.57422,-0.63037 0.23535,-0.16797 0.50293,-0.29248 0.80176,-0.37354 0.29785,-0.0806 0.59668,-0.12109 0.89453,-0.12109 0.34863,0 0.68262,0.0391 1.00293,0.11719 0.31934,0.0776 0.60449,0.2041 0.85254,0.37842 0.24902,0.17432 0.44629,0.39697 0.59277,0.66797 0.14551,0.271 0.21875,0.59961 0.21875,0.98535 h -1.42188 c -0.0127,-0.19922 -0.0547,-0.36426 -0.125,-0.49463 -0.0713,-0.13086 -0.16602,-0.2334 -0.2832,-0.30859 -0.11816,-0.0742 -0.25293,-0.12744 -0.4043,-0.1582 -0.15234,-0.0312 -0.31738,-0.0469 -0.49707,-0.0469 -0.11719,0 -0.23535,0.0127 -0.35254,0.0371 -0.11816,0.0254 -0.22461,0.0688 -0.32031,0.13086 -0.0967,0.0625 -0.17578,0.14014 -0.2373,0.2334 -0.0615,0.0937 -0.0928,0.21191 -0.0928,0.35498 0,0.13086 0.0244,0.23682 0.0742,0.31738 0.0498,0.0811 0.14844,0.15576 0.29395,0.22412 0.14551,0.0684 0.34766,0.13721 0.60547,0.20557 0.25781,0.0684 0.59473,0.15576 1.01172,0.26123 0.12402,0.0249 0.2959,0.0703 0.5166,0.13574 0.2207,0.0654 0.43945,0.16943 0.65723,0.3125 0.21777,0.14355 0.40527,0.33496 0.56445,0.57422 0.1582,0.23975 0.2373,0.54639 0.2373,0.91992 0,0.30518 -0.0596,0.58838 -0.17773,0.84961 -0.11816,0.26172 -0.29395,0.4873 -0.52734,0.67676 -0.2334,0.19043 -0.52246,0.33789 -0.86719,0.44385 -0.3457,0.10596 -0.74609,0.15869 -1.19922,0.15869 -0.36719,0 -0.72363,-0.0454 -1.06934,-0.13574 -0.34473,-0.0903 -0.65039,-0.23242 -0.91504,-0.42578 -0.26367,-0.19336 -0.47363,-0.43994 -0.62988,-0.73877 -0.15527,-0.29932 -0.22949,-0.65381 -0.22363,-1.06494 h 1.42188 c -3e-5,0.22412 0.04,0.41406 0.12106,0.56933 z" - id="path298" - style="fill:#ffffff" /> - <path - d="m 273.8667,107.8667 2.49316,6.66406 h -1.52246 l -0.50391,-1.48438 h -2.49316 l -0.52246,1.48438 h -1.47461 l 2.52051,-6.66406 z m 0.084,4.08594 -0.83984,-2.44336 h -0.0186 l -0.86914,2.44336 z" - id="path300" - style="fill:#ffffff" /> - </g> - <g - enable-background="new " - id="g302"> - <path - d="m 239.17821,107.8667 c 0.31738,0 0.60742,0.0283 0.86914,0.084 0.26172,0.0561 0.48633,0.14795 0.67383,0.27539 0.18652,0.12744 0.33203,0.29688 0.43457,0.5083 0.10254,0.21142 0.1543,0.47266 0.1543,0.78369 0,0.33594 -0.0762,0.61523 -0.22949,0.83936 -0.15234,0.22412 -0.37891,0.40723 -0.67773,0.55029 0.41211,0.11816 0.71973,0.3252 0.92285,0.62109 0.20312,0.29589 0.30469,0.65234 0.30469,1.06934 0,0.33594 -0.0654,0.62695 -0.19629,0.87305 -0.13086,0.24561 -0.30762,0.44629 -0.52832,0.60205 -0.22168,0.15576 -0.47461,0.271 -0.75781,0.34521 -0.28418,0.0752 -0.5752,0.1123 -0.875,0.1123 h -3.23633 v -6.66406 h 3.14159 z m -0.1875,2.69531 c 0.26172,0 0.47656,-0.062 0.64551,-0.18604 0.16797,-0.12451 0.25195,-0.32568 0.25195,-0.60498 0,-0.15527 -0.0283,-0.28271 -0.084,-0.38184 -0.0566,-0.0996 -0.13086,-0.17676 -0.22461,-0.23291 -0.0937,-0.0557 -0.20117,-0.0947 -0.32227,-0.11621 -0.12207,-0.022 -0.24805,-0.0327 -0.37891,-0.0327 h -1.37305 v 1.55469 z m 0.0859,2.82813 c 0.14355,0 0.28027,-0.0137 0.41113,-0.042 0.13086,-0.0278 0.24707,-0.0747 0.34668,-0.13965 0.0996,-0.0654 0.17871,-0.1543 0.23828,-0.26611 0.0596,-0.11181 0.0889,-0.25488 0.0889,-0.4292 0,-0.3418 -0.0967,-0.58594 -0.29004,-0.73193 -0.19336,-0.14599 -0.44922,-0.21924 -0.7666,-0.21924 h -1.59961 v 1.82812 z" - id="path304" - style="fill:#ffffff" /> - <path - d="m 241.88914,107.8667 h 1.64355 l 1.56055,2.63184 1.55078,-2.63184 h 1.63379 l -2.47363,4.10645 v 2.55762 h -1.46875 v -2.59473 z" - id="path306" - style="fill:#ffffff" /> - </g> - <g - id="g6316_1_" - transform="matrix(0.624995,0,0,0.624995,391.2294,176.9332)"> - <path - id="path6318_1_" - cx="475.97119" - ry="29.209877" - cy="252.08646" - type="arc" - rx="29.209877" - d="m -175.0083,-139.1153 c 0.006,9.4118 -7.61725,17.04779 -17.02982,17.05481 -9.41101,0.007 -17.047,-7.61725 -17.05481,-17.02979 0,-0.008 0,-0.0172 0,-0.025 -0.006,-9.41254 7.6188,-17.047 17.02982,-17.05481 9.41257,-0.007 17.04855,7.61804 17.05481,17.02985 0,0.009 0,0.0164 0,0.025 z" - style="fill:#ffffff" /> - <g - id="g6320_1_" - transform="translate(-23.9521,-89.72962)"> - <path - id="path6322_1_" - d="m -168.2204,-68.05536 c -5.17194,0 -9.54852,1.80469 -13.13135,5.41333 -3.67661,3.73444 -5.51413,8.1532 -5.51413,13.25635 0,5.10315 1.83752,9.49152 5.51413,13.1626 3.67502,3.67194 8.05316,5.50787 13.13135,5.50787 5.14066,0 9.59537,-1.85156 13.36728,-5.55475 3.55005,-3.51562 5.3266,-7.88831 5.3266,-13.11572 0,-5.22662 -1.8078,-9.64697 -5.42191,-13.25635 -3.61407,-3.60864 -8.03756,-5.41333 -13.27197,-5.41333 z m 0.0469,3.36017 c 4.23752,0 7.836,1.49298 10.79697,4.48053 2.98907,2.9563 4.48441,6.56567 4.48441,10.82898 0,4.29382 -1.46252,7.85712 -4.39224,10.68915 -3.08438,3.04926 -6.71411,4.57349 -10.88913,4.57349 -4.17505,0 -7.7735,-1.5094 -10.79541,-4.52661 -3.02188,-3.01953 -4.53284,-6.59692 -4.53284,-10.73602 0,-4.13831 1.52658,-7.74847 4.57971,-10.82898 2.92815,-2.98756 6.51098,-4.48054 10.74853,-4.48054 z" /> - <path - id="path6324_1_" - d="m -176.49548,-52.02087 c 0.74377,-4.69769 4.05161,-7.20862 8.1954,-7.20862 5.96097,0 9.59225,4.32501 9.59225,10.09229 0,5.62738 -3.86411,9.99927 -9.686,9.99927 -4.00473,0 -7.58914,-2.46484 -8.24228,-7.30084 h 4.70319 c 0.14062,2.51099 1.77032,3.39459 4.09845,3.39459 2.65317,0 4.37817,-2.4649 4.37817,-6.23291 0,-3.95233 -1.49063,-6.04535 -4.28598,-6.04535 -2.04846,0 -3.8172,0.74457 -4.19064,3.30157 l 1.36874,-0.007 -3.70316,3.7016 -3.7016,-3.7016 z" /> - </g> - </g> - <g - id="g313"> - <circle - cx="242.56226" - cy="90.224609" - r="10.8064" - id="circle315" - style="fill:#ffffff" /> - <g - id="g317"> - <path - d="m 245.68994,87.09766 c 0,-0.4165 -0.33789,-0.75342 -0.75391,-0.75342 h -4.77246 c -0.41602,0 -0.75391,0.33691 -0.75391,0.75342 v 4.77295 h 1.33105 v 5.65234 h 3.61719 v -5.65234 h 1.33203 v -4.77295 z" - id="path319" /> - <circle - cx="242.5498" - cy="84.083008" - r="1.63232" - id="circle321" /> - </g> - <path - clip-rule="evenodd" - d="m 242.53467,78.31836 c -3.23145,0 -5.96826,1.12744 -8.20752,3.38379 -2.29785,2.33301 -3.44629,5.09521 -3.44629,8.28418 0,3.18897 1.14844,5.93213 3.44629,8.22705 2.29785,2.29443 5.03418,3.44189 8.20752,3.44189 3.21289,0 5.99805,-1.15674 8.35352,-3.47168 2.2207,-2.19678 3.33008,-4.92969 3.33008,-8.19727 0,-3.26758 -1.12891,-6.02881 -3.3877,-8.28418 -2.25879,-2.25634 -5.02442,-3.38378 -8.2959,-3.38378 z m 0.0293,2.09961 c 2.64844,0 4.89746,0.93359 6.74707,2.80078 1.87012,1.84717 2.80469,4.10352 2.80469,6.76758 0,2.68359 -0.91504,4.91113 -2.74609,6.68066 -1.92773,1.90576 -4.19629,2.8584 -6.80566,2.8584 -2.60937,0 -4.8584,-0.94287 -6.74658,-2.82959 -1.88965,-1.88623 -2.8335,-4.12256 -2.8335,-6.70947 0,-2.58643 0.9541,-4.84229 2.8623,-6.76758 1.83057,-1.86719 4.07031,-2.80078 6.71777,-2.80078 z" - id="path323" - style="fill-rule:evenodd" /> - </g> - </g> - <text - inkscape:label="HeyMomLookAtMe" - inkscape:transform-center-y="-0.1984375" - inkscape:transform-center-x="-0.33072917" - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" - x="108.57767" - y="48.318489" - id="text921"><tspan - sodipodi:role="line" - x="108.57767" - y="48.318489" - style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;font-family:'Nunito Sans';-inkscape-font-specification:'Nunito Sans Bold';stroke-width:0.264583" - id="tspan923">Dmitry Isaenko</tspan></text> </svg> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 363f9ba..d774ae0 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -88,6 +88,8 @@ public class AppPreferences { public String getHostIp(){ return preferences.get("HOSTIP", "0.0.0.0").replaceAll("(\\s)|(\t)", "");} // who the hell said 'paranoid'? public void setHostIp(String ip){preferences.put("HOSTIP", ip);} + public void give(){preferences.putBoolean("man", true);} + public boolean take(){return preferences.getBoolean("man", false);} public String getHostPort(){ String value = preferences.get("HOSTPORT", "6042"); if (!value.matches("^[0-9]{1,5}$")) @@ -138,4 +140,10 @@ public class AppPreferences { public String getLastOpenedTab(){ return preferences.get("recent_tab", ""); } public void setLastOpenedTab(String tabId){ preferences.put("recent_tab", tabId); } + // Patches + public String getKeysLocation(){ return preferences.get("keys", ""); } + public void setKeysLocation(String path){ preferences.put("keys", path); } + + public String getPatchesSaveToLocation(){ return FilesHelper.getRealFolder(preferences.get("patches_saveto", System.getProperty("user.home"))); } + public void setPatchesSaveToLocation(String value){ preferences.put("patches_saveto", value); } } diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 99fa2d0..cc051b3 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -43,7 +43,7 @@ public class NSLMainController implements Initializable { @FXML private TabPane mainTabPane; @FXML - private Tab GamesTabHolder, RCMTabHolder, SMTabHolder; + private Tab GamesTabHolder, RCMTabHolder, SMTabHolder, PatchesTabHolder; @FXML private GamesController GamesTabController; @@ -55,6 +55,8 @@ public class NSLMainController implements Initializable { private RcmController RcmTabController; @FXML private NxdtController NXDTabController; + @FXML + private PatchesController PatchesTabController; @Override public void initialize(URL url, ResourceBundle rb) { @@ -71,7 +73,7 @@ public class NSLMainController implements Initializable { if (AppPreferences.getInstance().getAutoCheckUpdates()){ checkForUpdates(); } - + if (! AppPreferences.getInstance().take()) mainTabPane.getTabs().remove(3); openLastOpenedTab(); } private void checkForUpdates(){ @@ -127,6 +129,8 @@ public class NSLMainController implements Initializable { public RcmController getRcmCtrlr(){ return RcmTabController; } public NxdtController getNXDTabController(){ return NXDTabController; } + + public PatchesController getPatchesTabController(){ return PatchesTabController; } /** * Save preferences before exit * */ @@ -136,7 +140,7 @@ public class NSLMainController implements Initializable { SplitMergeTabController.updatePreferencesOnExit(); // NOTE: This shit above should be re-written to similar pattern RcmTabController.updatePreferencesOnExit(); NXDTabController.updatePreferencesOnExit(); - + PatchesTabController.updatePreferencesOnExit(); saveLastOpenedTab(); } @@ -152,6 +156,9 @@ public class NSLMainController implements Initializable { case "SMTabHolder": mainTabPane.getSelectionModel().select(SMTabHolder); break; + case "PatchesTabHolder": + mainTabPane.getSelectionModel().select(PatchesTabHolder); + break; } } private void saveLastOpenedTab(){ diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java new file mode 100644 index 0000000..66aaaf7 --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -0,0 +1,225 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.input.DragEvent; +import javafx.scene.input.TransferMode; + +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import nsusbloader.AppPreferences; +import nsusbloader.FilesHelper; +import nsusbloader.MediatorControl; +import nsusbloader.NSLDataTypes.EModule; +import nsusbloader.ServiceWindow; +import nsusbloader.Utilities.patches.es.EsPatchMaker; + +// TODO: CLI SUPPORT +public class PatchesController implements Initializable { + @FXML + private VBox patchesToolPane; + @FXML + private Button selFwFolderBtn, selProdKeysBtn, makeEsBtn; + @FXML + private Label shortNameFirmwareLbl, locationFirmwareLbl, saveToLbl, shortNameKeysLbl, locationKeysLbl, statusLbl; + private Thread workThread; + + private String previouslyOpenedPath; + private ResourceBundle resourceBundle; + private Region convertRegion; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + this.previouslyOpenedPath = System.getProperty("user.home"); + + String myRegexp; + if (File.separator.equals("/")) + myRegexp = "^.+/"; + else + myRegexp = "^.+\\\\"; + locationFirmwareLbl.textProperty().addListener((observableValue, currentText, updatedText) -> + shortNameFirmwareLbl.setText(updatedText.replaceAll(myRegexp, ""))); + + locationKeysLbl.textProperty().addListener((observableValue, currentText, updatedText) -> + shortNameKeysLbl.setText(updatedText.replaceAll(myRegexp, ""))); + + convertRegion = new Region(); + convertRegion.getStyleClass().add("regionCake"); + makeEsBtn.setGraphic(convertRegion); + + AppPreferences preferences = AppPreferences.getInstance(); + String keysLocation = preferences.getKeysLocation(); + File keysFile = new File(keysLocation); + + if (keysFile.exists() && keysFile.isFile()) { + locationKeysLbl.setText(keysLocation); + } + + saveToLbl.setText(preferences.getPatchesSaveToLocation()); + //makeEsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); + makeEsBtn.setOnAction(actionEvent -> makeEs()); + } + + /** + * Drag-n-drop support (dragOver consumer) + * */ + @FXML + private void handleDragOver(DragEvent event){ + if (event.getDragboard().hasFiles()) + event.acceptTransferModes(TransferMode.ANY); + event.consume(); + } + /** + * Drag-n-drop support (drop consumer) + * */ + @FXML + private void handleDrop(DragEvent event){ + List<File> filesDropped = event.getDragboard().getFiles(); + for (File file : filesDropped){ + if (file.isDirectory()) { + locationFirmwareLbl.setText(file.getAbsolutePath()); + continue; + } + String fileName = file.getName().toLowerCase(); + if ((fileName.endsWith(".dat")) || + (fileName.endsWith(".keys") && + ! fileName.equals("dev.keys") && + ! fileName.equals("title.keys"))) + locationKeysLbl.setText(file.getAbsolutePath()); + } + event.setDropCompleted(true); + event.consume(); + } + @FXML + private void selectFirmware(){ + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("tabPatches_Lbl_Firmware")); + directoryChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + File firmware = directoryChooser.showDialog(patchesToolPane.getScene().getWindow()); + if (firmware == null) + return; + locationFirmwareLbl.setText(firmware.getAbsolutePath()); + } + @FXML + private void selectSaveTo(){ + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("tabSplMrg_Btn_SelectFolder")); + directoryChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + File saveToDir = directoryChooser.showDialog(patchesToolPane.getScene().getWindow()); + if (saveToDir == null) + return; + saveToLbl.setText(saveToDir.getAbsolutePath()); + } + @FXML + private void selectProdKeys(){ + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(resourceBundle.getString("tabPatches_Lbl_Keys")); + fileChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("keys", "*.dat", "*.keys")); + File keys = fileChooser.showOpenDialog(patchesToolPane.getScene().getWindow()); + + if (keys != null && keys.exists()) { + locationKeysLbl.setText(keys.getAbsolutePath()); + } + } + + private void makeEs(){ + if (locationFirmwareLbl.getText().isEmpty() || locationKeysLbl.getText().isEmpty()){ + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("tabPatches_ServiceWindowMessage")); + return; + } + + if (workThread != null && workThread.isAlive()) + return; + statusLbl.setText(""); + + if (MediatorControl.getInstance().getTransferActive()) { + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); + return; + } + + EsPatchMaker esPatchMaker = new EsPatchMaker(locationFirmwareLbl.getText(), locationKeysLbl.getText(), + saveToLbl.getText()); + workThread = new Thread(esPatchMaker); + + workThread.setDaemon(true); + workThread.start(); + } + private void interruptProcessOfPatchMaking(){ + if (workThread == null || ! workThread.isAlive()) + return; + + workThread.interrupt(); + } + + public void notifyThreadStarted(boolean isActive, EModule type) { + if (! type.equals(EModule.PATCHES)) { + patchesToolPane.setDisable(isActive); + return; + } + + convertRegion.getStyleClass().clear(); + + if (isActive) { + MediatorControl.getInstance().getContoller().logArea.clear(); + convertRegion.getStyleClass().add("regionStop"); + + makeEsBtn.setOnAction(e-> interruptProcessOfPatchMaking()); + makeEsBtn.setText(resourceBundle.getString("btn_Stop")); + makeEsBtn.getStyleClass().remove("buttonUp"); + makeEsBtn.getStyleClass().add("buttonStop"); + } + else { + convertRegion.getStyleClass().add("regionCake"); + + makeEsBtn.setOnAction(actionEvent -> makeEs()); + makeEsBtn.setText(resourceBundle.getString("tabPatches_Btn_MakeEs")); + makeEsBtn.getStyleClass().remove("buttonStop"); + makeEsBtn.getStyleClass().add("buttonUp"); + } + } + + public void setOneLineStatus(boolean statusSuccess){ + if (statusSuccess) + statusLbl.setText(resourceBundle.getString("done_txt")); + else + statusLbl.setText(resourceBundle.getString("failure_txt")); + } + + void updatePreferencesOnExit(){ + AppPreferences.getInstance().setPatchesSaveToLocation(saveToLbl.getText()); + if (locationKeysLbl.getText().isEmpty()) + return; + AppPreferences.getInstance().setKeysLocation(locationKeysLbl.getText()); + } + +} \ No newline at end of file diff --git a/src/main/java/nsusbloader/MediatorControl.java b/src/main/java/nsusbloader/MediatorControl.java index 3717ad2..b15b409 100644 --- a/src/main/java/nsusbloader/MediatorControl.java +++ b/src/main/java/nsusbloader/MediatorControl.java @@ -40,11 +40,12 @@ public class MediatorControl { } public NSLMainController getContoller(){ return mainController; } - public GamesController getGamesController(){ return mainController.getGamesCtrlr(); }; - public SettingsController getSettingsController(){ return mainController.getSettingsCtrlr(); }; - public SplitMergeController getSplitMergeController(){ return mainController.getSmCtrlr(); }; - public RcmController getRcmController(){ return mainController.getRcmCtrlr(); }; - public NxdtController getNxdtController(){ return mainController.getNXDTabController(); }; + public GamesController getGamesController(){ return mainController.getGamesCtrlr(); } + public SettingsController getSettingsController(){ return mainController.getSettingsCtrlr(); } + public SplitMergeController getSplitMergeController(){ return mainController.getSmCtrlr(); } + public RcmController getRcmController(){ return mainController.getRcmCtrlr(); } + public NxdtController getNxdtController(){ return mainController.getNXDTabController(); } + public PatchesController getPatchesController(){ return mainController.getPatchesTabController(); } public ResourceBundle getResourceBundle(){ return mainController.getResourceBundle(); @@ -56,6 +57,7 @@ public class MediatorControl { getSplitMergeController().notifyThreadStarted(isActive, appModuleType); getRcmController().notifyThreadStarted(isActive, appModuleType); getNxdtController().notifyThreadStarted(isActive, appModuleType); + getPatchesController().notifyThreadStarted(isActive, appModuleType); } public synchronized boolean getTransferActive() { return this.isTransferActive.get(); } } diff --git a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java index ba4b294..08add56 100644 --- a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java +++ b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java @@ -112,6 +112,8 @@ public class MessagesConsumer extends AnimationTimer { case SPLIT_MERGE_TOOL: MediatorControl.getInstance().getSplitMergeController().setOneLineStatus(oneLinerStatus.get()); break; + case PATCHES: + MediatorControl.getInstance().getPatchesController().setOneLineStatus(oneLinerStatus.get()); } this.stop(); } diff --git a/src/main/java/nsusbloader/NSLDataTypes/EModule.java b/src/main/java/nsusbloader/NSLDataTypes/EModule.java index b8331a0..b144d7b 100644 --- a/src/main/java/nsusbloader/NSLDataTypes/EModule.java +++ b/src/main/java/nsusbloader/NSLDataTypes/EModule.java @@ -22,5 +22,6 @@ public enum EModule { USB_NET_TRANSFERS, SPLIT_MERGE_TOOL, RCM, - NXDT + NXDT, + PATCHES } diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index aa03af7..bd70c80 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -28,6 +28,7 @@ import nsusbloader.Controllers.NSLMainController; import nsusbloader.cli.CommandLineInterface; import java.util.Locale; +import java.util.Objects; import java.util.ResourceBundle; public class NSLMain extends Application { @@ -47,10 +48,10 @@ public class NSLMain extends Application { Parent root = loader.load(); primaryStage.getIcons().addAll( - new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")), - new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")), - new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")), - new Image(getClass().getResourceAsStream("/res/app_icon128x128.png")) + new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon32x32.png"))), + new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon48x48.png"))), + new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon64x64.png"))), + new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon128x128.png"))) ); primaryStage.setTitle("NS-USBloader "+appVersion); diff --git a/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java new file mode 100644 index 0000000..d3e2024 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java @@ -0,0 +1,526 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es; + +import libKonogonka.Converter; +import nsusbloader.Main; +import nsusbloader.NSLMain; + +public class BinToAsmPrinter { + static { + boolean notWindows = ! System.getProperty("os.name").toLowerCase().contains("windows"); + + if(notWindows && NSLMain.isCli){ + ANSI_RESET = "\u001B[0m"; + ANSI_GREEN = "\u001B[32m"; + ANSI_BLUE = "\u001B[34m"; + ANSI_YELLOW = "\u001B[33m"; + ANSI_PURPLE = "\u001B[35m"; + ANSI_CYAN = "\u001B[36m"; + ANSI_RED = "\u001B[31m"; + } + else { + ANSI_RESET = ANSI_RED = ANSI_GREEN = ANSI_BLUE = ANSI_YELLOW = ANSI_PURPLE = ANSI_CYAN = ""; + } + } + private static final String ANSI_RESET; + private static final String ANSI_RED; + private static final String ANSI_GREEN; + private static final String ANSI_BLUE; + private static final String ANSI_YELLOW; + private static final String ANSI_PURPLE; + private static final String ANSI_CYAN; + + public static String print(int instructionExpression, int offset){ + if (instructionExpression == 0xd503201f) + return printNOP(instructionExpression); + + if ((instructionExpression & 0x7FE0FFE0) == 0x2A0003E0) { + return printMOVRegister(instructionExpression); + } + + switch ((instructionExpression >> 23 & 0b011111111)){ + case 0xA5: + return printMOV(instructionExpression); + case 0x62: + if (((instructionExpression & 0x1f) == 0x1f)){ + return printCMN(instructionExpression, offset); + } + } + + switch (instructionExpression >> 24 & 0xff) { + case 0x34: + case 0xb4: + return printCBZ(instructionExpression, offset); + case 0xb5: + case 0x35: + return printCBNZ(instructionExpression, offset); + case 0x36: + case 0xb6: + return printTBZ(instructionExpression, offset); + case 0x54: + return printBConditional(instructionExpression, offset); + } + switch ((instructionExpression >> 26 & 0b111111)) { + case 0x5: + return printB(instructionExpression, offset); + case 0x25: + return printBL(instructionExpression, offset); + } + + return printUnknown(instructionExpression); + } + public static String printSimplified(int instructionExpression, int offset){ + if (instructionExpression == 0xd503201f) + return printNOPSimplified(instructionExpression, offset); + + if ((instructionExpression & 0x7FE0FFE0) == 0x2A0003E0) { + return printMOVRegisterSimplified(instructionExpression, offset); + } + + switch (instructionExpression >> 22 & 0b1011111111) { + case 0x2e5: + return printLRDImmUnsignSimplified(instructionExpression, offset); + case 0xe5: + return printLRDBImmUnsignSimplified(instructionExpression, offset); + } + + if ((instructionExpression >> 21 & 0x7FF) == 0x1C2) + return printLDURBSimplified(instructionExpression, offset); + + // same to (afterJumpExpression >> 23 & 0x1F9) != 0xA1 + switch (instructionExpression >> 22 & 0x1FF){ + case 0xA3: // 0b10100011 + case 0xA7: // 0b10100111 + case 0xA5: // 0b10100101 + return printLDPSimplified(instructionExpression, offset); + } + + switch ((instructionExpression >> 23 & 0xff)){ + case 0xA5: + return printMOVSimplified(instructionExpression, offset); + case 0x22: + return printADDSimplified(instructionExpression, offset); + case 0x62: + if (((instructionExpression & 0x1f) == 0x1f)){ + return printCMNSimplified(instructionExpression, offset); + } + case 0xA2: + return printSUBSimplified(instructionExpression, offset); + } + + switch (instructionExpression >> 24 & 0xff) { + case 0x34: + case 0xb4: + return printCBZSimplified(instructionExpression, offset); + case 0xb5: + case 0x35: + return printCBNZSimplified(instructionExpression, offset); + case 0x36: + case 0xb6: + return printTBZSimplified(instructionExpression, offset); + case 0x54: + return printBConditionalSimplified(instructionExpression, offset); + } + + switch ((instructionExpression >> 26 & 0b111111)) { + case 0x5: + return printBSimplified(instructionExpression, offset); + case 0x25: + return printBLSimplified(instructionExpression, offset); + } + return printUnknownSimplified(instructionExpression, offset); + } + + private static String printCBZ(int instructionExpression, int offset){ + int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + offset) & 0xfffff; + + return String.format(ANSI_YELLOW + "sf == 0 ? <Wt> else <Xt>\n" + + "CBZ <?t>, <label> |.....CBZ signature......|\n" + + ANSI_CYAN + " sf 0 1 1 0 1 0 0 |imm19..........................................................||Rd.............|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n\n"+ + ANSI_YELLOW + "CBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + + private static String printCBNZ(int instructionExpression, int offset){ + int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + offset) & 0xfffff; + + return String.format(ANSI_YELLOW + "sf == 0 ? <Wt> else <Xt>\n" + + "CBNZ <?t>, <label> |.....CBZ signature......|\n" + + ANSI_CYAN + " sf 0 1 1 0 1 0 |imm19..........................................................||Rd.............|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n\n"+ + ANSI_YELLOW + "CBNZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + private static String printCMN(int instructionExpression, int offset){ + int Rn = instructionExpression >> 5 & 0x1F; + int imm = instructionExpression >> 10 & 0xFFF; + + return String.format(ANSI_YELLOW + "sf == 0 ? <Wt> else <Xt>\n" + + "CMN <?n>, <label> |.....CMN signature...........| |..CMN signature.|\n" + + ANSI_CYAN+" sf 0 1 1 0 0 0 1 0 |imm12......................................||Rn.............| 1 1 1 1 1" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n\n" + + ANSI_YELLOW + "CMN " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (instructionExpression >> 31 == 0) ? "w" : "x", Rn, imm); + } + + private static String printB(int instructionExpression, int offset){ + int conditionalJumpLocationPatch = ((instructionExpression & 0x3ffffff) * 4 + offset) & 0xfffff; + + return String.format(ANSI_YELLOW+"B <label> |....B signature...|\n" + + " "+ANSI_CYAN+" 0 0 0 1 0 1 |imm26...................................................................................|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "%s " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + ((instructionExpression >> 26 & 0b111111) == 5)?"B":"Some weird stuff", + conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); + } + + + private static String printBL(int instructionExpression, int offset){ + int conditionalJumpLocationPatch = ((instructionExpression & 0x3ffffff) * 4 + offset) & 0xfffff; + + return String.format(ANSI_YELLOW+"BL <label> |...BL signature...|\n" + + " "+ANSI_CYAN+" 1 0 0 1 0 1 |imm26...................................................................................|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "%s " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + ((instructionExpression >> 26 & 0b111111) == 25)?"BL":"Some weird stuff", + conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); + } + + + private static String printMOV(int instructionExpression){ + int imm16 = instructionExpression >> 5 & 0xFFFF; + int sfHw = (instructionExpression >> 22 & 1); + + return String.format(ANSI_YELLOW + "sf == 0 && hw == 0x ? <Wt> else <Xt>\n" + + "MOV <?t>, <label> |.....MOV signature...........|\n" + + ANSI_CYAN +" sf 1 0 1 0 0 1 0 1 |hw...|imm16.................................................||Rd.............|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "MOV " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + "\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (sfHw == 0) ? "w" : "x", (instructionExpression & 0b11111), imm16); + } + + private static String printMOVRegister(int instructionExpression){ + String sfHw = (instructionExpression >> 31 & 1) == 0 ? "W" : "X"; + int Rm = instructionExpression >> 16 & 0xF; + int Rd = instructionExpression & 0xF; + + return String.format(ANSI_YELLOW + "sf == 0 && hw == 0x ? <Wt> else <Xt>\n" + + "MOV (register) <?d>, <?m> |.....MOV (register) signature.......|\n" + + ANSI_CYAN +" sf 0 1 0 1 0 1 0 0 0 0 |Rm..............| 0 0 0 0 0 0 1 1 1 1 1 |Rd.............|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "MOV(reg) " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "%s%d" + ANSI_RESET + "\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + sfHw, Rm, sfHw, Rd); + } + + private static String printNOP(int instructionExpression){ + return String.format( + ANSI_YELLOW+"NOP |.....NOP signature..........................................................................................|\n" + + ANSI_CYAN +" 1 1 0 1 0 1 0 1 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 " + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n"+ + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "%s" + ANSI_RESET + "\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (instructionExpression == 0xd503201f)?"NOP":"Some weird stuff"); + } + + private static String printTBZ(int instructionExpression, int offset){ + int xwSelector = (instructionExpression >> 31 & 1); + int imm = instructionExpression >> 18 & 0b11111; + int Rt = instructionExpression & 0b11111; + int label = (offset + (instructionExpression >> 5 & 0x3fff) * 4) & 0xfffff; + + //System.out.printf("\nInstruction: %x\n", instructionExpression); + return String.format(ANSI_YELLOW + "sf == 0 && hw == 0x ? <Wt> else <Xt>\n" + + "TBZ <?t>,#<imm>, <label> |.....TBZ signature.......|\n" + + ANSI_CYAN+" b5 0 1 1 0 1 1 0 |b40.............|imm14.........................................||Rt.............|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "TBZ " + ANSI_GREEN + "%s%d " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ", " + ANSI_PURPLE + "%x" + ANSI_RESET + "\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + (xwSelector == 0) ? "w" : "x", Rt, imm, label); + } + + private static String printBConditional(int instructionExpression, int offset){ + int conditionalJumpLocation = ((instructionExpression >> 4 & 0b1111111111111111111) * 4 + offset) & 0xfffff; + + return String.format( + ANSI_YELLOW+"B.%s <label> |...B.cond signature.......|\n" + + ANSI_CYAN+" 0 1 0 1 0 1 0 0 |imm19..........................................................| 0 |.condit...|" + ANSI_RESET + "\n" + + ANSI_GREEN +" 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n" + + ANSI_YELLOW + "B.%s " + ANSI_BLUE + "#0x%x" + ANSI_RESET + " (Real: " + ANSI_BLUE + "#0x%x" + ANSI_RESET + ")\n", + getBConditionalMarker(instructionExpression & 0xf), + intAsBinString(instructionExpression), intAsHexString(instructionExpression), + getBConditionalMarker(instructionExpression & 0xf), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + private static String printUnknown(int instructionExpression){ + return String.format(ANSI_RED + " 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 " + ANSI_RESET + "\n" + + "Instruction (BE) : %s | %s\n", + intAsBinString(instructionExpression), intAsHexString(instructionExpression)); + } + + private static String getBConditionalMarker(int cond){ + switch (cond){ + case 0b0000: return "EQ"; + case 0b0001: return "NE"; + case 0b0010: return "CS"; + case 0b0011: return "CC"; + case 0b0100: return "MI"; + case 0b0101: return "PL"; + case 0b0110: return "VS"; + case 0b0111: return "VC"; + case 0b1000: return "HI"; + case 0b1001: return "LS"; + case 0b1010: return "GE"; + case 0b1011: return "LT"; + case 0b1100: return "GT"; + case 0b1101: return "LE"; + case 0b1110: return "AL"; + default: return "??"; + } + /* + "__________________CheatSheet_____________________________________\n"+ + "0000 | EQ | Z set | equal\n"+ + "0001 | NE | Z clear | not equal\n"+ + "0010 | CS | C set | unsigned higher or same\n"+ + "0011 | CC | C clear | unsigned lower\n"+ + "0100 | MI | N set | negative\n"+ + "0101 | PL | N clear | positive or zero\n"+ + "0110 | VS | V set | overflow\n"+ + "0111 | VC | V clear | no overflow\n"+ + "1000 | HI | C set & V clear | unsigned higher\n"+ + "1001 | LS | C clear or Z set | unsigned lower or same\n"+ + "1010 | GE | N equals V | greater or equal\n"+ + "1011 | LT | N not equals V | less than\n"+ + "1100 | GT | Z clear AND (N equals V) | greater that\n"+ + "1101 | LE | Z set OR (N not equals V) | less than or equal\n"+ + "1110 | AL | (ignored) | always\n"; + */ + } + + + private static String printCBZSimplified(int instructionExpression, int offset){ + 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, + (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + private static String printCBNZSimplified(int instructionExpression, int offset){ + 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, + (instructionExpression >> 31 == 0) ? "w" : "x", (instructionExpression & 0b11111), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + private static String printBSimplified(int instructionExpression, int offset){ + 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, + conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); + } + + + private static String printBLSimplified(int instructionExpression, int offset){ + 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, + conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); + } + + + + private static String printMOVSimplified(int instructionExpression, int offset){ + int imm16 = instructionExpression >> 5 & 0xFFFF; + 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, + (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); + } + + private static String printTBZSimplified(int instructionExpression, int offset){ + int xwSelector = (instructionExpression >> 31 & 1); + int imm = instructionExpression >> 18 & 0b11111; + int Rt = instructionExpression & 0b11111; + int label = (offset + (instructionExpression >> 5 & 0x3fff) * 4) & 0xfffff; + + //System.out.printf("\nInstruction: %x\n", instructionExpression); + 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, + (xwSelector == 0) ? "w" : "x", Rt, imm, label); + } + + private static String printBConditionalSimplified(int instructionExpression, int offset){ + 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, + getBConditionalMarker(instructionExpression & 0xf), + conditionalJumpLocation, (conditionalJumpLocation + 0x100)); + } + + private static String printADDSimplified(int instructionExpression, int offset){ //ADD (immediate) + return String.format( + "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ADD . . . \n"+ ANSI_RESET, + offset, Integer.reverseBytes(instructionExpression), instructionExpression); + } + private static String printLDPSimplified(int instructionExpression, int offset){ + return String.format( + "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDP . . . \n"+ ANSI_RESET, + offset, Integer.reverseBytes(instructionExpression), instructionExpression); + } + private static String printLDURBSimplified(int instructionExpression, int offset){ + return String.format( + "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDURB . . . \n"+ ANSI_RESET, + offset, Integer.reverseBytes(instructionExpression), instructionExpression); + } + private static String printSUBSimplified(int instructionExpression, int offset){ + String wx = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rt = instructionExpression & 0x1f; + int Rn = instructionExpression >> 5 & 0x1F; + 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, + wx, Rt, wx, Rn, imm12); + } + + + private static String printMOVRegisterSimplified(int instructionExpression, int offset){ //ADD (immediate) + String sfHw = (instructionExpression >> 31 & 1) == 0 ? "W" : "X"; + int Rm = instructionExpression >> 16 & 0xF; + int Rd = instructionExpression & 0xF; + + 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, + sfHw, Rm, sfHw, Rd); + } + + private static String printCMNSimplified(int instructionExpression, int offset){ + int Rn = instructionExpression >> 5 & 0x1F; + 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, + (instructionExpression >> 31 == 0) ? "w" : "x", Rn, imm); + } + + private static String printLRDImmUnsignSimplified(int instructionExpression, int offset){ + String wx = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rt = instructionExpression & 0x1f; + int Rn = instructionExpression >> 5 & 0xF; + int imm12 = (instructionExpression >> 10 & 0xFFF) * 8; // unsigned only + + + 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, + wx, Rt, wx, Rn, imm12); + } + private static String printLRDBImmUnsignSimplified(int instructionExpression, int offset){ + String wx = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rt = instructionExpression & 0x1f; + int Rn = instructionExpression >> 5 & 0xF; + 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, + wx, Rt, wx, Rn, imm12); + } + + + + private static String printUnknownSimplified(int instructionExpression, int offset){ + return String.format( + "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ??? 0b"+ANSI_RESET+ Converter.intToBinaryString(Integer.reverseBytes(instructionExpression)) +"\n", + offset, Integer.reverseBytes(instructionExpression), instructionExpression); + } + + private static String intAsBinString(int number) { + StringBuilder result = new StringBuilder(); + for(int i = 31; i >= 0 ; i--) { + int mask = 1 << i; + result.append((number & mask) != 0 ? "1" : "0"); + result.append(" "); + if (i % 4 == 0) + result.append(" "); + } + result.replace(result.length() - 1, result.length(), ""); + + return result.toString(); + } + private static String intAsHexString(int number) { + number = Integer.reverseBytes(number); + StringBuilder result = new StringBuilder(); + for(int i = 0; i <= 3 ; i++) { + int mask = 0xff << i*8; + result.append(String.format("%02x", (byte)((number & mask) >> i*8))); + result.append(" "); + } + result.replace(result.length() - 1, result.length(), ""); + + return result.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java b/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java new file mode 100644 index 0000000..12edaff --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java @@ -0,0 +1,50 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es; + +import libKonogonka.Converter; +import libKonogonka.Tools.NCA.NCAProvider; + +import java.util.List; +import java.util.concurrent.Callable; + +class EsNcaSearchTask implements Callable<NCAProvider> { + private final List<NCAProvider> ncaProviders; + + EsNcaSearchTask(List<NCAProvider> ncaProviders){ + this.ncaProviders = ncaProviders; + } + + @Override + public NCAProvider call() { + try { + for (NCAProvider ncaProvider : ncaProviders) { + String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId()); + if (titleId.startsWith("0100000000000033") && ncaProvider.getContentType() == 0) { + return ncaProvider; + } + } + return null; + } + catch (Exception e){ + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java new file mode 100644 index 0000000..5ef3173 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -0,0 +1,174 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + --- + Based on ES-AutoIPS.py patch script made by GBATemp member MrDude. + Taken from: https://gbatemp.net/threads/info-on-sha-256-hashes-on-fs-patches.581550/ + */ +package nsusbloader.Utilities.patches.es; + +import libKonogonka.Converter; +import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.NSO.NSO0Header; +import libKonogonka.Tools.NSO.NSO0Provider; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.es.finders.HeuristicEsWizard; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; + +public class EsPatch { + private final NCAProvider ncaProvider; + private final String saveToLocation; + private final ILogPrinter logPrinter; + + private Long fwVersion; + private String buildId; + private byte[] _textSection; + + private HeuristicEsWizard wizard; + + EsPatch(NCAProvider ncaProvider, String saveToLocation, ILogPrinter logPrinter) throws Exception{ + this.ncaProvider = ncaProvider; + this.saveToLocation = saveToLocation + File.separator + + "atmosphere" + File.separator + "exefs_patches" + File.separator + "es_patches"; + this.logPrinter = logPrinter; + + getPlainFirmwareVersion(); + NSO0Provider nso0Provider = new NSO0Provider(ncaProvider.getNCAContentProvider(0).getPfs0().getStreamProducer(0)); + getBuildId(nso0Provider); + getTextSection(nso0Provider); + findAllOffsets(); + mkDirs(); + writeFile(); + logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); + } + private void getPlainFirmwareVersion() throws Exception{ + fwVersion = Long.parseLong(""+ncaProvider.getSdkVersion()[3]+ncaProvider.getSdkVersion()[2] + +ncaProvider.getSdkVersion()[1] +ncaProvider.getSdkVersion()[0]); + logPrinter.print("Internal firmware version: "+ncaProvider.getSdkVersion()[3] +"."+ncaProvider.getSdkVersion()[2] +"."+ncaProvider.getSdkVersion()[1] +"."+ncaProvider.getSdkVersion()[0], EMsgType.INFO); + } + private void getBuildId(NSO0Provider nso0Provider) throws Exception{ + NSO0Header nso0DecompressedHeader = nso0Provider.getAsDecompressedNSO0().getHeader(); + byte[] buildIdBytes = nso0DecompressedHeader.getModuleId(); + buildId = Converter.byteArrToHexStringAsLE(buildIdBytes).substring(0, 40).toUpperCase(); + logPrinter.print("Build ID: "+buildId, EMsgType.INFO); + } + private void getTextSection(NSO0Provider nso0Provider) throws Exception{ + _textSection = nso0Provider.getAsDecompressedNSO0().getTextRaw(); + } + private void findAllOffsets() throws Exception{ + this.wizard = new HeuristicEsWizard(fwVersion, _textSection); + String errorsAndNotes = wizard.getErrorsAndNotes(); + if (errorsAndNotes.length() > 0) + logPrinter.print(errorsAndNotes, EMsgType.WARNING); + } + private void mkDirs(){ + File parentFolder = new File(saveToLocation); + parentFolder.mkdirs(); + } + + private void writeFile() throws Exception{ + String patchFileLocation = saveToLocation + File.separator + buildId + ".ips"; + int offset1 = wizard.getOffset1(); + int offset2 = wizard.getOffset2(); + int offset3 = wizard.getOffset3(); + + ByteBuffer handyEsPatch = ByteBuffer.allocate(0x23).order(ByteOrder.LITTLE_ENDIAN); + handyEsPatch.put(getHeader()); + if (offset1 > 0) { + logPrinter.print("Patch component 1 will be used", EMsgType.PASS); + handyEsPatch.put(getPatch1(offset1)); + } + if (offset2 > 0) { + logPrinter.print("Patch component 2 will be used", EMsgType.PASS); + handyEsPatch.put(getPatch2(offset2)); + } + if (offset3 > 0) { + logPrinter.print("Patch component 3 will be used", EMsgType.PASS); + handyEsPatch.put(getPatch3(offset3)); + } + handyEsPatch.put(getFooter()); + + try (BufferedOutputStream stream = new BufferedOutputStream( + Files.newOutputStream(new File(patchFileLocation).toPath()))){ + stream.write(handyEsPatch.array()); + } + logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); + } + private byte[] getHeader(){ + return "PATCH".getBytes(StandardCharsets.US_ASCII); + } + private byte[] getFooter(){ + return "EOF".getBytes(StandardCharsets.US_ASCII); + } + + // WE EXPECT TO SEE CBZ (for patch 1) INSTRUCTION RIGHT BEFORE FOUND SEQUENCE (requiredInstructionOffsetInternal) + // IN RESULTING FILE InstructionOffset SHOULD BE INCREMENTED by 0x100 to get real offset + // (because header for decompressed NSO0 size = 0x100; it's fixed alignment produced by libKonogonka) + private byte[] getPatch1(int offset) throws Exception{ + int requiredInstructionOffsetInternal = offset - 4; + int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; + int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); + int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); + + logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + + // Somehow IPS patches uses offsets written as big_endian (0.o) and bytes dat should be patched as LE. + ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) + .putInt(requiredInstructionOffsetReal) + .putShort((short) 4) + .putInt(Integer.reverseBytes(patch)); + + return Arrays.copyOfRange(prePatch.array(), 1, 10); + } + private byte[] getPatch2(int offset) throws Exception{ + final int NopExpression = 0x1F2003D5; // reversed + int offsetReal = offset - 4 + 0x100; + + logPrinter.print(BinToAsmPrinter.printSimplified(Integer.reverseBytes(NopExpression), offset - 4), EMsgType.NULL); + + ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) + .putInt(offsetReal) + .putShort((short) 4) + .putInt(NopExpression); + + return Arrays.copyOfRange(prePatch.array(), 1, 10); + } + private byte[] getPatch3(int offset) throws Exception{ + int requiredInstructionOffsetInternal = offset - 4; + int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; + + int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); + int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); + + logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + + ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) + .putInt(requiredInstructionOffsetReal) + .putShort((short) 4) + .putInt(Integer.reverseBytes(patch)); + + return Arrays.copyOfRange(prePatch.array(), 1, 10); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java new file mode 100644 index 0000000..b28e2ec --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java @@ -0,0 +1,187 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es; + +import libKonogonka.KeyChainHolder; +import libKonogonka.Tools.NCA.NCAProvider; +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.ModelControllers.Log; +import nsusbloader.NSLDataTypes.EModule; +import nsusbloader.NSLDataTypes.EMsgType; + +import java.io.File; +import java.util.*; +import java.util.concurrent.*; + +public class EsPatchMaker extends CancellableRunnable { + private int THREADS_POOL_SIZE = 4; + private final ILogPrinter logPrinter; + private final String pathToFirmware; + private final String pathToKeysFile; + private final String saveTo; + + private File firmware; + private KeyChainHolder keyChainHolder; + private ExecutorService executorService; + private List<String> ncaFilesList; // inside the folder + + private boolean oneLinerStatus = false; + + public EsPatchMaker(String pathToFirmware, String pathToKeysFile, String saveTo){ + this.logPrinter = Log.getPrinter(EModule.PATCHES); //TODO: UNCOMMENT + /* + this.logPrinter = new ILogPrinter() { + @Override + public void print(String message, EMsgType type) throws InterruptedException {} + @Override + public void updateProgress(Double value) throws InterruptedException {} + @Override + public void update(HashMap<String, File> nspMap, EFileStatus status) {} + @Override + public void update(File file, EFileStatus status) {} + @Override + public void updateOneLinerStatus(boolean status) {} + @Override + public void close() {} + }; + */ + this.pathToFirmware = pathToFirmware; + this.pathToKeysFile = pathToKeysFile; + this.saveTo = saveTo; + } + + @Override + public void run() { + try { + logPrinter.print("..:: Make ES Patches ::..", EMsgType.INFO); + receiveFirmware(); + buildKeyChainHolder(); + receiveNcaFileNamesList(); + adjustThreadsPoolSize(); + createPool(); + executePool(); + } + catch (Exception e){ + e.printStackTrace(); + try{ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } catch (Exception ignore){} + } + finally { + logPrinter.updateOneLinerStatus(oneLinerStatus); + logPrinter.close(); + } + } + private void receiveFirmware() throws Exception{ + logPrinter.print("Looking at firmware", EMsgType.INFO); + this.firmware = new File(pathToFirmware); + } + private void buildKeyChainHolder() throws Exception{ + logPrinter.print("Reading keys", EMsgType.INFO); + this.keyChainHolder = new KeyChainHolder(pathToKeysFile, null); + } + private void receiveNcaFileNamesList() throws Exception{ + logPrinter.print("Collecting NCA files", EMsgType.INFO); + String[] fileNamesArray = firmware.list((File directory, String file) -> ( ! file.endsWith(".cnmt.nca") && file.endsWith(".nca"))); + ncaFilesList = Arrays.asList(Objects.requireNonNull(fileNamesArray)); + if (ncaFilesList.size() == 0) + throw new Exception("No NCA files found in firmware folder"); + } + private void adjustThreadsPoolSize(){ + if (ncaFilesList.size() < 4) + THREADS_POOL_SIZE = ncaFilesList.size(); + } + + private void createPool() throws Exception{ + logPrinter.print("Creating sub-tasks pool", EMsgType.INFO); + this.executorService = Executors.newFixedThreadPool( + THREADS_POOL_SIZE, + runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + return thread; + }); + } + + private void executePool() throws Exception{ //TODO: FIX. Exceptions thrown only by logPrinter + try { + logPrinter.print("Executing sub-tasks pool", EMsgType.INFO); + List<Future<NCAProvider>> futuresResults = executorService.invokeAll(getSubTasksCollection()); + for (Future<NCAProvider> future : futuresResults){ + NCAProvider ncaProvider = future.get(); + if (ncaProvider != null) { + makePatches(ncaProvider); + break; + } + } + executorService.shutdown(); + } + catch (InterruptedException ie){ + executorService.shutdownNow(); + boolean interruptedSuccessfully = false; + try { + interruptedSuccessfully = executorService.awaitTermination(20, TimeUnit.SECONDS); + } + catch (InterruptedException awaitInterrupt){ + logPrinter.print("Force interrupting task...", EMsgType.WARNING); + } + logPrinter.print("Task interrupted "+(interruptedSuccessfully?"successfully":"with some issues"), EMsgType.WARNING); + } + catch (Exception e){ + e.printStackTrace(); + logPrinter.print("Task failed: "+e.getMessage(), EMsgType.FAIL); + } + } + + private void makePatches(NCAProvider ncaProvider) throws Exception{ + logPrinter.print(String.format("File found: .."+File.separator+"%s"+File.separator+"%s", + ncaProvider.getFile().getParentFile().getName(), ncaProvider.getFile().getName()) + , EMsgType.INFO); + new EsPatch(ncaProvider, saveTo, logPrinter); + oneLinerStatus = true; + } + private List<Callable<NCAProvider>> getSubTasksCollection() throws Exception{ + logPrinter.print("Forming sub-tasks collection", EMsgType.INFO); + List<Callable<NCAProvider>> subTasks = new ArrayList<>(); + + int ncaPerThreadAmount = ncaFilesList.size() / THREADS_POOL_SIZE; + Iterator<String> iterator = ncaFilesList.listIterator(); + + for (int i = 1; i < THREADS_POOL_SIZE; i++){ + Callable<NCAProvider> task = new EsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount)); + subTasks.add(task); + } + + Callable<NCAProvider> task = new EsNcaSearchTask(getNextSet(iterator, + ncaFilesList.size() % THREADS_POOL_SIZE == 0 ? ncaPerThreadAmount : ncaPerThreadAmount+1)); + subTasks.add(task); + return subTasks; + } + private List<NCAProvider> getNextSet(Iterator<String> iterator, int amount) throws Exception{ + List<NCAProvider> ncas = new ArrayList<>(); + for (int j = 0; j < amount; j++){ + String ncaFileName = iterator.next(); + File nca = new File(firmware.getAbsolutePath()+File.separator+ncaFileName); + NCAProvider provider = new NCAProvider(nca, keyChainHolder.getRawKeySet()); + ncas.add(provider); + } + return ncas; + } +} \ No newline at end of file diff --git a/src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java b/src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java new file mode 100644 index 0000000..c3a648f --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java @@ -0,0 +1,161 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es; + +import libKonogonka.Converter; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SimplyFind { + private String what; + private final byte[] where; + + private Matcher matcherHex; + private Matcher matcherDot; + + private final List<Integer> findings = new ArrayList<>(); + private final int statementLength; + private final List<SearchBlock> searchBlocks = new ArrayList<>(); + /** + * Abstraction layer for searching patterns like "..CAFE..BE" in bytes array. + * It's 'String' combination of hex values and '.' which stands for unknown value. + * Returns offset of the first symbol. + * */ + public SimplyFind(String what, byte[] where){ + this.where = where; + if (! what.contains(".")){ + doKMPSearch(Converter.hexStringToByteArray(what), 0); + this.statementLength = what.length()/2; + return; + } + this.what = what.replaceAll("\\.", "\\.\\."); + this.statementLength = this.what.length()/2; + + buildSearchingSequence(); + complexSearch(); + } + private void buildSearchingSequence(){ + Pattern patternHex = Pattern.compile("[0-9]|[A-F]|[a-f]"); + Pattern patternDot = Pattern.compile("\\."); + this.matcherHex = patternHex.matcher(what); + this.matcherDot = patternDot.matcher(what); + + int nextDotPos = 0; + int nextHexPos; + + while(true){ + nextHexPos = getNextNumberPosition(nextDotPos); + if (nextHexPos == -1) + break; + + nextDotPos = getNextDotPosition(nextHexPos); + if (nextDotPos == -1) { + searchBlocks.add(new SearchBlock(what.substring(nextHexPos), nextHexPos)); + break; + } + String searchStatement = what.substring(nextHexPos, nextDotPos); + searchBlocks.add(new SearchBlock(searchStatement, nextHexPos)); + } + } + private int getNextNumberPosition(int since){ + if (matcherHex.find(since)) + return matcherHex.start(); + return -1; + } + + private int getNextDotPosition(int since){ + if (matcherDot.find(since)) + return matcherDot.start(); + return -1; + } + + private void complexSearch(){ + SearchBlock block = searchBlocks.get(0); + doKMPSearch(block.statement, block.offsetInStatement); + findings.removeIf(this::searchForward); + } + private boolean searchForward(int offset){ + for (int i = 1; i < searchBlocks.size(); i++) { + SearchBlock block = searchBlocks.get(i); + if (! doDumbSearch(block.statement, offset+block.offsetInStatement)){ + return true; + } + } + return false; + } + + private void doKMPSearch(byte[] subject, int skip){ + int whereSize = where.length; + int subjectSize = subject.length; + + int[] pf = new int[subjectSize]; + + int j = 0; + for (int i = 1; i < subjectSize; i++ ) { + while ((j > 0) && (subject[j] != subject[i])) + j = pf[j-1]; + if (subject[j] == subject[i]) + j++; + pf[i] = j; + } + + j = 0; + for (int i = 0; i < whereSize; i++){ + while ((j > 0) && (subject[j] != where[i])) + j = pf[j - 1]; + if (subject[j] == where[i]) + j++; + if (j == subjectSize) { + findings.add(i-j+1-skip); + j = 0; + } + } + } + + private boolean doDumbSearch(byte[] subject, int since){ + for (int i = 0; i < subject.length; i++) { + if (where[since + i] != subject[i]) + return false; + } + return true; + } + + public int getStatementLength() { + return statementLength; + } + + public List<Integer> getResults(){ + return findings; + } + + private static class SearchBlock{ + byte[] statement; + int offsetInStatement; + + SearchBlock(String statement, int offset){ + if (statement != null) { + this.statement = Converter.hexStringToByteArray(statement); + } + this.offsetInStatement = offset/2; + } + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java new file mode 100644 index 0000000..d063b39 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -0,0 +1,109 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.es.BinToAsmPrinter; +import nsusbloader.Utilities.patches.es.SimplyFind; + +import java.util.List; + +class HeuristicEs1 implements IHeuristicEs { + private static final String PATTERN = "1F90013128.8052"; + + private final List<Integer> findings; + private final byte[] where; + + HeuristicEs1(byte[] where){ + this.where = where; + SimplyFind simplyfind = new SimplyFind(PATTERN, where); + this.findings = simplyfind.getResults(); + + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + } + + // Check ranges + private boolean dropStep1(int offsetOfPatternFound){ + return ((offsetOfPatternFound < 0x10000 || offsetOfPatternFound > 0xffffc)); + } + // Remove non-CBZ + private boolean dropStep2(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = findings.get(0) - 4; + int instructionExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(instructionExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), + cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), + cbzOffsetInternal+8)); + builder.append("...\n"); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + return builder.toString(); + } + + @Override + public int getId(){ + return 1; + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java new file mode 100644 index 0000000..e139558 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -0,0 +1,168 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.es.BinToAsmPrinter; +import nsusbloader.Utilities.patches.es.SimplyFind; + +import java.util.ArrayList; +import java.util.List; + +class HeuristicEs2 implements IHeuristicEs { + private static final String PATTERN = ".D2.52"; + + private List<Integer> findings; + private final byte[] where; + + HeuristicEs2(byte[] where){ + this.where = where; + find(); + + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep4); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep5); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep6); + } + private void find(){ + SimplyFind simplyfind = new SimplyFind(PATTERN, where); + findings = new ArrayList<>(); + // This approach a bit different. We're looking for pattern we want to patch, not the next one. + // So easier way is just to shift every value and pretend that nothing happened. + for (int offset : simplyfind.getResults()) + findings.add(offset+4); + } + + // Limit range + private boolean dropStep1(int offsetOfPatternFound){ + return ((offsetOfPatternFound < 0x10000 || offsetOfPatternFound > 0xffffc)); + } + // Is CBNZ next? + private boolean dropStep2(int offsetOfPatternFound){ + return ! isCBNZ(Converter.getLEint(where, offsetOfPatternFound)); + } + // Check what's above + private boolean dropStep3(int offsetOfPatternFound){ + return ! isMOV(Converter.getLEint(where, offsetOfPatternFound-4)); + } + // Check what's beyond or after jump + private boolean dropStep4(int offsetOfPatternFound) { + int nextExpression = Converter.getLEint(where, offsetOfPatternFound + 4); + return ! isLDRB_LDURB(nextExpression); // Drop if not LDRB OR LDURB + } + + // Check second after jump if LDR-TBZ + private boolean dropStep5(int offsetOfPatternFound) { + int expression = Converter.getLEint(where, offsetOfPatternFound); + int afterJumpPosition = ((expression >> 5 & 0x7FFFF) * 4 + offsetOfPatternFound) & 0xfffff; + int secondAfterJumpExpression = Converter.getLEint(where, afterJumpPosition+4); + return ! isBL(secondAfterJumpExpression); //Second after jump = BL? No -> Drop + } + + // Check second after jump if LDR-TBZ + private boolean dropStep6(int offsetOfPatternFound) { + int expression = Converter.getLEint(where, offsetOfPatternFound); + int afterJumpPosition = ((expression >> 5 & 0x7FFFF) * 4 + offsetOfPatternFound) & 0xfffff; + int forthAfterJumpExpression = Converter.getLEint(where, afterJumpPosition+12); + return ! isBL(forthAfterJumpExpression); //Forth after jump = BL? No -> Drop + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int secondExpressionOffset = findings.get(0); + + int firstExpression = Converter.getLEint(where, secondExpressionOffset-4); + int secondExpression = Converter.getLEint(where, secondExpressionOffset); + int conditionalJumpLocation = 0; + if ((secondExpression >> 24 & 0x7f) == 0x35) { + conditionalJumpLocation = ((secondExpression >> 5 & 0x7FFFF) * 4 + secondExpressionOffset) & 0xfffff; + } + else if ((firstExpression >> 24 & 0x7f) == 0x36) { + conditionalJumpLocation = (secondExpressionOffset-4 + (firstExpression >> 5 & 0x3fff) * 4) & 0xfffff; + } + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation + 4); + int secondExpressionsPairElement3 = Converter.getLEint(where, conditionalJumpLocation + 8); + int secondExpressionsPairElement4 = Converter.getLEint(where, conditionalJumpLocation + 12); + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset-4), secondExpressionOffset-4)); + builder.append(BinToAsmPrinter.printSimplified(secondExpression, secondExpressionOffset)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset+4), secondExpressionOffset+4)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement3, conditionalJumpLocation + 8)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement4, conditionalJumpLocation + 12)); + + return builder.toString(); + } + + @Override + public int getId(){ + return 2; + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java new file mode 100644 index 0000000..fc4591f --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -0,0 +1,151 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.es.BinToAsmPrinter; +import nsusbloader.Utilities.patches.es.SimplyFind; + +import java.util.List; + +class HeuristicEs3 implements IHeuristicEs { + private static final String PATTERN0 = "..FF97"; + private static final String PATTERN1 = "......FF97"; // aka "E0230091..FF97"; + + private final List<Integer> findings; + private final byte[] where; + + HeuristicEs3(long fwVersion, byte[] where){ + this.where = where; + String pattern = getPattern(fwVersion); + SimplyFind simplyfind = new SimplyFind(pattern, where); + this.findings = simplyfind.getResults(); + + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + } + private String getPattern(long fwVersion){ + if (fwVersion < 10400) + return PATTERN0; + return PATTERN1; + } + // Let's focus on CBZ-ONLY statements + private boolean dropStep1(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + private boolean dropStep2(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); + + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); + int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + // Check first is 'MOV'; second is 'B' + return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); + } + + private boolean dropStep3(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); + int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); + // Check first is 'ADD'; second is 'BL' + return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); + } + + private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = findings.get(0) - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ + builder.append("...\n"); + int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); + + } + else { + builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); + } + return builder.toString(); + } + + @Override + public int getId(){ + return 3; + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java new file mode 100644 index 0000000..5afdf60 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java @@ -0,0 +1,127 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es.finders; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class HeuristicEsWizard { + private final List<IHeuristicEs> all; + private final List<IHeuristicEs> found; + private final List<IHeuristicEs> wantLessEntropy; + + private final StringBuilder errorsAndNotes; + + private int offset1 = -1; + private int offset2 = -1; + private int offset3 = -1; + + public HeuristicEsWizard(long fwVersion, byte[] where) throws Exception{ + this.errorsAndNotes = new StringBuilder(); + + this.all = Arrays.asList( + new HeuristicEs1(where), + new HeuristicEs2(where), + new HeuristicEs3(fwVersion, where) + ); + + this.found = all.stream() + .filter(IHeuristicEs::isFound) + .collect(Collectors.toList()); + + if (found.isEmpty()) + throw new Exception("Nothing found!"); + + this.wantLessEntropy = all.stream() + .filter(IHeuristicEs::wantLessEntropy) + .collect(Collectors.toList()); + + shareOffsetsWithEachOther(); + + assignOffset1(); + assignOffset2(); + assignOffset3(); + } + + private void shareOffsetsWithEachOther(){ + for (IHeuristicEs es : wantLessEntropy) { + if (shareWithNext(es)) + return; + } + } + private boolean shareWithNext(IHeuristicEs es){ + try { + for (IHeuristicEs foundEs : found) { + if (es.setOffsetsNearby(foundEs.getOffset())) { + found.add(es); + wantLessEntropy.remove(es); + shareOffsetsWithEachOther(); + return true; + } + } + } + catch (Exception e){ e.printStackTrace(); } + return false; + } + + private void assignOffset1(){ + try { + offset1 = all.get(0).getOffset(); + } + catch (Exception e){ errorsAndNotes.append(e.getLocalizedMessage()).append("\n"); } + } + private void assignOffset2(){ + try { + offset2 = all.get(1).getOffset(); + } + catch (Exception e){ errorsAndNotes.append(e.getLocalizedMessage()).append("\n"); } + } + private void assignOffset3(){ + try { + offset3 = all.get(2).getOffset(); + } + catch (Exception e){ errorsAndNotes.append(e.getLocalizedMessage()).append("\n"); } + } + + public String getErrorsAndNotes(){ + return errorsAndNotes.toString(); + } + + public String getDebug(){ + StringBuilder builder = new StringBuilder(); + if (all.get(0).isFound()){ + builder.append("\t\t-=== 1 ===-\n"); + builder.append(all.get(0).getDetails()); + } + if (all.get(1).isFound()){ + builder.append("\t\t-=== 2 ===-\n"); + builder.append(all.get(1).getDetails()); + } + if (all.get(2).isFound()){ + builder.append("\t\t-=== 3 ===-\n"); + builder.append(all.get(2).getDetails()); + } + return builder.toString(); + } + + public int getOffset1() { return offset1; } + public int getOffset2() { return offset2; } + public int getOffset3() { return offset3; } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java new file mode 100644 index 0000000..ade0733 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java @@ -0,0 +1,47 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.es.finders; +/** + * Searches instructions (via known patterns) that follows 'specific instruction' we want to patch. + * Returns offset of the pattern. Not offset of the 'specific instruction'. + * */ +interface IHeuristicEs { + default boolean isLDR(int expression){ return (expression >> 22 & 0x2FF) == 0x2e5; }// LDR ! Sounds like LDP, don't mess up + default boolean isLDP(int expression){ return (expression >> 22 & 0x1F9) == 0xA1; }// LDP ! + default boolean isCBNZ(int expression){ return (expression >> 24 & 0x7f) == 0x35; } + default boolean isMOV(int expression){ return (expression >> 23 & 0xff) == 0xA5; } + default boolean isTBZ(int expression){ return (expression >> 24 & 0x7f) == 0x36; } + default boolean isLDRB_LDURB(int expression){ return (expression >> 21 & 0x7f7) == 0x1c2; } + default boolean isMOV_REG(int expression){ return (expression & 0x7FE0FFE0) == 0x2A0003E0; } + default boolean isB(int expression) { return (expression >> 26 & 0x3f) == 0x5; } + default boolean isBL(int expression){ return (expression >> 26 & 0x3f) == 0x25; } + default boolean isADD(int expression){ return (expression >> 23 & 0xff) == 0x22; } + boolean isFound(); + boolean wantLessEntropy(); + int getOffset() throws Exception; + String getDetails(); + + /** + * Should be used if wantLessEntropy() == true + * @return isFound(); + * */ + boolean setOffsetsNearby(int offsetNearby); + + int getId(); +} diff --git a/src/main/java/nsusbloader/cli/CommandLineInterface.java b/src/main/java/nsusbloader/cli/CommandLineInterface.java index bf18aee..2ced0f1 100644 --- a/src/main/java/nsusbloader/cli/CommandLineInterface.java +++ b/src/main/java/nsusbloader/cli/CommandLineInterface.java @@ -18,9 +18,12 @@ */ package nsusbloader.cli; +import nsusbloader.AppPreferences; +import nsusbloader.Main; import nsusbloader.NSLMain; import org.apache.commons.cli.*; +import java.time.LocalTime; import java.util.prefs.Preferences; public class CommandLineInterface { @@ -32,10 +35,12 @@ public class CommandLineInterface { } final Options cliOptions = createCliOptions(); + final Options cliOptionsToBeExact = + createCliOptions().addOption(Option.builder().longOpt("gimmegimmegimme").hasArg(false).build()); CommandLineParser cliParser = new DefaultParser(); try{ - CommandLine cli = cliParser.parse(cliOptions, args); + CommandLine cli = cliParser.parse(cliOptionsToBeExact, args); if (cli.hasOption('v') || cli.hasOption("version")){ handleVersion(); return; @@ -75,6 +80,16 @@ public class CommandLineInterface { return; } */ + if (cli.hasOption("gimmegimmegimme")){ + if (LocalTime.now().isBefore(LocalTime.parse("09:00:00"))){ + AppPreferences.getInstance().give(); + AppPreferences.getInstance().setLastOpenedTab("PatchesTabHolder"); + System.out.println("=)"); + Main.main(new String[]{}); + return; + } + throw new ParseException("Unhandled LocalTime() exception;"); + } if (cli.hasOption("s") || cli.hasOption("split")){ final String[] arguments = cli.getOptionValues("split"); new SplitCli(arguments); diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml index 398e4b7..e096a0f 100644 --- a/src/main/resources/NSLMain.fxml +++ b/src/main/resources/NSLMain.fxml @@ -15,7 +15,7 @@ Steps to roll NXDT functionality back: * Set 'Visible' on NXDT Tab selector (SVGPath container) --> -<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSLMainController"> +<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSLMainController"> <children> <VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> @@ -47,6 +47,14 @@ Steps to roll NXDT functionality back: <SVGPath content="M 2.4003906 2 C 1.0683906 2 2.9605947e-16 3.1125 0 4.5 L 0 19.5 A 2.4 2.5 0 0 0 2.4003906 22 L 21.599609 22 A 2.4 2.5 0 0 0 24 19.5 L 24 7 C 24 5.6125 22.919609 4.5 21.599609 4.5 L 12 4.5 L 9.5996094 2 L 2.4003906 2 z M 9 5 L 13 8.5 L 9 12 L 9 10 L 6 10 L 6 7 L 9 7 L 9 5 z M 5 9 L 5 11 L 8 11 L 8 14 L 5 14 L 5 16 L 1 12.5 L 5 9 z M 13.193359 10.962891 C 14.113498 10.962891 14.814236 11.348741 15.296875 12.123047 C 15.779514 12.89388 16.021484 13.935113 16.021484 15.244141 C 16.021484 16.556641 15.779514 17.598741 15.296875 18.373047 C 14.814236 19.14388 14.113498 19.529297 13.193359 19.529297 C 12.276693 19.529297 11.575955 19.14388 11.089844 18.373047 C 10.607205 17.598741 10.365234 16.556641 10.365234 15.244141 C 10.365234 13.935113 10.607205 12.89388 11.089844 12.123047 C 11.575955 11.348741 12.276693 10.962891 13.193359 10.962891 z M 19.589844 10.962891 C 20.509983 10.962891 21.21072 11.348741 21.693359 12.123047 C 22.175998 12.89388 22.417969 13.935113 22.417969 15.244141 C 22.417969 16.556641 22.175998 17.598741 21.693359 18.373047 C 21.21072 19.14388 20.509983 19.529297 19.589844 19.529297 C 18.673177 19.529297 17.970486 19.14388 17.484375 18.373047 C 17.001736 17.598741 16.761719 16.556641 16.761719 15.244141 C 16.761719 13.935113 17.001736 12.89388 17.484375 12.123047 C 17.970486 11.348741 18.673177 10.962891 19.589844 10.962891 z M 13.193359 11.769531 C 12.613498 11.769531 12.173177 12.092448 11.871094 12.738281 C 11.56901 13.380642 11.417969 14.195964 11.417969 15.185547 C 11.417969 15.411241 11.423611 15.655599 11.4375 15.916016 C 11.451389 16.176432 11.511068 16.528212 11.615234 16.972656 L 14.412109 12.591797 C 14.235026 12.26888 14.042318 12.052517 13.833984 11.941406 C 13.629123 11.826823 13.415582 11.769531 13.193359 11.769531 z M 19.589844 11.769531 C 19.009983 11.769531 18.567708 12.092448 18.265625 12.738281 C 17.963542 13.380642 17.8125 14.195964 17.8125 15.185547 C 17.8125 15.411241 17.820095 15.655599 17.833984 15.916016 C 17.847873 16.176432 17.907552 16.528212 18.011719 16.972656 L 20.808594 12.591797 C 20.63151 12.26888 20.438802 12.052517 20.230469 11.941406 C 20.025608 11.826823 19.812066 11.769531 19.589844 11.769531 z M 14.761719 13.556641 L 11.984375 17.962891 C 12.133681 18.216363 12.305556 18.406684 12.5 18.535156 C 12.694444 18.660156 12.91276 18.722656 13.152344 18.722656 C 13.812066 18.722656 14.280816 18.355252 14.558594 17.619141 C 14.836372 16.879557 14.974609 16.059462 14.974609 15.160156 C 14.974609 14.604601 14.90408 14.07053 14.761719 13.556641 z M 21.15625 13.556641 L 18.380859 17.962891 C 18.530165 18.216363 18.70204 18.406684 18.896484 18.535156 C 19.090929 18.660156 19.307292 18.722656 19.546875 18.722656 C 20.206597 18.722656 20.675347 18.355252 20.953125 17.619141 C 21.230903 16.879557 21.371094 16.059462 21.371094 15.160156 C 21.371094 14.604601 21.298611 14.07053 21.15625 13.556641 z" /> </graphic> </Tab> + <Tab fx:id="PatchesTabHolder" closable="false"> + <content> + <fx:include fx:id="PatchesTab" source="PatchesTab.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" VBox.vgrow="ALWAYS" /> + </content> + <graphic> + <SVGPath content="M 5.8828125 0.2109375 C 5.0191331 0.17810553 4.0925755 0.5807669 3.421875 1.3671875 L 0.88671875 4.3398438 C -0.18640328 5.598116 -0.18550892 7.3497981 0.890625 8.2675781 L 4.625 11.451172 L 0.76367188 14.517578 C -0.34380932 15.397272 -0.4055432 17.146472 0.62304688 18.441406 L 3.0527344 21.501953 C 4.0813235 22.796885 5.8007221 23.131646 6.9082031 22.251953 L 12.283203 17.982422 L 17.5 22.431641 C 18.57613 23.34942 20.305782 23.07468 21.378906 21.816406 L 23.914062 18.84375 C 24.987183 17.585475 24.986236 15.833793 23.910156 14.916016 L 20.164062 11.722656 L 24.001953 8.6738281 C 25.109433 7.7941344 25.171167 6.0449323 24.142578 4.75 L 21.712891 1.6914062 C 20.684303 0.39647305 18.964905 0.059759405 17.857422 0.93945312 L 12.505859 5.1914062 L 7.3007812 0.75195312 C 6.8972331 0.40778617 6.4010201 0.23063678 5.8828125 0.2109375 z M 10.304688 5.703125 C 10.467609 5.685648 10.663263 5.7499911 10.828125 5.890625 L 11.210938 6.21875 L 6.5957031 9.8847656 L 10.060547 5.8242188 C 10.120897 5.7534558 10.206934 5.7136111 10.304688 5.703125 z M 14.550781 5.7402344 C 14.6898 5.7328444 14.815451 5.7759495 14.892578 5.8730469 L 18.494141 10.408203 C 18.648395 10.602401 18.552761 10.932817 18.28125 11.148438 L 10.611328 17.238281 C 10.339882 17.453897 9.9980038 17.47154 9.84375 17.277344 L 6.2421875 12.744141 C 6.0879337 12.549944 6.1836297 12.221473 6.4550781 12.005859 L 14.123047 5.9140625 C 14.258772 5.806255 14.411762 5.7476235 14.550781 5.7402344 z M 13.505859 7.1542969 A 0.76761252 0.76761252 0 0 0 13.0625 7.3222656 A 0.76761252 0.76761252 0 0 0 12.943359 8.4023438 A 0.76761252 0.76761252 0 0 0 14.023438 8.5195312 A 0.76761252 0.76761252 0 0 0 14.140625 7.4414062 A 0.76761252 0.76761252 0 0 0 13.505859 7.1542969 z M 16.201172 10.509766 A 0.76742482 0.76742482 0 0 0 15.757812 10.677734 A 0.76742482 0.76742482 0 0 0 15.640625 11.757812 A 0.76742482 0.76742482 0 0 0 16.71875 11.875 A 0.76742482 0.76742482 0 0 0 16.837891 10.796875 A 0.76742482 0.76742482 0 0 0 16.201172 10.509766 z M 12.322266 10.855469 A 0.76742482 0.76742482 0 0 0 11.878906 11.023438 A 0.76742482 0.76742482 0 0 0 11.759766 12.101562 A 0.76742482 0.76742482 0 0 0 12.839844 12.220703 A 0.76742482 0.76742482 0 0 0 12.957031 11.140625 A 0.76742482 0.76742482 0 0 0 12.322266 10.855469 z M 8.4589844 11.199219 A 0.76742482 0.76742482 0 0 0 8.0136719 11.367188 A 0.76742482 0.76742482 0 0 0 7.8964844 12.447266 A 0.76742482 0.76742482 0 0 0 8.9746094 12.566406 A 0.76742482 0.76742482 0 0 0 9.09375 11.486328 A 0.76742482 0.76742482 0 0 0 8.4589844 11.199219 z M 18.248047 13.244141 L 14.707031 17.396484 C 14.546099 17.585183 14.205167 17.555044 13.941406 17.330078 L 13.537109 16.986328 L 18.248047 13.244141 z M 11.15625 14.570312 A 0.76742482 0.76742482 0 0 0 10.712891 14.738281 A 0.76742482 0.76742482 0 0 0 10.595703 15.816406 A 0.76742482 0.76742482 0 0 0 11.671875 15.935547 A 0.76742482 0.76742482 0 0 0 11.791016 14.855469 A 0.76742482 0.76742482 0 0 0 11.15625 14.570312 z" /> + </graphic> + </Tab> <Tab closable="false"> <content> <fx:include fx:id="SettingsTab" source="SettingsTab.fxml" VBox.vgrow="ALWAYS" /> diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml new file mode 100644 index 0000000..801bf17 --- /dev/null +++ b/src/main/resources/PatchesTab.fxml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.shape.SVGPath?> +<?import javafx.scene.text.Font?> + +<ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> +<VBox fx:id="patchesToolPane" spacing="15.0"> + <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #2cd882 40%, transparent 45%);" /> + <HBox alignment="CENTER"> + <children> + <Label text="%tabPatches_Lbl_Title"> + <font> + <Font name="System Bold" size="15.0" /> + </font> + </Label> + </children> + </HBox> + <GridPane> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" /> + <ColumnConstraints hgrow="SOMETIMES" percentWidth="90.0" /> + <ColumnConstraints hgrow="SOMETIMES" /> + </columnConstraints> + <rowConstraints> + <RowConstraints vgrow="SOMETIMES" /> + </rowConstraints> + <children> + <Separator prefWidth="200.0" styleClass="strangeSeparator" GridPane.columnIndex="1" /> + </children> + </GridPane> + <VBox spacing="8.0"> + <children> + <VBox spacing="5.0"> + <children> + <HBox alignment="CENTER_LEFT" spacing="5.0"> + <children> + <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Firmware" wrapText="true" /> + <Label fx:id="shortNameFirmwareLbl" textOverrun="LEADING_WORD_ELLIPSIS" /> + <Pane HBox.hgrow="ALWAYS" /> + <Button fx:id="selFwFolderBtn" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectFirmware" styleClass="buttonSelect" text="%tabSplMrg_Btn_SelectFolder" wrapText="true"> + <graphic> + <SVGPath content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" fill="#289de8" /> + </graphic> + </Button> + </children> + </HBox> + <Label fx:id="locationFirmwareLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> + <font> + <Font name="System Italic" size="13.0" /> + </font> + </Label> + </children> + </VBox> + <Separator prefWidth="200.0" /> + <VBox spacing="5.0"> + <children> + <HBox alignment="CENTER_LEFT" spacing="5.0"> + <children> + <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Keys" wrapText="true" /> + <Label fx:id="shortNameKeysLbl" /> + <Pane HBox.hgrow="ALWAYS" /> + <Button fx:id="selProdKeysBtn" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectProdKeys" styleClass="buttonSelect" text="%btn_Select"> + <graphic> + <SVGPath content="M22,18V22H18V19H15V16H12L9.74,13.74C9.19,13.91 8.61,14 8,14A6,6 0 0,1 2,8A6,6 0 0,1 8,2A6,6 0 0,1 14,8C14,8.61 13.91,9.19 13.74,9.74L22,18M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5Z" fill="#289de8" /> + </graphic> + </Button> + </children> + </HBox> + <Label fx:id="locationKeysLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> + <font> + <Font name="System Italic" size="13.0" /> + </font></Label> + </children> + </VBox> + <VBox spacing="5.0"> + <children> + <HBox alignment="CENTER_LEFT" spacing="5.0"> + <children> + <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabSplMrg_Lbl_SaveToLocation" wrapText="true" /> + <Label fx:id="saveToLbl" /> + <Pane HBox.hgrow="ALWAYS" /> + <Button fx:id="selProdKeysBtn1" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectSaveTo" styleClass="buttonSelect" text="%btn_Select"> + <graphic> + <SVGPath content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" fill="#289de8" /> + </graphic> + </Button> + </children> + </HBox> + </children> + </VBox> + </children> + <VBox.margin> + <Insets left="15.0" right="15.0" /> + </VBox.margin> + </VBox> + <HBox alignment="CENTER"> + <children> + <Label fx:id="statusLbl" /> + </children> + </HBox> + <Pane VBox.vgrow="ALWAYS" /> + <HBox alignment="CENTER"> + <children> + <Button fx:id="makeEsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeEs" /> + </children> + </HBox> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> +</VBox> +</ScrollPane> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index bfb2ae7..94d63ee 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -79,3 +79,14 @@ windowBodyFilesScanned=Files scanned: %d\nWould be added: %d tab2_Lbl_AwooBlockTitle=Awoo Installer and compatible tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Lbl_Firmware=Firmware: +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Btn_fromFolder=From folder +tabPatches_Btn_asZipFile=as ZIP file +tabPatches_Lbl_Title=Patches +tabPatches_Lbl_Keys=Keys: +tabPatches_Btn_MakeEs=Make ES +tabPatches_Btn_MakeFs=Make FS +tabPatches_Btn_MakeAtmo=Make Atmo +tabPatches_Btn_MakeAll=Make all +tabPatches_ServiceWindowMessage=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 6620554..4190c70 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -77,5 +77,16 @@ windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u043E\u0432 \u043F\u0440\u043E\u tab2_Lbl_AwooBlockTitle=Awoo Installer \u0438 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u044B\u0435 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Btn_asZipFile=\u0432 \u0432\u0438\u0434\u0435 ZIP +tabPatches_Btn_fromFolder=\u0418\u0437 \u043F\u0430\u043F\u043A\u0438 +tabPatches_Btn_MakeAll=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0441\u0451 +tabPatches_Btn_MakeAtmo=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F Atmo +tabPatches_Btn_MakeEs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F ES +tabPatches_Btn_MakeFs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F FS +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: +tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0438 +tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 +tabPatches_ServiceWindowMessage=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index aad36fc..ce8524c 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -77,4 +77,15 @@ windowBodyFilesScanned=\u0424\u0430\u0439\u043B\u0456\u0432 \u043F\u0440\u043E\u tab2_Lbl_AwooBlockTitle=Awoo Installer \u0442\u0430 \u0441\u0443\u043C\u0456\u0441\u043D\u0456 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Btn_asZipFile=\u044F\u043A ZIP +tabPatches_Btn_fromFolder=\u0417 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0456\u0457 +tabPatches_Btn_MakeAll=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0432\u0441\u0456 +tabPatches_Btn_MakeAtmo=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F Atmo +tabPatches_Btn_MakeEs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F ES +tabPatches_Btn_MakeFs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F FS +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: +tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0456 +tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 +tabPatches_ServiceWindowMessage=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index e895189..57898be 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -409,6 +409,13 @@ -fx-min-height: 24; -fx-min-width: 36; } +.regionCake{ + -fx-shape: "M12,1.5A2.5,2.5 0 0,1 14.5,4A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 9.5,4A2.5,2.5 0 0,1 12,1.5M15.87,5C18,5 20,7 20,9C22.7,9 22.7,13 20,13H4C1.3,13 1.3,9 4,9C4,7 6,5 8.13,5C8.57,6.73 10.14,8 12,8C13.86,8 15.43,6.73 15.87,5M5,15H8L9,22H7L5,15M10,15H14L13,22H11L10,15M16,15H19L17,22H15L16,15Z"; + -fx-background-color: #71e016; + -size: 24; + -fx-min-height: -size; + -fx-min-width: -size; +} .regionDump{ -fx-shape: "M 4.0078125 0 C 1.5078125 0 0 1.4882812 0 3.984375 L 0 15.988281 C 0 18.417969 1.4927148 20 4.0078125 20 L 6.5 20 L 6.5 0 L 4.0078125 0 z M 23.5 0 L 23.5 20 C 24.504057 19.999294 25.159942 20 25.992188 20 C 28.414062 20 30 18.496094 30 15.996094 L 30 3.9765625 C 30 1.5195311 28.508726 0 26.003906 0 L 23.5 0 z M 11.990234 2.8886719 L 11.990234 9.9003906 L 6.9902344 9.9003906 L 14.990234 20 L 22.990234 9.9003906 L 17.990234 9.9003906 C 17.990234 8.2387016 17.9999 3.6538029 18 2.8886719 L 11.990234 2.8886719 z M 3.1015625 2.9570312 C 4.1485235 2.9562481 4.9977514 3.8046013 4.9980469 4.8515625 C 4.998831 5.8992865 4.1492865 6.7488309 3.1015625 6.7480469 C 2.0546013 6.7477509 1.2062483 5.8985235 1.2070312 4.8515625 C 1.2073268 3.8053642 2.0553642 2.9573267 3.1015625 2.9570312 z M 26.865234 11.148438 C 27.912958 11.147652 28.762503 11.997198 28.761719 13.044922 C 28.761423 14.091883 27.912195 14.940236 26.865234 14.939453 C 25.819036 14.939158 24.970999 14.09112 24.970703 13.044922 C 24.96992 11.997961 25.818273 11.148733 26.865234 11.148438 z "; -fx-background-color: #71e016; diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index cd2b17c..3bda23e 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -327,6 +327,13 @@ -fx-min-height: 24; -fx-min-width: 36; } +.regionCake{ + -fx-shape: "M12,1.5A2.5,2.5 0 0,1 14.5,4A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 9.5,4A2.5,2.5 0 0,1 12,1.5M15.87,5C18,5 20,7 20,9C22.7,9 22.7,13 20,13H4C1.3,13 1.3,9 4,9C4,7 6,5 8.13,5C8.57,6.73 10.14,8 12,8C13.86,8 15.43,6.73 15.87,5M5,15H8L9,22H7L5,15M10,15H14L13,22H11L10,15M16,15H19L17,22H15L16,15Z"; + -fx-background-color: #71e016; + -size: 24; + -fx-min-height: -size; + -fx-min-width: -size; +} .regionDump{ -fx-shape: "M 4.0078125 0 C 1.5078125 0 0 1.4882812 0 3.984375 L 0 15.988281 C 0 18.417969 1.4927148 20 4.0078125 20 L 6.5 20 L 6.5 0 L 4.0078125 0 z M 23.5 0 L 23.5 20 C 24.504057 19.999294 25.159942 20 25.992188 20 C 28.414062 20 30 18.496094 30 15.996094 L 30 3.9765625 C 30 1.5195311 28.508726 0 26.003906 0 L 23.5 0 z M 11.990234 2.8886719 L 11.990234 9.9003906 L 6.9902344 9.9003906 L 14.990234 20 L 22.990234 9.9003906 L 17.990234 9.9003906 C 17.990234 8.2387016 17.9999 3.6538029 18 2.8886719 L 11.990234 2.8886719 z M 3.1015625 2.9570312 C 4.1485235 2.9562481 4.9977514 3.8046013 4.9980469 4.8515625 C 4.998831 5.8992865 4.1492865 6.7488309 3.1015625 6.7480469 C 2.0546013 6.7477509 1.2062483 5.8985235 1.2070312 4.8515625 C 1.2073268 3.8053642 2.0553642 2.9573267 3.1015625 2.9570312 z M 26.865234 11.148438 C 27.912958 11.147652 28.762503 11.997198 28.761719 13.044922 C 28.761423 14.091883 27.912195 14.940236 26.865234 14.939453 C 25.819036 14.939158 24.970999 14.09112 24.970703 13.044922 C 24.96992 11.997961 25.818273 11.148733 26.865234 11.148438 z "; -fx-background-color: #71e016; From 115625e3a309edd7cc147aaf6fcd3868b91edebe Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 23 Dec 2022 07:22:24 +0300 Subject: [PATCH 073/134] Correct drone config to archive Launch4j artifact --- .drone.yml | 6 +++--- pom.xml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7d3a283..9ef64de 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,7 +4,7 @@ name: default steps: - name: test - image: maven:3-jdk-11 + image: maven:3-openjdk-18 commands: - mvn -B -DskipTests clean package - mvn test -B @@ -17,7 +17,7 @@ steps: commands: - mkdir -p /builds/ns-usbloader - cp target/ns-usbloader-*jar /builds/ns-usbloader/ - - cp target/ns-usbloader-*exe /builds/ns-usbloader/ + - cp target/NS-USBloader-*exe /builds/ns-usbloader/ volumes: - name: builds path: /builds @@ -28,7 +28,7 @@ steps: - . ./.make_legacy - mvn -B -DskipTests clean package - cp target/ns-usbloader-*jar /builds/ns-usbloader/ - - cp target/ns-usbloader-*exe /builds/ns-usbloader/ + - cp target/NS-USBloader-*exe /builds/ns-usbloader/ volumes: - name: m2 path: /root/.m2 diff --git a/pom.xml b/pom.xml index 346cec4..048eb11 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,6 @@ </executions> </plugin> <plugin> - <!-- https://mvnrepository.com/artifact/com.akathist.maven.plugins.launch4j/launch4j-maven-plugin --> <groupId>com.akathist.maven.plugins.launch4j</groupId> <version>2.2.0</version> <artifactId>launch4j-maven-plugin</artifactId> From 338bb699d1af753ab5f33b4dedf10a9102f8ff6a Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 23 Dec 2022 07:47:10 +0300 Subject: [PATCH 074/134] Another one build-system-related fix --- .drone.yml | 4 ++-- .make_legacy | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 9ef64de..bc26d63 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,7 +4,7 @@ name: default steps: - name: test - image: maven:3-openjdk-18 + image: maven:3-openjdk-17 commands: - mvn -B -DskipTests clean package - mvn test -B @@ -23,7 +23,7 @@ steps: path: /builds - name: emerge-legacy-artifact - image: maven:3-jdk-11 + image: maven:3-openjdk-17 commands: - . ./.make_legacy - mvn -B -DskipTests clean package diff --git a/.make_legacy b/.make_legacy index f0f75ee..e907c26 100644 --- a/.make_legacy +++ b/.make_legacy @@ -1,3 +1,4 @@ sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}<\/finalName>/g' pom.xml sed -z -i -e 's/<outfile>target\/NS-USBloader-${project.version}-${maven.build.timestamp}.exe<\/outfile>/<outfile>target\/NS-USBloader-${project.version}-legacy-${maven.build.timestamp}.exe<\/outfile>/g' pom.xml +sed -z -i -e 's/<jar>target\/${project.artifactId}-${project.version}-${maven.build.timestamp}.jar<\/jar>/<jar>target\/${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}.jar<\/jar>/g' pom.xml From a60d929dcdef63f0b0d3d8b7ba3f565b9ad89541 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 23 Dec 2022 21:53:34 +0300 Subject: [PATCH 075/134] Update readme --- README.md | 6 ++--- .../Utilities/patches/es/BinToAsmPrinter.java | 26 ++++++------------- .../Utilities/patches/es/EsPatch.java | 2 ++ .../patches/es/finders/HeuristicEs1.java | 5 ---- .../patches/es/finders/HeuristicEs2.java | 5 ---- .../patches/es/finders/HeuristicEs3.java | 5 ---- .../patches/es/finders/IHeuristicEs.java | 2 -- 7 files changed, 13 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 3bfb3f1..885e4d1 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ With GUI and cookies. Works on Windows, macOS and Linux. Sometimes I add new posts about this project [on my blog page](https://developersu.blogspot.com/search/label/NS-USBloader). - -<img src="screenshots/2.png" alt="screenshot" width="250"/> <img src="screenshots/3.png" alt="screenshot" width="250"/> -<img src="screenshots/4.png" alt="screenshot" width="250"/> <img src="screenshots/5.png" alt="screenshot" width="250"/> +<img src="screenshots/1.png" alt="screenshot" width="250"/> <img src="screenshots/2.png" alt="screenshot" width="250"/> +<img src="screenshots/3.png" alt="screenshot" width="250"/> <img src="screenshots/4.png" alt="screenshot" width="250"/> +<img src="screenshots/5.png" alt="screenshot" width="250"/> #### License diff --git a/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java index d3e2024..65a8fed 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java @@ -102,21 +102,21 @@ public class BinToAsmPrinter { } if ((instructionExpression >> 21 & 0x7FF) == 0x1C2) - return printLDURBSimplified(instructionExpression, offset); + return printImTooLazy("LDURB", instructionExpression, offset); // same to (afterJumpExpression >> 23 & 0x1F9) != 0xA1 switch (instructionExpression >> 22 & 0x1FF){ case 0xA3: // 0b10100011 case 0xA7: // 0b10100111 case 0xA5: // 0b10100101 - return printLDPSimplified(instructionExpression, offset); + return printImTooLazy("LDP", instructionExpression, offset); } switch ((instructionExpression >> 23 & 0xff)){ case 0xA5: return printMOVSimplified(instructionExpression, offset); case 0x22: - return printADDSimplified(instructionExpression, offset); + return printImTooLazy("ADD", instructionExpression, offset); case 0x62: if (((instructionExpression & 0x1f) == 0x1f)){ return printCMNSimplified(instructionExpression, offset); @@ -416,22 +416,12 @@ public class BinToAsmPrinter { 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); + } - private static String printADDSimplified(int instructionExpression, int offset){ //ADD (immediate) - return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ADD . . . \n"+ ANSI_RESET, - offset, Integer.reverseBytes(instructionExpression), instructionExpression); - } - private static String printLDPSimplified(int instructionExpression, int offset){ - return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDP . . . \n"+ ANSI_RESET, - offset, Integer.reverseBytes(instructionExpression), instructionExpression); - } - private static String printLDURBSimplified(int instructionExpression, int offset){ - return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " LDURB . . . \n"+ ANSI_RESET, - offset, Integer.reverseBytes(instructionExpression), instructionExpression); - } private static String printSUBSimplified(int instructionExpression, int offset){ String wx = (instructionExpression >> 31 == 0) ? "W" : "X"; int Rt = instructionExpression & 0x1f; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java index 5ef3173..e0b2003 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -67,6 +67,8 @@ public class EsPatch { fwVersion = Long.parseLong(""+ncaProvider.getSdkVersion()[3]+ncaProvider.getSdkVersion()[2] +ncaProvider.getSdkVersion()[1] +ncaProvider.getSdkVersion()[0]); logPrinter.print("Internal firmware version: "+ncaProvider.getSdkVersion()[3] +"."+ncaProvider.getSdkVersion()[2] +"."+ncaProvider.getSdkVersion()[1] +"."+ncaProvider.getSdkVersion()[0], EMsgType.INFO); + if (fwVersion < 9300) + logPrinter.print("WARNING! FIRMWARES VERSIONS BEFORE 9.0.0 ARE NOT SUPPORTED! USING PRODUCED ES PATCHES (IF ANY) COULD BREAK SOMETHING! IT'S NEVER BEEN TESTED!", EMsgType.WARNING); } private void getBuildId(NSO0Provider nso0Provider) throws Exception{ NSO0Header nso0DecompressedHeader = nso0Provider.getAsDecompressedNSO0().getHeader(); diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java index d063b39..cfd1974 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -101,9 +101,4 @@ class HeuristicEs1 implements IHeuristicEs { return builder.toString(); } - - @Override - public int getId(){ - return 1; - } } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java index e139558..b3fd297 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -160,9 +160,4 @@ class HeuristicEs2 implements IHeuristicEs { return builder.toString(); } - - @Override - public int getId(){ - return 2; - } } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java index fc4591f..8d7d5fd 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -143,9 +143,4 @@ class HeuristicEs3 implements IHeuristicEs { } return builder.toString(); } - - @Override - public int getId(){ - return 3; - } } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java index ade0733..944b474 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java @@ -42,6 +42,4 @@ interface IHeuristicEs { * @return isFound(); * */ boolean setOffsetsNearby(int offsetNearby); - - int getId(); } From 4ccf833aa5cd71fc2938e718971d88be2104a3df Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 26 Dec 2022 07:59:56 +0300 Subject: [PATCH 076/134] Fix issue where version was not showing in CLI mode; clear locale_en_US since it's just placeholder --- README.md | 4 +- src/main/java/nsusbloader/NSLMain.java | 2 +- src/main/resources/locale_en_US.properties | 80 ---------------------- 3 files changed, 3 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 885e4d1..72bf437 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ With GUI and cookies. Works on Windows, macOS and Linux. Sometimes I add new posts about this project [on my blog page](https://developersu.blogspot.com/search/label/NS-USBloader). -<img src="screenshots/1.png" alt="screenshot" width="250"/> <img src="screenshots/2.png" alt="screenshot" width="250"/> +<img src="screenshots/1.png" alt="screenshot" width="250"/> <img src="screenshots/2.png" alt="screenshot" width="250"/> <img src="screenshots/3.png" alt="screenshot" width="250"/> <img src="screenshots/4.png" alt="screenshot" width="250"/> <img src="screenshots/5.png" alt="screenshot" width="250"/> @@ -59,7 +59,7 @@ 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) -* angelodalzotto makes packages in AUR +* Angelo Elias Dalzotto makes packages in AUR ### System requirements diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index bd70c80..040b5bc 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -42,7 +42,6 @@ public class NSLMain extends Application { Locale userLocale = AppPreferences.getInstance().getLocale(); ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale); - NSLMain.appVersion = ResourceBundle.getBundle("app").getString("_version"); loader.setResources(rb); Parent root = loader.load(); @@ -84,6 +83,7 @@ public class NSLMain extends Application { } public static void main(String[] args) { + NSLMain.appVersion = ResourceBundle.getBundle("app").getString("_version"); if (args.length == 0) { launch(args); } diff --git a/src/main/resources/locale_en_US.properties b/src/main/resources/locale_en_US.properties index 62027f7..e69de29 100644 --- a/src/main/resources/locale_en_US.properties +++ b/src/main/resources/locale_en_US.properties @@ -1,80 +0,0 @@ -btn_OpenFile=Select files -btn_OpenFolders=Select folder -btn_Upload=Upload to NS -btn_OpenFolders_tooltip=Select a folder to be scanned.\nThis folder and all of its subfolders will be scanned.\nAll matching files will be added to the list. -tab3_Txt_EnteredAsMsg1=You have been entered as: -tab3_Txt_EnteredAsMsg2=You should be root or have configured 'udev' rules for this user to avoid any issues. -tab3_Txt_FilesToUploadTitle=Files to upload: -tab3_Txt_GreetingsMessage=Welcome to NS-USBloader -tab3_Txt_NoFolderOrFileSelected=No files selected: nothing to upload. -windowBodyConfirmExit=Data transfer is in progress and closing this application will interrupt it.\nIt's the worse thing you can do now.\nInterrupt proccess and exit? -windowTitleConfirmExit=No, don't do this! -btn_Stop=Interrupt -tab3_Txt_GreetingsMessage2=--\n\ -Source: https://git.redrise.ru/desu/ns-usbloader\n\ -Mirror: https://github.com/developersu/ns-usbloader/\n\ -Site: https://redrise.ru\n\ -Dmitry Isaenko [developer.su] -tab1_table_Lbl_Status=Status -tab1_table_Lbl_FileName=File name -tab1_table_Lbl_Size=Size -tab1_table_Lbl_Upload=Upload? -tab1_table_contextMenu_Btn_BtnDelete=Remove -tab1_table_contextMenu_Btn_DeleteAll=Remove all -tab2_Lbl_HostIP=Host IP -tab1_Lbl_NSIP=NS IP: -tab2_Cb_ValidateNSHostName=Always validate NS IP input. -windowBodyBadIp=Are you sure that you entered NS IP address correctly? -windowTitleBadIp=IP address of NS most likely incorrect -tab2_Cb_ExpertMode=Expert mode (NET setup) -tab2_Lbl_HostPort=port -tab2_Cb_AutoDetectIp=Auto-detect IP -tab2_Cb_RandSelectPort=Randomly get port -tab2_Cb_DontServeRequests=Don't serve requests -tab2_Lbl_DontServeRequestsDesc=If selected, this computer won't reply to NSP files requests coming from NS (over the net) and use defined host settings to tell Awoo where should it look for files. -tab2_Lbl_HostExtra=extra -windowTitleErrorPort=Port set incorrectly! -windowBodyErrorPort=Port can't be 0 or greater than 65535. -tab2_Cb_AutoCheckForUpdates=Auto check for updates -windowTitleNewVersionAval=New version available -windowTitleNewVersionNOTAval=No new versions available -windowTitleNewVersionUnknown=Unable to check for new versions -windowBodyNewVersionUnknown=Something went wrong\nMaybe internet unavailable, or GitHub is down -windowBodyNewVersionNOTAval=You're using the latest version -tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Awoo -tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Awoo (aka Adubbz/TinFoil) transfer protocol. Don't change if not sure. Enable for Awoo Installer. -tab2_Lbl_Language=Language -windowBodyRestartToApplyLang=Please restart application to apply changes. -btn_OpenSplitFile=Select split NSP -tab2_Lbl_ApplicationSettings=Main settings -tabSplMrg_Lbl_SplitNMergeTitle=Split & merge files tool -tabSplMrg_RadioBtn_Split=Split -tabSplMrg_RadioBtn_Merge=Merge -tabSplMrg_Txt_File=File: -tabSplMrg_Txt_Folder=Split file (folder): -tabSplMrg_Btn_SelectFile=Select File -tabSplMrg_Btn_SelectFolder=Select Folder -tabSplMrg_Lbl_SaveToLocation=Save to: -tabSplMrg_Btn_ChangeSaveToLocation=Change -tabSplMrg_Btn_Convert=Convert -windowTitleError=Error -windowBodyPleaseFinishTransfersFirst=Unable to split/merge files when application USB/Network process active. Please interrupt active transfers first. -done_txt=Done! -failure_txt=Failed -btn_Select=Select -btn_InjectPayloader=Inject payload -tabNXDT_Btn_Start=Start! -tab2_Btn_InstallDrivers=Download and install drivers -windowTitleDownloadDrivers=Download and install drivers -windowBodyDownloadDrivers=Downloading drivers (libusbK v3.0.7.0)... -btn_Cancel=Cancel -btn_Close=Close -tab2_Cb_GlVersion=GoldLeaf version -tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. -windowBodyPleaseStopOtherProcessFirst=Please stop other active process before continuing. -tab2_Cb_foldersSelectorForRoms=Select folder with ROM files instead of selecting ROMs individually. -tab2_Cb_foldersSelectorForRomsDesc=Changes 'Select files' button behaviour on 'Games' tab: instead of selecting ROM files one-by-one you can choose folder to add every supported file at once. -windowTitleAddingFiles=Searching for files... -windowBodyFilesScanned=Files scanned: %d\nWould be added: %d -tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM -tabRcm_Lbl_Payload=Payload: From f8b60af41ba990bd3b844eee9aed188405405e01 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 1 Feb 2023 12:43:47 +0300 Subject: [PATCH 077/134] FS drafts --- .../Controllers/PatchesController.java | 43 +++- .../Utilities/patches/AHeuristic.java | 45 ++++ .../patches/{es => }/BinToAsmPrinter.java | 47 +++- .../patches/{es => }/SimplyFind.java | 2 +- .../Utilities/patches/es/EsNcaSearchTask.java | 2 +- .../Utilities/patches/es/EsPatch.java | 10 +- .../Utilities/patches/es/EsPatchMaker.java | 6 +- .../patches/es/finders/HeuristicEs1.java | 7 +- .../patches/es/finders/HeuristicEs2.java | 7 +- .../patches/es/finders/HeuristicEs3.java | 7 +- .../patches/es/finders/HeuristicEsWizard.java | 18 +- .../patches/es/finders/IHeuristicEs.java | 45 ---- .../Utilities/patches/fs/FsNcaSearchTask.java | 50 +++++ .../Utilities/patches/fs/FsPatch.java | 208 ++++++++++++++++++ .../Utilities/patches/fs/FsPatchMaker.java | 192 ++++++++++++++++ .../patches/fs/finders/HeuristicFsExFAT1.java | 179 +++++++++++++++ .../patches/fs/finders/HeuristicFsExFAT2.java | 182 +++++++++++++++ .../patches/fs/finders/HeuristicFsFAT1.java | 176 +++++++++++++++ .../patches/fs/finders/HeuristicFsFAT2.java | 163 ++++++++++++++ .../patches/fs/finders/HeuristicFsWizard.java | 139 ++++++++++++ src/main/resources/PatchesTab.fxml | 5 +- 21 files changed, 1447 insertions(+), 86 deletions(-) create mode 100644 src/main/java/nsusbloader/Utilities/patches/AHeuristic.java rename src/main/java/nsusbloader/Utilities/patches/{es => }/BinToAsmPrinter.java (92%) rename src/main/java/nsusbloader/Utilities/patches/{es => }/SimplyFind.java (99%) delete mode 100644 src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 66aaaf7..471bbe0 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -39,20 +39,21 @@ import nsusbloader.MediatorControl; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.ServiceWindow; import nsusbloader.Utilities.patches.es.EsPatchMaker; +import nsusbloader.Utilities.patches.fs.FsPatchMaker; // TODO: CLI SUPPORT public class PatchesController implements Initializable { @FXML private VBox patchesToolPane; @FXML - private Button selFwFolderBtn, selProdKeysBtn, makeEsBtn; + private Button selFwFolderBtn, selProdKeysBtn, makeEsBtn, makeFsBtn; @FXML private Label shortNameFirmwareLbl, locationFirmwareLbl, saveToLbl, shortNameKeysLbl, locationKeysLbl, statusLbl; private Thread workThread; private String previouslyOpenedPath; private ResourceBundle resourceBundle; - private Region convertRegion; + private Region convertRegionEs; @Override public void initialize(URL url, ResourceBundle resourceBundle) { @@ -70,9 +71,10 @@ public class PatchesController implements Initializable { locationKeysLbl.textProperty().addListener((observableValue, currentText, updatedText) -> shortNameKeysLbl.setText(updatedText.replaceAll(myRegexp, ""))); - convertRegion = new Region(); - convertRegion.getStyleClass().add("regionCake"); - makeEsBtn.setGraphic(convertRegion); + convertRegionEs = new Region(); + convertRegionEs.getStyleClass().add("regionCake"); + makeEsBtn.setGraphic(convertRegionEs); + //makeFsBtn.setGraphic(convertRegionEs); AppPreferences preferences = AppPreferences.getInstance(); String keysLocation = preferences.getKeysLocation(); @@ -85,6 +87,7 @@ public class PatchesController implements Initializable { saveToLbl.setText(preferences.getPatchesSaveToLocation()); //makeEsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); makeEsBtn.setOnAction(actionEvent -> makeEs()); + makeFsBtn.setOnAction(actionEvent -> makeFs()); } /** @@ -174,6 +177,30 @@ public class PatchesController implements Initializable { workThread.setDaemon(true); workThread.start(); } + private void makeFs(){ + if (locationFirmwareLbl.getText().isEmpty() || locationKeysLbl.getText().isEmpty()){ + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("tabPatches_ServiceWindowMessage")); + return; + } + + if (workThread != null && workThread.isAlive()) + return; + statusLbl.setText(""); + + if (MediatorControl.getInstance().getTransferActive()) { + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); + return; + } + + FsPatchMaker fsPatchMaker = new FsPatchMaker(locationFirmwareLbl.getText(), locationKeysLbl.getText(), + saveToLbl.getText()); + workThread = new Thread(fsPatchMaker); + + workThread.setDaemon(true); + workThread.start(); + } private void interruptProcessOfPatchMaking(){ if (workThread == null || ! workThread.isAlive()) return; @@ -187,11 +214,11 @@ public class PatchesController implements Initializable { return; } - convertRegion.getStyleClass().clear(); + convertRegionEs.getStyleClass().clear(); if (isActive) { MediatorControl.getInstance().getContoller().logArea.clear(); - convertRegion.getStyleClass().add("regionStop"); + convertRegionEs.getStyleClass().add("regionStop"); makeEsBtn.setOnAction(e-> interruptProcessOfPatchMaking()); makeEsBtn.setText(resourceBundle.getString("btn_Stop")); @@ -199,7 +226,7 @@ public class PatchesController implements Initializable { makeEsBtn.getStyleClass().add("buttonStop"); } else { - convertRegion.getStyleClass().add("regionCake"); + convertRegionEs.getStyleClass().add("regionCake"); makeEsBtn.setOnAction(actionEvent -> makeEs()); makeEsBtn.setText(resourceBundle.getString("tabPatches_Btn_MakeEs")); diff --git a/src/main/java/nsusbloader/Utilities/patches/AHeuristic.java b/src/main/java/nsusbloader/Utilities/patches/AHeuristic.java new file mode 100644 index 0000000..1d1b6ea --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/AHeuristic.java @@ -0,0 +1,45 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches; +/** + * Searches instructions (via known patterns) that follows 'specific instruction' we want to patch. + * Returns offset of the pattern. Not offset of the 'specific instruction'. + * */ +public abstract class AHeuristic { + protected boolean isLDR(int expression){ return (expression >> 22 & 0x2FF) == 0x2e5; }// LDR ! Sounds like LDP, don't mess up + protected boolean isLDP(int expression){ return (expression >> 22 & 0x1F9) == 0xA1; }// LDP ! + protected boolean isCBNZ(int expression){ return (expression >> 24 & 0x7f) == 0x35; } + protected boolean isMOV(int expression){ return (expression >> 23 & 0xff) == 0xA5; } + protected boolean isTBZ(int expression){ return (expression >> 24 & 0x7f) == 0x36; } + protected boolean isLDRB_LDURB(int expression){ return (expression >> 21 & 0x7f7) == 0x1c2; } + protected boolean isMOV_REG(int expression){ return (expression & 0x7FE0FFE0) == 0x2A0003E0; } + protected boolean isB(int expression) { return (expression >> 26 & 0x3f) == 0x5; } + protected boolean isBL(int expression){ return (expression >> 26 & 0x3f) == 0x25; } + protected boolean isADD(int expression){ return (expression >> 23 & 0xff) == 0x22; } + public abstract boolean isFound(); + public abstract boolean wantLessEntropy(); + public abstract int getOffset() throws Exception; + public abstract String getDetails(); + + /** + * Should be used if wantLessEntropy() == true + * @return isFound(); + * */ + public abstract boolean setOffsetsNearby(int offsetNearby); +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java similarity index 92% rename from src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java rename to src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java index 65a8fed..36cb41f 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/BinToAsmPrinter.java +++ b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java @@ -16,10 +16,9 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.Utilities.patches.es; +package nsusbloader.Utilities.patches; import libKonogonka.Converter; -import nsusbloader.Main; import nsusbloader.NSLMain; public class BinToAsmPrinter { @@ -123,6 +122,12 @@ public class BinToAsmPrinter { } case 0xA2: return printSUBSimplified(instructionExpression, offset); + case 0xE2: + case 0x1e2: + return printCMPSimplified(instructionExpression, offset); + case 0x24: + case 0x124: + return printANDSimplified(instructionExpression, offset); } switch (instructionExpression >> 24 & 0xff) { @@ -145,6 +150,7 @@ public class BinToAsmPrinter { case 0x25: return printBLSimplified(instructionExpression, offset); } + System.out.printf("0x%x\n", (instructionExpression >> 23 & 0xff)); return printUnknownSimplified(instructionExpression, offset); } @@ -411,7 +417,7 @@ 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", + "%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, getBConditionalMarker(instructionExpression & 0xf), conditionalJumpLocation, (conditionalJumpLocation + 0x100)); @@ -437,8 +443,8 @@ public class BinToAsmPrinter { private static String printMOVRegisterSimplified(int instructionExpression, int offset){ //ADD (immediate) String sfHw = (instructionExpression >> 31 & 1) == 0 ? "W" : "X"; - int Rm = instructionExpression >> 16 & 0xF; - int Rd = instructionExpression & 0xF; + int Rm = instructionExpression >> 16 & 0x1F; + 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", @@ -480,11 +486,40 @@ public class BinToAsmPrinter { wx, Rt, wx, Rn, imm12); } + private static String printCMPSimplified(int instructionExpression, int offset){ + String sf = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rn = instructionExpression >> 5 & 0x1F; + int conditionalJumpLocation = (instructionExpression >> 10) & 0xfff; + int LSL = (instructionExpression >> 22 & 0b1) == 1 ? 12 : 0; + return String.format( + "%05x "+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, + Rn, + conditionalJumpLocation, (conditionalJumpLocation + 0x100), + LSL); + } + + private static String printANDSimplified(int instructionExpression, int offset){ + String sf = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rn = instructionExpression & 0x1F; + int Rd = instructionExpression >> 5 & 0x1F; + int imm; + if (sf.equals("W")) + imm = instructionExpression >> 10 & 0xfff; + else + imm = instructionExpression >> 10 & 0x1fff; + + return String.format( + "%05x "+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)); + } private static String printUnknownSimplified(int instructionExpression, int offset){ return String.format( - "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ??? 0b"+ANSI_RESET+ Converter.intToBinaryString(Integer.reverseBytes(instructionExpression)) +"\n", + "%05x "+ANSI_CYAN+"%08x (%08x)"+ANSI_YELLOW + " ??? 0b"+ANSI_RESET+ Converter.intToBinaryString(instructionExpression) +"\n", offset, Integer.reverseBytes(instructionExpression), instructionExpression); } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java b/src/main/java/nsusbloader/Utilities/patches/SimplyFind.java similarity index 99% rename from src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java rename to src/main/java/nsusbloader/Utilities/patches/SimplyFind.java index c3a648f..e16a824 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/SimplyFind.java +++ b/src/main/java/nsusbloader/Utilities/patches/SimplyFind.java @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. */ -package nsusbloader.Utilities.patches.es; +package nsusbloader.Utilities.patches; import libKonogonka.Converter; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java b/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java index 12edaff..36b9716 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsNcaSearchTask.java @@ -19,7 +19,7 @@ package nsusbloader.Utilities.patches.es; import libKonogonka.Converter; -import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.fs.NCA.NCAProvider; import java.util.List; import java.util.concurrent.Callable; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java index e0b2003..f513c8b 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -22,11 +22,12 @@ package nsusbloader.Utilities.patches.es; import libKonogonka.Converter; -import libKonogonka.Tools.NCA.NCAProvider; -import libKonogonka.Tools.NSO.NSO0Header; -import libKonogonka.Tools.NSO.NSO0Provider; +import libKonogonka.fs.NCA.NCAProvider; +import libKonogonka.fs.NSO.NSO0Header; +import libKonogonka.fs.NSO.NSO0Provider; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.es.finders.HeuristicEsWizard; import java.io.BufferedOutputStream; @@ -35,6 +36,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; public class EsPatch { @@ -113,7 +115,7 @@ public class EsPatch { handyEsPatch.put(getFooter()); try (BufferedOutputStream stream = new BufferedOutputStream( - Files.newOutputStream(new File(patchFileLocation).toPath()))){ + Files.newOutputStream(Paths.get(patchFileLocation)))){ stream.write(handyEsPatch.array()); } logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java index b28e2ec..e7d85fd 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java @@ -19,7 +19,7 @@ package nsusbloader.Utilities.patches.es; import libKonogonka.KeyChainHolder; -import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.fs.NCA.NCAProvider; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; import nsusbloader.ModelControllers.Log; @@ -92,6 +92,8 @@ public class EsPatchMaker extends CancellableRunnable { private void receiveFirmware() throws Exception{ logPrinter.print("Looking at firmware", EMsgType.INFO); this.firmware = new File(pathToFirmware); + if (! firmware.exists()) + throw new Exception("Firmware directory does not exist " + pathToFirmware); } private void buildKeyChainHolder() throws Exception{ logPrinter.print("Reading keys", EMsgType.INFO); @@ -174,7 +176,7 @@ public class EsPatchMaker extends CancellableRunnable { subTasks.add(task); return subTasks; } - private List<NCAProvider> getNextSet(Iterator<String> iterator, int amount) throws Exception{ + private List<NCAProvider> getNextSet(Iterator<String> iterator, int amount) throws Exception{ List<NCAProvider> ncas = new ArrayList<>(); for (int j = 0; j < amount; j++){ String ncaFileName = iterator.next(); diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java index cfd1974..550d002 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -19,12 +19,13 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; -import nsusbloader.Utilities.patches.es.BinToAsmPrinter; -import nsusbloader.Utilities.patches.es.SimplyFind; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; -class HeuristicEs1 implements IHeuristicEs { +class HeuristicEs1 extends AHeuristic { private static final String PATTERN = "1F90013128.8052"; private final List<Integer> findings; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java index b3fd297..4dba546 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -19,13 +19,14 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; -import nsusbloader.Utilities.patches.es.BinToAsmPrinter; -import nsusbloader.Utilities.patches.es.SimplyFind; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; import java.util.ArrayList; import java.util.List; -class HeuristicEs2 implements IHeuristicEs { +class HeuristicEs2 extends AHeuristic { private static final String PATTERN = ".D2.52"; private List<Integer> findings; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java index 8d7d5fd..728a98a 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -19,12 +19,13 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; -import nsusbloader.Utilities.patches.es.BinToAsmPrinter; -import nsusbloader.Utilities.patches.es.SimplyFind; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; -class HeuristicEs3 implements IHeuristicEs { +class HeuristicEs3 extends AHeuristic { private static final String PATTERN0 = "..FF97"; private static final String PATTERN1 = "......FF97"; // aka "E0230091..FF97"; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java index 5afdf60..e130ca9 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEsWizard.java @@ -18,14 +18,16 @@ */ package nsusbloader.Utilities.patches.es.finders; +import nsusbloader.Utilities.patches.AHeuristic; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class HeuristicEsWizard { - private final List<IHeuristicEs> all; - private final List<IHeuristicEs> found; - private final List<IHeuristicEs> wantLessEntropy; + private final List<AHeuristic> all; + private final List<AHeuristic> found; + private final List<AHeuristic> wantLessEntropy; private final StringBuilder errorsAndNotes; @@ -43,14 +45,14 @@ public class HeuristicEsWizard { ); this.found = all.stream() - .filter(IHeuristicEs::isFound) + .filter(AHeuristic::isFound) .collect(Collectors.toList()); if (found.isEmpty()) throw new Exception("Nothing found!"); this.wantLessEntropy = all.stream() - .filter(IHeuristicEs::wantLessEntropy) + .filter(AHeuristic::wantLessEntropy) .collect(Collectors.toList()); shareOffsetsWithEachOther(); @@ -61,14 +63,14 @@ public class HeuristicEsWizard { } private void shareOffsetsWithEachOther(){ - for (IHeuristicEs es : wantLessEntropy) { + for (AHeuristic es : wantLessEntropy) { if (shareWithNext(es)) return; } } - private boolean shareWithNext(IHeuristicEs es){ + private boolean shareWithNext(AHeuristic es){ try { - for (IHeuristicEs foundEs : found) { + for (AHeuristic foundEs : found) { if (es.setOffsetsNearby(foundEs.getOffset())) { found.add(es); wantLessEntropy.remove(es); diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java deleted file mode 100644 index 944b474..0000000 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/IHeuristicEs.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2018-2022 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. - */ -package nsusbloader.Utilities.patches.es.finders; -/** - * Searches instructions (via known patterns) that follows 'specific instruction' we want to patch. - * Returns offset of the pattern. Not offset of the 'specific instruction'. - * */ -interface IHeuristicEs { - default boolean isLDR(int expression){ return (expression >> 22 & 0x2FF) == 0x2e5; }// LDR ! Sounds like LDP, don't mess up - default boolean isLDP(int expression){ return (expression >> 22 & 0x1F9) == 0xA1; }// LDP ! - default boolean isCBNZ(int expression){ return (expression >> 24 & 0x7f) == 0x35; } - default boolean isMOV(int expression){ return (expression >> 23 & 0xff) == 0xA5; } - default boolean isTBZ(int expression){ return (expression >> 24 & 0x7f) == 0x36; } - default boolean isLDRB_LDURB(int expression){ return (expression >> 21 & 0x7f7) == 0x1c2; } - default boolean isMOV_REG(int expression){ return (expression & 0x7FE0FFE0) == 0x2A0003E0; } - default boolean isB(int expression) { return (expression >> 26 & 0x3f) == 0x5; } - default boolean isBL(int expression){ return (expression >> 26 & 0x3f) == 0x25; } - default boolean isADD(int expression){ return (expression >> 23 & 0xff) == 0x22; } - boolean isFound(); - boolean wantLessEntropy(); - int getOffset() throws Exception; - String getDetails(); - - /** - * Should be used if wantLessEntropy() == true - * @return isFound(); - * */ - boolean setOffsetsNearby(int offsetNearby); -} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java new file mode 100644 index 0000000..718a234 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java @@ -0,0 +1,50 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs; + +import libKonogonka.Converter; +import libKonogonka.fs.NCA.NCAProvider; + +import java.util.List; +import java.util.concurrent.Callable; + +class FsNcaSearchTask implements Callable<NCAProvider> { + private final List<NCAProvider> ncaProviders; + + FsNcaSearchTask(List<NCAProvider> ncaProviders){ + this.ncaProviders = ncaProviders; + } + + @Override + public NCAProvider call() { + try { + for (NCAProvider ncaProvider : ncaProviders) { + String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId()); + if (titleId.equals("0100000000000819") || titleId.equals("010000000000081b")) { // eq. FAT || exFAT + return ncaProvider; + } + } + return null; + } + catch (Exception e){ + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java new file mode 100644 index 0000000..84649ce --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -0,0 +1,208 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + --- + Based on FS-AutoIPS.py patch script made by GBATemp member MrDude. + Taken from: https://gbatemp.net/threads/info-on-sha-256-hashes-on-fs-patches.581550/ + */ +package nsusbloader.Utilities.patches.fs; + +import libKonogonka.Converter; +import libKonogonka.KeyChainHolder; +import libKonogonka.RainbowDump; +import libKonogonka.fs.NCA.NCAProvider; +import libKonogonka.fs.NSO.NSO0Provider; +import libKonogonka.fs.RomFs.FileSystemEntry; +import libKonogonka.fs.RomFs.RomFsProvider; +import libKonogonka.fs.other.System2.System2Provider; +import libKonogonka.fs.other.System2.ini1.Ini1Provider; +import libKonogonka.fs.other.System2.ini1.KIP1Provider; +import libKonogonka.aesctr.InFileStreamProducer; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.fs.finders.HeuristicFsWizard; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class FsPatch { + private final NCAProvider ncaProvider; + private final String saveToLocation; + private final KeyChainHolder keyChainHolder; + private final ILogPrinter logPrinter; + + private long fwVersion; + private String patchName; + private byte[] _textSection; + private boolean filesystemTypeFat32; + + private HeuristicFsWizard wizard; + + FsPatch(NCAProvider ncaProvider, String saveToLocation, KeyChainHolder keyChainHolder, ILogPrinter logPrinter) throws Exception{ + this.ncaProvider = ncaProvider; + this.saveToLocation = saveToLocation + File.separator + + "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches"; + this.keyChainHolder = keyChainHolder; + this.logPrinter = logPrinter; + + KIP1Provider kip1Provider = getKIP1Provider(); + getPatchName(kip1Provider); + getTextSection(kip1Provider); + checkFirmwareVersion(); + getFilesystemType(); + findAllOffsets(); + //mkDirs(); + //writeFile(); + //updatePatchesIni(); + //logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); + } + private KIP1Provider getKIP1Provider() throws Exception{ + RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs(); + + FileSystemEntry package2FSEntry = romFsProvider.getRootEntry().getContent() + .stream() + .filter(e -> e.getName().equals("nx")) + .collect(Collectors.toList()) + .get(0) + .getContent() + .stream() + .filter(e -> e.getName().equals("package2")) + .collect(Collectors.toList()) + .get(0); + InFileStreamProducer producer = romFsProvider.getStreamProducer(package2FSEntry); + System2Provider system2Provider = new System2Provider(producer, keyChainHolder); + Ini1Provider ini1Provider = system2Provider.getIni1Provider(); + + KIP1Provider kip1Provider = ini1Provider.getKip1List().stream() + .filter(provider -> provider.getHeader().getName().startsWith("FS")) + .collect(Collectors.toList()) + .get(0); + + if (kip1Provider == null) + throw new Exception("No FS KIP1"); + return kip1Provider; + } + private void getPatchName(KIP1Provider kip1Provider) throws Exception{ + int kip1EncryptedSize = (int) kip1Provider.getSize(); + byte[] kip1EncryptedRaw = new byte[kip1EncryptedSize]; + + try (BufferedInputStream kip1ProviderStream = kip1Provider.getStreamProducer().produce()) { + if (kip1EncryptedSize != kip1ProviderStream.read(kip1EncryptedRaw)) + throw new Exception("Unencrypted FS KIP1 read failure"); + } + + byte[] sha256ofKip1 = MessageDigest.getInstance("SHA-256").digest(kip1EncryptedRaw); + patchName = Converter.byteArrToHexStringAsLE(sha256ofKip1, true) + ".ips"; + } + private void getTextSection(KIP1Provider kip1Provider) throws Exception{ + _textSection = kip1Provider.getAsDecompressed().getTextRaw(); + } + private void checkFirmwareVersion() throws Exception{ + final byte[] byteSdkVersion = ncaProvider.getSdkVersion(); + fwVersion = Long.parseLong(""+byteSdkVersion[3] + byteSdkVersion[2] + byteSdkVersion[1] + byteSdkVersion[0]); + logPrinter.print("Internal firmware version: " + + byteSdkVersion[3] +"."+byteSdkVersion[2] +"."+byteSdkVersion[1] +"."+byteSdkVersion[0], EMsgType.INFO); + System.out.println("FW "+byteSdkVersion[3] +"."+byteSdkVersion[2] +"."+byteSdkVersion[1] +"."+byteSdkVersion[0]); // TODO:REMOVE! + if (fwVersion < 9300) + logPrinter.print("WARNING! FIRMWARES VERSIONS BEFORE 9.0.0 ARE NOT SUPPORTED! " + + "USING PRODUCED ES PATCHES (IF ANY) COULD BREAK SOMETHING! IT'S NEVER BEEN TESTED!", EMsgType.WARNING); + } + private void getFilesystemType(){ + String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId()); + filesystemTypeFat32 = titleId.equals("0100000000000819"); + } + private void findAllOffsets() throws Exception{ + // TODO: FIX, IMPLEMENT, DEPLOY + this.wizard = new HeuristicFsWizard(fwVersion, _textSection, filesystemTypeFat32); + String errorsAndNotes = wizard.getErrorsAndNotes(); + if (errorsAndNotes.length() > 0) + logPrinter.print(errorsAndNotes, EMsgType.WARNING); + } + private void mkDirs(){ + File parentFolder = new File(saveToLocation); + parentFolder.mkdirs(); + } + + private void writeFile() throws Exception{ + String patchFileLocation = saveToLocation + File.separator + patchName; // THIS IS GOOD + int offset1 = wizard.getOffset1(); + + ByteBuffer handyFsPatch = ByteBuffer.allocate(0x23).order(ByteOrder.LITTLE_ENDIAN); + handyFsPatch.put(getHeader()); + // TODO: FIX, UPDATE + if (offset1 > 0) { + logPrinter.print("Patch component 1 will be used", EMsgType.PASS); + handyFsPatch.put(getPatch1(offset1)); + } + handyFsPatch.put(getFooter()); + + try (BufferedOutputStream stream = new BufferedOutputStream( + Files.newOutputStream(Paths.get(patchFileLocation)))){ + stream.write(handyFsPatch.array()); + } + logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); + } + private byte[] getHeader(){ + return "PATCH".getBytes(StandardCharsets.US_ASCII); + } + private byte[] getFooter(){ + return "EOF".getBytes(StandardCharsets.US_ASCII); + } + + private byte[] getPatch1(int offset) throws Exception{ + int requiredInstructionOffsetInternal = offset - 4; + int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; + int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); + int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); + + logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + + // Somehow IPS patches uses offsets written as big_endian (0.o) and bytes dat should be patched as LE. + ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) + .putInt(requiredInstructionOffsetReal) + .putShort((short) 4) + .putInt(Integer.reverseBytes(patch)); + + return Arrays.copyOfRange(prePatch.array(), 1, 10); + } + private byte[] getPatch2(int offset) throws Exception{ + int requiredInstructionOffsetInternal = offset - 4; + int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; + int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); + int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); + + logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + + // Somehow IPS patches uses offsets written as big_endian (0.o) and bytes dat should be patched as LE. + ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) + .putInt(requiredInstructionOffsetReal) + .putShort((short) 4) + .putInt(Integer.reverseBytes(patch)); + + return Arrays.copyOfRange(prePatch.array(), 1, 10); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java new file mode 100644 index 0000000..f33c4fa --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java @@ -0,0 +1,192 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs; + +import libKonogonka.KeyChainHolder; +import libKonogonka.fs.NCA.NCAProvider; +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.NSLDataTypes.EFileStatus; + +import java.io.File; +import java.util.*; +import java.util.concurrent.*; + +public class FsPatchMaker extends CancellableRunnable { + private int THREADS_POOL_SIZE = 4; + private final ILogPrinter logPrinter; + private final String pathToFirmware; + private final String pathToKeysFile; + private final String saveTo; + + private File firmware; + private KeyChainHolder keyChainHolder; + private ExecutorService executorService; + private List<String> ncaFilesList; // inside the folder + + private boolean oneLinerStatus = false; + + public FsPatchMaker(String pathToFirmware, String pathToKeysFile, String saveTo){ + //this.logPrinter = Log.getPrinter(EModule.PATCHES); //TODO: UNCOMMENT + + this.logPrinter = new ILogPrinter() { + @Override + public void print(String message, EMsgType type) throws InterruptedException {} + @Override + public void updateProgress(Double value) throws InterruptedException {} + @Override + public void update(HashMap<String, File> nspMap, EFileStatus status) {} + @Override + public void update(File file, EFileStatus status) {} + @Override + public void updateOneLinerStatus(boolean status) {} + @Override + public void close() {} + }; + // */ + this.pathToFirmware = pathToFirmware; + this.pathToKeysFile = pathToKeysFile; + this.saveTo = saveTo; + } + + @Override + public void run() { + try { + logPrinter.print("..:: Make FS Patches ::..", EMsgType.INFO); + receiveFirmware(); + buildKeyChainHolder(); + receiveNcaFileNamesList(); + adjustThreadsPoolSize(); + createPool(); + executePool(); + } + catch (Exception e){ + e.printStackTrace(); + try{ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } catch (Exception ignore){} + } + finally { + logPrinter.updateOneLinerStatus(oneLinerStatus); + logPrinter.close(); + } + } + private void receiveFirmware() throws Exception{ + logPrinter.print("Looking at firmware", EMsgType.INFO); + this.firmware = new File(pathToFirmware); + if (! firmware.exists()) + throw new Exception("Firmware directory does not exist " + pathToFirmware); + } + private void buildKeyChainHolder() throws Exception{ + logPrinter.print("Reading keys", EMsgType.INFO); + this.keyChainHolder = new KeyChainHolder(pathToKeysFile, null); + } + private void receiveNcaFileNamesList() throws Exception{ + logPrinter.print("Collecting NCA files", EMsgType.INFO); + String[] fileNamesArray = firmware.list( + (File directory, String file) -> ( ! file.endsWith(".cnmt.nca") && file.endsWith(".nca"))); + ncaFilesList = Arrays.asList(Objects.requireNonNull(fileNamesArray)); + if (ncaFilesList.size() == 0) + throw new Exception("No NCA files found in firmware folder"); + } + private void adjustThreadsPoolSize(){ + if (ncaFilesList.size() < 4) + THREADS_POOL_SIZE = ncaFilesList.size(); + } + + private void createPool() throws Exception{ + logPrinter.print("Creating sub-tasks pool", EMsgType.INFO); + this.executorService = Executors.newFixedThreadPool( + THREADS_POOL_SIZE, + runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + return thread; + }); + } + + private void executePool() throws Exception{ //TODO: FIX. Exceptions thrown only by logPrinter + try { + logPrinter.print("Executing sub-tasks pool", EMsgType.INFO); + List<Future<NCAProvider>> futuresResults = executorService.invokeAll(getSubTasksCollection()); + int counter = 0; + for (Future<NCAProvider> future : futuresResults){ + NCAProvider ncaProvider = future.get(); + if (ncaProvider != null) { + makePatches(ncaProvider); + if (++counter > 1) + break; + } + } + executorService.shutdown(); + } + catch (InterruptedException ie){ + executorService.shutdownNow(); + boolean interruptedSuccessfully = false; + try { + interruptedSuccessfully = executorService.awaitTermination(20, TimeUnit.SECONDS); + } + catch (InterruptedException awaitInterrupt){ + logPrinter.print("Force interrupting task...", EMsgType.WARNING); + } + logPrinter.print("Task interrupted "+(interruptedSuccessfully?"successfully":"with some issues"), EMsgType.WARNING); + } + catch (Exception e){ + e.printStackTrace(); + logPrinter.print("Task failed: "+e.getMessage(), EMsgType.FAIL); + } + } + + private void makePatches(NCAProvider ncaProvider) throws Exception{ + logPrinter.print(String.format("File found: .."+File.separator+"%s"+File.separator+"%s", + ncaProvider.getFile().getParentFile().getName(), ncaProvider.getFile().getName()) + , EMsgType.INFO); + //TODO : FIX; IMPLEMENT; DEPLOY ;) + new FsPatch(ncaProvider, saveTo, keyChainHolder, logPrinter); + oneLinerStatus = true; + } + private List<Callable<NCAProvider>> getSubTasksCollection() throws Exception{ + logPrinter.print("Forming sub-tasks collection", EMsgType.INFO); + List<Callable<NCAProvider>> subTasks = new ArrayList<>(); + + int ncaPerThreadAmount = ncaFilesList.size() / THREADS_POOL_SIZE; + Iterator<String> iterator = ncaFilesList.listIterator(); + + for (int i = 1; i < THREADS_POOL_SIZE; i++){ + Callable<NCAProvider> task = new FsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount)); + subTasks.add(task); + } + + Callable<NCAProvider> task = new FsNcaSearchTask(getNextSet(iterator, + ncaFilesList.size() % THREADS_POOL_SIZE == 0 ? ncaPerThreadAmount : ncaPerThreadAmount+1)); + subTasks.add(task); + return subTasks; + } + private List<NCAProvider> getNextSet(Iterator<String> iterator, int amount) throws Exception{ + List<NCAProvider> ncas = new ArrayList<>(); + for (int j = 0; j < amount; j++){ + String ncaFileName = iterator.next(); + File nca = new File(firmware.getAbsolutePath()+File.separator+ncaFileName); + NCAProvider provider = new NCAProvider(nca, keyChainHolder.getRawKeySet()); + ncas.add(provider); + } + return ncas; + } +} \ No newline at end of file diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java new file mode 100644 index 0000000..148d3e5 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java @@ -0,0 +1,179 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.List; + +class HeuristicFsExFAT1 extends AHeuristic { + private static final String PATTERN0 = ".1e42b91fc14271"; + private static final String PATTERN1 = "9408...1F05.....54"; + + private final List<Integer> findings; + private final byte[] where; + + HeuristicFsExFAT1(long fwVersion, byte[] where){ + this.where = where; + String pattern = getPattern(fwVersion); + SimplyFind simplyfind = new SimplyFind(pattern, where); + this.findings = simplyfind.getResults(); + for (Integer find : findings) + System.out.println(getDetails(find)); +/* FIXME + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + */ + } + private String getPattern(long fwVersion){ + if (fwVersion < 15300) // & fwVersion >= 9300 + return PATTERN0; + return PATTERN1; + } + // Let's focus on CBZ-ONLY statements + private boolean dropStep1(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + private boolean dropStep2(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); + + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); + int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + // Check first is 'MOV'; second is 'B' + return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); + } + + private boolean dropStep3(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); + int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); + // Check first is 'ADD'; second is 'BL' + return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); + } + + private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + public String getDetails(Integer value){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = value - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ + builder.append("...\n"); + int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); + + } + else { + builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); + } + return builder.toString(); + } + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = findings.get(0) - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ + builder.append("...\n"); + int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); + + } + else { + builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); + } + return builder.toString(); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java new file mode 100644 index 0000000..5463a6e --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java @@ -0,0 +1,182 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.List; + +class HeuristicFsExFAT2 extends AHeuristic { + private static final String PATTERN0 = ".94081C00121F05007181000054"; + private static final String PATTERN1 = "003688...1F"; + + private final List<Integer> findings; + private final byte[] where; + + HeuristicFsExFAT2(long fwVersion, byte[] where){ + this.where = where; + String pattern = getPattern(fwVersion); + SimplyFind simplyfind = new SimplyFind(pattern, where); + this.findings = simplyfind.getResults(); + + for (Integer find : findings) + System.out.println(getDetails(find)); + /* FIXME + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + + */ + } + private String getPattern(long fwVersion){ + if (fwVersion < 15300) // & fwVersion >= 9300 + return PATTERN0; + return PATTERN1; + } + // Let's focus on CBZ-ONLY statements + private boolean dropStep1(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + private boolean dropStep2(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); + + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); + int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + // Check first is 'MOV'; second is 'B' + return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); + } + + private boolean dropStep3(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); + int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); + // Check first is 'ADD'; second is 'BL' + return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); + } + + private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + public String getDetails(Integer value){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = value - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ + builder.append("...\n"); + int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); + + } + else { + builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); + } + return builder.toString(); + } + + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = findings.get(0) - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ + builder.append("...\n"); + int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); + + } + else { + builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); + } + return builder.toString(); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java new file mode 100644 index 0000000..bae72b3 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java @@ -0,0 +1,176 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.ArrayList; +import java.util.List; + +class HeuristicFsFAT1 extends AHeuristic { + private static final String PATTERN0 = ".1e42b91fc14271"; + private static final String PATTERN1 = "...9408...1F05.....54"; // ...94 081C0012 1F050071 4101 +/* + 710006eba0 c0 02 40 f9 ldr x0 , [ x22 ] + 710006eba4 5c c9 02 94 bl FUN_7100121114 undefined FUN_7100121114() + 710006eba8 08 1c 00 12 and w8 , w0 , # 0xff + */ + private final List<Integer> findings; + private final byte[] where; + + HeuristicFsFAT1(long fwVersion, byte[] where){ + this.where = where; + String pattern = getPattern(fwVersion); + SimplyFind simplyfind = new SimplyFind(pattern, where); + List<Integer> temporary = simplyfind.getResults(); + if (fwVersion >= 15300){ + this.findings = new ArrayList<>(); + temporary.forEach(var -> findings.add(var + 4)); + } + else + findings = temporary; + + System.out.println("\t\tFAT32 # 1 +++++++++++++++++++++++++++++++"); + for (Integer find : findings) { + System.out.println(getDetails(find)); + System.out.println("------------------------------------------------------------------"); + } +/* FIXME + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + */ + } + private String getPattern(long fwVersion){ + if (fwVersion < 15300) // & fwVersion >= 9300 + return PATTERN0; + return PATTERN1; + } + // Let's focus on CBZ-ONLY statements + private boolean dropStep1(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + private boolean dropStep2(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); + + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); + int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + // Check first is 'MOV'; second is 'B' + return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); + } + + private boolean dropStep3(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); + int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); + // Check first is 'ADD'; second is 'BL' + return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); + } + + private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + public String getDetails(Integer value){ + int firstOffsetInternal = value - 4; + int firstExpression = Converter.getLEint(where, firstOffsetInternal); + int conditionalJumpLocation = ((firstExpression >> 5 & 0x7FFFF) * 4 + firstOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + StringBuilder builder = new StringBuilder(); + //builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal-4*11), firstOffsetInternal-4*11)); + //builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal-4*10), firstOffsetInternal-4*10)); + //builder.append("^ ^ ^ ...\n"); + builder.append(BinToAsmPrinter.printSimplified(firstExpression, firstOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+4), firstOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+8), firstOffsetInternal+8)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+12), firstOffsetInternal+12)); + builder.append("...\n"); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + return builder.toString(); + } + @Override + public String getDetails(){ + int firstOffsetInternal = findings.get(0) - 4; + int firstExpression = Converter.getLEint(where, firstOffsetInternal); + int conditionalJumpLocation = ((firstExpression >> 5 & 0x7FFFF) * 4 + firstOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + return BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 11), firstOffsetInternal - 4 * 11) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 10), firstOffsetInternal - 4 * 10) + + "^ ^ ^ ...\n" + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 2), firstOffsetInternal - 4 * 2) + + "^ ^ ^ ...\n" + + BinToAsmPrinter.printSimplified(firstExpression, firstOffsetInternal) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal + 4), firstOffsetInternal + 4) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal + 8), firstOffsetInternal + 8) + + "...\n" + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java new file mode 100644 index 0000000..c265a31 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java @@ -0,0 +1,163 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.List; + +class HeuristicFsFAT2 extends AHeuristic { + private static final String PATTERN0 = "...94081C00121F05007181000054"; + private static final String PATTERN1 = "..003688...1F"; + + private final List<Integer> findings; + private final byte[] where; + + HeuristicFsFAT2(long fwVersion, byte[] where){ + this.where = where; + String pattern = getPattern(fwVersion); + SimplyFind simplyfind = new SimplyFind(pattern, where); + this.findings = simplyfind.getResults(); + /* + System.out.println("\t\tFAT32 # 2 +++++++++++++++++++++++++++++++"); + for (Integer find : findings) { + System.out.println(getDetails(find)); + System.out.println("------------------------------------------------------------------"); + } + /* FIXME + this.findings.removeIf(this::dropStep1); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep2); + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep3); + + */ + } + private String getPattern(long fwVersion){ + if (fwVersion < 15300) // & fwVersion >= 9300 + return PATTERN0; + return PATTERN1; + } + // Let's focus on CBZ-ONLY statements + private boolean dropStep1(int offsetOfPatternFound){ + return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); + } + + private boolean dropStep2(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); + + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); + int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + // Check first is 'MOV'; second is 'B' + return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); + } + + private boolean dropStep3(int offsetOfPatternFound){ + int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); + int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); + int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; + + int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); + int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); + // Check first is 'ADD'; second is 'BL' + return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); + } + + private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + public String getDetails(Integer value){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = value - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + return builder.toString(); + } + + @Override + public String getDetails(){ + StringBuilder builder = new StringBuilder(); + int cbzOffsetInternal = findings.get(0) - 4; + int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); + int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); + builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); + builder.append("...\n"); + + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); + builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); + + return builder.toString(); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java new file mode 100644 index 0000000..3d3d240 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java @@ -0,0 +1,139 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import nsusbloader.Utilities.patches.AHeuristic; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class HeuristicFsWizard { + /* + private final List<AHeuristic> all; + private final List<AHeuristic> found; + private final List<AHeuristic> wantLessEntropy; + */ + private final boolean isFat; + private List<AHeuristic> all; + private List<AHeuristic> found; + private List<AHeuristic> wantLessEntropy; + + private final StringBuilder errorsAndNotes; + + private int offset1 = -1; + private int offset2 = -1; + + public HeuristicFsWizard(long fwVersion, byte[] where, boolean isFat) throws Exception{ + this.isFat = isFat; + this.errorsAndNotes = new StringBuilder(); + + if (isFat){ + this.all = Arrays.asList( + new HeuristicFsFAT1(fwVersion, where), + new HeuristicFsFAT2(fwVersion, where) + ); + } + else { + System.out.println("TODO: IMPLEMENT FOR EXFAT"); + return; + /* + this.all = Arrays.asList( + new HeuristicFsExFAT1(fwVersion, where), + new HeuristicFsExFAT2(fwVersion, where) + ); + */ + } +/* + this.found = all.stream() + .filter(AHeuristic::isFound) + .collect(Collectors.toList()); + + if (found.isEmpty()) + throw new Exception("Nothing found!"); + + this.wantLessEntropy = all.stream() + .filter(AHeuristic::wantLessEntropy) + .collect(Collectors.toList()); +/* FIXME + shareOffsetsWithEachOther(); + + assignOffset1(); + assignOffset2(); + */ + } + + private void shareOffsetsWithEachOther(){ + for (AHeuristic es : wantLessEntropy) { + if (shareWithNext(es)) + return; + } + } + private boolean shareWithNext(AHeuristic es){ + try { + for (AHeuristic foundEs : found) { + if (es.setOffsetsNearby(foundEs.getOffset())) { + found.add(es); + wantLessEntropy.remove(es); + shareOffsetsWithEachOther(); + return true; + } + } + } + catch (Exception e){ e.printStackTrace(); } + return false; + } + + private void assignOffset1(){ + try { + offset1 = all.get(0).getOffset(); + } + catch (Exception e){ errorsAndNotes.append(e.getLocalizedMessage()).append("\n"); } + } + private void assignOffset2(){ + try { + offset2 = all.get(1).getOffset(); + } + catch (Exception e){ errorsAndNotes.append(e.getLocalizedMessage()).append("\n"); } + } + + public String getErrorsAndNotes(){ + return errorsAndNotes.toString(); + } + + public String getDebug(){ + StringBuilder builder = new StringBuilder(); + if (isFat) + builder.append("\t\t--[ FAT32 ]--\n"); + else + builder.append("\t\t--[ ExFAT ]--\n"); + if (all.get(0).isFound()){ + builder.append("\t\t-=== 1 ===-\n"); + builder.append(all.get(0).getDetails()); + } + if (all.get(1).isFound()){ + builder.append("\t\t-=== 2 ===-\n"); + builder.append(all.get(1).getDetails()); + } + return builder.toString(); + } + + public int getOffset1() { return offset1; } + public int getOffset2() { return offset2; } +} diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml index 801bf17..d888c2c 100644 --- a/src/main/resources/PatchesTab.fxml +++ b/src/main/resources/PatchesTab.fxml @@ -14,7 +14,7 @@ <?import javafx.scene.shape.SVGPath?> <?import javafx.scene.text.Font?> -<ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> +<ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> <VBox fx:id="patchesToolPane" spacing="15.0"> <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #2cd882 40%, transparent 45%);" /> <HBox alignment="CENTER"> @@ -110,9 +110,10 @@ </children> </HBox> <Pane VBox.vgrow="ALWAYS" /> - <HBox alignment="CENTER"> + <HBox alignment="CENTER" spacing="5.0"> <children> <Button fx:id="makeEsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeEs" /> + <Button fx:id="makeFsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeFs" /> </children> </HBox> <padding> From 6c51b8e1b6a2bb423f52c71aa93d62a14773601c Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sat, 4 Feb 2023 15:25:34 +0300 Subject: [PATCH 078/134] Corrections at front and back end --- .../Controllers/PatchesController.java | 11 +- .../Utilities/patches/BinToAsmPrinter.java | 29 +-- .../patches/MalformedIniFileException.java | 26 +++ .../Utilities/patches/es/EsPatch.java | 27 ++- .../Utilities/patches/es/EsPatchMaker.java | 23 +-- .../patches/es/finders/HeuristicEs1.java | 19 +- .../patches/es/finders/HeuristicEs2.java | 20 +- .../patches/es/finders/HeuristicEs3.java | 5 +- .../Utilities/patches/fs/FsNcaSearchTask.java | 14 +- .../Utilities/patches/fs/FsPatch.java | 105 +++++----- .../Utilities/patches/fs/FsPatchMaker.java | 49 ++--- .../Utilities/patches/fs/IniMaker.java | 163 ++++++++++++++++ .../patches/fs/finders/HeuristicFs1.java | 89 +++++++++ .../patches/fs/finders/HeuristicFs2.java | 96 +++++++++ .../patches/fs/finders/HeuristicFsExFAT1.java | 179 ----------------- .../patches/fs/finders/HeuristicFsExFAT2.java | 182 ------------------ .../patches/fs/finders/HeuristicFsFAT1.java | 176 ----------------- .../patches/fs/finders/HeuristicFsFAT2.java | 163 ---------------- .../patches/fs/finders/HeuristicFsWizard.java | 40 +--- src/main/resources/PatchesTab.fxml | 2 +- 20 files changed, 531 insertions(+), 887 deletions(-) create mode 100644 src/main/java/nsusbloader/Utilities/patches/MalformedIniFileException.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java delete mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java delete mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java delete mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java delete mode 100644 src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 471bbe0..7b0261b 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -18,6 +18,7 @@ */ package nsusbloader.Controllers; +import javafx.beans.binding.Bindings; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.input.DragEvent; @@ -74,7 +75,10 @@ public class PatchesController implements Initializable { convertRegionEs = new Region(); convertRegionEs.getStyleClass().add("regionCake"); makeEsBtn.setGraphic(convertRegionEs); - //makeFsBtn.setGraphic(convertRegionEs); + + Region cakeRegionFs = new Region(); + cakeRegionFs.getStyleClass().add("regionCake"); + makeFsBtn.setGraphic(cakeRegionFs); AppPreferences preferences = AppPreferences.getInstance(); String keysLocation = preferences.getKeysLocation(); @@ -85,8 +89,10 @@ public class PatchesController implements Initializable { } saveToLbl.setText(preferences.getPatchesSaveToLocation()); - //makeEsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); + makeEsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); makeEsBtn.setOnAction(actionEvent -> makeEs()); + + makeFsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); makeFsBtn.setOnAction(actionEvent -> makeFs()); } @@ -215,6 +221,7 @@ public class PatchesController implements Initializable { } convertRegionEs.getStyleClass().clear(); + makeFsBtn.setVisible(! isActive); if (isActive) { MediatorControl.getInstance().getContoller().logArea.clear(); diff --git a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java index 36cb41f..622d27c 100644 --- a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java +++ b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java @@ -59,7 +59,7 @@ public class BinToAsmPrinter { return printMOV(instructionExpression); case 0x62: if (((instructionExpression & 0x1f) == 0x1f)){ - return printCMN(instructionExpression, offset); + return printCMN(instructionExpression); } } @@ -123,10 +123,10 @@ public class BinToAsmPrinter { case 0xA2: return printSUBSimplified(instructionExpression, offset); case 0xE2: - case 0x1e2: + //case 0x1e2: return printCMPSimplified(instructionExpression, offset); case 0x24: - case 0x124: + //case 0x124: return printANDSimplified(instructionExpression, offset); } @@ -144,13 +144,15 @@ public class BinToAsmPrinter { return printBConditionalSimplified(instructionExpression, offset); } - switch ((instructionExpression >> 26 & 0b111111)) { + switch (instructionExpression >> 26 & 0b111111) { case 0x5: return printBSimplified(instructionExpression, offset); case 0x25: return printBLSimplified(instructionExpression, offset); } - System.out.printf("0x%x\n", (instructionExpression >> 23 & 0xff)); + + if ((instructionExpression >> 10 & 0x3FFFFF) == 0x3597c0 && ((instructionExpression & 0x1F) == 0)) + return printRetSimplified(instructionExpression, offset); return printUnknownSimplified(instructionExpression, offset); } @@ -183,7 +185,7 @@ public class BinToAsmPrinter { conditionalJumpLocation, (conditionalJumpLocation + 0x100)); } - private static String printCMN(int instructionExpression, int offset){ + private static String printCMN(int instructionExpression){ int Rn = instructionExpression >> 5 & 0x1F; int imm = instructionExpression >> 10 & 0xFFF; @@ -271,7 +273,6 @@ public class BinToAsmPrinter { int Rt = instructionExpression & 0b11111; int label = (offset + (instructionExpression >> 5 & 0x3fff) * 4) & 0xfffff; - //System.out.printf("\nInstruction: %x\n", instructionExpression); return String.format(ANSI_YELLOW + "sf == 0 && hw == 0x ? <Wt> else <Xt>\n" + "TBZ <?t>,#<imm>, <label> |.....TBZ signature.......|\n" + ANSI_CYAN+" b5 0 1 1 0 1 1 0 |b40.............|imm14.........................................||Rt.............|" + ANSI_RESET + "\n" + @@ -382,8 +383,6 @@ public class BinToAsmPrinter { conditionalJumpLocationPatch, (conditionalJumpLocationPatch + 0x100)); } - - private static String printMOVSimplified(int instructionExpression, int offset){ int imm16 = instructionExpression >> 5 & 0xFFFF; int sfHw = (instructionExpression >> 22 & 1); @@ -404,9 +403,8 @@ public class BinToAsmPrinter { int xwSelector = (instructionExpression >> 31 & 1); int imm = instructionExpression >> 18 & 0b11111; int Rt = instructionExpression & 0b11111; - int label = (offset + (instructionExpression >> 5 & 0x3fff) * 4) & 0xfffff; + int label = offset + (instructionExpression >> 5 & 0x3fff) * 4; - //System.out.printf("\nInstruction: %x\n", instructionExpression); 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, @@ -440,7 +438,6 @@ public class BinToAsmPrinter { wx, Rt, wx, Rn, imm12); } - private static String printMOVRegisterSimplified(int instructionExpression, int offset){ //ADD (immediate) String sfHw = (instructionExpression >> 31 & 1) == 0 ? "W" : "X"; int Rm = instructionExpression >> 16 & 0x1F; @@ -517,6 +514,14 @@ public class BinToAsmPrinter { 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); + } + 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", diff --git a/src/main/java/nsusbloader/Utilities/patches/MalformedIniFileException.java b/src/main/java/nsusbloader/Utilities/patches/MalformedIniFileException.java new file mode 100644 index 0000000..6b8e741 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/MalformedIniFileException.java @@ -0,0 +1,26 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches; + +public class MalformedIniFileException extends Exception{ + public MalformedIniFileException(){} + public MalformedIniFileException(String message){ + super(message); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java index f513c8b..115ac4c 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -40,6 +40,9 @@ import java.nio.file.Paths; import java.util.Arrays; public class EsPatch { + private static final byte[] HEADER = "PATCH".getBytes(StandardCharsets.US_ASCII); + private static final byte[] FOOTER = "EOF".getBytes(StandardCharsets.US_ASCII); + private final NCAProvider ncaProvider; private final String saveToLocation; private final ILogPrinter logPrinter; @@ -66,10 +69,10 @@ public class EsPatch { logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); } private void getPlainFirmwareVersion() throws Exception{ - fwVersion = Long.parseLong(""+ncaProvider.getSdkVersion()[3]+ncaProvider.getSdkVersion()[2] - +ncaProvider.getSdkVersion()[1] +ncaProvider.getSdkVersion()[0]); - logPrinter.print("Internal firmware version: "+ncaProvider.getSdkVersion()[3] +"."+ncaProvider.getSdkVersion()[2] +"."+ncaProvider.getSdkVersion()[1] +"."+ncaProvider.getSdkVersion()[0], EMsgType.INFO); - if (fwVersion < 9300) + final byte[] byteSdkVersion = ncaProvider.getSdkVersion(); + fwVersion = Long.parseLong(""+byteSdkVersion[3]+byteSdkVersion[2]+byteSdkVersion[1]+byteSdkVersion[0]); + logPrinter.print("Internal firmware version: "+byteSdkVersion[3] +"."+byteSdkVersion[2] +"."+byteSdkVersion[1] +"."+byteSdkVersion[0], EMsgType.INFO); + if (byteSdkVersion[3] < 9 || fwVersion < 9300) logPrinter.print("WARNING! FIRMWARES VERSIONS BEFORE 9.0.0 ARE NOT SUPPORTED! USING PRODUCED ES PATCHES (IF ANY) COULD BREAK SOMETHING! IT'S NEVER BEEN TESTED!", EMsgType.WARNING); } private void getBuildId(NSO0Provider nso0Provider) throws Exception{ @@ -99,7 +102,7 @@ public class EsPatch { int offset3 = wizard.getOffset3(); ByteBuffer handyEsPatch = ByteBuffer.allocate(0x23).order(ByteOrder.LITTLE_ENDIAN); - handyEsPatch.put(getHeader()); + handyEsPatch.put(HEADER); if (offset1 > 0) { logPrinter.print("Patch component 1 will be used", EMsgType.PASS); handyEsPatch.put(getPatch1(offset1)); @@ -112,7 +115,7 @@ public class EsPatch { logPrinter.print("Patch component 3 will be used", EMsgType.PASS); handyEsPatch.put(getPatch3(offset3)); } - handyEsPatch.put(getFooter()); + handyEsPatch.put(FOOTER); try (BufferedOutputStream stream = new BufferedOutputStream( Files.newOutputStream(Paths.get(patchFileLocation)))){ @@ -120,12 +123,6 @@ public class EsPatch { } logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); } - private byte[] getHeader(){ - return "PATCH".getBytes(StandardCharsets.US_ASCII); - } - private byte[] getFooter(){ - return "EOF".getBytes(StandardCharsets.US_ASCII); - } // WE EXPECT TO SEE CBZ (for patch 1) INSTRUCTION RIGHT BEFORE FOUND SEQUENCE (requiredInstructionOffsetInternal) // IN RESULTING FILE InstructionOffset SHOULD BE INCREMENTED by 0x100 to get real offset @@ -147,15 +144,15 @@ public class EsPatch { return Arrays.copyOfRange(prePatch.array(), 1, 10); } private byte[] getPatch2(int offset) throws Exception{ - final int NopExpression = 0x1F2003D5; // reversed + final int NopInstruction = 0x1F2003D5; // reversed int offsetReal = offset - 4 + 0x100; - logPrinter.print(BinToAsmPrinter.printSimplified(Integer.reverseBytes(NopExpression), offset - 4), EMsgType.NULL); + logPrinter.print(BinToAsmPrinter.printSimplified(Integer.reverseBytes(NopInstruction), offset - 4), EMsgType.NULL); ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) .putInt(offsetReal) .putShort((short) 4) - .putInt(NopExpression); + .putInt(NopInstruction); return Arrays.copyOfRange(prePatch.array(), 1, 10); } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java index e7d85fd..51cd29d 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatchMaker.java @@ -31,7 +31,7 @@ import java.util.*; import java.util.concurrent.*; public class EsPatchMaker extends CancellableRunnable { - private int THREADS_POOL_SIZE = 4; + private int THREADS_POOL_SIZE; private final ILogPrinter logPrinter; private final String pathToFirmware; private final String pathToKeysFile; @@ -45,20 +45,14 @@ public class EsPatchMaker extends CancellableRunnable { private boolean oneLinerStatus = false; public EsPatchMaker(String pathToFirmware, String pathToKeysFile, String saveTo){ - this.logPrinter = Log.getPrinter(EModule.PATCHES); //TODO: UNCOMMENT + this.logPrinter = Log.getPrinter(EModule.PATCHES); /* this.logPrinter = new ILogPrinter() { - @Override public void print(String message, EMsgType type) throws InterruptedException {} - @Override public void updateProgress(Double value) throws InterruptedException {} - @Override public void update(HashMap<String, File> nspMap, EFileStatus status) {} - @Override public void update(File file, EFileStatus status) {} - @Override public void updateOneLinerStatus(boolean status) {} - @Override public void close() {} }; */ @@ -74,7 +68,7 @@ public class EsPatchMaker extends CancellableRunnable { receiveFirmware(); buildKeyChainHolder(); receiveNcaFileNamesList(); - adjustThreadsPoolSize(); + specifyThreadsPoolSize(); createPool(); executePool(); } @@ -106,9 +100,9 @@ public class EsPatchMaker extends CancellableRunnable { if (ncaFilesList.size() == 0) throw new Exception("No NCA files found in firmware folder"); } - private void adjustThreadsPoolSize(){ - if (ncaFilesList.size() < 4) - THREADS_POOL_SIZE = ncaFilesList.size(); + private void specifyThreadsPoolSize(){ + THREADS_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()+1, 4); + THREADS_POOL_SIZE = Math.min(THREADS_POOL_SIZE, ncaFilesList.size()); } private void createPool() throws Exception{ @@ -170,9 +164,8 @@ public class EsPatchMaker extends CancellableRunnable { Callable<NCAProvider> task = new EsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount)); subTasks.add(task); } - - Callable<NCAProvider> task = new EsNcaSearchTask(getNextSet(iterator, - ncaFilesList.size() % THREADS_POOL_SIZE == 0 ? ncaPerThreadAmount : ncaPerThreadAmount+1)); + int leftovers = ncaFilesList.size() % THREADS_POOL_SIZE; + Callable<NCAProvider> task = new EsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount+leftovers)); subTasks.add(task); return subTasks; } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java index 550d002..7f84c6f 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -83,7 +83,6 @@ class HeuristicEs1 extends AHeuristic { @Override public String getDetails(){ - StringBuilder builder = new StringBuilder(); int cbzOffsetInternal = findings.get(0) - 4; int instructionExpression = Converter.getLEint(where, cbzOffsetInternal); int conditionalJumpLocation = ((instructionExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; @@ -91,15 +90,13 @@ class HeuristicEs1 extends AHeuristic { int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - builder.append(BinToAsmPrinter.printSimplified(instructionExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), - cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), - cbzOffsetInternal+8)); - builder.append("...\n"); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - return builder.toString(); + return BinToAsmPrinter.printSimplified(instructionExpression, cbzOffsetInternal) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal + 4), + cbzOffsetInternal + 4) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal + 8), + cbzOffsetInternal + 8) + + "...\n" + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4); } } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java index 4dba546..365c17f 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -132,7 +132,6 @@ class HeuristicEs2 extends AHeuristic { @Override public String getDetails(){ - StringBuilder builder = new StringBuilder(); int secondExpressionOffset = findings.get(0); int firstExpression = Converter.getLEint(where, secondExpressionOffset-4); @@ -149,16 +148,13 @@ class HeuristicEs2 extends AHeuristic { int secondExpressionsPairElement3 = Converter.getLEint(where, conditionalJumpLocation + 8); int secondExpressionsPairElement4 = Converter.getLEint(where, conditionalJumpLocation + 12); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset-4), secondExpressionOffset-4)); - builder.append(BinToAsmPrinter.printSimplified(secondExpression, secondExpressionOffset)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset+4), secondExpressionOffset+4)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement3, conditionalJumpLocation + 8)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement4, conditionalJumpLocation + 12)); - - return builder.toString(); + return BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset - 4), secondExpressionOffset - 4) + + BinToAsmPrinter.printSimplified(secondExpression, secondExpressionOffset) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, secondExpressionOffset + 4), secondExpressionOffset + 4) + + "...\n" + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement3, conditionalJumpLocation + 8) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement4, conditionalJumpLocation + 12); } } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java index 728a98a..2bde638 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -112,17 +112,15 @@ class HeuristicEs3 extends AHeuristic { return isFound(); } - @Override public String getDetails(){ - StringBuilder builder = new StringBuilder(); int cbzOffsetInternal = findings.get(0) - 4; int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - + StringBuilder builder = new StringBuilder(); builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); @@ -137,7 +135,6 @@ class HeuristicEs3 extends AHeuristic { builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); - } else { builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java index 718a234..c242f0a 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsNcaSearchTask.java @@ -21,10 +21,11 @@ package nsusbloader.Utilities.patches.fs; import libKonogonka.Converter; import libKonogonka.fs.NCA.NCAProvider; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; -class FsNcaSearchTask implements Callable<NCAProvider> { +class FsNcaSearchTask implements Callable<List<NCAProvider>> { private final List<NCAProvider> ncaProviders; FsNcaSearchTask(List<NCAProvider> ncaProviders){ @@ -32,19 +33,18 @@ class FsNcaSearchTask implements Callable<NCAProvider> { } @Override - public NCAProvider call() { + public List<NCAProvider> call() { + List<NCAProvider> result = new ArrayList<>(); try { for (NCAProvider ncaProvider : ncaProviders) { String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId()); - if (titleId.equals("0100000000000819") || titleId.equals("010000000000081b")) { // eq. FAT || exFAT - return ncaProvider; - } + if (titleId.equals("0100000000000819") || titleId.equals("010000000000081b")) // eq. FAT32 || exFAT + result.add(ncaProvider); } - return null; } catch (Exception e){ e.printStackTrace(); - return null; } + return result; } } diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java index 84649ce..368ea0b 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -16,16 +16,13 @@ You should have received a copy of the GNU General Public License along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. --- - Based on FS-AutoIPS.py patch script made by GBATemp member MrDude. - Taken from: https://gbatemp.net/threads/info-on-sha-256-hashes-on-fs-patches.581550/ + Based on https://github.com/mrdude2478/IPS_Patch_Creator patch script made by GBATemp member MrDude. */ package nsusbloader.Utilities.patches.fs; import libKonogonka.Converter; import libKonogonka.KeyChainHolder; -import libKonogonka.RainbowDump; import libKonogonka.fs.NCA.NCAProvider; -import libKonogonka.fs.NSO.NSO0Provider; import libKonogonka.fs.RomFs.FileSystemEntry; import libKonogonka.fs.RomFs.RomFsProvider; import libKonogonka.fs.other.System2.System2Provider; @@ -37,9 +34,7 @@ import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.fs.finders.HeuristicFsWizard; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; +import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -50,22 +45,26 @@ import java.util.Arrays; import java.util.stream.Collectors; public class FsPatch { + private static final byte[] HEADER = "PATCH".getBytes(StandardCharsets.US_ASCII); + private static final byte[] FOOTER = "EOF".getBytes(StandardCharsets.US_ASCII); + private final NCAProvider ncaProvider; private final String saveToLocation; private final KeyChainHolder keyChainHolder; private final ILogPrinter logPrinter; - private long fwVersion; private String patchName; private byte[] _textSection; private boolean filesystemTypeFat32; private HeuristicFsWizard wizard; - - FsPatch(NCAProvider ncaProvider, String saveToLocation, KeyChainHolder keyChainHolder, ILogPrinter logPrinter) throws Exception{ + // Called twice: once for FAT32, once for ExFAT + FsPatch(NCAProvider ncaProvider, + String saveToLocation, + KeyChainHolder keyChainHolder, + ILogPrinter logPrinter) throws Exception{ this.ncaProvider = ncaProvider; - this.saveToLocation = saveToLocation + File.separator + - "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches"; + this.saveToLocation = saveToLocation; this.keyChainHolder = keyChainHolder; this.logPrinter = logPrinter; @@ -75,15 +74,22 @@ public class FsPatch { checkFirmwareVersion(); getFilesystemType(); findAllOffsets(); - //mkDirs(); - //writeFile(); - //updatePatchesIni(); - //logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); + mkDirs(); + writeFile(); + new IniMaker(logPrinter, + saveToLocation, + _textSection, + wizard.getOffset1(), + wizard.getOffset2(), + ncaProvider.getSdkVersion(), + patchName, + filesystemTypeFat32); + logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); } private KIP1Provider getKIP1Provider() throws Exception{ RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs(); - FileSystemEntry package2FSEntry = romFsProvider.getRootEntry().getContent() + FileSystemEntry package2FsEntry = romFsProvider.getRootEntry().getContent() .stream() .filter(e -> e.getName().equals("nx")) .collect(Collectors.toList()) @@ -93,7 +99,7 @@ public class FsPatch { .filter(e -> e.getName().equals("package2")) .collect(Collectors.toList()) .get(0); - InFileStreamProducer producer = romFsProvider.getStreamProducer(package2FSEntry); + InFileStreamProducer producer = romFsProvider.getStreamProducer(package2FsEntry); System2Provider system2Provider = new System2Provider(producer, keyChainHolder); Ini1Provider ini1Provider = system2Provider.getIni1Provider(); @@ -104,6 +110,7 @@ public class FsPatch { if (kip1Provider == null) throw new Exception("No FS KIP1"); + return kip1Provider; } private void getPatchName(KIP1Provider kip1Provider) throws Exception{ @@ -123,85 +130,87 @@ public class FsPatch { } private void checkFirmwareVersion() throws Exception{ final byte[] byteSdkVersion = ncaProvider.getSdkVersion(); - fwVersion = Long.parseLong(""+byteSdkVersion[3] + byteSdkVersion[2] + byteSdkVersion[1] + byteSdkVersion[0]); - logPrinter.print("Internal firmware version: " + - byteSdkVersion[3] +"."+byteSdkVersion[2] +"."+byteSdkVersion[1] +"."+byteSdkVersion[0], EMsgType.INFO); - System.out.println("FW "+byteSdkVersion[3] +"."+byteSdkVersion[2] +"."+byteSdkVersion[1] +"."+byteSdkVersion[0]); // TODO:REMOVE! - if (fwVersion < 9300) + long fwVersion = Long.parseLong(""+byteSdkVersion[3]+byteSdkVersion[2]+byteSdkVersion[1]+byteSdkVersion[0]); + logPrinter.print("Internal firmware version: " + byteSdkVersion[3] +"."+ byteSdkVersion[2] +"."+ byteSdkVersion[1] +"."+ byteSdkVersion[0], EMsgType.INFO); + if (byteSdkVersion[3] < 9 || fwVersion < 9300) logPrinter.print("WARNING! FIRMWARES VERSIONS BEFORE 9.0.0 ARE NOT SUPPORTED! " + "USING PRODUCED ES PATCHES (IF ANY) COULD BREAK SOMETHING! IT'S NEVER BEEN TESTED!", EMsgType.WARNING); } - private void getFilesystemType(){ + private void getFilesystemType() throws Exception{ String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId()); filesystemTypeFat32 = titleId.equals("0100000000000819"); + if (filesystemTypeFat32) + logPrinter.print("\n\t\t-- [ FAT32 ] --\n", EMsgType.INFO); + else + logPrinter.print("\n\t\t-- [ ExFAT ] --\n", EMsgType.INFO); } private void findAllOffsets() throws Exception{ - // TODO: FIX, IMPLEMENT, DEPLOY - this.wizard = new HeuristicFsWizard(fwVersion, _textSection, filesystemTypeFat32); + this.wizard = new HeuristicFsWizard(_textSection); String errorsAndNotes = wizard.getErrorsAndNotes(); if (errorsAndNotes.length() > 0) logPrinter.print(errorsAndNotes, EMsgType.WARNING); } private void mkDirs(){ - File parentFolder = new File(saveToLocation); + File parentFolder = new File(saveToLocation + File.separator + + "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches"); parentFolder.mkdirs(); } private void writeFile() throws Exception{ - String patchFileLocation = saveToLocation + File.separator + patchName; // THIS IS GOOD + String patchFileLocation = saveToLocation + File.separator + + "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches" + File.separator + patchName; int offset1 = wizard.getOffset1(); + int offset2 = wizard.getOffset2(); - ByteBuffer handyFsPatch = ByteBuffer.allocate(0x23).order(ByteOrder.LITTLE_ENDIAN); - handyFsPatch.put(getHeader()); - // TODO: FIX, UPDATE + ByteBuffer handyFsPatch = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN); + handyFsPatch.put(HEADER); if (offset1 > 0) { logPrinter.print("Patch component 1 will be used", EMsgType.PASS); handyFsPatch.put(getPatch1(offset1)); } - handyFsPatch.put(getFooter()); + if (offset2 > 0) { + logPrinter.print("Patch component 2 will be used", EMsgType.PASS); + handyFsPatch.put(getPatch2(offset2)); + } + handyFsPatch.put(FOOTER); + + byte[] fsPatch = new byte[handyFsPatch.position()]; + handyFsPatch.rewind(); + handyFsPatch.get(fsPatch); try (BufferedOutputStream stream = new BufferedOutputStream( Files.newOutputStream(Paths.get(patchFileLocation)))){ - stream.write(handyFsPatch.array()); + stream.write(fsPatch); } logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); } - private byte[] getHeader(){ - return "PATCH".getBytes(StandardCharsets.US_ASCII); - } - private byte[] getFooter(){ - return "EOF".getBytes(StandardCharsets.US_ASCII); - } private byte[] getPatch1(int offset) throws Exception{ int requiredInstructionOffsetInternal = offset - 4; int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; - int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); - int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); + final int patch = 0x1F2003D5; // NOP - logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + logPrinter.print(BinToAsmPrinter.printSimplified(Integer.reverseBytes(patch), requiredInstructionOffsetInternal), EMsgType.NULL); // Somehow IPS patches uses offsets written as big_endian (0.o) and bytes dat should be patched as LE. ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) .putInt(requiredInstructionOffsetReal) .putShort((short) 4) - .putInt(Integer.reverseBytes(patch)); + .putInt(patch); return Arrays.copyOfRange(prePatch.array(), 1, 10); } private byte[] getPatch2(int offset) throws Exception{ int requiredInstructionOffsetInternal = offset - 4; int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; - int instructionExpression = Converter.getLEint(_textSection, requiredInstructionOffsetInternal); - int patch = ((0x14 << 24) | (instructionExpression >> 5) & 0x7FFFF); - - logPrinter.print(BinToAsmPrinter.printSimplified(patch, requiredInstructionOffsetInternal), EMsgType.NULL); + final int patch = 0xE0031F2A; // mov w0, wzr + logPrinter.print(BinToAsmPrinter.printSimplified(Integer.reverseBytes(patch), requiredInstructionOffsetInternal), EMsgType.NULL); // Somehow IPS patches uses offsets written as big_endian (0.o) and bytes dat should be patched as LE. ByteBuffer prePatch = ByteBuffer.allocate(10).order(ByteOrder.BIG_ENDIAN) .putInt(requiredInstructionOffsetReal) .putShort((short) 4) - .putInt(Integer.reverseBytes(patch)); + .putInt(patch); return Arrays.copyOfRange(prePatch.array(), 1, 10); } diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java index f33c4fa..fce8b50 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatchMaker.java @@ -22,15 +22,16 @@ import libKonogonka.KeyChainHolder; import libKonogonka.fs.NCA.NCAProvider; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.ModelControllers.Log; +import nsusbloader.NSLDataTypes.EModule; import nsusbloader.NSLDataTypes.EMsgType; -import nsusbloader.NSLDataTypes.EFileStatus; import java.io.File; import java.util.*; import java.util.concurrent.*; public class FsPatchMaker extends CancellableRunnable { - private int THREADS_POOL_SIZE = 4; + private int THREADS_POOL_SIZE; private final ILogPrinter logPrinter; private final String pathToFirmware; private final String pathToKeysFile; @@ -44,23 +45,17 @@ public class FsPatchMaker extends CancellableRunnable { private boolean oneLinerStatus = false; public FsPatchMaker(String pathToFirmware, String pathToKeysFile, String saveTo){ - //this.logPrinter = Log.getPrinter(EModule.PATCHES); //TODO: UNCOMMENT - + this.logPrinter = Log.getPrinter(EModule.PATCHES); + /* this.logPrinter = new ILogPrinter() { - @Override public void print(String message, EMsgType type) throws InterruptedException {} - @Override public void updateProgress(Double value) throws InterruptedException {} - @Override public void update(HashMap<String, File> nspMap, EFileStatus status) {} - @Override public void update(File file, EFileStatus status) {} - @Override public void updateOneLinerStatus(boolean status) {} - @Override public void close() {} }; - // */ + */ this.pathToFirmware = pathToFirmware; this.pathToKeysFile = pathToKeysFile; this.saveTo = saveTo; @@ -73,7 +68,7 @@ public class FsPatchMaker extends CancellableRunnable { receiveFirmware(); buildKeyChainHolder(); receiveNcaFileNamesList(); - adjustThreadsPoolSize(); + specifyThreadsPoolSize(); createPool(); executePool(); } @@ -106,9 +101,9 @@ public class FsPatchMaker extends CancellableRunnable { if (ncaFilesList.size() == 0) throw new Exception("No NCA files found in firmware folder"); } - private void adjustThreadsPoolSize(){ - if (ncaFilesList.size() < 4) - THREADS_POOL_SIZE = ncaFilesList.size(); + private void specifyThreadsPoolSize(){ + THREADS_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()+1, 4); + THREADS_POOL_SIZE = Math.min(THREADS_POOL_SIZE, ncaFilesList.size()); } private void createPool() throws Exception{ @@ -125,11 +120,11 @@ public class FsPatchMaker extends CancellableRunnable { private void executePool() throws Exception{ //TODO: FIX. Exceptions thrown only by logPrinter try { logPrinter.print("Executing sub-tasks pool", EMsgType.INFO); - List<Future<NCAProvider>> futuresResults = executorService.invokeAll(getSubTasksCollection()); + List<Future<List<NCAProvider>>> futuresResults = executorService.invokeAll(getSubTasksCollection()); int counter = 0; - for (Future<NCAProvider> future : futuresResults){ - NCAProvider ncaProvider = future.get(); - if (ncaProvider != null) { + for (Future<List<NCAProvider>> future : futuresResults){ + List<NCAProvider> ncaProviders = future.get(); + for (NCAProvider ncaProvider : ncaProviders) { makePatches(ncaProvider); if (++counter > 1) break; @@ -155,27 +150,25 @@ public class FsPatchMaker extends CancellableRunnable { } private void makePatches(NCAProvider ncaProvider) throws Exception{ + final File foundFile = ncaProvider.getFile(); logPrinter.print(String.format("File found: .."+File.separator+"%s"+File.separator+"%s", - ncaProvider.getFile().getParentFile().getName(), ncaProvider.getFile().getName()) - , EMsgType.INFO); - //TODO : FIX; IMPLEMENT; DEPLOY ;) + foundFile.getParentFile().getName(), foundFile.getName()), EMsgType.INFO); new FsPatch(ncaProvider, saveTo, keyChainHolder, logPrinter); oneLinerStatus = true; } - private List<Callable<NCAProvider>> getSubTasksCollection() throws Exception{ + private List<Callable<List<NCAProvider>>> getSubTasksCollection() throws Exception{ logPrinter.print("Forming sub-tasks collection", EMsgType.INFO); - List<Callable<NCAProvider>> subTasks = new ArrayList<>(); + List<Callable<List<NCAProvider>>> subTasks = new ArrayList<>(); int ncaPerThreadAmount = ncaFilesList.size() / THREADS_POOL_SIZE; Iterator<String> iterator = ncaFilesList.listIterator(); for (int i = 1; i < THREADS_POOL_SIZE; i++){ - Callable<NCAProvider> task = new FsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount)); + Callable<List<NCAProvider>> task = new FsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount)); subTasks.add(task); } - - Callable<NCAProvider> task = new FsNcaSearchTask(getNextSet(iterator, - ncaFilesList.size() % THREADS_POOL_SIZE == 0 ? ncaPerThreadAmount : ncaPerThreadAmount+1)); + int leftovers = ncaFilesList.size() % THREADS_POOL_SIZE; + Callable<List<NCAProvider>> task = new FsNcaSearchTask(getNextSet(iterator, ncaPerThreadAmount+leftovers)); subTasks.add(task); return subTasks; } diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java b/src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java new file mode 100644 index 0000000..ecf42fc --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java @@ -0,0 +1,163 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs; + +import libKonogonka.Converter; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.MalformedIniFileException; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; + +public class IniMaker { + private static final String FILE_HEADER_TEXT = "# UTF-8\n" + + "# A KIP section is [kip1_name:sha256_hex_8bytes]\n" + + "# A patchset is .patch_name=kip_section_dec:offset_hex_0x:length_hex_0x:src_data_hex,dst_data_hex\n" + + "# _dec: 1 char decimal | _hex_0x: max u32 prefixed with 0x | _hex: hex array.\n" + + "# Kip1 section decimals: TEXT: 0, RODATA: 1, DATA: 2.\n"; // Sending good vibes to Mr. ITotalJustice + + private final ILogPrinter logPrinter; + private final String saveToLocation; + private final int offset1; + private final int offset2; + + private String firmwareVersionInformationNotice; + private String sectionDeclaration; + private String patchSet1; + private String patchSet2; + + IniMaker(ILogPrinter logPrinter, + String saveToLocation, + byte[] _textSection, + int wizardOffset1, + int wizardOffset2, + byte[] sdkVersion, + String patchName, + boolean filesystemTypeFat32) throws Exception{ + this.logPrinter = logPrinter; + this.saveToLocation = saveToLocation; + this.offset1 = wizardOffset1 - 4; + this.offset2 = wizardOffset2 - 4; + + mkDirs(); + makeFwVersionInformationNotice(filesystemTypeFat32, sdkVersion); + makeSectionDeclaration(patchName); + makePatchSet1(_textSection); + makePatchSet2(_textSection); + writeFile(); + } + + private void mkDirs(){ + File parentFolder = new File(saveToLocation + File.separator + "bootloader"); + parentFolder.mkdirs(); + } + + private void makeFwVersionInformationNotice(boolean isFat32, byte[] fwVersion){ + String fwVersionFormatted = fwVersion[3]+"."+fwVersion[2]+"."+fwVersion[1]+"."+fwVersion[0]; + if (isFat32) + firmwareVersionInformationNotice = "\n#FS "+fwVersionFormatted+"\n"; + else + firmwareVersionInformationNotice = "\n#FS "+fwVersionFormatted+"-ExFAT\n"; + } + + private void makeSectionDeclaration(String patchName){ + sectionDeclaration = "[FS:"+patchName.substring(0, 16)+"]"; + } + + private void makePatchSet1(byte[] _textSection){ + if (offset1 > 0) { + byte[] originalInstruction = Arrays.copyOfRange(_textSection, offset1, offset1 + 4); + patchSet1 = String.format(".nosigchk=0:0x%02X:0x4:%s,1F2003D5", + offset1, Converter.byteArrToHexStringAsLE(originalInstruction, true)); + } + } + + private void makePatchSet2(byte[] _textSection){ + if (offset2 > 0) { + byte[] originalInstruction = Arrays.copyOfRange(_textSection, offset2, offset2 + 4); + patchSet2 = String.format(".nosigchk=0:0x%02X:0x4:%s,E0031F2A", + offset2, Converter.byteArrToHexStringAsLE(originalInstruction, true)); + } + } + + private void writeFile() throws Exception{ + final String iniLocation = saveToLocation + File.separator + "bootloader" + File.separator + "patches.ini"; + final Path iniLocationPath = Paths.get(iniLocation); + + boolean iniNotExists = Files.notExists(iniLocationPath); + + try (RandomAccessFile ini = new RandomAccessFile(iniLocation, "rw")){ + if (iniNotExists) + ini.writeBytes(FILE_HEADER_TEXT); + else { + String line; + while ((line = ini.readLine()) != null){ + if (! line.startsWith(sectionDeclaration)) + continue; + + if (offset1 > 0) { + String expression1 = ini.readLine(); + if (expression1 == null || ! expression1.startsWith(patchSet1)) + throw new MalformedIniFileException("Somewhere near "+ini.getFilePointer()); + } + String expression2 = ini.readLine(); + if (offset2 > 0) { + if (expression2 == null || ! expression2.startsWith(patchSet2)) + throw new MalformedIniFileException("Somewhere near "+ini.getFilePointer()); + } + else { + if (expression2 == null || ! expression2.startsWith(".nosigchk")) + return; + throw new MalformedIniFileException("Somewhere near "+ini.getFilePointer()); + } + return; // Ini file already contains correct information regarding patch file we made. + } + } + + ini.writeBytes(firmwareVersionInformationNotice); + ini.writeBytes(sectionDeclaration); + ini.writeBytes("\n"); + + if (offset1 > 0) { + ini.writeBytes(patchSet1); + ini.writeBytes("\n"); + } + if (offset2 > 0) { + ini.writeBytes(patchSet2); + ini.writeBytes("\n"); + } + } + catch (MalformedIniFileException e){ + e.printStackTrace(); + logPrinter.print( + "Existing patches.ini file is malformed or contains incorrect (outdated) information regarding current patch.\n" + + "It's now saved at "+iniLocation+".OLD\n" + + "New patches.ini file created instead.", EMsgType.WARNING); + Files.move(iniLocationPath, Paths.get(iniLocation+".OLD"), + StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + writeFile(); + } + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java new file mode 100644 index 0000000..db539b4 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java @@ -0,0 +1,89 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.ArrayList; +import java.util.List; + +class HeuristicFs1 extends AHeuristic { + private static final String PATTERN = "..0036....1F..71..0054..4839"; // TBZ + + private final byte[] where; + private final List<Integer> findings; + + HeuristicFs1(byte[] where) { + this.where = where; + this.findings = new ArrayList<>(); + SimplyFind simplyfind = new SimplyFind(PATTERN, where); + simplyfind.getResults().forEach(var -> findings.add(var + 4)); + } + + @Override + public boolean isFound() { + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy() { + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception { + if (findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return !(offset < offsetNearby - 0xffff); + return !(offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + @Override + public String getDetails() { + int offsetInternal = findings.get(0) - 4; + int firstExpression = Converter.getLEint(where, offsetInternal); + int conditionalJumpLocation = offsetInternal + (firstExpression >> 5 & 0x3fff) * 4; + + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation + 4); + + return BinToAsmPrinter.printSimplified(firstExpression, offsetInternal) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 4), offsetInternal + 4) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 8), offsetInternal + 8) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 12), offsetInternal + 12) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 16), offsetInternal + 16) + + "...\n" + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java new file mode 100644 index 0000000..8047cae --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java @@ -0,0 +1,96 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.fs.finders; + +import libKonogonka.Converter; +import nsusbloader.Utilities.patches.AHeuristic; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.util.ArrayList; +import java.util.List; + +class HeuristicFs2 extends AHeuristic { + private static final String PATTERN = "...94081c00121f050071..0054"; // "...94"->BL "081c0012"->AND "1f050071"->CMP "..0054"->B.cond (only '54' is signature!) + + private final byte[] where; + private final List<Integer> findings; + + HeuristicFs2(byte[] where){ + this.where = where; + this.findings = new ArrayList<>(); + SimplyFind simplyfind = new SimplyFind(PATTERN, where); + simplyfind.getResults().forEach(var -> findings.add(var + 4)); + + if(findings.size() < 2) + return; + + this.findings.removeIf(this::dropStep1); + } + // All matches are somewhere in 0x6xxxx-0x7xxxx, then let's just limit search field by 0x80000 + private boolean dropStep1(int offsetOfPatternFound){ + return (offsetOfPatternFound > 0x80000); + } + + @Override + public boolean isFound(){ + return findings.size() == 1; + } + + @Override + public boolean wantLessEntropy(){ + return findings.size() > 1; + } + + @Override + public int getOffset() throws Exception{ + if(findings.isEmpty()) + throw new Exception("Nothing found"); + if (findings.size() > 1) + throw new Exception("Too many offsets"); + return findings.get(0); + } + + @Override + public boolean setOffsetsNearby(int offsetNearby) { + findings.removeIf(offset -> { + if (offset > offsetNearby) + return ! (offset < offsetNearby - 0xffff); + return ! (offset > offsetNearby - 0xffff); + }); + return isFound(); + } + + @Override + public String getDetails(){ + int offsetInternal = findings.get(0) - 4; + int firstExpression = Converter.getLEint(where, offsetInternal); + int conditionalJumpLocation = ((firstExpression & 0x3ffffff) * 4 + offsetInternal) & 0xfffff; + int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); + int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); + + return BinToAsmPrinter.printSimplified(firstExpression, offsetInternal) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 4), offsetInternal + 4) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 8), offsetInternal + 8) + + BinToAsmPrinter.printSimplified(Converter.getLEint(where, offsetInternal + 12), offsetInternal + 12) + + "...\n" + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + + BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java deleted file mode 100644 index 148d3e5..0000000 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT1.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - Copyright 2018-2023 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. - */ -package nsusbloader.Utilities.patches.fs.finders; - -import libKonogonka.Converter; -import nsusbloader.Utilities.patches.AHeuristic; -import nsusbloader.Utilities.patches.BinToAsmPrinter; -import nsusbloader.Utilities.patches.SimplyFind; - -import java.util.List; - -class HeuristicFsExFAT1 extends AHeuristic { - private static final String PATTERN0 = ".1e42b91fc14271"; - private static final String PATTERN1 = "9408...1F05.....54"; - - private final List<Integer> findings; - private final byte[] where; - - HeuristicFsExFAT1(long fwVersion, byte[] where){ - this.where = where; - String pattern = getPattern(fwVersion); - SimplyFind simplyfind = new SimplyFind(pattern, where); - this.findings = simplyfind.getResults(); - for (Integer find : findings) - System.out.println(getDetails(find)); -/* FIXME - this.findings.removeIf(this::dropStep1); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep2); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep3); - */ - } - private String getPattern(long fwVersion){ - if (fwVersion < 15300) // & fwVersion >= 9300 - return PATTERN0; - return PATTERN1; - } - // Let's focus on CBZ-ONLY statements - private boolean dropStep1(int offsetOfPatternFound){ - return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); - } - - private boolean dropStep2(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); - - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); - int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - // Check first is 'MOV'; second is 'B' - return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); - } - - private boolean dropStep3(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); - int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); - // Check first is 'ADD'; second is 'BL' - return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); - } - - private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - } - - @Override - public boolean isFound(){ - return findings.size() == 1; - } - - @Override - public boolean wantLessEntropy(){ - return findings.size() > 1; - } - - @Override - public int getOffset() throws Exception{ - if(findings.isEmpty()) - throw new Exception("Nothing found"); - if (findings.size() > 1) - throw new Exception("Too many offsets"); - return findings.get(0); - } - - @Override - public boolean setOffsetsNearby(int offsetNearby) { - findings.removeIf(offset -> { - if (offset > offsetNearby) - return ! (offset < offsetNearby - 0xffff); - return ! (offset > offsetNearby - 0xffff); - }); - return isFound(); - } - - public String getDetails(Integer value){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = value - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ - builder.append("...\n"); - int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); - - } - else { - builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); - } - return builder.toString(); - } - @Override - public String getDetails(){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = findings.get(0) - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ - builder.append("...\n"); - int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); - - } - else { - builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); - } - return builder.toString(); - } -} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java deleted file mode 100644 index 5463a6e..0000000 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsExFAT2.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - Copyright 2018-2023 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. - */ -package nsusbloader.Utilities.patches.fs.finders; - -import libKonogonka.Converter; -import nsusbloader.Utilities.patches.AHeuristic; -import nsusbloader.Utilities.patches.BinToAsmPrinter; -import nsusbloader.Utilities.patches.SimplyFind; - -import java.util.List; - -class HeuristicFsExFAT2 extends AHeuristic { - private static final String PATTERN0 = ".94081C00121F05007181000054"; - private static final String PATTERN1 = "003688...1F"; - - private final List<Integer> findings; - private final byte[] where; - - HeuristicFsExFAT2(long fwVersion, byte[] where){ - this.where = where; - String pattern = getPattern(fwVersion); - SimplyFind simplyfind = new SimplyFind(pattern, where); - this.findings = simplyfind.getResults(); - - for (Integer find : findings) - System.out.println(getDetails(find)); - /* FIXME - this.findings.removeIf(this::dropStep1); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep2); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep3); - - */ - } - private String getPattern(long fwVersion){ - if (fwVersion < 15300) // & fwVersion >= 9300 - return PATTERN0; - return PATTERN1; - } - // Let's focus on CBZ-ONLY statements - private boolean dropStep1(int offsetOfPatternFound){ - return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); - } - - private boolean dropStep2(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); - - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); - int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - // Check first is 'MOV'; second is 'B' - return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); - } - - private boolean dropStep3(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); - int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); - // Check first is 'ADD'; second is 'BL' - return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); - } - - private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - } - - @Override - public boolean isFound(){ - return findings.size() == 1; - } - - @Override - public boolean wantLessEntropy(){ - return findings.size() > 1; - } - - @Override - public int getOffset() throws Exception{ - if(findings.isEmpty()) - throw new Exception("Nothing found"); - if (findings.size() > 1) - throw new Exception("Too many offsets"); - return findings.get(0); - } - - @Override - public boolean setOffsetsNearby(int offsetNearby) { - findings.removeIf(offset -> { - if (offset > offsetNearby) - return ! (offset < offsetNearby - 0xffff); - return ! (offset > offsetNearby - 0xffff); - }); - return isFound(); - } - - public String getDetails(Integer value){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = value - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ - builder.append("...\n"); - int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); - - } - else { - builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); - } - return builder.toString(); - } - - @Override - public String getDetails(){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = findings.get(0) - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - if (((secondExpressionsPairElement2 >> 26 & 0b111111) == 0x5)){ - builder.append("...\n"); - int conditionalJumpLocation2 = ((secondExpressionsPairElement2 & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2), conditionalJumpLocation2)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, conditionalJumpLocation2+4), conditionalJumpLocation2+4)); - - } - else { - builder.append("NO CONDITIONAL JUMP ON 2nd iteration (HeuristicEs3)"); - } - return builder.toString(); - } -} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java deleted file mode 100644 index bae72b3..0000000 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT1.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - Copyright 2018-2023 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. - */ -package nsusbloader.Utilities.patches.fs.finders; - -import libKonogonka.Converter; -import nsusbloader.Utilities.patches.AHeuristic; -import nsusbloader.Utilities.patches.BinToAsmPrinter; -import nsusbloader.Utilities.patches.SimplyFind; - -import java.util.ArrayList; -import java.util.List; - -class HeuristicFsFAT1 extends AHeuristic { - private static final String PATTERN0 = ".1e42b91fc14271"; - private static final String PATTERN1 = "...9408...1F05.....54"; // ...94 081C0012 1F050071 4101 -/* - 710006eba0 c0 02 40 f9 ldr x0 , [ x22 ] - 710006eba4 5c c9 02 94 bl FUN_7100121114 undefined FUN_7100121114() - 710006eba8 08 1c 00 12 and w8 , w0 , # 0xff - */ - private final List<Integer> findings; - private final byte[] where; - - HeuristicFsFAT1(long fwVersion, byte[] where){ - this.where = where; - String pattern = getPattern(fwVersion); - SimplyFind simplyfind = new SimplyFind(pattern, where); - List<Integer> temporary = simplyfind.getResults(); - if (fwVersion >= 15300){ - this.findings = new ArrayList<>(); - temporary.forEach(var -> findings.add(var + 4)); - } - else - findings = temporary; - - System.out.println("\t\tFAT32 # 1 +++++++++++++++++++++++++++++++"); - for (Integer find : findings) { - System.out.println(getDetails(find)); - System.out.println("------------------------------------------------------------------"); - } -/* FIXME - this.findings.removeIf(this::dropStep1); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep2); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep3); - */ - } - private String getPattern(long fwVersion){ - if (fwVersion < 15300) // & fwVersion >= 9300 - return PATTERN0; - return PATTERN1; - } - // Let's focus on CBZ-ONLY statements - private boolean dropStep1(int offsetOfPatternFound){ - return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); - } - - private boolean dropStep2(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); - - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); - int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - // Check first is 'MOV'; second is 'B' - return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); - } - - private boolean dropStep3(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); - int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); - // Check first is 'ADD'; second is 'BL' - return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); - } - - private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - } - - @Override - public boolean isFound(){ - return findings.size() == 1; - } - - @Override - public boolean wantLessEntropy(){ - return findings.size() > 1; - } - - @Override - public int getOffset() throws Exception{ - if(findings.isEmpty()) - throw new Exception("Nothing found"); - if (findings.size() > 1) - throw new Exception("Too many offsets"); - return findings.get(0); - } - - @Override - public boolean setOffsetsNearby(int offsetNearby) { - findings.removeIf(offset -> { - if (offset > offsetNearby) - return ! (offset < offsetNearby - 0xffff); - return ! (offset > offsetNearby - 0xffff); - }); - return isFound(); - } - - public String getDetails(Integer value){ - int firstOffsetInternal = value - 4; - int firstExpression = Converter.getLEint(where, firstOffsetInternal); - int conditionalJumpLocation = ((firstExpression >> 5 & 0x7FFFF) * 4 + firstOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - StringBuilder builder = new StringBuilder(); - //builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal-4*11), firstOffsetInternal-4*11)); - //builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal-4*10), firstOffsetInternal-4*10)); - //builder.append("^ ^ ^ ...\n"); - builder.append(BinToAsmPrinter.printSimplified(firstExpression, firstOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+4), firstOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+8), firstOffsetInternal+8)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal+12), firstOffsetInternal+12)); - builder.append("...\n"); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - return builder.toString(); - } - @Override - public String getDetails(){ - int firstOffsetInternal = findings.get(0) - 4; - int firstExpression = Converter.getLEint(where, firstOffsetInternal); - int conditionalJumpLocation = ((firstExpression >> 5 & 0x7FFFF) * 4 + firstOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - return BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 11), firstOffsetInternal - 4 * 11) + - BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 10), firstOffsetInternal - 4 * 10) + - "^ ^ ^ ...\n" + - BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal - 4 * 2), firstOffsetInternal - 4 * 2) + - "^ ^ ^ ...\n" + - BinToAsmPrinter.printSimplified(firstExpression, firstOffsetInternal) + - BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal + 4), firstOffsetInternal + 4) + - BinToAsmPrinter.printSimplified(Converter.getLEint(where, firstOffsetInternal + 8), firstOffsetInternal + 8) + - "...\n" + - BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation) + - BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation + 4); - } -} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java deleted file mode 100644 index c265a31..0000000 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsFAT2.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - Copyright 2018-2023 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. - */ -package nsusbloader.Utilities.patches.fs.finders; - -import libKonogonka.Converter; -import nsusbloader.Utilities.patches.AHeuristic; -import nsusbloader.Utilities.patches.BinToAsmPrinter; -import nsusbloader.Utilities.patches.SimplyFind; - -import java.util.List; - -class HeuristicFsFAT2 extends AHeuristic { - private static final String PATTERN0 = "...94081C00121F05007181000054"; - private static final String PATTERN1 = "..003688...1F"; - - private final List<Integer> findings; - private final byte[] where; - - HeuristicFsFAT2(long fwVersion, byte[] where){ - this.where = where; - String pattern = getPattern(fwVersion); - SimplyFind simplyfind = new SimplyFind(pattern, where); - this.findings = simplyfind.getResults(); - /* - System.out.println("\t\tFAT32 # 2 +++++++++++++++++++++++++++++++"); - for (Integer find : findings) { - System.out.println(getDetails(find)); - System.out.println("------------------------------------------------------------------"); - } - /* FIXME - this.findings.removeIf(this::dropStep1); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep2); - if(findings.size() < 2) - return; - - this.findings.removeIf(this::dropStep3); - - */ - } - private String getPattern(long fwVersion){ - if (fwVersion < 15300) // & fwVersion >= 9300 - return PATTERN0; - return PATTERN1; - } - // Let's focus on CBZ-ONLY statements - private boolean dropStep1(int offsetOfPatternFound){ - return ((where[offsetOfPatternFound - 1] & (byte) 0b01111111) != 0x34); - } - - private boolean dropStep2(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound - 4); - - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation); - int afterJumpThirdExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - // Check first is 'MOV'; second is 'B' - return (! isMOV_REG(afterJumpSecondExpressions)) || ! isB(afterJumpThirdExpressions); - } - - private boolean dropStep3(int offsetOfPatternFound){ - int conditionalJumpLocation = getCBZConditionalJumpLocation(offsetOfPatternFound-4); - int afterJumpSecondExpressions = Converter.getLEint(where, conditionalJumpLocation+4); - int secondPairConditionalJumpLocation = ((afterJumpSecondExpressions & 0x3ffffff) * 4 + (conditionalJumpLocation+4)) & 0xfffff; - - int thirdExpressionsPairElement1 = Converter.getLEint(where, secondPairConditionalJumpLocation); - int thirdExpressionsPairElement2 = Converter.getLEint(where, secondPairConditionalJumpLocation+4); - // Check first is 'ADD'; second is 'BL' - return (! isADD(thirdExpressionsPairElement1)) || (! isBL(thirdExpressionsPairElement2)); - } - - private int getCBZConditionalJumpLocation(int cbzOffsetInternal){ - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - return ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - } - - @Override - public boolean isFound(){ - return findings.size() == 1; - } - - @Override - public boolean wantLessEntropy(){ - return findings.size() > 1; - } - - @Override - public int getOffset() throws Exception{ - if(findings.isEmpty()) - throw new Exception("Nothing found"); - if (findings.size() > 1) - throw new Exception("Too many offsets"); - return findings.get(0); - } - - @Override - public boolean setOffsetsNearby(int offsetNearby) { - findings.removeIf(offset -> { - if (offset > offsetNearby) - return ! (offset < offsetNearby - 0xffff); - return ! (offset > offsetNearby - 0xffff); - }); - return isFound(); - } - - public String getDetails(Integer value){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = value - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - return builder.toString(); - } - - @Override - public String getDetails(){ - StringBuilder builder = new StringBuilder(); - int cbzOffsetInternal = findings.get(0) - 4; - int cbzExpression = Converter.getLEint(where, cbzOffsetInternal); - int conditionalJumpLocation = ((cbzExpression >> 5 & 0x7FFFF) * 4 + cbzOffsetInternal) & 0xfffff; - - int secondExpressionsPairElement1 = Converter.getLEint(where, conditionalJumpLocation); - int secondExpressionsPairElement2 = Converter.getLEint(where, conditionalJumpLocation+4); - - builder.append(BinToAsmPrinter.printSimplified(cbzExpression, cbzOffsetInternal)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+4), cbzOffsetInternal+4)); - builder.append(BinToAsmPrinter.printSimplified(Converter.getLEint(where, cbzOffsetInternal+8), cbzOffsetInternal+8)); - builder.append("...\n"); - - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement1, conditionalJumpLocation)); - builder.append(BinToAsmPrinter.printSimplified(secondExpressionsPairElement2, conditionalJumpLocation+4)); - - return builder.toString(); - } -} diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java index 3d3d240..30cdec6 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFsWizard.java @@ -1,5 +1,5 @@ /* - Copyright 2018-2022 Dmitry Isaenko + Copyright 2018-2023 Dmitry Isaenko This file is part of NS-USBloader. @@ -25,42 +25,23 @@ import java.util.List; import java.util.stream.Collectors; public class HeuristicFsWizard { - /* private final List<AHeuristic> all; private final List<AHeuristic> found; private final List<AHeuristic> wantLessEntropy; - */ - private final boolean isFat; - private List<AHeuristic> all; - private List<AHeuristic> found; - private List<AHeuristic> wantLessEntropy; private final StringBuilder errorsAndNotes; private int offset1 = -1; private int offset2 = -1; - public HeuristicFsWizard(long fwVersion, byte[] where, boolean isFat) throws Exception{ - this.isFat = isFat; + public HeuristicFsWizard(byte[] where) throws Exception{ this.errorsAndNotes = new StringBuilder(); - if (isFat){ - this.all = Arrays.asList( - new HeuristicFsFAT1(fwVersion, where), - new HeuristicFsFAT2(fwVersion, where) - ); - } - else { - System.out.println("TODO: IMPLEMENT FOR EXFAT"); - return; - /* - this.all = Arrays.asList( - new HeuristicFsExFAT1(fwVersion, where), - new HeuristicFsExFAT2(fwVersion, where) - ); - */ - } -/* + this.all = Arrays.asList( + new HeuristicFs1(where), + new HeuristicFs2(where) + ); + this.found = all.stream() .filter(AHeuristic::isFound) .collect(Collectors.toList()); @@ -71,12 +52,11 @@ public class HeuristicFsWizard { this.wantLessEntropy = all.stream() .filter(AHeuristic::wantLessEntropy) .collect(Collectors.toList()); -/* FIXME + shareOffsetsWithEachOther(); assignOffset1(); assignOffset2(); - */ } private void shareOffsetsWithEachOther(){ @@ -119,10 +99,6 @@ public class HeuristicFsWizard { public String getDebug(){ StringBuilder builder = new StringBuilder(); - if (isFat) - builder.append("\t\t--[ FAT32 ]--\n"); - else - builder.append("\t\t--[ ExFAT ]--\n"); if (all.get(0).isFound()){ builder.append("\t\t-=== 1 ===-\n"); builder.append(all.get(0).getDetails()); diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml index d888c2c..c7d0422 100644 --- a/src/main/resources/PatchesTab.fxml +++ b/src/main/resources/PatchesTab.fxml @@ -14,7 +14,7 @@ <?import javafx.scene.shape.SVGPath?> <?import javafx.scene.text.Font?> -<ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> +<ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> <VBox fx:id="patchesToolPane" spacing="15.0"> <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #2cd882 40%, transparent 45%);" /> <HBox alignment="CENTER"> From c84f70ec106625d035c076cbdef8594fd8e2e79d Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sat, 4 Feb 2023 17:05:57 +0300 Subject: [PATCH 079/134] "Installs Split NSP/XCI/NSZ/XCZ over Lan or USB", not only NSP. Also breaking every locale --- .../Controllers/GamesController.java | 33 ++++++++++++++----- .../Utilities/patches/es/EsPatch.java | 7 +++- src/main/resources/GamesTab.fxml | 2 +- src/main/resources/locale.properties | 2 +- src/main/resources/locale_ar_AR.properties | 2 +- src/main/resources/locale_cs_CZ.properties | 2 +- src/main/resources/locale_de_DE.properties | 2 +- src/main/resources/locale_it_IT.properties | 2 +- src/main/resources/locale_ja_JP.properties | 2 +- src/main/resources/locale_ja_RYU.properties | 2 +- src/main/resources/locale_ko_KR.properties | 2 +- src/main/resources/locale_pt_BR.properties | 2 +- src/main/resources/locale_ro_RO.properties | 2 +- src/main/resources/locale_ru_RU.properties | 2 +- src/main/resources/locale_sv_SE.properties | 2 +- src/main/resources/locale_uk_UA.properties | 2 +- src/main/resources/locale_vi_VN.properties | 2 +- src/main/resources/locale_zh_CN.properties | 2 +- src/main/resources/locale_zh_TW.properties | 2 +- 19 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 39bb64d..3c78b99 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -66,7 +66,7 @@ public class GamesController implements Initializable { public NSTableViewController tableFilesListController; // Accessible from Mediator (for drag-n-drop support) @FXML - private Button selectNspBtn, selectSplitNspBtn, uploadStopBtn; + private Button selectNspBtn, selectSplitBtn, uploadStopBtn; private String previouslyOpenedPath; private Region btnUpStopImage, btnSelectImage; private ResourceBundle resourceBundle; @@ -141,8 +141,8 @@ public class GamesController implements Initializable { this.btnSelectImage = new Region(); setFilesSelectorButtonBehaviour(preferences.getDirectoriesChooserForRoms()); - selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction()); - selectSplitNspBtn.getStyleClass().add("buttonSelect"); + selectSplitBtn.setOnAction(e-> selectSplitBtnAction()); + selectSplitBtn.getStyleClass().add("buttonSelect"); uploadStopBtn.setOnAction(e-> uploadBtnAction()); uploadStopBtn.setDisable(isTinfoil()); @@ -326,7 +326,7 @@ public class GamesController implements Initializable { } /** - * Functionality for selecting Split NSP button. + * Functionality for selecting Split-file button. * */ private void selectSplitBtnAction(){ File splitFile; @@ -338,11 +338,28 @@ public class GamesController implements Initializable { splitFile = dirChooser.showDialog(usbNetPane.getScene().getWindow()); - if (splitFile != null && splitFile.getName().toLowerCase().endsWith(".nsp")) { + if (splitFile == null) + return; + + int fileNameLen = splitFile.getName().length(); + String fileExtension = splitFile.getName().toLowerCase().substring(fileNameLen-4, fileNameLen); + + if (fileExtension.equals(".nsp")){ tableFilesListController.setFile(splitFile); uploadStopBtn.setDisable(false); // Is it useful? previouslyOpenedPath = splitFile.getParent(); } + + if (isTinfoil() && isXciNszXczSupport()){ + switch(fileExtension){ + case ".xci": + case ".nsz": + case ".xcz": + tableFilesListController.setFile(splitFile); + uploadStopBtn.setDisable(false); // Is it useful? + previouslyOpenedPath = splitFile.getParent(); + } + } } /** * It's button listener when no transmission executes @@ -456,7 +473,7 @@ public class GamesController implements Initializable { } selectNspBtn.setDisable(isActive); - selectSplitNspBtn.setDisable(isActive); + selectSplitBtn.setDisable(isActive); btnUpStopImage.getStyleClass().clear(); if (isActive) { @@ -506,12 +523,12 @@ public class GamesController implements Initializable { if (isDirectoryChooser){ selectNspBtn.setOnAction(e -> selectFoldersBtnAction()); btnSelectImage.getStyleClass().add("regionScanFolders"); - selectSplitNspBtn.setVisible(false); + selectSplitBtn.setVisible(false); } else { selectNspBtn.setOnAction(e -> selectFilesBtnAction()); btnSelectImage.getStyleClass().add("regionSelectFiles"); - selectSplitNspBtn.setVisible(true); + selectSplitBtn.setVisible(true); } selectNspBtn.setGraphic(btnSelectImage); } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java index 115ac4c..71d53bd 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -117,10 +117,15 @@ public class EsPatch { } handyEsPatch.put(FOOTER); + byte[] esPatch = new byte[handyEsPatch.position()]; + handyEsPatch.rewind(); + handyEsPatch.get(esPatch); + try (BufferedOutputStream stream = new BufferedOutputStream( Files.newOutputStream(Paths.get(patchFileLocation)))){ - stream.write(handyEsPatch.array()); + stream.write(esPatch); } + logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); } diff --git a/src/main/resources/GamesTab.fxml b/src/main/resources/GamesTab.fxml index 8f23f8e..a62f8ba 100644 --- a/src/main/resources/GamesTab.fxml +++ b/src/main/resources/GamesTab.fxml @@ -55,7 +55,7 @@ <Insets /> </HBox.margin> </Button> - <Button fx:id="selectSplitNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenSplitFile"> + <Button fx:id="selectSplitBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenSplitFile"> <graphic> <SVGPath content="M 2.4003906 2 C 1.0683906 2 0 3.1125 0 4.5 L 0 19.5 A 2.4 2.5 0 0 0 2.4003906 22 L 21.599609 22 A 2.4 2.5 0 0 0 24 19.5 L 24 7 C 24 5.6125 22.919609 4.5 21.599609 4.5 L 12 4.5 L 9.5996094 2 L 2.4003906 2 z M 13.193359 10.962891 C 14.113498 10.962891 14.814236 11.348741 15.296875 12.123047 C 15.779514 12.89388 16.021484 13.935113 16.021484 15.244141 C 16.021484 16.556641 15.779514 17.598741 15.296875 18.373047 C 14.814236 19.14388 14.113498 19.529297 13.193359 19.529297 C 12.276693 19.529297 11.575955 19.14388 11.089844 18.373047 C 10.607205 17.598741 10.365234 16.556641 10.365234 15.244141 C 10.365234 13.935113 10.607205 12.89388 11.089844 12.123047 C 11.575955 11.348741 12.276693 10.962891 13.193359 10.962891 z M 19.589844 10.962891 C 20.509983 10.962891 21.21072 11.348741 21.693359 12.123047 C 22.175998 12.89388 22.417969 13.935113 22.417969 15.244141 C 22.417969 16.556641 22.175998 17.598741 21.693359 18.373047 C 21.21072 19.14388 20.509983 19.529297 19.589844 19.529297 C 18.673177 19.529297 17.970486 19.14388 17.484375 18.373047 C 17.001736 17.598741 16.761719 16.556641 16.761719 15.244141 C 16.761719 13.935113 17.001736 12.89388 17.484375 12.123047 C 17.970486 11.348741 18.673177 10.962891 19.589844 10.962891 z M 13.193359 11.769531 C 12.613498 11.769531 12.173177 12.092448 11.871094 12.738281 C 11.56901 13.380642 11.417969 14.195964 11.417969 15.185547 C 11.417969 15.411241 11.423611 15.655599 11.4375 15.916016 C 11.451389 16.176432 11.511068 16.528212 11.615234 16.972656 L 14.412109 12.591797 C 14.235026 12.26888 14.042318 12.052517 13.833984 11.941406 C 13.629123 11.826823 13.415582 11.769531 13.193359 11.769531 z M 19.589844 11.769531 C 19.009983 11.769531 18.567708 12.092448 18.265625 12.738281 C 17.963542 13.380642 17.8125 14.195964 17.8125 15.185547 C 17.8125 15.411241 17.820095 15.655599 17.833984 15.916016 C 17.847873 16.176432 17.907552 16.528212 18.011719 16.972656 L 20.808594 12.591797 C 20.63151 12.26888 20.438802 12.052517 20.230469 11.941406 C 20.025608 11.826823 19.812066 11.769531 19.589844 11.769531 z M 14.761719 13.556641 L 11.984375 17.962891 C 12.133681 18.216363 12.305556 18.406684 12.5 18.535156 C 12.694444 18.660156 12.91276 18.722656 13.152344 18.722656 C 13.812066 18.722656 14.280816 18.355252 14.558594 17.619141 C 14.836372 16.879557 14.974609 16.059462 14.974609 15.160156 C 14.974609 14.604601 14.90408 14.07053 14.761719 13.556641 z M 21.15625 13.556641 L 18.380859 17.962891 C 18.530165 18.216363 18.70204 18.406684 18.896484 18.535156 C 19.090929 18.660156 19.307292 18.722656 19.546875 18.722656 C 20.206597 18.722656 20.675347 18.355252 20.953125 17.619141 C 21.230903 16.879557 21.371094 16.059462 21.371094 15.160156 C 21.371094 14.604601 21.298611 14.07053 21.15625 13.556641 z" fill="#289de8" /> </graphic></Button> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index 94d63ee..c387135 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Allow XCI / NSZ / XCZ files selection for Awoo tab2_Lbl_AllowXciNszXczDesc=Used by applications that support XCI/NSZ/XCZ and utilizes Awoo (aka Adubbz/TinFoil) transfer protocol. Don't change if not sure. Enable for Awoo Installer. tab2_Lbl_Language=Language windowBodyRestartToApplyLang=Please restart application to apply changes. -btn_OpenSplitFile=Select split NSP +btn_OpenSplitFile=Select split tab2_Lbl_ApplicationSettings=Main settings tabSplMrg_Lbl_SplitNMergeTitle=Split & merge files tool tabSplMrg_RadioBtn_Split=Split diff --git a/src/main/resources/locale_ar_AR.properties b/src/main/resources/locale_ar_AR.properties index 1fb7fc0..5a08e27 100644 --- a/src/main/resources/locale_ar_AR.properties +++ b/src/main/resources/locale_ar_AR.properties @@ -43,7 +43,7 @@ tab2_Cb_AllowXciNszXcz=\u0627\u0644\u0633\u0645\u0627\u062D \u0644\u0628\u0631\u tab2_Lbl_AllowXciNszXczDesc=\u0645\u0633\u062A\u062E\u062F\u0645 \u0628\u0627\u0644\u0628\u0631\u0627\u0645\u062C \u0627\u0644\u062A\u064A \u062A\u062F\u0639\u0645 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0645\u0646 \u0627\u0644\u0646\u0648\u0639 "\u0625\u0643\u0633 \u0633\u064A \u0622\u064A" \u0623\u0648 "\u0625\u0646 \u0625\u0633 \u0632\u062F" \u0623\u0648 "\u0625\u0643\u0633 \u0633\u064A \u0632\u062F" \u0648\u062A\u0633\u062A\u062E\u062F\u0645 \u0628\u0631\u0648\u062A\u0648\u0643\u0648\u0644 \u0627\u0644\u0646\u0642\u0644 \u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644 "\u062A\u064A\u0646\u0641\u0648\u064A\u0644". \u0644\u0627 \u062A\u0639\u062F\u0644 \u0625\u0630\u0627 \u0644\u0645 \u062A\u0643\u0646 \u0645\u062A\u0623\u0643\u062F . \u0648\u0641\u0639\u0644\u0647 \u0625\u0630\u0627 \u0643\u0646\u062A \u062A\u0633\u062A\u062E\u062F\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u062A\u0646\u0635\u064A\u0628 "\u0623\u0648\u0648\u0648". tab2_Lbl_Language=\u0627\u0644\u0644\u063A\u0629 windowBodyRestartToApplyLang=\u0645\u0646 \u0641\u0636\u0644\u0643 \u0623\u0639\u062F \u062A\u0634\u0641\u064A\u0644 \u0627\u0644\u0628\u0631\u0646\u0627\u0645\u062C \u0644\u062A\u0637\u0628\u064A\u0642 \u0627\u0644\u062A\u0639\u062F\u064A\u0644\u0627\u062A. -btn_OpenSplitFile=\u0627\u062E\u062A\u0631 \u062A\u0642\u0633\u064A\u0645 \u0645\u0644\u0641 \u0627\u0644 "\u0625\u0646 \u0625\u0633 \u0628\u064A" +btn_OpenSplitFile=\u0627\u062E\u062A\u0631 \u062A\u0642\u0633\u064A\u0645 \u0645\u0644\u0641 \u0627\u0644 tab2_Lbl_ApplicationSettings=\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629 tabSplMrg_Lbl_SplitNMergeTitle=\u0623\u062F\u0627\u0629 \u062A\u0642\u0633\u064A\u0645 \u0648\u062F\u0645\u062C \u0627\u0644\u0645\u0644\u0641\u0627\u062A tabSplMrg_RadioBtn_Split=\u062A\u0642\u0633\u064A\u0645 diff --git a/src/main/resources/locale_cs_CZ.properties b/src/main/resources/locale_cs_CZ.properties index 03ebe60..59b204b 100644 --- a/src/main/resources/locale_cs_CZ.properties +++ b/src/main/resources/locale_cs_CZ.properties @@ -43,7 +43,7 @@ tab2_Cb_AllowXciNszXcz=Umo\u017Enit volbu XCI / NSZ / XCZ soubor\u016F pro Awoo tab2_Lbl_AllowXciNszXczDesc=Lze vyu\u017E\u00EDt v aplikac\u00EDch, kter\u00E9 podporuj\u00ED soubory typu XCI/NSZ/XCZ a pro p\u0159enos vyu\u017E\u00EDvaj\u00ED protokol Tinfoil. Nem\u011B\u0148te, jestli tomu nerozum\u00EDte. Aktivujte pro Awoo Installer. tab2_Lbl_Language=Jazyk windowBodyRestartToApplyLang=Pro aplikov\u00E1n\u00ED zm\u011Bn restartujte aplikaci. -btn_OpenSplitFile=Zvolit rozd\u011Blen\u00E9 NSP +btn_OpenSplitFile=Zvolit rozd\u011Blen\u00E9 soubor tab2_Lbl_ApplicationSettings=Hlavn\u00ED nastaven\u00ED tabSplMrg_Lbl_SplitNMergeTitle=Utilita k rozd\u011Blen\u00ED/slou\u010Den\u00ED soubor\u016F tabSplMrg_RadioBtn_Split=Rozd\u011Blit diff --git a/src/main/resources/locale_de_DE.properties b/src/main/resources/locale_de_DE.properties index 2e10d92..0d4b7f6 100644 --- a/src/main/resources/locale_de_DE.properties +++ b/src/main/resources/locale_de_DE.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Erlaube XCI- NSZ- XCZ-Dateien-Verwendung f\u00FCr Awoo tab2_Lbl_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt z Transfer Protocol. Nicht \u00E4ndern, wenn unsicher. tab2_Lbl_Language=Sprache windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen. -btn_OpenSplitFile=Split-NSP ausw\uFFFDhlen +btn_OpenSplitFile=Split-file ausw\uFFFDhlen tab2_Cb_GLshowNspOnly=Nur *.nsp in GoldLeaf zeigen. btn_Cancel=Abbrechen diff --git a/src/main/resources/locale_it_IT.properties b/src/main/resources/locale_it_IT.properties index 103b64d..2319da5 100644 --- a/src/main/resources/locale_it_IT.properties +++ b/src/main/resources/locale_it_IT.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Consenti la selezione di file XCI / NSZ / XCZ per Awoo tab2_Lbl_AllowXciNszXczDesc=Usato dalle applicazioni che supportano XCI/NSZ/XCZ ed utilizza il protocollo di trasferimento di Awoo (o Adubbz/TinFoil). Non cambiarlo se non sei sicuro. Attivalo per Awoo Installer. tab2_Lbl_Language=Lingua windowBodyRestartToApplyLang=Riavvia l'applicazione per applicare le modifiche. -btn_OpenSplitFile=Seleziona NSP troncato +btn_OpenSplitFile=Seleziona il file truncato tab2_Lbl_ApplicationSettings=Impostazioni principali tabSplMrg_Lbl_SplitNMergeTitle=Strumento tronca e unisci file tabSplMrg_RadioBtn_Split=Tronca diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties index 5f617b0..8302a5c 100644 --- a/src/main/resources/locale_ja_JP.properties +++ b/src/main/resources/locale_ja_JP.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Awoo \u306E XCI / NSZ / XCZ \u30D5\u30A1\u30A4\u30EB\u306 tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ \u3092\u30B5\u30DD\u30FC\u30C8\u3057\u3001Awoo (\u5225\u540D Adubbz/TinFoil) \u8EE2\u9001\u30D7\u30ED\u30C8\u30B3\u30EB\u3092\u5229\u7528\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3067\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002 \u3088\u304F\u308F\u304B\u3089\u306A\u3044\u5834\u5408\u306F\u5909\u66F4\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3092\u6709\u52B9\u306B\u3057\u307E\u3059\u3002 tab2_Lbl_Language=\u8A00\u8A9E windowBodyRestartToApplyLang=\u5909\u66F4\u3092\u9069\u7528\u3059\u308B\u306B\u306F\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u518D\u8D77\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002 -btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8NSP\u3092\u9078\u629E +btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8ROM\u3092\u9078\u629E tab2_Lbl_ApplicationSettings=\u4E3B\u306A\u8A2D\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u30D5\u30A1\u30A4\u30EB\u306E\u5206\u5272\u3068\u7D50\u5408\u30C4\u30FC\u30EB tabSplMrg_RadioBtn_Split=\u30D5\u30A1\u30A4\u30EB\u306E\u5206\u5272\u3068\u7D50\u5408\u30C4\u30FC\u30EB diff --git a/src/main/resources/locale_ja_RYU.properties b/src/main/resources/locale_ja_RYU.properties index 69972da..76afe50 100644 --- a/src/main/resources/locale_ja_RYU.properties +++ b/src/main/resources/locale_ja_RYU.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Awoo \u306C XCI / NSZ / XCZ \u30D5\u30A1\u30A4\u30EB\u306 tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ \u30B5\u30DD\u30FC\u30C8\u3057\u30FC\u3001Awoo (\u5225\u540D Adubbz/TinFoil) \u8EE2\u9001\u30D7\u30ED\u30C8\u30B3\u30EB\u5229\u7528\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3063\u3057\u4F7F\u7528\u3055\u308A\u3084\u3073\u30FC\u3093\u3002 \u3086\u30FC\u308F\u304B\u3089\u3093\u3070\u30FC\u3084\u5909\u66F4\u3055\u3093\u3050\u30FC\u3068\u3045\u30FC\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 Awoo \u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u6709\u52B9\u306A\u3055\u3073\u30FC\u3093\u3002 tab2_Lbl_Language=\u8A00\u8A9E windowBodyRestartToApplyLang=\u5909\u66F4\u9069\u7528\u3059\u3093\u304C\u30FC\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u518D\u8D77\u52D5\u3057\u304F\u3043\u307F\u305D\u30FC\u308C\u30FC\u3002 -btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8NSP\u9078\u629E +btn_OpenSplitFile=\u30B9\u30D7\u30EA\u30C3\u30C8ROM\u9078\u629E tab2_Lbl_ApplicationSettings=\u4E3B\u306A\u8A2D\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u30D5\u30A1\u30A4\u30EB\u306C\u5206\u5272\u3068\u3045\u7D50\u5408\u30C4\u30FC\u30EB tabSplMrg_RadioBtn_Split=\u30D5\u30A1\u30A4\u30EB\u306C\u5206\u5272\u3068\u3045\u7D50\u5408\u30C4\u30FC\u30EB diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index c491ead..3a192cf 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Awoo \uC6A9 XCI / NSZ / XCZ \uD30C\uC77C \uC120\uD0DD \uD tab2_Lbl_AllowXciNszXczDesc=XCI / NSZ / XCZ\uB97C \uC9C0\uC6D0\uD558\uACE0 Tinfoil \uC804\uC1A1 \uD504\uB85C\uD1A0\uCF5C\uC744 \uD65C\uC6A9\uD558\uB294 \uC560\uD50C\uB9AC\uCF00\uC774\uC158\uC5D0\uC11C \uC0AC\uC6A9\uB429\uB2C8\uB2E4. \uD655\uC2E4\uD558\uC9C0 \uC54A\uC740 \uACBD\uC6B0 \uBCC0\uACBD\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624. Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uC5D0 \uB300\uD574 \uD65C\uC131\uD654\uD569\uB2C8\uB2E4. tab2_Lbl_Language=\uC5B8\uC5B4 windowBodyRestartToApplyLang=\uBCC0\uACBD \uC0AC\uD56D\uC744 \uC801\uC6A9\uD558\uB824\uBA74 \uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC2ED\uC2DC\uC624. -btn_OpenSplitFile=\uBD84\uD560 NSP \uC120\uD0DD +btn_OpenSplitFile=\uBD84\uD560 ROM \uC120\uD0DD tab2_Lbl_ApplicationSettings=\uC8FC\uC694 \uC124\uC815 tabSplMrg_Lbl_SplitNMergeTitle=\uD30C\uC77C \uBD84\uD560 & \uBCD1\uD569 \uB3C4\uAD6C tabSplMrg_RadioBtn_Split=\uBD84\uD560 diff --git a/src/main/resources/locale_pt_BR.properties b/src/main/resources/locale_pt_BR.properties index 5b6f5ba..a16bcba 100644 --- a/src/main/resources/locale_pt_BR.properties +++ b/src/main/resources/locale_pt_BR.properties @@ -43,7 +43,7 @@ tab2_Cb_AllowXciNszXcz=permitir arquivos XCI / NSZ / XCZ para o awoo tab2_Lbl_AllowXciNszXczDesc=Usado por aplica\u00E7\u00F5es que suportam XCI/NSZ/XCZ e utiliza protocolos de transfer\u00EAncia do Tinfoil. N\u00E3o mude o que n\u00E3o tem certeza. Ative para uso com o Awoo-Installer. tab2_Lbl_Language=Idioma windowBodyRestartToApplyLang=Por favor, reinicie para aplicar as modifica\u00E7\u00F5es. -btn_OpenSplitFile=Select split NSP +btn_OpenSplitFile=Select split file tab2_Lbl_ApplicationSettings=Configura\u00E7\u00F5es principais tabSplMrg_Lbl_SplitNMergeTitle=Ferramenta de Fragmentar (Split) & Mesclar (Merge) arquivos tabSplMrg_RadioBtn_Split=Fragmentar (Dividir) diff --git a/src/main/resources/locale_ro_RO.properties b/src/main/resources/locale_ro_RO.properties index 6929a64..a85d286 100644 --- a/src/main/resources/locale_ro_RO.properties +++ b/src/main/resources/locale_ro_RO.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Adaug\u0103 fi\u0219iere XCI / NSZ / XCZ pentru selec\u02 tab2_Lbl_AllowXciNszXczDesc=Folosit de aplica\u021Bii care suport\u0103 XCI/NSZ/XCZ \u0219i folosesc protocolul de transfer al lui Tinfoil. Nu schimba dac\u0103\u00A0nu e\u0219ti sigur. Bifeaz\u0103 pentru Awoo Installer. tab2_Lbl_Language=Limb\u0103 windowBodyRestartToApplyLang=Te rog restarteaz\u0103 aplica\u021Bia pentru a aplica set\u0103rile noi. -btn_OpenSplitFile=Selecteaz\u0103 pentru NSP frac\u021Bionat +btn_OpenSplitFile=Selecteaz\u0103 pentru ROM frac\u021Bionat tab2_Lbl_ApplicationSettings=Set\u0103ri principale tabSplMrg_Lbl_SplitNMergeTitle=Unealt\u0103 pentru \u00EEmp\u0103r\u021Bire \u0219i lipire de fi\u0219iere tabSplMrg_RadioBtn_Split=\u00CEmparte diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 4190c70..01261c8 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -44,7 +44,7 @@ tab2_Lbl_AllowXciNszXczDesc=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u04 tab2_Lbl_Language=\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. tab2_Cb_GLshowNspOnly=\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. -btn_OpenSplitFile=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0437\u0431\u0438\u0442\u044B\u0439 NSP +btn_OpenSplitFile=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0437\u0431\u0438\u0442\u044B\u0439 \u0444\u0430\u0439\u043B tab2_Lbl_ApplicationSettings=\u041E\u0441\u043D\u043E\u0432\u043D\u044B\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 tabSplMrg_Lbl_SplitNMergeTitle=\u0423\u0442\u0438\u043B\u0438\u0442\u0430 \u0440\u0430\u0437\u0431\u0438\u0432\u043A\u0438 \u0438 \u0441\u043B\u0438\u044F\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u043E\u0432 tabSplMrg_Btn_Convert=\u041A\u043E\u043D\u0432\u0435\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C diff --git a/src/main/resources/locale_sv_SE.properties b/src/main/resources/locale_sv_SE.properties index 03718c7..6c6b526 100644 --- a/src/main/resources/locale_sv_SE.properties +++ b/src/main/resources/locale_sv_SE.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Till\u00E5t val av XCI / NSZ / XCZ-filer f\u00F6r Awoo tab2_Lbl_AllowXciNszXczDesc=Anv\u00E4nds av program som har st\u00F6d f\u00F6r XCI/NSZ/XCZ och anv\u00E4nder Awoo (aka Adubbz/TinFoil) \u00F6verf\u00F6ringsprotokoll. \u00C4ndra inte om du \u00E4r os\u00E4ker. Aktivera f\u00F6r Awoo Installer. tab2_Lbl_Language=Spr\u00E5k windowBodyRestartToApplyLang=Starta om programmet f\u00F6r att verkst\u00E4lla \u00E4ndringar. -btn_OpenSplitFile=V\u00E4lj delad NSP +btn_OpenSplitFile=V\u00E4lj delad ROM tab2_Lbl_ApplicationSettings=Huvudinst\u00E4llningar tabSplMrg_Lbl_SplitNMergeTitle=Verktyg f\u00F6r att dela upp och sl\u00E5 ihop filer tabSplMrg_RadioBtn_Split=Dela upp diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index ce8524c..c4503a5 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -44,7 +44,7 @@ tab2_Lbl_AllowXciNszXczDesc=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u04 tab2_Lbl_Language=\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. tab2_Cb_GLshowNspOnly=\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. -btn_OpenSplitFile=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0440\u043E\u0437\u0431\u0438\u0442\u0438\u0439 NSP +btn_OpenSplitFile=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0440\u043E\u0437\u0431\u0438\u0442\u0438\u0439 \u0444\u0430\u0439\u043B tab2_Lbl_ApplicationSettings=\u041E\u0441\u043D\u043E\u0432\u043D\u0456 \u043D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F tabSplMrg_Lbl_SplitNMergeTitle=\u041F\u043E\u043C\u0456\u0447\u043D\u0438\u043A \u0440\u043E\u0437\u0431\u0432\u043A\u0438 \u0442\u0430 \u043E\u0431'\u0454\u0434\u043D\u0430\u043D\u043D\u044F \u0444\u0430\u0439\u043B\u0456\u0432 tabSplMrg_Btn_Convert=\u041A\u043E\u043D\u0432\u0435\u0440\u0442\u0443\u0432\u0430\u0442\u0438 diff --git a/src/main/resources/locale_vi_VN.properties b/src/main/resources/locale_vi_VN.properties index a8db7dc..fc19894 100644 --- a/src/main/resources/locale_vi_VN.properties +++ b/src/main/resources/locale_vi_VN.properties @@ -2,7 +2,7 @@ btn_InjectPayloader=N\u1EA1p payload btn_OpenFile=Ch\u1ECDn t\u1EADp tin .NSP -btn_OpenSplitFile=Ch\u1ECDn NSP t\u00E1ch +btn_OpenSplitFile=Ch\u1ECDn ROM t\u00E1ch btn_Select=Ch\u1ECDn btn_Stop=Ng\u1EAFt btn_Upload=T\u1EA3i l\u00EAn NS diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 8039b0a..1f02792 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=Awoo\u6A21\u5F0F\u5141\u8BB8\u9009\u62E9XCI\u6587\u4EF6 tab2_Lbl_AllowXciNszXczDesc=\u7528\u4E8E\u4E00\u4E9B\u652F\u6301XCI/NSZ/XCZ\u548CTinfoil\u4F20\u8F93\u534F\u8BAE\u7684\u7B2C\u4E09\u65B9\u5E94\u7528\u3002\u5982\u679C\u4E0D\u6E05\u695A\u4E0D\u8981\u4FEE\u6539\u3002 tab2_Lbl_Language=\u8BED\u8A00 windowBodyRestartToApplyLang=\u8BF7\u91CD\u542F\u5E94\u7528\u4EE5\u5E94\u7528\u66F4\u6539\u3002 -btn_OpenSplitFile=\u9009\u62E9\u5206\u5272\u7684NSP +btn_OpenSplitFile=\u9009\u62E9\u5206\u5272\u7684ROM tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8BBE\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 diff --git a/src/main/resources/locale_zh_TW.properties b/src/main/resources/locale_zh_TW.properties index 615b87f..25494ea 100644 --- a/src/main/resources/locale_zh_TW.properties +++ b/src/main/resources/locale_zh_TW.properties @@ -45,7 +45,7 @@ tab2_Cb_AllowXciNszXcz=\u5141\u8A31Awoo\u6A21\u5F0F\u6642\u9078\u53D6XCI / NSZ / tab2_Lbl_AllowXciNszXczDesc=\u6B64\u8A2D\u5B9A\u5C08\u70BA\u652F\u63F4XCI/NSZ/XCZ\u6A94\u6848\u683C\u5F0F\u8207Tinfoil\u50B3\u8F38\u5354\u8B70\u7684\u7B2C\u4E09\u65B9\u7A0B\u5F0F\u4F7F\u7528. \u5982\u4E0D\u78BA\u5B9A,\u8ACB\u52FF\u8B8A\u66F4\u6B64\u9805\u8A2D\u5B9A. \u4F7F\u7528Awoo Installer\u8ACB\u555F\u7528\u6B64\u8A2D\u5B9A. tab2_Lbl_Language=\u4ECB\u9762\u8A9E\u7CFB windowBodyRestartToApplyLang=\u8ACB\u91CD\u65B0\u555F\u52D5\u7A0B\u5F0F\u4EE5\u5957\u7528\u8B8A\u66F4\u7684\u8A2D\u5B9A. -btn_OpenSplitFile=\u9078\u64C7\u5206\u5272\u7684NSP +btn_OpenSplitFile=\u9078\u64C7\u5206\u5272\u7684ROM tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8A2D\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 tabSplMrg_RadioBtn_Split=\u5206\u5272&\u5408\u4F75\u6A94\u6848\u5DE5\u5177 From 83695511d3ddd5f9f2316bb2bf0ac3f503d347e4 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 6 Feb 2023 02:00:05 +0300 Subject: [PATCH 080/134] Hide 'extended network settings' instead of setting it disabled --- pom.xml | 38 ++--- .../Controllers/PatchesController.java | 93 ++++++++--- .../SettingsBlockTinfoilController.java | 2 +- src/main/java/nsusbloader/RainbowHexDump.java | 71 --------- .../Utilities/patches/BinToAsmPrinter.java | 41 ++++- .../fs/{IniMaker.java => FsIniMaker.java} | 18 +-- .../Utilities/patches/fs/FsPatch.java | 2 +- .../patches/loader/LoaderIniMaker.java | 115 ++++++++++++++ .../Utilities/patches/loader/LoaderPatch.java | 147 ++++++++++++++++++ .../patches/loader/LoaderPatchMaker.java | 125 +++++++++++++++ src/main/resources/PatchesTab.fxml | 24 ++- src/main/resources/locale.properties | 5 +- src/main/resources/locale_ru_RU.properties | 5 +- src/main/resources/locale_uk_UA.properties | 5 +- 14 files changed, 557 insertions(+), 134 deletions(-) delete mode 100644 src/main/java/nsusbloader/RainbowHexDump.java rename src/main/java/nsusbloader/Utilities/patches/fs/{IniMaker.java => FsIniMaker.java} (94%) create mode 100644 src/main/java/nsusbloader/Utilities/patches/loader/LoaderIniMaker.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java create mode 100644 src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatchMaker.java diff --git a/pom.xml b/pom.xml index 048eb11..daebc2b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,13 +8,13 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>6.2</version> + <version>7.0</version> <url>https://redrise.ru</url> <description> NS multi tool </description> - <inceptionYear>2019</inceptionYear> + <inceptionYear>2019.0.2.1</inceptionYear> <organization> <name>Dmitry Isaenko</name> <url>https://developersu.blogspot.com/</url> @@ -61,28 +61,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -90,28 +90,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -119,28 +119,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> @@ -148,28 +148,28 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-media</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-graphics</artifactId> - <version>19</version> + <version>19.0.2.1</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> @@ -309,7 +309,7 @@ <fileVersion>1.0.0.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> <fileDescription>NS multi tool</fileDescription> - <copyright>GNU General Public License v3, 2019 ${project.organization.name}, Russia.</copyright> + <copyright>GNU General Public License v3, 2019.0.2.1 ${project.organization.name}, Russia.</copyright> <productVersion>1.0.0.0</productVersion> <txtProductVersion>${project.version}</txtProductVersion> <companyName>${project.organization.name}</companyName> diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 7b0261b..5aeb672 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -41,15 +41,17 @@ import nsusbloader.NSLDataTypes.EModule; import nsusbloader.ServiceWindow; import nsusbloader.Utilities.patches.es.EsPatchMaker; import nsusbloader.Utilities.patches.fs.FsPatchMaker; +import nsusbloader.Utilities.patches.loader.LoaderPatchMaker; // TODO: CLI SUPPORT public class PatchesController implements Initializable { @FXML private VBox patchesToolPane; @FXML - private Button selFwFolderBtn, selProdKeysBtn, makeEsBtn, makeFsBtn; + private Button makeEsBtn, makeFsBtn, makeLoaderBtn; @FXML - private Label shortNameFirmwareLbl, locationFirmwareLbl, saveToLbl, shortNameKeysLbl, locationKeysLbl, statusLbl; + private Label shortNameFirmwareLbl, locationFirmwareLbl, saveToLbl, shortNameKeysLbl, locationKeysLbl, statusLbl, + locationAtmosphereLbl, shortNameAtmoLbl; private Thread workThread; private String previouslyOpenedPath; @@ -72,13 +74,14 @@ public class PatchesController implements Initializable { locationKeysLbl.textProperty().addListener((observableValue, currentText, updatedText) -> shortNameKeysLbl.setText(updatedText.replaceAll(myRegexp, ""))); - convertRegionEs = new Region(); - convertRegionEs.getStyleClass().add("regionCake"); + locationAtmosphereLbl.textProperty().addListener((observableValue, currentText, updatedText) -> + shortNameAtmoLbl.setText(updatedText.replaceAll(myRegexp, ""))); + + convertRegionEs = createCakeRegion(); makeEsBtn.setGraphic(convertRegionEs); - Region cakeRegionFs = new Region(); - cakeRegionFs.getStyleClass().add("regionCake"); - makeFsBtn.setGraphic(cakeRegionFs); + makeFsBtn.setGraphic(createCakeRegion()); + makeLoaderBtn.setGraphic(createCakeRegion()); AppPreferences preferences = AppPreferences.getInstance(); String keysLocation = preferences.getKeysLocation(); @@ -89,11 +92,23 @@ public class PatchesController implements Initializable { } saveToLbl.setText(preferences.getPatchesSaveToLocation()); - makeEsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); + makeEsBtn.disableProperty().bind(Bindings.or( + Bindings.isEmpty(locationFirmwareLbl.textProperty()), + Bindings.isEmpty(locationKeysLbl.textProperty()))); makeEsBtn.setOnAction(actionEvent -> makeEs()); - makeFsBtn.disableProperty().bind(Bindings.isEmpty(locationFirmwareLbl.textProperty())); + makeFsBtn.disableProperty().bind(Bindings.or( + Bindings.isEmpty(locationFirmwareLbl.textProperty()), + Bindings.isEmpty(locationKeysLbl.textProperty()))); makeFsBtn.setOnAction(actionEvent -> makeFs()); + + makeLoaderBtn.disableProperty().bind(Bindings.isEmpty(locationAtmosphereLbl.textProperty())); + makeLoaderBtn.setOnAction(actionEvent -> makeLoader()); + } + private Region createCakeRegion(){ + Region cakeRegion = new Region(); + cakeRegion.getStyleClass().add("regionCake"); + return cakeRegion; } /** @@ -135,6 +150,18 @@ public class PatchesController implements Initializable { if (firmware == null) return; locationFirmwareLbl.setText(firmware.getAbsolutePath()); + previouslyOpenedPath = firmware.getParent(); + } + @FXML + private void selectAtmosphereFolder(){ + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("tabPatches_Lbl_Atmo")); + directoryChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); + File firmware = directoryChooser.showDialog(patchesToolPane.getScene().getWindow()); + if (firmware == null) + return; + locationAtmosphereLbl.setText(firmware.getAbsolutePath()); + previouslyOpenedPath = firmware.getParent(); } @FXML private void selectSaveTo(){ @@ -153,16 +180,17 @@ public class PatchesController implements Initializable { fileChooser.setInitialDirectory(new File(FilesHelper.getRealFolder(previouslyOpenedPath))); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("keys", "*.dat", "*.keys")); File keys = fileChooser.showOpenDialog(patchesToolPane.getScene().getWindow()); + if (keys == null || ! keys.exists()) + return; - if (keys != null && keys.exists()) { - locationKeysLbl.setText(keys.getAbsolutePath()); - } + locationKeysLbl.setText(keys.getAbsolutePath()); + previouslyOpenedPath = keys.getParent(); } private void makeEs(){ if (locationFirmwareLbl.getText().isEmpty() || locationKeysLbl.getText().isEmpty()){ ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), - resourceBundle.getString("tabPatches_ServiceWindowMessage")); + resourceBundle.getString("tabPatches_ServiceWindowMessageEsFs")); return; } @@ -186,7 +214,7 @@ public class PatchesController implements Initializable { private void makeFs(){ if (locationFirmwareLbl.getText().isEmpty() || locationKeysLbl.getText().isEmpty()){ ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), - resourceBundle.getString("tabPatches_ServiceWindowMessage")); + resourceBundle.getString("tabPatches_ServiceWindowMessageEsFs")); return; } @@ -207,6 +235,29 @@ public class PatchesController implements Initializable { workThread.setDaemon(true); workThread.start(); } + private void makeLoader(){ + if (locationAtmosphereLbl.getText().isEmpty()){ + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("tabPatches_ServiceWindowMessageLoader")); + return; + } + + if (workThread != null && workThread.isAlive()) + return; + statusLbl.setText(""); + + if (MediatorControl.getInstance().getTransferActive()) { + ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), + resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); + return; + } + + LoaderPatchMaker loaderPatchMaker = new LoaderPatchMaker(locationAtmosphereLbl.getText(), saveToLbl.getText()); + workThread = new Thread(loaderPatchMaker); + + workThread.setDaemon(true); + workThread.start(); + } private void interruptProcessOfPatchMaking(){ if (workThread == null || ! workThread.isAlive()) return; @@ -222,6 +273,7 @@ public class PatchesController implements Initializable { convertRegionEs.getStyleClass().clear(); makeFsBtn.setVisible(! isActive); + makeLoaderBtn.setVisible(! isActive); if (isActive) { MediatorControl.getInstance().getContoller().logArea.clear(); @@ -231,15 +283,14 @@ public class PatchesController implements Initializable { makeEsBtn.setText(resourceBundle.getString("btn_Stop")); makeEsBtn.getStyleClass().remove("buttonUp"); makeEsBtn.getStyleClass().add("buttonStop"); + return; } - else { - convertRegionEs.getStyleClass().add("regionCake"); + convertRegionEs.getStyleClass().add("regionCake"); - makeEsBtn.setOnAction(actionEvent -> makeEs()); - makeEsBtn.setText(resourceBundle.getString("tabPatches_Btn_MakeEs")); - makeEsBtn.getStyleClass().remove("buttonStop"); - makeEsBtn.getStyleClass().add("buttonUp"); - } + makeEsBtn.setOnAction(actionEvent -> makeEs()); + makeEsBtn.setText(resourceBundle.getString("tabPatches_Btn_MakeEs")); + makeEsBtn.getStyleClass().remove("buttonStop"); + makeEsBtn.getStyleClass().add("buttonUp"); } public void setOneLineStatus(boolean statusSuccess){ diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockTinfoilController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockTinfoilController.java index 8c28be3..ec6c5f9 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockTinfoilController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockTinfoilController.java @@ -55,7 +55,7 @@ public class SettingsBlockTinfoilController implements Initializable { final AppPreferences preferences = AppPreferences.getInstance(); - networkExpertSettingsVBox.disableProperty().bind(networkExpertModeCB.selectedProperty().not()); + networkExpertSettingsVBox.visibleProperty().bind(networkExpertModeCB.selectedProperty()); pcIpTF.disableProperty().bind(autoDetectIpCB.selectedProperty()); pcPortTF.disableProperty().bind(randomlySelectPortCB.selectedProperty()); diff --git a/src/main/java/nsusbloader/RainbowHexDump.java b/src/main/java/nsusbloader/RainbowHexDump.java deleted file mode 100644 index 109957d..0000000 --- a/src/main/java/nsusbloader/RainbowHexDump.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright 2019-2020 Dmitry Isaenko - - This file is part of NS-USBloader. - - NS-USBloader is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - NS-USBloader is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. -*/ -package nsusbloader; - -import java.nio.charset.StandardCharsets; - -/** - * Debug tool like hexdump <3 - */ -public class RainbowHexDump { - private static final String ANSI_RESET = "\u001B[0m"; - private static final String ANSI_BLACK = "\u001B[30m"; - private static final String ANSI_RED = "\u001B[31m"; - private static final String ANSI_GREEN = "\u001B[32m"; - private static final String ANSI_YELLOW = "\u001B[33m"; - private static final String ANSI_BLUE = "\u001B[34m"; - private static final String ANSI_PURPLE = "\u001B[35m"; - private static final String ANSI_CYAN = "\u001B[36m"; - private static final String ANSI_WHITE = "\u001B[37m"; - - public static void hexDumpUTF8(byte[] byteArray){ - System.out.print(ANSI_BLUE); - for (int i=0; i < byteArray.length; i++) - System.out.printf("%02d-", i%100); - System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); - for (byte b: byteArray) - System.out.printf("%02x ", b); - System.out.println(); - System.out.print("\t\t\t" - + new String(byteArray, StandardCharsets.UTF_8) - + "\n"); - } - - public static void hexDumpUTF8ForWin(byte[] byteArray){ - for (int i=0; i < byteArray.length; i++) - System.out.printf("%02d-", i%100); - System.out.println(">"+byteArray.length); - for (byte b: byteArray) - System.out.printf("%02x ", b); - System.out.println(); - System.out.print(new String(byteArray, StandardCharsets.UTF_8) - + "\n"); - } - - public static void hexDumpUTF16LE(byte[] byteArray){ - System.out.print(ANSI_BLUE); - for (int i=0; i < byteArray.length; i++) - System.out.printf("%02d-", i%100); - System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); - for (byte b: byteArray) - System.out.printf("%02x ", b); - System.out.print(new String(byteArray, StandardCharsets.UTF_16LE) - + "\n"); - } -} diff --git a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java index 622d27c..0109258 100644 --- a/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java +++ b/src/main/java/nsusbloader/Utilities/patches/BinToAsmPrinter.java @@ -111,7 +111,7 @@ public class BinToAsmPrinter { return printImTooLazy("LDP", instructionExpression, offset); } - switch ((instructionExpression >> 23 & 0xff)){ + switch ((instructionExpression >> 23 & 0x1ff)){ case 0xA5: return printMOVSimplified(instructionExpression, offset); case 0x22: @@ -123,10 +123,10 @@ public class BinToAsmPrinter { case 0xA2: return printSUBSimplified(instructionExpression, offset); case 0xE2: - //case 0x1e2: + case 0x1e2: return printCMPSimplified(instructionExpression, offset); case 0x24: - //case 0x124: + case 0x124: return printANDSimplified(instructionExpression, offset); } @@ -142,6 +142,10 @@ public class BinToAsmPrinter { return printTBZSimplified(instructionExpression, offset); case 0x54: return printBConditionalSimplified(instructionExpression, offset); + case 0xeb: + case 0x6b: + if ((instructionExpression & 0x1f) == 0b11111) + return printCMPShiftedRegisterSimplified(instructionExpression, offset); } switch (instructionExpression >> 26 & 0b111111) { @@ -498,6 +502,37 @@ public class BinToAsmPrinter { LSL); } + private static String printCMPShiftedRegisterSimplified(int instructionExpression, int offset){ + String sf = (instructionExpression >> 31 == 0) ? "W" : "X"; + int Rn = instructionExpression >> 5 & 0x1F; + int Rm = instructionExpression >> 16 & 0x1F; + int imm6 = instructionExpression >> 10 & 0x3f; + int LSL = (instructionExpression >> 22 & 0b11); + String LSLStr; + switch (LSL){ + case 0b00: + LSLStr = "LSL"; + break; + case 0b01: + LSLStr = "LSR"; + break; + case 0b10: + LSLStr = "ASR"; + break; + case 0b11: + LSLStr = "RESERVED"; + break; + default: + LSLStr = "?"; + } + + return String.format( + "%05x "+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, + Rn, Rm, imm6); + } + private static String printANDSimplified(int instructionExpression, int offset){ String sf = (instructionExpression >> 31 == 0) ? "W" : "X"; int Rn = instructionExpression & 0x1F; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java similarity index 94% rename from src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java rename to src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java index ecf42fc..64551ef 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/IniMaker.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java @@ -31,7 +31,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; -public class IniMaker { +public class FsIniMaker { private static final String FILE_HEADER_TEXT = "# UTF-8\n" + "# A KIP section is [kip1_name:sha256_hex_8bytes]\n" + "# A patchset is .patch_name=kip_section_dec:offset_hex_0x:length_hex_0x:src_data_hex,dst_data_hex\n" + @@ -48,14 +48,14 @@ public class IniMaker { private String patchSet1; private String patchSet2; - IniMaker(ILogPrinter logPrinter, - String saveToLocation, - byte[] _textSection, - int wizardOffset1, - int wizardOffset2, - byte[] sdkVersion, - String patchName, - boolean filesystemTypeFat32) throws Exception{ + public FsIniMaker(ILogPrinter logPrinter, + String saveToLocation, + byte[] _textSection, + int wizardOffset1, + int wizardOffset2, + byte[] sdkVersion, + String patchName, + boolean filesystemTypeFat32) throws Exception{ this.logPrinter = logPrinter; this.saveToLocation = saveToLocation; this.offset1 = wizardOffset1 - 4; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java index 368ea0b..7849f50 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -76,7 +76,7 @@ public class FsPatch { findAllOffsets(); mkDirs(); writeFile(); - new IniMaker(logPrinter, + new FsIniMaker(logPrinter, saveToLocation, _textSection, wizard.getOffset1(), diff --git a/src/main/java/nsusbloader/Utilities/patches/loader/LoaderIniMaker.java b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderIniMaker.java new file mode 100644 index 0000000..d8ef04d --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderIniMaker.java @@ -0,0 +1,115 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.loader; + +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.MalformedIniFileException; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class LoaderIniMaker { + private static final String FILE_HEADER_TEXT = "# UTF-8\n" + + "# A KIP section is [kip1_name:sha256_hex_8bytes]\n" + + "# A patchset is .patch_name=kip_section_dec:offset_hex_0x:length_hex_0x:src_data_hex,dst_data_hex\n" + + "# _dec: 1 char decimal | _hex_0x: max u32 prefixed with 0x | _hex: hex array.\n" + + "# Kip1 section decimals: TEXT: 0, RODATA: 1, DATA: 2.\n"; // Sending good vibes to Mr. ITotalJustice + + private final ILogPrinter logPrinter; + private final String saveToLocation; + private final int offset; + + private String sectionDeclaration; + private String patchSet; + + LoaderIniMaker(ILogPrinter logPrinter, + String saveToLocation, + int foundOffset, + String patchName) throws Exception{ + this.logPrinter = logPrinter; + this.saveToLocation = saveToLocation; + this.offset = foundOffset + 6; + + mkDirs(); + makeSectionDeclaration(patchName); + makePatchSet1(); + writeFile(); + } + + private void mkDirs(){ + File parentFolder = new File(saveToLocation + File.separator + "bootloader"); + parentFolder.mkdirs(); + } + + private void makeSectionDeclaration(String patchName){ + sectionDeclaration = "[Loader:"+patchName.substring(0, 16)+"]"; + } + + private void makePatchSet1(){ + patchSet = String.format(".nosigchk=0:0x%02X:0x1:01,00", offset); + } + + private void writeFile() throws Exception{ + final String iniLocation = saveToLocation + File.separator + "bootloader" + File.separator + "patches.ini"; + final Path iniLocationPath = Paths.get(iniLocation); + + boolean iniNotExists = Files.notExists(iniLocationPath); + + try (RandomAccessFile ini = new RandomAccessFile(iniLocation, "rw")){ + if (iniNotExists) + ini.writeBytes(FILE_HEADER_TEXT); + else { + String line; + while ((line = ini.readLine()) != null){ + if (! line.startsWith(sectionDeclaration)) + continue; + + String expression = ini.readLine(); + + if (expression == null || ! expression.startsWith(patchSet)) + throw new MalformedIniFileException("Somewhere near "+ini.getFilePointer()); + + return; // Ini file already contains correct information regarding patch file we made. + } + } + + ini.writeBytes("\n#Loader (Atmosphere)\n"); + ini.writeBytes(sectionDeclaration); + ini.writeBytes("\n"); + + ini.writeBytes(patchSet); + ini.writeBytes("\n"); + } + catch (MalformedIniFileException e){ + e.printStackTrace(); + logPrinter.print( + "Existing patches.ini file is malformed or contains incorrect (outdated) information regarding current patch.\n" + + "It's now saved at "+iniLocation+".OLD\n" + + "New patches.ini file created instead.", EMsgType.WARNING); + Files.move(iniLocationPath, Paths.get(iniLocation+".OLD"), + StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + writeFile(); + } + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java new file mode 100644 index 0000000..ad25afd --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java @@ -0,0 +1,147 @@ +/* + Copyright 2018-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + --- + Based on https://github.com/mrdude2478/IPS_Patch_Creator patch script made by GBATemp member MrDude. + */ +package nsusbloader.Utilities.patches.loader; + +import libKonogonka.Converter; +import libKonogonka.fs.other.System2.ini1.KIP1Provider; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.BinToAsmPrinter; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.Arrays; + +public class LoaderPatch { + private static final byte[] HEADER = "PATCH".getBytes(StandardCharsets.US_ASCII); + private static final byte[] FOOTER = "EOF".getBytes(StandardCharsets.US_ASCII); + + private static final String ATMOSPHERE_NEW_PATTERN = "01C0BE121F00016B"; + //private static final String ATMOSPHERE_OLD_PATTERN = "003C00121F280071"; Must be patched using different (to current implementation) code + + private final String saveToLocation; + private final ILogPrinter logPrinter; + + private String patchName; + private byte[] _textSection; + + private int offset; + + LoaderPatch(KIP1Provider loaderProvider, + String saveToLocation, + ILogPrinter logPrinter) throws Exception{ + this.saveToLocation = saveToLocation; + this.logPrinter = logPrinter; + + getPatchName(loaderProvider); + getTextSection(loaderProvider); + findOffset(); + mkDirs(); + writeFile(); + new LoaderIniMaker(logPrinter, saveToLocation, offset, patchName); + } + private void getPatchName(KIP1Provider kip1Provider) throws Exception{ + int kip1EncryptedSize = (int) kip1Provider.getSize(); + byte[] kip1EncryptedRaw = new byte[kip1EncryptedSize]; + + try (BufferedInputStream kip1ProviderStream = kip1Provider.getStreamProducer().produce()) { + if (kip1EncryptedSize != kip1ProviderStream.read(kip1EncryptedRaw)) + throw new Exception("Unencrypted FS KIP1 read failure"); + } + + byte[] sha256ofKip1 = MessageDigest.getInstance("SHA-256").digest(kip1EncryptedRaw); + patchName = Converter.byteArrToHexStringAsLE(sha256ofKip1, true) + ".ips"; + } + private void getTextSection(KIP1Provider kip1Provider) throws Exception{ + _textSection = kip1Provider.getAsDecompressed().getTextRaw(); + } + private void findOffset() throws Exception{ + SimplyFind simplyFind = new SimplyFind(ATMOSPHERE_NEW_PATTERN, _textSection); // Atm 13+ + if (simplyFind.getResults().size() == 0) + throw new Exception("Offset not found"); + + offset = simplyFind.getResults().get(0); + + if (offset <= 0) + throw new Exception("Found offset is incorrect"); + + for (int i = 0; i < simplyFind.getResults().size(); i++) { + int offsetInternal = simplyFind.getResults().get(i) + 4; + logPrinter.print("Only first (#1) found record will be patched!", EMsgType.INFO); + logPrinter.print("Found #" + (i+1) +"\n"+ + BinToAsmPrinter.printSimplified(Converter.getLEint(_textSection, offsetInternal), offsetInternal) + + BinToAsmPrinter.printSimplified(Converter.getLEint(_textSection, offsetInternal + 4), offsetInternal + 4) + + BinToAsmPrinter.printSimplified(Converter.getLEint(_textSection, offsetInternal + 8), offsetInternal + 8) + + BinToAsmPrinter.printSimplified(Converter.getLEint(_textSection, offsetInternal + 12), offsetInternal + 12), + EMsgType.NULL); + } + } + private void mkDirs(){ + File parentFolder = new File(saveToLocation + File.separator + + "atmosphere" + File.separator + "kip_patches" + File.separator + "loader_patches"); + parentFolder.mkdirs(); + } + + private void writeFile() throws Exception{ + String patchFileLocation = saveToLocation + File.separator + + "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches" + File.separator + patchName; + + ByteBuffer handyFsPatch = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN); + handyFsPatch.put(HEADER); + handyFsPatch.put(getPatch1(offset)); + handyFsPatch.put(FOOTER); + + byte[] fsPatch = new byte[handyFsPatch.position()]; + handyFsPatch.rewind(); + handyFsPatch.get(fsPatch); + + try (BufferedOutputStream stream = new BufferedOutputStream( + Files.newOutputStream(Paths.get(patchFileLocation)))){ + stream.write(fsPatch); + } + logPrinter.print("Patch created at "+patchFileLocation, EMsgType.PASS); + } + + private byte[] getPatch1(int offset) throws Exception{ + int requiredInstructionOffsetInternal = offset + 6; + int requiredInstructionOffsetReal = requiredInstructionOffsetInternal + 0x100; + final byte[] patch = new byte[]{0x00, 0x01, 0x00}; + + int instructionPatched = Converter.getLEint(_textSection, offset + 4) & 0xff00ffff; + + logPrinter.print("Patch will be applied", EMsgType.PASS); + logPrinter.print(BinToAsmPrinter.printSimplified(instructionPatched, offset+4), EMsgType.NULL); + + ByteBuffer prePatch = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN) + .putInt(requiredInstructionOffsetReal) + .put(patch); + + return Arrays.copyOfRange(prePatch.array(), 1, 7); + } +} diff --git a/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatchMaker.java b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatchMaker.java new file mode 100644 index 0000000..ce575f5 --- /dev/null +++ b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatchMaker.java @@ -0,0 +1,125 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Utilities.patches.loader; + +import libKonogonka.Converter; +import libKonogonka.fs.other.System2.ini1.KIP1Provider; +import nsusbloader.ModelControllers.CancellableRunnable; +import nsusbloader.ModelControllers.ILogPrinter; +import nsusbloader.ModelControllers.Log; +import nsusbloader.NSLDataTypes.EModule; +import nsusbloader.NSLDataTypes.EMsgType; +import nsusbloader.Utilities.patches.SimplyFind; + +import java.io.BufferedInputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +public class LoaderPatchMaker extends CancellableRunnable { + + private final ILogPrinter logPrinter; + private final String atmosphereLocation; + private final String saveTo; + + private String package3Location; + private KIP1Provider loaderProvider; + + private boolean oneLinerStatus = false; + + public LoaderPatchMaker(String atmosphereLocation, String saveTo){ + this.logPrinter = Log.getPrinter(EModule.PATCHES); + /* + this.logPrinter = new ILogPrinter() { + public void print(String message, EMsgType type) throws InterruptedException {} + public void updateProgress(Double value) throws InterruptedException {} + public void update(HashMap<String, File> nspMap, EFileStatus status) {} + public void update(File file, EFileStatus status) {} + public void updateOneLinerStatus(boolean status) {} + public void close() {} + }; + //*/ + this.atmosphereLocation = atmosphereLocation; + this.saveTo = saveTo; + } + + @Override + public void run() { + try { + logPrinter.print("..:: Make Loader Patches ::..", EMsgType.INFO); + checkPackage3(); + createLoaderKip1Provider(); + makePatches(); + } + catch (Exception e){ + e.printStackTrace(); + try{ + logPrinter.print(e.getMessage(), EMsgType.FAIL); + } catch (Exception ignore){} + } + finally { + logPrinter.updateOneLinerStatus(oneLinerStatus); + logPrinter.close(); + } + } + private void checkPackage3() throws Exception{ + logPrinter.print("Looking at Atmosphere", EMsgType.INFO); + if (Files.notExists(Paths.get(atmosphereLocation))) + throw new Exception("Atmosphere directory does not exist at " + atmosphereLocation); + + package3Location = atmosphereLocation +File.separator+"package3"; + if (Files.exists(Paths.get(package3Location))) + return; + + package3Location = atmosphereLocation +File.separator+"fusee-secondary.bin"; + if (Files.notExists(Paths.get(package3Location))) + throw new Exception("package3 / fusee-secondary.bin file not found at " + atmosphereLocation); + } + + private void createLoaderKip1Provider() throws Exception{ + Path package3Path = Paths.get(package3Location); + + try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(package3Path))) { + byte[] data = new byte[0x400]; + if (0x400 != stream.read(data)) + throw new Exception("Failed to read first 0x400 bytes of package3 / fusee-secondary file."); + + SimplyFind simplyFind = new SimplyFind(".6f61646572", data); // eq. '.oader' + List<Integer> results = simplyFind.getResults(); + if (results.size() == 0) + throw new Exception("Failed to find 'Loader' offset at package3 / fusee-secondary file."); + + int offset = results.get(0); + int kip1Offset = Converter.getLEint(data, offset - 0x10); + int kip1Size = Converter.getLEint(data, offset - 0xC); + + loaderProvider = new KIP1Provider(package3Location, kip1Offset); + + if (kip1Size != loaderProvider.getSize()) + throw new Exception("Incorrect calculations for KIP1. PK31 value: "+kip1Size+"KIP1Provider value: "+loaderProvider.getSize()); + logPrinter.print("Loader KIP1 found", EMsgType.PASS); + } + } + private void makePatches() throws Exception{ + new LoaderPatch(loaderProvider, saveTo, logPrinter); + oneLinerStatus = true; + } +} \ No newline at end of file diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml index c7d0422..ee72f6c 100644 --- a/src/main/resources/PatchesTab.fxml +++ b/src/main/resources/PatchesTab.fxml @@ -48,7 +48,7 @@ <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Firmware" wrapText="true" /> <Label fx:id="shortNameFirmwareLbl" textOverrun="LEADING_WORD_ELLIPSIS" /> <Pane HBox.hgrow="ALWAYS" /> - <Button fx:id="selFwFolderBtn" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectFirmware" styleClass="buttonSelect" text="%tabSplMrg_Btn_SelectFolder" wrapText="true"> + <Button minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectFirmware" styleClass="buttonSelect" text="%tabSplMrg_Btn_SelectFolder" wrapText="true"> <graphic> <SVGPath content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" fill="#289de8" /> </graphic> @@ -60,6 +60,23 @@ <Font name="System Italic" size="13.0" /> </font> </Label> + <HBox alignment="CENTER_LEFT" spacing="5.0"> + <children> + <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Atmo" wrapText="true" /> + <Label fx:id="shortNameAtmoLbl" textOverrun="LEADING_WORD_ELLIPSIS" /> + <Pane HBox.hgrow="ALWAYS" /> + <Button minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectAtmosphereFolder" styleClass="buttonSelect" text="%tabSplMrg_Btn_SelectFolder" wrapText="true"> + <graphic> + <SVGPath content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" fill="#289de8" /> + </graphic> + </Button> + </children> + </HBox> + <Label fx:id="locationAtmosphereLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> + <font> + <Font name="System Italic" size="13.0" /> + </font> + </Label> </children> </VBox> <Separator prefWidth="200.0" /> @@ -70,7 +87,7 @@ <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Keys" wrapText="true" /> <Label fx:id="shortNameKeysLbl" /> <Pane HBox.hgrow="ALWAYS" /> - <Button fx:id="selProdKeysBtn" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectProdKeys" styleClass="buttonSelect" text="%btn_Select"> + <Button minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectProdKeys" styleClass="buttonSelect" text="%btn_Select"> <graphic> <SVGPath content="M22,18V22H18V19H15V16H12L9.74,13.74C9.19,13.91 8.61,14 8,14A6,6 0 0,1 2,8A6,6 0 0,1 8,2A6,6 0 0,1 14,8C14,8.61 13.91,9.19 13.74,9.74L22,18M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5Z" fill="#289de8" /> </graphic> @@ -90,7 +107,7 @@ <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabSplMrg_Lbl_SaveToLocation" wrapText="true" /> <Label fx:id="saveToLbl" /> <Pane HBox.hgrow="ALWAYS" /> - <Button fx:id="selProdKeysBtn1" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectSaveTo" styleClass="buttonSelect" text="%btn_Select"> + <Button minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#selectSaveTo" styleClass="buttonSelect" text="%btn_Select"> <graphic> <SVGPath content="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" fill="#289de8" /> </graphic> @@ -114,6 +131,7 @@ <children> <Button fx:id="makeEsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeEs" /> <Button fx:id="makeFsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeFs" /> + <Button fx:id="makeLoaderBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeAtmo" /> </children> </HBox> <padding> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index c387135..ea87ce7 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -87,6 +87,7 @@ tabPatches_Lbl_Title=Patches tabPatches_Lbl_Keys=Keys: tabPatches_Btn_MakeEs=Make ES tabPatches_Btn_MakeFs=Make FS -tabPatches_Btn_MakeAtmo=Make Atmo +tabPatches_Btn_MakeAtmo=Make Loader (Atmosphere) tabPatches_Btn_MakeAll=Make all -tabPatches_ServiceWindowMessage=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. +tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. +tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 01261c8..8e4a177 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -80,13 +80,14 @@ tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM tabPatches_Btn_asZipFile=\u0432 \u0432\u0438\u0434\u0435 ZIP tabPatches_Btn_fromFolder=\u0418\u0437 \u043F\u0430\u043F\u043A\u0438 tabPatches_Btn_MakeAll=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0441\u0451 -tabPatches_Btn_MakeAtmo=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F Atmo +tabPatches_Btn_MakeAtmo=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F Loader (Atmosphere) tabPatches_Btn_MakeEs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F ES tabPatches_Btn_MakeFs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F FS tabPatches_Lbl_Atmo=Atmosphere: tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0438 tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 -tabPatches_ServiceWindowMessage=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. +tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. +tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0430 \u00ABLoader\u00BB \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0443\u0442\u044C \u043A Atmosphere. diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index c4503a5..1a2fd7f 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -80,12 +80,13 @@ tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM tabPatches_Btn_asZipFile=\u044F\u043A ZIP tabPatches_Btn_fromFolder=\u0417 \u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0440\u0456\u0457 tabPatches_Btn_MakeAll=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0432\u0441\u0456 -tabPatches_Btn_MakeAtmo=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F Atmo +tabPatches_Btn_MakeAtmo=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F Loader (Atmosphere) tabPatches_Btn_MakeEs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F ES tabPatches_Btn_MakeFs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F FS tabPatches_Lbl_Atmo=Atmosphere: tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0456 tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 -tabPatches_ServiceWindowMessage=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. +tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. +tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0456\u0457 "Loader"-\u043F\u0430\u0442\u0447\u0443 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u0448\u043B\u044F\u0445 \u0434\u043E Atmosphere. From c34531a92fe49d5be881168364c6753a936d6927 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 6 Feb 2023 16:16:03 +0300 Subject: [PATCH 081/134] Fix dependencies resolution --- pom.xml | 12 ++++++++++-- src/main/java/nsusbloader/AppPreferences.java | 2 -- .../Controllers/NSLMainController.java | 2 +- .../nsusbloader/cli/CommandLineInterface.java | 18 +----------------- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index daebc2b..24bf149 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,14 @@ </developer> </developers> + <repositories> + <repository> + <id>redrise</id> + <name>redrise.ru repository</name> + <url>http://repo.redrise.ru/releases</url> + </repository> + </repositories> + <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format> @@ -199,11 +207,11 @@ <version>5.9.0</version> <scope>test</scope> </dependency> - <!-- picked from local repo --> + <!-- redrise repository --> <dependency> <groupId>ru.redrise</groupId> <artifactId>libKonogonka</artifactId> - <version>0.1-SNAPSHOT</version> + <version>0.1</version> <scope>compile</scope> </dependency> </dependencies> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index d774ae0..58fae45 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -88,8 +88,6 @@ public class AppPreferences { public String getHostIp(){ return preferences.get("HOSTIP", "0.0.0.0").replaceAll("(\\s)|(\t)", "");} // who the hell said 'paranoid'? public void setHostIp(String ip){preferences.put("HOSTIP", ip);} - public void give(){preferences.putBoolean("man", true);} - public boolean take(){return preferences.getBoolean("man", false);} public String getHostPort(){ String value = preferences.get("HOSTPORT", "6042"); if (!value.matches("^[0-9]{1,5}$")) diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index cc051b3..cfff368 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -73,7 +73,7 @@ public class NSLMainController implements Initializable { if (AppPreferences.getInstance().getAutoCheckUpdates()){ checkForUpdates(); } - if (! AppPreferences.getInstance().take()) mainTabPane.getTabs().remove(3); + openLastOpenedTab(); } private void checkForUpdates(){ diff --git a/src/main/java/nsusbloader/cli/CommandLineInterface.java b/src/main/java/nsusbloader/cli/CommandLineInterface.java index 2ced0f1..d5965c8 100644 --- a/src/main/java/nsusbloader/cli/CommandLineInterface.java +++ b/src/main/java/nsusbloader/cli/CommandLineInterface.java @@ -18,12 +18,9 @@ */ package nsusbloader.cli; -import nsusbloader.AppPreferences; -import nsusbloader.Main; import nsusbloader.NSLMain; import org.apache.commons.cli.*; -import java.time.LocalTime; import java.util.prefs.Preferences; public class CommandLineInterface { @@ -35,12 +32,9 @@ public class CommandLineInterface { } final Options cliOptions = createCliOptions(); - final Options cliOptionsToBeExact = - createCliOptions().addOption(Option.builder().longOpt("gimmegimmegimme").hasArg(false).build()); - CommandLineParser cliParser = new DefaultParser(); try{ - CommandLine cli = cliParser.parse(cliOptionsToBeExact, args); + CommandLine cli = cliParser.parse(cliOptions, args); if (cli.hasOption('v') || cli.hasOption("version")){ handleVersion(); return; @@ -80,16 +74,6 @@ public class CommandLineInterface { return; } */ - if (cli.hasOption("gimmegimmegimme")){ - if (LocalTime.now().isBefore(LocalTime.parse("09:00:00"))){ - AppPreferences.getInstance().give(); - AppPreferences.getInstance().setLastOpenedTab("PatchesTabHolder"); - System.out.println("=)"); - Main.main(new String[]{}); - return; - } - throw new ParseException("Unhandled LocalTime() exception;"); - } if (cli.hasOption("s") || cli.hasOption("split")){ final String[] arguments = cli.getOptionValues("split"); new SplitCli(arguments); From ffa9c6903f433a2a89b2e6a9bd23a9947cfaed51 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 6 Feb 2023 16:21:11 +0300 Subject: [PATCH 082/134] https for secure! --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24bf149..09d9b8e 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ <repository> <id>redrise</id> <name>redrise.ru repository</name> - <url>http://repo.redrise.ru/releases</url> + <url>https://repo.redrise.ru/releases</url> </repository> </repositories> From c39a5f4c5ba9c6c05875da83c5a84b65b725d268 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 7 Feb 2023 00:05:13 +0300 Subject: [PATCH 083/134] Correct Java 8 related things --- .../java/nsusbloader/Utilities/patches/es/EsPatch.java | 3 ++- .../java/nsusbloader/Utilities/patches/fs/FsPatch.java | 10 +++++++++- .../Utilities/patches/loader/LoaderPatch.java | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java index 71d53bd..0a6b426 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/EsPatch.java @@ -32,6 +32,7 @@ import nsusbloader.Utilities.patches.es.finders.HeuristicEsWizard; import java.io.BufferedOutputStream; import java.io.File; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -118,7 +119,7 @@ public class EsPatch { handyEsPatch.put(FOOTER); byte[] esPatch = new byte[handyEsPatch.position()]; - handyEsPatch.rewind(); + ((Buffer) handyEsPatch).rewind(); handyEsPatch.get(esPatch); try (BufferedOutputStream stream = new BufferedOutputStream( diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java index 7849f50..0e49699 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -35,6 +35,7 @@ import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.fs.finders.HeuristicFsWizard; import java.io.*; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -87,6 +88,13 @@ public class FsPatch { logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); } private KIP1Provider getKIP1Provider() throws Exception{ + System.out.println("ncaProvider "+ncaProvider); + System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(0)); + System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(1)); + System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(2)); + System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(3)); + + RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs(); FileSystemEntry package2FsEntry = romFsProvider.getRootEntry().getContent() @@ -175,7 +183,7 @@ public class FsPatch { handyFsPatch.put(FOOTER); byte[] fsPatch = new byte[handyFsPatch.position()]; - handyFsPatch.rewind(); + ((Buffer) handyFsPatch).rewind(); handyFsPatch.get(fsPatch); try (BufferedOutputStream stream = new BufferedOutputStream( diff --git a/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java index ad25afd..bd9b03b 100644 --- a/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/loader/LoaderPatch.java @@ -30,6 +30,7 @@ import nsusbloader.Utilities.patches.SimplyFind; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -110,7 +111,7 @@ public class LoaderPatch { private void writeFile() throws Exception{ String patchFileLocation = saveToLocation + File.separator + - "atmosphere" + File.separator + "kip_patches" + File.separator + "fs_patches" + File.separator + patchName; + "atmosphere" + File.separator + "kip_patches" + File.separator + "loader_patches" + File.separator + patchName; ByteBuffer handyFsPatch = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN); handyFsPatch.put(HEADER); @@ -118,7 +119,7 @@ public class LoaderPatch { handyFsPatch.put(FOOTER); byte[] fsPatch = new byte[handyFsPatch.position()]; - handyFsPatch.rewind(); + ((Buffer) handyFsPatch).rewind(); handyFsPatch.get(fsPatch); try (BufferedOutputStream stream = new BufferedOutputStream( From c408f70b79c25e36e4e8b0706cfef6f3ee8cc698 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 01:15:30 +0300 Subject: [PATCH 084/134] Switch to Java 11. Add win installer --- .drone.yml | 32 +- .make_legacy | 1 - misc/windows/HOWTO_JRE.md | 8 + misc/windows/NSIS/installer.nsi | 158 ++++ misc/windows/NSIS/installer_logo.ico | Bin 0 -> 127648 bytes misc/windows/NSIS/leftbar.bmp | Bin 0 -> 154542 bytes misc/windows/NSIS/leftbar_uninstall.bmp | Bin 0 -> 154542 bytes misc/windows/NSIS/license.txt | 1070 +++++++++++++++++++++++ misc/windows/NSIS/logo.ico | Bin 0 -> 107911 bytes misc/windows/NSIS/uninstaller_logo.ico | Bin 0 -> 117081 bytes misc/windows/update_version.sh | 12 + pom.xml | 45 +- 12 files changed, 1301 insertions(+), 25 deletions(-) create mode 100644 misc/windows/HOWTO_JRE.md create mode 100644 misc/windows/NSIS/installer.nsi create mode 100644 misc/windows/NSIS/installer_logo.ico create mode 100644 misc/windows/NSIS/leftbar.bmp create mode 100644 misc/windows/NSIS/leftbar_uninstall.bmp create mode 100644 misc/windows/NSIS/license.txt create mode 100644 misc/windows/NSIS/logo.ico create mode 100644 misc/windows/NSIS/uninstaller_logo.ico create mode 100755 misc/windows/update_version.sh diff --git a/.drone.yml b/.drone.yml index bc26d63..abf06cc 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,24 +17,49 @@ steps: commands: - mkdir -p /builds/ns-usbloader - cp target/ns-usbloader-*jar /builds/ns-usbloader/ - - cp target/NS-USBloader-*exe /builds/ns-usbloader/ volumes: - name: builds path: /builds + - name: make-win-installer + image: wheatstalk/makensis:3 + commands: + - cp target/NS-USBloader.exe misc/windows/NSIS/ + - misc/windows/update_version.sh + - /entrypoint.sh misc/windows/NSIS/installer.nsi + - cp Installer-*.exe /builds/ns-usbloader/ + - rm misc/windows/NSIS/NS-USBloader.exe + volumes: + - name: builds + path: /builds + - name: jdk + path: /misc/windows/NSIS/jdk + - name: emerge-legacy-artifact image: maven:3-openjdk-17 commands: - . ./.make_legacy - mvn -B -DskipTests clean package - cp target/ns-usbloader-*jar /builds/ns-usbloader/ - - cp target/NS-USBloader-*exe /builds/ns-usbloader/ volumes: - name: m2 path: /root/.m2 - name: builds path: /builds + - name: make-legacy-win-installer + image: wheatstalk/makensis:3 + commands: + - cp target/NS-USBloader.exe misc/windows/NSIS/ + - misc/windows/update_version.sh legacy + - /entrypoint.sh misc/windows/NSIS/installer.nsi + - cp Installer-*.exe /builds/ns-usbloader/ + volumes: + - name: builds + path: /builds + - name: jdk + path: /misc/windows/NSIS/jdk + volumes: - name: m2 host: @@ -42,3 +67,6 @@ volumes: - name: builds host: path: /home/www/builds + - name: jdk + host: + path: /home/docker/drone/files/assembly/openjdk-19.0.2 \ No newline at end of file diff --git a/.make_legacy b/.make_legacy index e907c26..890cd0c 100644 --- a/.make_legacy +++ b/.make_legacy @@ -1,4 +1,3 @@ sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n\s*<artifactId>usb4java<\/artifactId>\s*<version>1.3.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/g' pom.xml sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}<\/finalName>/g' pom.xml -sed -z -i -e 's/<outfile>target\/NS-USBloader-${project.version}-${maven.build.timestamp}.exe<\/outfile>/<outfile>target\/NS-USBloader-${project.version}-legacy-${maven.build.timestamp}.exe<\/outfile>/g' pom.xml sed -z -i -e 's/<jar>target\/${project.artifactId}-${project.version}-${maven.build.timestamp}.jar<\/jar>/<jar>target\/${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}.jar<\/jar>/g' pom.xml diff --git a/misc/windows/HOWTO_JRE.md b/misc/windows/HOWTO_JRE.md new file mode 100644 index 0000000..5916b6e --- /dev/null +++ b/misc/windows/HOWTO_JRE.md @@ -0,0 +1,8 @@ +#### How to prepare JRE from JDK to bundle it with application + +1. Run `java --list-modules` +2. Update resulting list s/@.*\n/\,/g +3. Run `jlink --no-header-files --no-man-pages --compress=2 --add-modules !!!_PASTE_RESULT_HERE_!!! --output jre` +4. JRE created at folder 'jre + +jlink --no-header-files --no-man-pages --compress=2 --add-modules $($(java --list-modules) -join "," -replace "@[0-9].*") --output jre-11' \ No newline at end of file diff --git a/misc/windows/NSIS/installer.nsi b/misc/windows/NSIS/installer.nsi new file mode 100644 index 0000000..c5007de --- /dev/null +++ b/misc/windows/NSIS/installer.nsi @@ -0,0 +1,158 @@ +;Include Modern UI + !include "MUI.nsh" + Unicode true +;Name and file + + !define APPNAME "NS-USBloader" + !define COMPANYNAME "Dmitry Isaenko" + !define VERSIONMAJOR 0 + !define VERSIONMINOR 0 + !define VERSIONBUILD 0 + + Name "NS-USBloader" + OutFile "Installer.exe" + +;Default installation folder + InstallDir "$PROGRAMFILES\${APPNAME}" + +;Get installation folder from registry if available + InstallDirRegKey HKCU "Software\${APPNAME}" "" + +;Request application privileges for Windows Vista + RequestExecutionLevel admin + + !define MUI_ICON installer_logo.ico + !define MUI_UNICON uninstaller_logo.ico +; !define MUI_FINISHPAGE_NOAUTOCLOSE + + !define MUI_WELCOMEFINISHPAGE_BITMAP "leftbar.bmp" + !define MUI_UNWELCOMEFINISHPAGE_BITMAP "leftbar_uninstall.bmp" + + !define MUI_FINISHPAGE_LINK "NS-USBloader at GitHub" + !define MUI_FINISHPAGE_LINK_LOCATION https://github.com/developersu/NS-USBloader/ + + !define MUI_FINISHPAGE_RUN "$INSTDIR\NS-USBloader.exe" + !define MUI_FINISHPAGE_SHOWREADME + !define MUI_FINISHPAGE_SHOWREADME_TEXT $(l10n_CreateShortcut) + !define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortCut + !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING +;-------------------------------- +;Language Selection Dialog Settings + + ;Remember the installer language + !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" + !define MUI_LANGDLL_REGISTRY_KEY "Software\${APPNAME}" + !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" + +;-------------------------------- +;Pages +;!define MUI_HEADERIMAGE +;!define MUI_HEADERIMAGE_RIGHTi +;!define MUI_HEADERIMAGE_BITMAP "install_header.bmp" +;!define MUI_HEADERIMAGE_UNBITMAP "install_header.bmp" + + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_LICENSE "license.txt" + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + !insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +;Languages + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "SpanishInternational" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Vietnamese" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "Swedish" + +;Language strings + LangString l10n_CreateShortcut ${LANG_ENGLISH} "Create Desktop Shortcut" + LangString l10n_CreateShortcut ${LANG_RUSSIAN} "Создать ярлык на Рабочем столе" + +;-------------------------------- +Section "NS-USBloader" Install + + SetOutPath "$INSTDIR" + file /r jdk + file NS-USBloader.exe + file logo.ico + +; Registry information for add/remove programs + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$\"$INSTDIR\logo.ico$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "$\"${COMPANYNAME}$\"" + +; Start Menu + CreateDirectory "$SMPROGRAMS\${APPNAME}" + CreateShortCut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\NS-USBloader.exe" "" "$INSTDIR\logo.ico" + CreateShortCut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + + ;Store installation folder + WriteRegStr HKCU "Software\${APPNAME}" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd +;-------------------------------- +;Installer Functions + +Function .onInit +; set mandatory installation rule to section + SectionSetFlags ${Install} 17 +FunctionEnd + +Function un.onInit + !insertmacro MUI_UNGETLANGUAGE +FunctionEnd + + +Function CreateDesktopShortCut + CreateShortcut "$DESKTOP\NS-USBloader.lnk" "$INSTDIR\NS-USBloader.exe" +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + +; Start Menu + Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" + Delete "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" + Delete "$DESKTOP\NS-USBloader.lnk" + rmDir "$SMPROGRAMS\${APPNAME}" + +;Delete installed files + RMDir /r "$INSTDIR\jdk\*" + Delete "$INSTDIR\NS-USBloader.exe" + Delete "$INSTDIR\logo.ico" + Delete "$SMPROGRAMS\Uninstall.exe" + + RMDir "$INSTDIR" + + DeleteRegKey /ifempty HKCU "Software\${APPNAME}" +; Cleanup records stored for uninstaller from the registry + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" + +SectionEnd +;-------------------------------- +;Uninstaller Functions diff --git a/misc/windows/NSIS/installer_logo.ico b/misc/windows/NSIS/installer_logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..b64874c9e1a748455793a869c1397a89086686eb GIT binary patch literal 127648 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 b1hIA#cG5Gj`39&y)&3%Y?Zj*|#&7=*0FW}? literal 0 HcmV?d00001 diff --git a/misc/windows/NSIS/leftbar.bmp b/misc/windows/NSIS/leftbar.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6e5fe08b8fc78278427c8e5239588a2706353e40 GIT binary patch literal 154542 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 W*hauM0=5ybjeu<gY$IUS5%@n1x^esf literal 0 HcmV?d00001 diff --git a/misc/windows/NSIS/leftbar_uninstall.bmp b/misc/windows/NSIS/leftbar_uninstall.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5ac42bf6e138865d2ea9091b262e09a935315740 GIT binary patch literal 154542 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 B>#P6( literal 0 HcmV?d00001 diff --git a/misc/windows/NSIS/license.txt b/misc/windows/NSIS/license.txt new file mode 100644 index 0000000..e11bdec --- /dev/null +++ b/misc/windows/NSIS/license.txt @@ -0,0 +1,1070 @@ +1. OpenJDK distibuted under GNU General Public License, version 2, +with the Classpath Exception + +2. NS-USBloader distibuted under GNU General Public License version 3, +or any later version. + *** +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) <year> <name of author> + + This program 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 2 of the License, or (at your option) + any later version. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. + + + + +ADDITIONAL INFORMATION ABOUT LICENSING + +Certain files distributed by Oracle America, Inc. and/or its affiliates are +subject to the following clarification and special exception to the GPLv2, +based on the GNU Project exception for its Classpath libraries, known as the +GNU Classpath Exception. + +Note that Oracle includes multiple, independent programs in this software +package. Some of those programs are provided under licenses deemed +incompatible with the GPLv2 by the Free Software Foundation and others. +For example, the package includes programs licensed under the Apache +License, Version 2.0 and may include FreeType. Such programs are licensed +to you under their original licenses. + +Oracle facilitates your further distribution of this package by adding the +Classpath Exception to the necessary parts of its GPLv2 code, which permits +you to use that code in combination with other independent modules not +licensed under the GPLv2. However, note that this would not permit you to +commingle code under an incompatible license with Oracle's GPLv2 licensed +code by, for example, cutting and pasting such code into a file also +containing Oracle's GPLv2 licensed code and then distributing the result. + +Additionally, if you were to remove the Classpath Exception from any of the +files to which it applies and distribute the result, you would likely be +required to license some or all of the other code in that distribution under +the GPLv2 as well, and since the GPLv2 is incompatible with the license terms +of some items included in the distribution by Oracle, removing the Classpath +Exception could therefore effectively compromise your ability to further +distribute the package. + +Failing to distribute notices associated with some files may also create +unexpected legal consequences. + +Proceed with caution and we recommend that you obtain the advice of a lawyer +skilled in open source matters before removing the Classpath Exception or +making modifications to this package which may subsequently be redistributed +and/or involve the use of third party software. + +============================================================================ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program 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. + + This program 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 this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/misc/windows/NSIS/logo.ico b/misc/windows/NSIS/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..9bc9867d45860bd4555cf0d18895d6e1e3a731f9 GIT binary patch literal 107911 zcmeHQ30zFy7k@2?NY)Ut@5)};O0rj$$XeND%N8LNBJ`u5H4>4ZEFpwQn<bS<DI%p+ zi}rn+nfE{E<!PFxX{M%>fA90TbLXx1ocq1Yx#ymHE<uP8jR`zNh;Bs3ZUnI!{+pSV zocEIuAxz<#s_L(EHG;U&PlOmUq~!c&Z-V%+4&H=k@co<81Y!KI2q6tIo*Rf0gxpmT z;^-``DJ`0HYDN%5i)mBU=ipro5}5Rotv4Y}xu|LC6Xxy=OO4odzU?P>GBf{{-fFqS znmgJ~+M;-bH8+N^UoPEf<n$4J402c@J9CfC&pI+R|HkqV*^%-==03e=Z#gPCpojfr z`)<dgTK{wSQw#OH?Rgi{?v3_T>2%<&(ad~}d3N`HrYG+@)%U99?ew)pdt7h31}HaE z30m~PY;V(V76(UnzS~k{x7awdd#d976m=aGmYAk3VU6Fb?r?FF=*IoSbf0XV|HVjT z<05I<f0`YikR<oOcf751JA-b8`f5j{d_9T4uo(B({myi<h#7Ik$!JG+x05O&_ZQ3$ zido{-qj<oekzPj1YHdZcMecVS)xp1L)ABrJ)ssnE&HMF=7mXU{-tw)7R6$FxQ_g$3 zZ&k<_$#{~cH9Mx${l4};Wc4LZZ<QeyEgrP>o_KNlM+1gOZ0@dpPc%x`UCuJaVt*?K z*+J%C-hU%@>R2SEGn%Eynk^4L-cD_gwXw+ksm7DO%@nz&p*Tk(X!Iu0r4u#`3ES88 z-^lDkzAMfpn=Co=w1fX5GoyXovo1du$+*0Cu#LsZ<h|YZytp)jxZBRMwekMT2Cfs- zj=bAGmN=K#&oX80m3_-yhpHV(F&adS4{5b7HNp5qSR1thjpB3>gS%6|w49zG^U^a+ zj(~SXWNZx0r0tnuGK6HT;z1h&*PY$feOBVVg#AUwlU^Q@?qaP=4BDXF`Tax(%l2wd zdu9{ooyJ^KQy_+&nSaF0cM$PNM?Ix)P|6zDrQOX<hDn;`Uz)K$$icFm+Ed3=V&}~C zgP&b4%FmUE_Q(;r^7zn0IgtSxG6T#v>lYCV4D`)4VtcDEy)I?uVx)Ymzk_63wVi#p z6KDGL5p&m&>1Upyl`L{!HGhJmqozz>^9-G2k)>Zf8&9WBK3|4g#v<}Vbk9q_G%;yx z_G8KZv1tR<ClnYGZr3d*=o`BB>#lCN$9Vev<;8QaSR~nRz4iLQFv$TE{Yd?E)q^cs zoJe|Wp7GsSOm)kNqyuhqTOIh>%)9B?V=q>1R()Wv|0s8(;_GF*PRE~~ywh1{xRZ|a zt5Ff6-NPS`fo#3rE5+-5y3Cv*4%3HTy%_Uoz@*uS#jco4zOqJrsC{de<Bc9yqaSyA zAG>(O@F#1RhZ*^sCq9i3S^Djo+1B7j&wAfps%GpRr`Nvt*?o;^3tp_9=O<m*r?K%p z<DCYl9cIh7Gq>8CTs&+3Mfo>_Ukz+$*-|nykT4!i=I0)NInHG9ov}S$n;X70+IGmm zB|_xNr=bp(NjK+6vfP}!!w(LUlFwxA+-sllWncT?e3zTeE*QFJ9QgV1kiDh+Fx|sz zPKjh(JwY7QlnHKi@L2T0i4&KEjLVPoRY|ii@LAUV@EO0as)1*|8?{-LAAazY^NplA zV^20W>tGTaJH^yWUi+Pj(wWnnm~$Jqec^U+@Z|Z^B*Z5rMjh<$kuzuRq(;594=4{A z)sfI~yO^XF=IE-H`T0frh@K7ur;VREB$lw;a;dlYfh#HZo_9ONSlUj1&(P>=Cx+>s zN<JlWWsrVjudBU}SxepTx6Sxuw$#x9J9l64zTd2=h>GP2*(q^nw0DsESIyp$?tbM! zZn{m%Xqy4ShgLe8i0h3Dw09mOY5V;4&V&nI<0m-`Y?a#j_?+FvmxGrcwvg&QdY@a; z&n1tun-lsGuXMa#i47{6JjQL{2P3Ih&c%z>59S`4Ej|67hfSbJlSf61zFso6pT5`R z`G%IejIwN>`S0%crYLH3apEv<r{aRF!SV}qth%0AVyyc!v^ekj&X2u<SGk7j1vgsh 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 L-iIclTF3tYINnf$ literal 0 HcmV?d00001 diff --git a/misc/windows/NSIS/uninstaller_logo.ico b/misc/windows/NSIS/uninstaller_logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..49688a807514148a55a51736abb4ef984e21e24c GIT binary patch literal 117081 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 IqFmSi0-7&sHvj+t literal 0 HcmV?d00001 diff --git a/misc/windows/update_version.sh b/misc/windows/update_version.sh new file mode 100755 index 0000000..1c72ef2 --- /dev/null +++ b/misc/windows/update_version.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +VERSIONMAJOR=`grep '<version>' pom.xml | head -1 | sed -e 's/^.*<version>//g' -e 's/\..*$//g'` +VERSIONMINOR=`grep '<version>' pom.xml | head -1 | sed -E 's/^.*<version>[0-9]+?\.//g' | sed -E -e 's/(\..*|-SNAPSHOT|)<\/version>.*$//g'` +sed -z -i -e "s/!define\ VERSIONMAJOR\ [0-9]/!define\ VERSIONMAJOR $VERSIONMAJOR\ /" misc/windows/NSIS/installer.nsi +sed -z -i -e "s/!define\ VERSIONMINOR\ [0-9]/!define\ VERSIONMINOR $VERSIONMINOR\ /" misc/windows/NSIS/installer.nsi +if [ $# -eq 0 ] + then + sed -z -i -e "s/OutFile\ \"Installer.exe\"/OutFile\ \"Installer-$VERSIONMAJOR.$VERSIONMINOR.exe\"\ /" misc/windows/NSIS/installer.nsi + else + sed -z -i -e "s/OutFile\ \"Installer.exe\"/OutFile\ \"Installer-legacy-$VERSIONMAJOR.$VERSIONMINOR.exe\"\ /" misc/windows/NSIS/installer.nsi +fi diff --git a/pom.xml b/pom.xml index 09d9b8e..2e49e54 100644 --- a/pom.xml +++ b/pom.xml @@ -8,13 +8,11 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>7.0</version> + <version>7.0</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers. May have end on '-SNAPSHOT' --> <url>https://redrise.ru</url> - <description> - NS multi tool - </description> - <inceptionYear>2019.0.2.1</inceptionYear> + <description>NS multi-tool</description> + <inceptionYear>2019</inceptionYear> <organization> <name>Dmitry Isaenko</name> <url>https://developersu.blogspot.com/</url> @@ -243,10 +241,10 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.1</version> + <version>3.10.1</version> <configuration> - <source>1.8</source> - <target>1.8</target> + <source>11</source> + <target>11</target> </configuration> </plugin> <!-- Don't generate default JAR without dependencies --> @@ -293,7 +291,7 @@ <artifactId>launch4j-maven-plugin</artifactId> <executions> <execution> - <id>l4j-NS-USBloader</id> + <id>l4j-ns-usbloader</id> <phase>package</phase> <goals> <goal>launch4j</goal> @@ -301,30 +299,33 @@ <configuration> <headerType>gui</headerType> <icon>appicon.ico</icon> - <outfile>target/NS-USBloader-${project.version}-${maven.build.timestamp}.exe</outfile> + <outfile>target/${project.name}.exe</outfile> <jar>target/${project.artifactId}-${project.version}-${maven.build.timestamp}.jar</jar> - <errTitle>NS-USBloader</errTitle> - <classPath> - <mainClass>nsusbloader.Main</mainClass> - <addDependencies>false</addDependencies> - <preCp>anything</preCp> - </classPath> + <!-- <downloadUrl>https://download.oracle.com/java/17/archive/jdk-17.0.1_windows-x64_bin.msi</downloadUrl> --> + <errTitle>Launching error</errTitle> + <!-- <dontWrapJar>true</dontWrapJar> --> <jre> - <minVersion>1.8.0</minVersion> - <path>%JAVA_HOME%;%PATH%</path> + <path>%PWD%/jdk</path> + <minVersion>11.0.0</minVersion> </jre> <versionInfo> - <fileVersion>1.0.0.0</fileVersion> + <fileVersion>${project.version}.0.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> - <fileDescription>NS multi tool</fileDescription> - <copyright>GNU General Public License v3, 2019.0.2.1 ${project.organization.name}, Russia.</copyright> - <productVersion>1.0.0.0</productVersion> + <fileDescription>NS multi-tool</fileDescription> + <copyright>GNU General Public License v3, ${project.inceptionYear} ${project.organization.name}, Russia.</copyright> + <productVersion>${project.version}.0.0</productVersion> <txtProductVersion>${project.version}</txtProductVersion> <companyName>${project.organization.name}</companyName> <productName>${project.name}</productName> <internalName>${project.name}</internalName> <originalFilename>${project.name}.exe</originalFilename> </versionInfo> + <messages> + <startupErr>Startup error</startupErr> + <jreNotFoundErr>JDK not found</jreNotFoundErr> + <jreVersionErr>JDK Version mismatch</jreVersionErr> + <launcherErr>Launcher Error</launcherErr> + </messages> </configuration> </execution> </executions> From fc02a8af6b898e80489f1cff3f2993ced36fd795 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 01:32:45 +0300 Subject: [PATCH 085/134] Fix CI --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index abf06cc..e222e8b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,7 +26,7 @@ steps: commands: - cp target/NS-USBloader.exe misc/windows/NSIS/ - misc/windows/update_version.sh - - /entrypoint.sh misc/windows/NSIS/installer.nsi + - makensis -V4 misc/windows/NSIS/installer.nsi - cp Installer-*.exe /builds/ns-usbloader/ - rm misc/windows/NSIS/NS-USBloader.exe volumes: @@ -52,7 +52,7 @@ steps: commands: - cp target/NS-USBloader.exe misc/windows/NSIS/ - misc/windows/update_version.sh legacy - - /entrypoint.sh misc/windows/NSIS/installer.nsi + - makensis -V4 misc/windows/NSIS/installer.nsi - cp Installer-*.exe /builds/ns-usbloader/ volumes: - name: builds From 2356bfe0e9651afc361301bb14d49edc9941a29e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 01:50:27 +0300 Subject: [PATCH 086/134] Fix CI+1 --- .drone.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index e222e8b..541c304 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,9 +26,11 @@ steps: commands: - cp target/NS-USBloader.exe misc/windows/NSIS/ - misc/windows/update_version.sh - - makensis -V4 misc/windows/NSIS/installer.nsi + - cd misc/windows/NSIS + - makensis -V4 ./installer.nsi - cp Installer-*.exe /builds/ns-usbloader/ - - rm misc/windows/NSIS/NS-USBloader.exe + - rm ./NS-USBloader.exe + - cd ../../../ volumes: - name: builds path: /builds @@ -52,7 +54,8 @@ steps: commands: - cp target/NS-USBloader.exe misc/windows/NSIS/ - misc/windows/update_version.sh legacy - - makensis -V4 misc/windows/NSIS/installer.nsi + - cd misc/windows/NSIS + - makensis -V4 ./installer.nsi - cp Installer-*.exe /builds/ns-usbloader/ volumes: - name: builds From f82275e82d530b3b1a55311882ddc0d70137ce91 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 01:58:27 +0300 Subject: [PATCH 087/134] Fix CI+2 --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 541c304..82244f6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -35,7 +35,7 @@ steps: - name: builds path: /builds - name: jdk - path: /misc/windows/NSIS/jdk + path: /drone/src/misc/windows/NSIS/jdk - name: emerge-legacy-artifact image: maven:3-openjdk-17 @@ -61,7 +61,7 @@ steps: - name: builds path: /builds - name: jdk - path: /misc/windows/NSIS/jdk + path: /drone/src/misc/windows/NSIS/jdk volumes: - name: m2 From 7f01805cd53bb896f7f204dec8d8b8fdab77aaf7 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 02:21:55 +0300 Subject: [PATCH 088/134] Fix CI+3: Append timestamps to installer artifact --- misc/windows/update_version.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/windows/update_version.sh b/misc/windows/update_version.sh index 1c72ef2..0ffe6d4 100755 --- a/misc/windows/update_version.sh +++ b/misc/windows/update_version.sh @@ -1,12 +1,13 @@ #!/bin/bash +TIMESTAMP=`date +%Y%m%d.%H%M%S` VERSIONMAJOR=`grep '<version>' pom.xml | head -1 | sed -e 's/^.*<version>//g' -e 's/\..*$//g'` VERSIONMINOR=`grep '<version>' pom.xml | head -1 | sed -E 's/^.*<version>[0-9]+?\.//g' | sed -E -e 's/(\..*|-SNAPSHOT|)<\/version>.*$//g'` sed -z -i -e "s/!define\ VERSIONMAJOR\ [0-9]/!define\ VERSIONMAJOR $VERSIONMAJOR\ /" misc/windows/NSIS/installer.nsi sed -z -i -e "s/!define\ VERSIONMINOR\ [0-9]/!define\ VERSIONMINOR $VERSIONMINOR\ /" misc/windows/NSIS/installer.nsi if [ $# -eq 0 ] then - sed -z -i -e "s/OutFile\ \"Installer.exe\"/OutFile\ \"Installer-$VERSIONMAJOR.$VERSIONMINOR.exe\"\ /" misc/windows/NSIS/installer.nsi + sed -z -i -e "s/OutFile\ \"Installer.exe\"/OutFile\ \"Installer-$VERSIONMAJOR.$VERSIONMINOR-$TIMESTAMP.exe\"\ /" misc/windows/NSIS/installer.nsi else - sed -z -i -e "s/OutFile\ \"Installer.exe\"/OutFile\ \"Installer-legacy-$VERSIONMAJOR.$VERSIONMINOR.exe\"\ /" misc/windows/NSIS/installer.nsi + sed -z -i -e "s/OutFile\ \"Installer-$VERSIONMAJOR.$VERSIONMINOR-[0-9]*\.[0-9]*.exe\"/OutFile\ \"Installer-legacy-$VERSIONMAJOR.$VERSIONMINOR-$TIMESTAMP.exe\"\ /" misc/windows/NSIS/installer.nsi fi From 38f495ebc1f50fc013f6999e5378da5409e2fb6d Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Feb 2023 17:43:38 +0300 Subject: [PATCH 089/134] Correct pom.xml --- pom.xml | 93 ++++++++++++++++----------------------------------------- 1 file changed, 26 insertions(+), 67 deletions(-) diff --git a/pom.xml b/pom.xml index 2e49e54..aa26717 100644 --- a/pom.xml +++ b/pom.xml @@ -49,13 +49,13 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format> + <javafx.version>19.0.2.1</javafx.version> </properties> <issueManagement> <system>GitHub</system> <url>https://github.com/developer_su/${project.artifactId}/issues</url> </issueManagement> - <!-- openJFX Linux --> <dependencies> <!-- https://mvnrepository.com/artifact/commons-cli/commons-cli --> <dependency> @@ -64,31 +64,18 @@ <version>1.5.0</version> <scope>compile</scope> </dependency> + <!-- openJFX Linux --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19.0.2.1</version> - <classifier>linux</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-media</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19.0.2.1</version> - <classifier>linux</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-graphics</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>linux</classifier> <scope>compile</scope> </dependency> @@ -96,28 +83,14 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19.0.2.1</version> - <classifier>win</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-media</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>win</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19.0.2.1</version> - <classifier>win</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-graphics</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>win</classifier> <scope>compile</scope> </dependency> @@ -125,57 +98,29 @@ <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19.0.2.1</version> - <classifier>mac</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-media</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>mac</classifier> <scope>compile</scope> </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-graphics</artifactId> - <version>19.0.2.1</version> - <classifier>mac</classifier> - <scope>compile</scope> - </dependency> - + <!-- openJFX MAC aarch64 --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> - <version>19.0.2.1</version> - <classifier>mac-aarch64</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-media</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> - <version>19.0.2.1</version> - <classifier>mac-aarch64</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-graphics</artifactId> - <version>19.0.2.1</version> + <version>${javafx.version}</version> <classifier>mac-aarch64</classifier> <scope>compile</scope> </dependency> @@ -227,6 +172,15 @@ </resources> <plugins> + <!--OpenJFX for Java9+ --> + <plugin> + <groupId>org.openjfx</groupId> + <artifactId>javafx-maven-plugin</artifactId> + <version>0.0.8</version> + <configuration> + <mainClass>nsusbloader.NSLMain</mainClass> + </configuration> + </plugin> <!-- Junit5 --> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -251,7 +205,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>2.4</version> + <version>3.1.2</version> + <configuration> + <manifestEntries> + <Automatic-Module-Name>nsusbloader</Automatic-Module-Name> + </manifestEntries> + </configuration> <executions> <execution> <id>default-jar</id> From 29f29b7d316091bd29122668f66d93ae1629e5de Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 9 Feb 2023 15:25:13 +0300 Subject: [PATCH 090/134] Minor fixes --- README.md | 13 ++---- src/main/java/nsusbloader/AppPreferences.java | 5 ++ .../Controllers/NSLMainController.java | 8 +++- .../Controllers/PatchesController.java | 39 +++++++++++++++- .../WindowsDrivers/DriversInstall.java | 3 -- .../patches/es/finders/HeuristicEs1.java | 3 +- .../patches/es/finders/HeuristicEs2.java | 3 +- .../patches/es/finders/HeuristicEs3.java | 5 +- .../Utilities/patches/fs/FsIniMaker.java | 4 +- .../Utilities/patches/fs/FsPatch.java | 7 --- .../patches/fs/finders/HeuristicFs1.java | 3 +- .../patches/fs/finders/HeuristicFs2.java | 3 +- .../nsusbloader/cli/CommandLineInterface.java | 12 +++++ .../java/nsusbloader/cli/ExperimentalCli.java | 46 +++++++++++++++++++ .../java/nsusbloader/cli/GoldLeafCli.java | 10 ++-- src/main/java/nsusbloader/cli/NxdtCli.java | 2 +- .../java/nsusbloader/cli/TinfoilUsbCli.java | 2 +- src/main/resources/NSLMain.fxml | 2 +- src/main/resources/PatchesTab.fxml | 2 +- src/main/resources/locale_ja_JP.properties | 1 + src/main/resources/locale_ru_RU.properties | 2 +- src/main/resources/locale_uk_UA.properties | 2 +- src/main/resources/res/app_dark.css | 2 +- src/main/resources/res/app_light.css | 2 +- 24 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 src/main/java/nsusbloader/cli/ExperimentalCli.java diff --git a/README.md b/README.md index 72bf437..1b1d899 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * [Pablo Curiel (DarkMatterCore)](https://github.com/DarkMatterCore) * [wolfposd](https://github.com/wolfposd) * [agungrbudiman](https://github.com/agungrbudiman) +* Perfect algorithms and great examples taken from mrdude project [mrdude2478/IPS_Patch_Creator](https://github.com/mrdude2478/IPS_Patch_Creator/) * French by [Stephane Meden (JackFromNice)](https://github.com/JackFromNice) * Italian by [unbranched](https://github.com/unbranched) @@ -63,9 +64,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer ### System requirements -JRE/JDK 8u60 or higher for Windows - -JDK 11 for MacOS and Linux +JDK 11 for macOS and Linux ### Supported Goldleaf versions | Goldleaf version | NS-USBloader version | @@ -130,8 +129,6 @@ Set 'Security & Privacy' settings if needed. ##### Windows: -* [Download and install Java JRE](http://java.com/download/) (8u60 or higher) -* Get this application (JAR file) and double-click on it (alternatively open 'cmd', go to place where jar located and execute via `java -jar thisAppName.jar`) * Once application opens click on 'Gear' icon. * Click 'Download and install drivers' * Install drivers @@ -235,10 +232,10 @@ Handling successful/failed installation is a purpose of the other side applicati #### What is this '-legacy' jar?! -**JAR with NO postfixes** recommended for Windows users, Linux users and MacOS users who're using Mojave or later versions. +**JAR with NO postfixes** recommended for Windows users, Linux users and macOS users who're using Mojave or later versions. -**JAR with '-legacy' postfix** is for MacOS users who're still using OS X releases before (!) Mojave. -(It also works for Linux and for Windows but sometimes it doesn't work for Windows and I don't know why). +**JAR with '-legacy' postfix** is for macOS users who're still using OS X releases before (!) Mojave. +(It also works for Linux and for Windows, but sometimes it doesn't work for Windows and I don't know why). We have this situation because of weird behaviour inside usb4java library used in this application for USB interactions. In '-legacy' it's v1.2.0 and in 'normal' it's v1.3.0 diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 58fae45..0b54289 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -144,4 +144,9 @@ public class AppPreferences { public String getPatchesSaveToLocation(){ return FilesHelper.getRealFolder(preferences.get("patches_saveto", System.getProperty("user.home"))); } public void setPatchesSaveToLocation(String value){ preferences.put("patches_saveto", value); } + + public boolean getPatchesTabInvisible(){return preferences.getBoolean("patches_tab_visible", true); } + public void setPatchesTabInvisible(boolean value){preferences.putBoolean("patches_tab_visible", value);} + public String getPatchOffset(String type, int moduleNumber, int offsetId){ return preferences.get(String.format("%s_%02x_%02x", type, moduleNumber, offsetId), ""); } + public void setPatchOffset(String fullTypeSpecifier, String offset){ preferences.put(fullTypeSpecifier, offset); } } diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index cfff368..533c450 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -70,9 +70,13 @@ public class NSLMainController implements Initializable { MediatorControl.getInstance().setController(this); - if (AppPreferences.getInstance().getAutoCheckUpdates()){ + AppPreferences preferences = AppPreferences.getInstance(); + + if (preferences.getAutoCheckUpdates()) checkForUpdates(); - } + + if (preferences.getPatchesTabInvisible()) + mainTabPane.getTabs().remove(3); openLastOpenedTab(); } diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 5aeb672..20f1244 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -24,7 +24,9 @@ import javafx.fxml.Initializable; import javafx.scene.input.DragEvent; import javafx.scene.input.TransferMode; +import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; import java.net.URL; import java.util.List; import java.util.ResourceBundle; @@ -128,7 +130,10 @@ public class PatchesController implements Initializable { List<File> filesDropped = event.getDragboard().getFiles(); for (File file : filesDropped){ if (file.isDirectory()) { - locationFirmwareLbl.setText(file.getAbsolutePath()); + if (file.getName().toLowerCase().contains("atmosphe")) + locationAtmosphereLbl.setText(file.getAbsolutePath()); + else + locationFirmwareLbl.setText(file.getAbsolutePath()); continue; } String fileName = file.getName().toLowerCase(); @@ -137,10 +142,42 @@ public class PatchesController implements Initializable { ! fileName.equals("dev.keys") && ! fileName.equals("title.keys"))) locationKeysLbl.setText(file.getAbsolutePath()); + else if (fileName.equals("offsets.txt")) + setOffsets(file); } event.setDropCompleted(true); event.consume(); } + + private void setOffsets(File fileWithOffsets){ + AppPreferences preferences = AppPreferences.getInstance(); + + try (BufferedReader reader = new BufferedReader(new FileReader(fileWithOffsets))) { + String fileLine; + String[] lineValues; + while ((fileLine = reader.readLine()) != null) { + lineValues = fileLine.trim().split("\\s+?=\\s+?", 2); + if (lineValues.length == 2) { + String[] pointer = lineValues[0].split("_", 3); + if (! pointer[0].equals("ES") && ! pointer[0].equals("FS")) + continue; + if (! pointer[1].matches("^([0-9A-Fa-f]{2})$")) + continue; + if (! pointer[2].matches("^([0-9A-Fa-f]{2})$")) + continue; + if (! lineValues[1].matches("^(([0-9A-Fa-f]{2})|\\.)+?$")) + continue; + preferences.setPatchOffset(lineValues[0], lineValues[1]); + + System.out.println(pointer[0]+"_"+pointer[1]+"_"+pointer[2]+" = "+lineValues[1]); + } + } + } + catch (Exception e){ + e.printStackTrace(); + } + } + @FXML private void selectFirmware(){ DirectoryChooser directoryChooser = new DirectoryChooser(); diff --git a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java index 0526134..daa483e 100644 --- a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java +++ b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java @@ -32,9 +32,6 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; import nsusbloader.AppPreferences; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.util.ResourceBundle; public class DriversInstall { diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java index 7f84c6f..7130d2e 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -19,6 +19,7 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; +import nsusbloader.AppPreferences; import nsusbloader.Utilities.patches.AHeuristic; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.SimplyFind; @@ -26,7 +27,7 @@ import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; class HeuristicEs1 extends AHeuristic { - private static final String PATTERN = "1F90013128.8052"; + private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("ES", 1, 0); private final List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java index 365c17f..6ee3ce1 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -19,6 +19,7 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; +import nsusbloader.AppPreferences; import nsusbloader.Utilities.patches.AHeuristic; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.SimplyFind; @@ -27,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicEs2 extends AHeuristic { - private static final String PATTERN = ".D2.52"; + private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("ES", 2, 0); private List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java index 2bde638..ecc8149 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -19,6 +19,7 @@ package nsusbloader.Utilities.patches.es.finders; import libKonogonka.Converter; +import nsusbloader.AppPreferences; import nsusbloader.Utilities.patches.AHeuristic; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.SimplyFind; @@ -26,8 +27,8 @@ import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; class HeuristicEs3 extends AHeuristic { - private static final String PATTERN0 = "..FF97"; - private static final String PATTERN1 = "......FF97"; // aka "E0230091..FF97"; + private static final String PATTERN0 = AppPreferences.getInstance().getPatchOffset("ES", 3, 0); + private static final String PATTERN1 = AppPreferences.getInstance().getPatchOffset("ES", 3, 1); private final List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java index 64551ef..4b4084d 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsIniMaker.java @@ -77,9 +77,9 @@ public class FsIniMaker { private void makeFwVersionInformationNotice(boolean isFat32, byte[] fwVersion){ String fwVersionFormatted = fwVersion[3]+"."+fwVersion[2]+"."+fwVersion[1]+"."+fwVersion[0]; if (isFat32) - firmwareVersionInformationNotice = "\n#FS "+fwVersionFormatted+"\n"; + firmwareVersionInformationNotice = "\n#FS (FAT)"+fwVersionFormatted+"\n"; else - firmwareVersionInformationNotice = "\n#FS "+fwVersionFormatted+"-ExFAT\n"; + firmwareVersionInformationNotice = "\n#FS (ExFAT) "+fwVersionFormatted+"\n"; } private void makeSectionDeclaration(String patchName){ diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java index 0e49699..cd2b528 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/FsPatch.java @@ -88,13 +88,6 @@ public class FsPatch { logPrinter.print(" == Debug information ==\n"+wizard.getDebug(), EMsgType.NULL); } private KIP1Provider getKIP1Provider() throws Exception{ - System.out.println("ncaProvider "+ncaProvider); - System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(0)); - System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(1)); - System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(2)); - System.out.println("CONTENT "+ncaProvider.getNCAContentProvider(3)); - - RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs(); FileSystemEntry package2FsEntry = romFsProvider.getRootEntry().getContent() diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java index db539b4..d5e6f67 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java @@ -19,6 +19,7 @@ package nsusbloader.Utilities.patches.fs.finders; import libKonogonka.Converter; +import nsusbloader.AppPreferences; import nsusbloader.Utilities.patches.AHeuristic; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.SimplyFind; @@ -27,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicFs1 extends AHeuristic { - private static final String PATTERN = "..0036....1F..71..0054..4839"; // TBZ + private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("FS", 1, 0); // TBZ private final byte[] where; private final List<Integer> findings; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java index 8047cae..bc8f5c8 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java @@ -19,6 +19,7 @@ package nsusbloader.Utilities.patches.fs.finders; import libKonogonka.Converter; +import nsusbloader.AppPreferences; import nsusbloader.Utilities.patches.AHeuristic; import nsusbloader.Utilities.patches.BinToAsmPrinter; import nsusbloader.Utilities.patches.SimplyFind; @@ -27,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicFs2 extends AHeuristic { - private static final String PATTERN = "...94081c00121f050071..0054"; // "...94"->BL "081c0012"->AND "1f050071"->CMP "..0054"->B.cond (only '54' is signature!) + private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("FS", 2, 0); private final byte[] where; private final List<Integer> findings; diff --git a/src/main/java/nsusbloader/cli/CommandLineInterface.java b/src/main/java/nsusbloader/cli/CommandLineInterface.java index d5965c8..461c5f5 100644 --- a/src/main/java/nsusbloader/cli/CommandLineInterface.java +++ b/src/main/java/nsusbloader/cli/CommandLineInterface.java @@ -67,6 +67,11 @@ public class CommandLineInterface { new GoldLeafCli(arguments); return; } + if (cli.hasOption("experimental")){ + final String[] arguments = cli.getOptionValues("experimental"); + new ExperimentalCli(arguments); + return; + } /* if (cli.hasOption("x") || cli.hasOption("nxdt")){ final String[] arguments = cli.getOptionValues("nxdt"); @@ -153,6 +158,12 @@ public class CommandLineInterface { .hasArgs() .argName("...") .build(); + final Option experimentalOption = Option.builder() + .longOpt("experimental") + .desc("Enable testing and experimental functions") + .hasArgs() + .argName("y|n") + .build(); /* nxdumptool */ /* final Option nxdtOption = Option.builder("x") @@ -183,6 +194,7 @@ public class CommandLineInterface { group.addOption(helpOption); group.addOption(tinfoilOption); group.addOption(glOption); + group.addOption(experimentalOption); //group.addOption(nxdtOption); group.addOption(splitOption); group.addOption(mergeOption); diff --git a/src/main/java/nsusbloader/cli/ExperimentalCli.java b/src/main/java/nsusbloader/cli/ExperimentalCli.java new file mode 100644 index 0000000..ebe7ee2 --- /dev/null +++ b/src/main/java/nsusbloader/cli/ExperimentalCli.java @@ -0,0 +1,46 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.cli; + +import nsusbloader.AppPreferences; + +public class ExperimentalCli { + ExperimentalCli(String[] arguments) throws IncorrectSetupException{ + if (arguments == null || arguments.length == 0) + throw new IncorrectSetupException("No arguments.\nShould be 'y' or 'n'"); + + if (arguments.length > 1) + throw new IncorrectSetupException("Too many arguments.\nShould be 'y' or 'n' only"); + + String arg = arguments[0].toLowerCase().substring(0, 1); + + if (arg.equals("y")) { + AppPreferences.getInstance().setPatchesTabInvisible(false); + System.out.println("Experimental functions enabled"); + return; + } + if (arg.equals("n")) { + AppPreferences.getInstance().setPatchesTabInvisible(true); + System.out.println("Experimental functions disabled"); + return; + } + + throw new IncorrectSetupException("Incorrect arguments.\nCould be 'y' or 'n' only"); + } +} diff --git a/src/main/java/nsusbloader/cli/GoldLeafCli.java b/src/main/java/nsusbloader/cli/GoldLeafCli.java index c9a146f..21e2610 100644 --- a/src/main/java/nsusbloader/cli/GoldLeafCli.java +++ b/src/main/java/nsusbloader/cli/GoldLeafCli.java @@ -34,7 +34,7 @@ public class GoldLeafCli { private int parseFileSince = 1; - public GoldLeafCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ + GoldLeafCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ this.arguments = arguments; checkArguments(); @@ -43,7 +43,7 @@ public class GoldLeafCli { runGoldLeafBackend(); } - public void checkArguments() throws IncorrectSetupException{ + private void checkArguments() throws IncorrectSetupException{ if (arguments == null || arguments.length == 0) { throw new IncorrectSetupException("No arguments.\n" + "Try 'ns-usbloader -g help' for more information."); @@ -83,7 +83,7 @@ public class GoldLeafCli { return builder.toString(); } - public void parseGoldLeafVersion() throws IncorrectSetupException{ + private void parseGoldLeafVersion() throws IncorrectSetupException{ String argument1 = arguments[0]; if (! argument1.startsWith("ver=")) { @@ -107,7 +107,7 @@ public class GoldLeafCli { getGlSupportedVersions()); } - public void parseFilesArguments() throws IncorrectSetupException{ + private void parseFilesArguments() throws IncorrectSetupException{ filesList = new ArrayList<>(); File file; @@ -123,7 +123,7 @@ public class GoldLeafCli { } } - public void runGoldLeafBackend() throws InterruptedException { + private void runGoldLeafBackend() throws InterruptedException { Runnable task = new UsbCommunications(filesList, "GoldLeaf"+goldLeafVersion, filterForNsp); diff --git a/src/main/java/nsusbloader/cli/NxdtCli.java b/src/main/java/nsusbloader/cli/NxdtCli.java index e914988..240e89c 100644 --- a/src/main/java/nsusbloader/cli/NxdtCli.java +++ b/src/main/java/nsusbloader/cli/NxdtCli.java @@ -27,7 +27,7 @@ public class NxdtCli { private final String[] arguments; private String saveTo; - public NxdtCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ + NxdtCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ this.arguments = arguments; parseArgument(); runBackend(); diff --git a/src/main/java/nsusbloader/cli/TinfoilUsbCli.java b/src/main/java/nsusbloader/cli/TinfoilUsbCli.java index ad751f0..07b8cd3 100644 --- a/src/main/java/nsusbloader/cli/TinfoilUsbCli.java +++ b/src/main/java/nsusbloader/cli/TinfoilUsbCli.java @@ -29,7 +29,7 @@ public class TinfoilUsbCli { private final String[] arguments; private List<File> filesList; - public TinfoilUsbCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ + TinfoilUsbCli(String[] arguments) throws InterruptedException, IncorrectSetupException{ this.arguments = arguments; checkArguments(); parseFilesArguments(); diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml index e096a0f..caf33c2 100644 --- a/src/main/resources/NSLMain.fxml +++ b/src/main/resources/NSLMain.fxml @@ -52,7 +52,7 @@ Steps to roll NXDT functionality back: <fx:include fx:id="PatchesTab" source="PatchesTab.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" VBox.vgrow="ALWAYS" /> </content> <graphic> - <SVGPath content="M 5.8828125 0.2109375 C 5.0191331 0.17810553 4.0925755 0.5807669 3.421875 1.3671875 L 0.88671875 4.3398438 C -0.18640328 5.598116 -0.18550892 7.3497981 0.890625 8.2675781 L 4.625 11.451172 L 0.76367188 14.517578 C -0.34380932 15.397272 -0.4055432 17.146472 0.62304688 18.441406 L 3.0527344 21.501953 C 4.0813235 22.796885 5.8007221 23.131646 6.9082031 22.251953 L 12.283203 17.982422 L 17.5 22.431641 C 18.57613 23.34942 20.305782 23.07468 21.378906 21.816406 L 23.914062 18.84375 C 24.987183 17.585475 24.986236 15.833793 23.910156 14.916016 L 20.164062 11.722656 L 24.001953 8.6738281 C 25.109433 7.7941344 25.171167 6.0449323 24.142578 4.75 L 21.712891 1.6914062 C 20.684303 0.39647305 18.964905 0.059759405 17.857422 0.93945312 L 12.505859 5.1914062 L 7.3007812 0.75195312 C 6.8972331 0.40778617 6.4010201 0.23063678 5.8828125 0.2109375 z M 10.304688 5.703125 C 10.467609 5.685648 10.663263 5.7499911 10.828125 5.890625 L 11.210938 6.21875 L 6.5957031 9.8847656 L 10.060547 5.8242188 C 10.120897 5.7534558 10.206934 5.7136111 10.304688 5.703125 z M 14.550781 5.7402344 C 14.6898 5.7328444 14.815451 5.7759495 14.892578 5.8730469 L 18.494141 10.408203 C 18.648395 10.602401 18.552761 10.932817 18.28125 11.148438 L 10.611328 17.238281 C 10.339882 17.453897 9.9980038 17.47154 9.84375 17.277344 L 6.2421875 12.744141 C 6.0879337 12.549944 6.1836297 12.221473 6.4550781 12.005859 L 14.123047 5.9140625 C 14.258772 5.806255 14.411762 5.7476235 14.550781 5.7402344 z M 13.505859 7.1542969 A 0.76761252 0.76761252 0 0 0 13.0625 7.3222656 A 0.76761252 0.76761252 0 0 0 12.943359 8.4023438 A 0.76761252 0.76761252 0 0 0 14.023438 8.5195312 A 0.76761252 0.76761252 0 0 0 14.140625 7.4414062 A 0.76761252 0.76761252 0 0 0 13.505859 7.1542969 z M 16.201172 10.509766 A 0.76742482 0.76742482 0 0 0 15.757812 10.677734 A 0.76742482 0.76742482 0 0 0 15.640625 11.757812 A 0.76742482 0.76742482 0 0 0 16.71875 11.875 A 0.76742482 0.76742482 0 0 0 16.837891 10.796875 A 0.76742482 0.76742482 0 0 0 16.201172 10.509766 z M 12.322266 10.855469 A 0.76742482 0.76742482 0 0 0 11.878906 11.023438 A 0.76742482 0.76742482 0 0 0 11.759766 12.101562 A 0.76742482 0.76742482 0 0 0 12.839844 12.220703 A 0.76742482 0.76742482 0 0 0 12.957031 11.140625 A 0.76742482 0.76742482 0 0 0 12.322266 10.855469 z M 8.4589844 11.199219 A 0.76742482 0.76742482 0 0 0 8.0136719 11.367188 A 0.76742482 0.76742482 0 0 0 7.8964844 12.447266 A 0.76742482 0.76742482 0 0 0 8.9746094 12.566406 A 0.76742482 0.76742482 0 0 0 9.09375 11.486328 A 0.76742482 0.76742482 0 0 0 8.4589844 11.199219 z M 18.248047 13.244141 L 14.707031 17.396484 C 14.546099 17.585183 14.205167 17.555044 13.941406 17.330078 L 13.537109 16.986328 L 18.248047 13.244141 z M 11.15625 14.570312 A 0.76742482 0.76742482 0 0 0 10.712891 14.738281 A 0.76742482 0.76742482 0 0 0 10.595703 15.816406 A 0.76742482 0.76742482 0 0 0 11.671875 15.935547 A 0.76742482 0.76742482 0 0 0 11.791016 14.855469 A 0.76742482 0.76742482 0 0 0 11.15625 14.570312 z" /> + <SVGPath content="M 5.7207031,0.20898438 C 4.9048038,0.22904941 4.0506554,0.62991967 3.421875,1.3671875 L 0.88671875,4.3398438 C -0.18640114,5.5981134 -0.18550676,7.3497999 0.890625,8.2675781 L 4.0273438,10.941406 C 6.6544469,8.8548374 9.2821295,6.7689991 11.910156,4.6835938 L 7.3007812,0.75195312 C 6.8972341,0.40778687 6.4010191,0.23063674 5.8828125,0.2109375 5.8288326,0.20888551 5.7750964,0.20764671 5.7207031,0.20898438 Z M 19.292969,0.44921875 C 18.77426,0.4506047 18.272727,0.60956864 17.857422,0.93945312 13.098348,4.719624 9.1497641,7.8546793 4.625,11.451172 H 4.62305 c -1.286759,1.021757 -2.5730291,2.044129 -3.85937502,3.066406 -1.107479,0.879692 -1.16921304,2.628898 -0.140625,3.923828 l 2.42968752,3.060547 c 1.0285869,1.29493 2.7479899,1.629691 3.8554687,0.75 L 24.001876,8.6737831 C 25.109431,7.7941362 25.171165,6.0449297 24.142578,4.75 L 21.712891,1.6914062 C 21.070024,0.8820747 20.157482,0.44690884 19.292969,0.44921875 Z M 14.550781,5.7402344 c 0.139019,-0.00739 0.26467,0.035715 0.341797,0.1328125 l 3.601563,4.5351561 c 0.154252,0.194198 0.05862,0.524614 -0.212891,0.740235 l -7.669922,6.089843 C 10.339882,17.453897 9.9980034,17.47154 9.84375,17.277344 L 6.2421875,12.744141 C 6.0879341,12.549944 6.1836303,12.221473 6.4550781,12.005859 L 14.123047,5.9140625 c 0.135725,-0.1078073 0.288715,-0.166439 0.427734,-0.1738281 z m -1.044922,1.4140625 c -0.161808,0.0078 -0.316999,0.066599 -0.443359,0.1679687 -0.331549,0.2651389 -0.384928,0.7490485 -0.119141,1.0800782 0.265722,0.3309315 0.749564,0.3834278 1.080079,0.1171874 0.329672,-0.2655771 0.3821,-0.7479194 0.117187,-1.078125 C 13.98715,7.2496671 13.751194,7.1429424 13.505859,7.1542969 Z m 2.695313,3.3554691 c -0.161811,0.0078 -0.317006,0.06659 -0.44336,0.167968 -0.331029,0.265668 -0.383535,0.749595 -0.117187,1.080078 0.265523,0.32977 0.74795,0.382208 1.078125,0.117188 0.330388,-0.264941 0.383708,-0.747437 0.119141,-1.078125 -0.153867,-0.192337 -0.390703,-0.299131 -0.636719,-0.287109 z m -3.878906,0.345703 c -0.161811,0.0078 -0.317006,0.06659 -0.44336,0.167969 -0.330387,0.264941 -0.383706,0.747436 -0.11914,1.078124 0.265085,0.331648 0.74908,0.385036 1.080078,0.119141 0.331029,-0.265668 0.383535,-0.749595 0.117187,-1.080078 -0.153858,-0.191033 -0.389764,-0.297009 -0.634765,-0.285156 z m -3.8632816,0.34375 c -0.1624871,0.0074 -0.3184262,0.0662 -0.4453125,0.167969 -0.3310296,0.265668 -0.3835352,0.749595 -0.1171875,1.080078 0.2649414,0.330388 0.747437,0.383707 1.078125,0.11914 C 9.3062569,12.301321 9.3596452,11.817326 9.09375,11.486328 8.9402947,11.294564 8.7043254,11.187834 8.4589844,11.199219 Z M 20.761719,12.232422 12.880859,18.492188 17.5,22.431641 c 1.076128,0.917777 2.805784,0.643037 3.878906,-0.615235 l 2.535156,-2.972656 c 1.073119,-1.258273 1.072172,-3.009959 -0.0039,-3.927734 z m -9.605469,2.33789 c -0.161811,0.0078 -0.317006,0.06659 -0.443359,0.167969 -0.32977,0.265523 -0.382208,0.74795 -0.117188,1.078125 0.264795,0.329134 0.745796,0.382385 1.076172,0.119141 0.331648,-0.265085 0.385036,-0.74908 0.119141,-1.080078 -0.153858,-0.191034 -0.389765,-0.29701 -0.634766,-0.285157 z" /> </graphic> </Tab> <Tab closable="false"> diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml index ee72f6c..6daa977 100644 --- a/src/main/resources/PatchesTab.fxml +++ b/src/main/resources/PatchesTab.fxml @@ -129,8 +129,8 @@ <Pane VBox.vgrow="ALWAYS" /> <HBox alignment="CENTER" spacing="5.0"> <children> - <Button fx:id="makeEsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeEs" /> <Button fx:id="makeFsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeFs" /> + <Button fx:id="makeEsBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeEs" /> <Button fx:id="makeLoaderBtn" contentDisplay="TOP" mnemonicParsing="false" styleClass="buttonUp" text="%tabPatches_Btn_MakeAtmo" /> </children> </HBox> diff --git a/src/main/resources/locale_ja_JP.properties b/src/main/resources/locale_ja_JP.properties index 8302a5c..b7d06eb 100644 --- a/src/main/resources/locale_ja_JP.properties +++ b/src/main/resources/locale_ja_JP.properties @@ -79,3 +79,4 @@ windowBodyFilesScanned=\u30B9\u30AD\u30E3\u30F3\u3055\u308C\u305F\u30D5\u30A1\u3 tab2_Lbl_AwooBlockTitle=Awoo\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3068\u4E92\u63DB\u6027 tabRcm_Lbl_Payload=\u30DA\u30A4\u30ED\u30FC\u30C9\uFF1A tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM + diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 8e4a177..6674ddd 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -85,7 +85,7 @@ tabPatches_Btn_MakeEs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u04 tabPatches_Btn_MakeFs=\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0434\u043B\u044F FS tabPatches_Lbl_Atmo=Atmosphere: tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: -tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0438 +tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0438: tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0430 \u00ABLoader\u00BB \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0443\u0442\u044C \u043A Atmosphere. diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_UA.properties index 1a2fd7f..d38027d 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_UA.properties @@ -85,7 +85,7 @@ tabPatches_Btn_MakeEs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u0 tabPatches_Btn_MakeFs=\u0421\u0442\u0432\u043E\u0440\u0438\u0442\u0438 \u0434\u043B\u044F FS tabPatches_Lbl_Atmo=Atmosphere: tabPatches_Lbl_Firmware=\u041F\u0440\u043E\u0448\u0438\u0432\u043A\u0430: -tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0456 +tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0456: tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0456\u0457 "Loader"-\u043F\u0430\u0442\u0447\u0443 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u0448\u043B\u044F\u0445 \u0434\u043E Atmosphere. diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 57898be..75d160c 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -410,7 +410,7 @@ -fx-min-width: 36; } .regionCake{ - -fx-shape: "M12,1.5A2.5,2.5 0 0,1 14.5,4A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 9.5,4A2.5,2.5 0 0,1 12,1.5M15.87,5C18,5 20,7 20,9C22.7,9 22.7,13 20,13H4C1.3,13 1.3,9 4,9C4,7 6,5 8.13,5C8.57,6.73 10.14,8 12,8C13.86,8 15.43,6.73 15.87,5M5,15H8L9,22H7L5,15M10,15H14L13,22H11L10,15M16,15H19L17,22H15L16,15Z"; + -fx-shape: "M4,8H8V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V8H20A2,2 0 0,1 22,10V14A2,2 0 0,1 20,16H16V20A2,2 0 0,1 14,22H10A2,2 0 0,1 8,20V16H4A2,2 0 0,1 2,14V10A2,2 0 0,1 4,8Z"; -fx-background-color: #71e016; -size: 24; -fx-min-height: -size; diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index 3bda23e..33f78e3 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -328,7 +328,7 @@ -fx-min-width: 36; } .regionCake{ - -fx-shape: "M12,1.5A2.5,2.5 0 0,1 14.5,4A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 9.5,4A2.5,2.5 0 0,1 12,1.5M15.87,5C18,5 20,7 20,9C22.7,9 22.7,13 20,13H4C1.3,13 1.3,9 4,9C4,7 6,5 8.13,5C8.57,6.73 10.14,8 12,8C13.86,8 15.43,6.73 15.87,5M5,15H8L9,22H7L5,15M10,15H14L13,22H11L10,15M16,15H19L17,22H15L16,15Z"; + -fx-shape: "M 52.622991,6.7185427 C 52.213045,6.4681532 51.742193,6.3616112 51.258618,6.4438832 45.71725,7.3866127 41.12023,8.1676407 35.850536,9.0660487 l -0.0016,-9.79e-4 c -1.498122,0.254615 -2.996147,0.509996 -4.49419,0.765259 C 30.065184,10.04972 29.18995,11.524098 29.389694,13.13662 l 0.471017,3.81051 c 0.199745,1.61252 1.398275,2.734352 2.687806,2.51496 l 19.903814,-3.38653 c 1.28957,-0.219316 2.164805,-1.693694 1.965061,-3.306216 L 53.945504,8.9605237 c -0.12484,-1.007826 -0.639267,-1.824665 -1.322511,-2.241981 z m -6.241938,2.250248 c 0.113185,0.06107 0.191964,0.159083 0.206942,0.279995 l 0.698924,5.6470533 c 0.02995,0.241824 -0.201633,0.4796 -0.517755,0.533347 l -8.92955,1.517244 c -0.316067,0.05378 -0.59416,-0.0969 -0.62412,-0.338696 l -0.699845,-5.645371 c -0.02995,-0.241824 0.200761,-0.477898 0.516831,-0.53167 l 8.92893,-1.5198703 c 0.158036,-0.02689 0.306459,-0.0031 0.419645,0.05797 z m -1.492669,0.709292 c -0.131362,-0.07177 -0.281599,-0.09647 -0.429205,-0.07058 -0.386891,0.06721 -0.657665,0.4574873 -0.60436,0.8710843 0.0533,0.41348 0.410276,0.693298 0.796873,0.624636 0.38562,-0.0685 0.654902,-0.457888 0.601897,-0.870353 -0.0305,-0.2393373 -0.166256,-0.4455623 -0.365205,-0.5547893 z m 0.541258,4.1929763 c -0.131364,-0.07178 -0.2816,-0.09649 -0.42919,-0.07067 -0.386733,0.06792 -0.656825,0.458632 -0.602817,0.87203 0.05369,0.412385 0.409579,0.691467 0.79533,0.62369 0.385885,-0.06761 0.655942,-0.456693 0.603438,-0.869405 -0.03053,-0.240037 -0.166944,-0.446749 -0.366746,-0.555731 z m -3.224035,-1.584024 c -0.131362,-0.07178 -0.281601,-0.09649 -0.429187,-0.07067 -0.385883,0.06761 -0.65594,0.456692 -0.603438,0.869403 0.05246,0.413787 0.409134,0.694445 0.79595,0.626315 0.386733,-0.06792 0.656825,-0.458632 0.602818,-0.872031 -0.03114,-0.238911 -0.167205,-0.44447 -0.366127,-0.553105 z m -3.210781,-1.578124 c -0.131709,-0.07245 -0.282538,-0.0975 -0.430755,-0.07148 -0.386733,0.06792 -0.656824,0.458632 -0.602819,0.872031 0.05295,0.412634 0.408466,0.692506 0.79441,0.625367 0.386945,-0.06712 0.657765,-0.457462 0.604359,-0.871084 -0.03048,-0.239347 -0.166236,-0.445584 -0.365203,-0.554786 z m 0.535415,4.207358 c -0.131364,-0.07178 -0.281601,-0.09649 -0.429189,-0.07067 -0.385669,0.06841 -0.654999,0.457862 -0.601896,0.870353 0.05342,0.411484 0.407796,0.690572 0.792868,0.624421 0.386945,-0.06712 0.657766,-0.457462 0.60436,-0.871085 -0.03114,-0.238912 -0.167207,-0.444469 -0.366128,-0.553106 z"; -fx-background-color: #71e016; -size: 24; -fx-min-height: -size; From 5f0278fc7ba8dddc5ca0bdf03cd8e6f3b30116fd Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 9 Feb 2023 17:22:22 +0300 Subject: [PATCH 091/134] Fix pom.xml --- pom.xml | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aa26717..841f1b6 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,13 @@ <scope>compile</scope> </dependency> <!-- openJFX Linux --> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + <classifier>linux</classifier> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> @@ -80,6 +87,13 @@ <scope>compile</scope> </dependency> <!-- openJFX Windows --> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + <classifier>win</classifier> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> @@ -95,6 +109,13 @@ <scope>compile</scope> </dependency> <!-- openJFX MAC --> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + <classifier>mac</classifier> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> @@ -110,6 +131,13 @@ <scope>compile</scope> </dependency> <!-- openJFX MAC aarch64 --> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + <classifier>mac-aarch64</classifier> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> @@ -172,7 +200,7 @@ </resources> <plugins> - <!--OpenJFX for Java9+ --> + <!--OpenJFX for Java9+ <plugin> <groupId>org.openjfx</groupId> <artifactId>javafx-maven-plugin</artifactId> @@ -181,6 +209,7 @@ <mainClass>nsusbloader.NSLMain</mainClass> </configuration> </plugin> + --> <!-- Junit5 --> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -206,11 +235,13 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> + <!-- <configuration> <manifestEntries> <Automatic-Module-Name>nsusbloader</Automatic-Module-Name> </manifestEntries> </configuration> + --> <executions> <execution> <id>default-jar</id> From cbb9fd60b9a0effe198af041a1c4d933f5ca8b18 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 9 Feb 2023 18:16:10 +0300 Subject: [PATCH 092/134] Correct CSS --- .../java/nsusbloader/Controllers/PatchesController.java | 6 +++++- src/main/resources/res/app_light.css | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 20f1244..7395d3a 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -151,11 +151,13 @@ public class PatchesController implements Initializable { private void setOffsets(File fileWithOffsets){ AppPreferences preferences = AppPreferences.getInstance(); - + int count = 0; try (BufferedReader reader = new BufferedReader(new FileReader(fileWithOffsets))) { String fileLine; String[] lineValues; while ((fileLine = reader.readLine()) != null) { + if (fileLine.startsWith("#")) + continue; lineValues = fileLine.trim().split("\\s+?=\\s+?", 2); if (lineValues.length == 2) { String[] pointer = lineValues[0].split("_", 3); @@ -170,6 +172,8 @@ public class PatchesController implements Initializable { preferences.setPatchOffset(lineValues[0], lineValues[1]); System.out.println(pointer[0]+"_"+pointer[1]+"_"+pointer[2]+" = "+lineValues[1]); + count++; + statusLbl.setText("OK "+count); } } } diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index 33f78e3..46fc6cb 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -328,7 +328,7 @@ -fx-min-width: 36; } .regionCake{ - -fx-shape: "M 52.622991,6.7185427 C 52.213045,6.4681532 51.742193,6.3616112 51.258618,6.4438832 45.71725,7.3866127 41.12023,8.1676407 35.850536,9.0660487 l -0.0016,-9.79e-4 c -1.498122,0.254615 -2.996147,0.509996 -4.49419,0.765259 C 30.065184,10.04972 29.18995,11.524098 29.389694,13.13662 l 0.471017,3.81051 c 0.199745,1.61252 1.398275,2.734352 2.687806,2.51496 l 19.903814,-3.38653 c 1.28957,-0.219316 2.164805,-1.693694 1.965061,-3.306216 L 53.945504,8.9605237 c -0.12484,-1.007826 -0.639267,-1.824665 -1.322511,-2.241981 z m -6.241938,2.250248 c 0.113185,0.06107 0.191964,0.159083 0.206942,0.279995 l 0.698924,5.6470533 c 0.02995,0.241824 -0.201633,0.4796 -0.517755,0.533347 l -8.92955,1.517244 c -0.316067,0.05378 -0.59416,-0.0969 -0.62412,-0.338696 l -0.699845,-5.645371 c -0.02995,-0.241824 0.200761,-0.477898 0.516831,-0.53167 l 8.92893,-1.5198703 c 0.158036,-0.02689 0.306459,-0.0031 0.419645,0.05797 z m -1.492669,0.709292 c -0.131362,-0.07177 -0.281599,-0.09647 -0.429205,-0.07058 -0.386891,0.06721 -0.657665,0.4574873 -0.60436,0.8710843 0.0533,0.41348 0.410276,0.693298 0.796873,0.624636 0.38562,-0.0685 0.654902,-0.457888 0.601897,-0.870353 -0.0305,-0.2393373 -0.166256,-0.4455623 -0.365205,-0.5547893 z m 0.541258,4.1929763 c -0.131364,-0.07178 -0.2816,-0.09649 -0.42919,-0.07067 -0.386733,0.06792 -0.656825,0.458632 -0.602817,0.87203 0.05369,0.412385 0.409579,0.691467 0.79533,0.62369 0.385885,-0.06761 0.655942,-0.456693 0.603438,-0.869405 -0.03053,-0.240037 -0.166944,-0.446749 -0.366746,-0.555731 z m -3.224035,-1.584024 c -0.131362,-0.07178 -0.281601,-0.09649 -0.429187,-0.07067 -0.385883,0.06761 -0.65594,0.456692 -0.603438,0.869403 0.05246,0.413787 0.409134,0.694445 0.79595,0.626315 0.386733,-0.06792 0.656825,-0.458632 0.602818,-0.872031 -0.03114,-0.238911 -0.167205,-0.44447 -0.366127,-0.553105 z m -3.210781,-1.578124 c -0.131709,-0.07245 -0.282538,-0.0975 -0.430755,-0.07148 -0.386733,0.06792 -0.656824,0.458632 -0.602819,0.872031 0.05295,0.412634 0.408466,0.692506 0.79441,0.625367 0.386945,-0.06712 0.657765,-0.457462 0.604359,-0.871084 -0.03048,-0.239347 -0.166236,-0.445584 -0.365203,-0.554786 z m 0.535415,4.207358 c -0.131364,-0.07178 -0.281601,-0.09649 -0.429189,-0.07067 -0.385669,0.06841 -0.654999,0.457862 -0.601896,0.870353 0.05342,0.411484 0.407796,0.690572 0.792868,0.624421 0.386945,-0.06712 0.657766,-0.457462 0.60436,-0.871085 -0.03114,-0.238912 -0.167207,-0.444469 -0.366128,-0.553106 z"; + -fx-shape: "M4,8H8V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V8H20A2,2 0 0,1 22,10V14A2,2 0 0,1 20,16H16V20A2,2 0 0,1 14,22H10A2,2 0 0,1 8,20V16H4A2,2 0 0,1 2,14V10A2,2 0 0,1 4,8Z"; -fx-background-color: #71e016; -size: 24; -fx-min-height: -size; From b463a63180fbba102d05470175879744b84e4b84 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 10 Feb 2023 03:15:58 +0300 Subject: [PATCH 093/134] Fix 'class file version' mismatch. Aligned to Java11 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 841f1b6..5233e04 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format> <javafx.version>19.0.2.1</javafx.version> + <maven.compiler.release>11</maven.compiler.release> </properties> <issueManagement> @@ -226,8 +227,7 @@ <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> - <source>11</source> - <target>11</target> + <release>11</release> </configuration> </plugin> <!-- Don't generate default JAR without dependencies --> From 5b69435e8910b8775216a8b9f1e7271a968d1ece Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 10 Feb 2023 21:09:26 +0300 Subject: [PATCH 094/134] Setup builds for M1 --- .drone.yml | 11 +++++++++++ .make_m1 | 5 +++++ README.md | 8 ++++---- pom.xml | 22 ---------------------- 4 files changed, 20 insertions(+), 26 deletions(-) create mode 100644 .make_m1 diff --git a/.drone.yml b/.drone.yml index 82244f6..364d391 100644 --- a/.drone.yml +++ b/.drone.yml @@ -57,12 +57,23 @@ steps: - cd misc/windows/NSIS - makensis -V4 ./installer.nsi - cp Installer-*.exe /builds/ns-usbloader/ + - cd ../../../ volumes: - name: builds path: /builds - name: jdk path: /drone/src/misc/windows/NSIS/jdk + - name: emerge-mac-m1-artifact + image: alpine:latest + commands: + - . ./.make_m1 + - mvn -B -DskipTests clean package + - cp target/ns-usbloader-*jar /builds/ns-usbloader/ + volumes: + - name: builds + path: /builds + volumes: - name: m2 host: diff --git a/.make_m1 b/.make_m1 new file mode 100644 index 0000000..5ac4f07 --- /dev/null +++ b/.make_m1 @@ -0,0 +1,5 @@ +sed -z -i -e 's/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.2.0<\/version>/<groupId>org.usb4java<\/groupId>\n<artifactId>usb4java<\/artifactId>\n<version>1.3.0<\/version>/g' pom.xml +sed -z -i -e 's/<classifier>mac<\/classifier>/<classifier>mac-aarch64<\/classifier>/g' pom.xml +sed -z -i -e 's/<finalName>${project.artifactId}-${project.version}-legacy-${maven.build.timestamp}<\/finalName>/<finalName>${project.artifactId}-${project.version}-m1-${maven.build.timestamp}<\/finalName>/g' pom.xml +sed -i -e '/<groupId>com.akathist.maven.plugins.launch4j/,/<\/executions>/d' pom.xml +sed -z -i -e 's/<plugin>\n\s*<\/plugin>//g' pom.xml \ No newline at end of file diff --git a/README.md b/README.md index 1b1d899..a2670d4 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Ryukyuan languages by [kuragehime](https://github.com/kuragehimekurara1) * Angelo Elias Dalzotto makes packages in AUR +* Phoenix[Msc] provides his shiny Mac M1 for debug ### System requirements @@ -120,12 +121,11 @@ Double-click on downloaded .jar file. Follow instructions. Or see 'Linux' sectio Set 'Security & Privacy' settings if needed. -*Please note: JDK 11 is recommended for using on MacOS (EXCEPT APPLE SILICON). There are few really weird issues already reported from JDK 14 users on Mac.* +*Please note: JDK 19 is recommended for using on macOS. There are issues already reported from users on Mac with JDK 14.* ##### macOS on Apple Silicon (ARM) -* Some users [tested](https://github.com/developersu/ns-usbloader/issues/91) this application with [Zulu-JDK with FX support](https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx). Try it! -* OpenJDK 17 also should be a working solution. [Tell us if it works for you!]((https://github.com/developersu/ns-usbloader/issues/91)) +Download application with `-m1.jar` postfix. ##### Windows: @@ -153,7 +153,7 @@ Table. There you can select checkbox for files that will be sent to application (AW/GL). ~~Since Goldleaf v0.5 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. +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. diff --git a/pom.xml b/pom.xml index 5233e04..21352e0 100644 --- a/pom.xml +++ b/pom.xml @@ -131,28 +131,6 @@ <classifier>mac</classifier> <scope>compile</scope> </dependency> - <!-- openJFX MAC aarch64 --> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-graphics</artifactId> - <version>${javafx.version}</version> - <classifier>mac-aarch64</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-controls</artifactId> - <version>${javafx.version}</version> - <classifier>mac-aarch64</classifier> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.openjfx</groupId> - <artifactId>javafx-fxml</artifactId> - <version>${javafx.version}</version> - <classifier>mac-aarch64</classifier> - <scope>compile</scope> - </dependency> <!-- https://mvnrepository.com/artifact/org.usb4java/usb4java --> <dependency> <groupId>org.usb4java</groupId> From 7cf3970aa7e3dae0e59df1321199c4e2f7e2896c Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 10 Feb 2023 21:23:22 +0300 Subject: [PATCH 095/134] Fix CI --- .drone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 364d391..e35d98e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -65,12 +65,14 @@ steps: path: /drone/src/misc/windows/NSIS/jdk - name: emerge-mac-m1-artifact - image: alpine:latest + image: maven:3-openjdk-17 commands: - . ./.make_m1 - mvn -B -DskipTests clean package - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: + - name: m2 + path: /root/.m2 - name: builds path: /builds From 94845c1411b7525a9de690007e80cd06a0898be5 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 9 Mar 2023 04:12:28 +0300 Subject: [PATCH 096/134] readme update --- README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a2670d4..1471fb4 100644 --- a/README.md +++ b/README.md @@ -255,17 +255,14 @@ This is non-commercial project. Nevertheless, I'll be more than happy if you find a chance to make a donation for charity to people I trust: -* BTC → bc1q67j4yjmt67mes0hv03fyydjejmw6ahw0v932su -* ETH → 0x82Ab0ddE183C12cAa6eD61DF3671675C4bdC42fc -* DOGE → D8o42b952yjEWZ5Ajq4ZtjGvx1kR7DyDHm -* DOT → 1511KQqm6Mme5Za4rtgsSdzCyuEzQ6CC1a6XLxQJXXW3GWpo -* LTC → LRFXTN4rTEiQ3RxzssMFC5S1WMRd1raN2Q -* LUNA → terra1n0sfmljpuu87h7lrn9nzxadfa3qxthlmvtq5lf -* TRX → TU3AyFkF12Jg1yApcXGz91R8xKvXV1EzkW -* ETC → 0x78A946fC9708c024298c9a4f8961A49B7a830d53 -* USDT (TRC20) → TU3AyFkF12Jg1yApcXGz91R8xKvXV1EzkW -* USDT (ERC20) → 0x82Ab0ddE183C12cAa6eD61DF3671675C4bdC42fc -* XRP → r91kfRiRsvnDp7evHNmXkrL3yAL7eakWk1 +* BTC → 1BnErE3n6LEdEjvvFrt4FMdXd1UGa5L7Ge +* ETH → 0x9c29418129553bE171181bb6245151aa0576A3b7 +* DOT → 15BWSwmA4xEHZdq3gGftWg7dctMQk9vXwqA92Pg22gsxDweF +* LTC → ltc1qfjvzxm04tax077ra9rvmxdnsum8alws2n20fag +* ETC → 0xe9064De288C8454942533a005AB72515e689226E +* USDT (TRC20) → TKgp5SvJGiqYNFtvJfEDGLFbezFEHq1tBy +* USDT (ERC20) → 0x9c29418129553bE171181bb6245151aa0576A3b7 +* XRP → rGmGaLsKmSUbxWfyi4mujtVamTzj3Nqxbw. Thanks! From c1651e874ba879f8dd6bb4f12c2ab8c6955a6be3 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 17 Mar 2023 04:00:48 +0300 Subject: [PATCH 097/134] Correct AppPreferences class. Fix #139 --- src/main/java/nsusbloader/AppPreferences.java | 9 ++++++--- .../java/nsusbloader/Controllers/PatchesController.java | 2 +- .../Utilities/WindowsDrivers/DriversInstall.java | 2 +- .../Utilities/patches/es/finders/HeuristicEs1.java | 2 +- .../Utilities/patches/es/finders/HeuristicEs2.java | 2 +- .../Utilities/patches/es/finders/HeuristicEs3.java | 4 ++-- .../Utilities/patches/fs/finders/HeuristicFs1.java | 2 +- .../Utilities/patches/fs/finders/HeuristicFs2.java | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 0b54289..dfe8e49 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -32,7 +32,10 @@ public class AppPreferences { private AppPreferences(){ this.preferences = Preferences.userRoot().node("NS-USBloader"); String localeCode = preferences.get("locale", Locale.getDefault().toString()); - this.locale = new Locale(localeCode.substring(0, 2), localeCode.substring(3)); + if (localeCode.length() < 5) + this.locale = new Locale("en", "EN"); + else + this.locale = new Locale(localeCode.substring(0, 2), localeCode.substring(3)); } public String getTheme(){ @@ -147,6 +150,6 @@ public class AppPreferences { public boolean getPatchesTabInvisible(){return preferences.getBoolean("patches_tab_visible", true); } public void setPatchesTabInvisible(boolean value){preferences.putBoolean("patches_tab_visible", value);} - public String getPatchOffset(String type, int moduleNumber, int offsetId){ return preferences.get(String.format("%s_%02x_%02x", type, moduleNumber, offsetId), ""); } - public void setPatchOffset(String fullTypeSpecifier, String offset){ preferences.put(fullTypeSpecifier, offset); } + public String getPatchPattern(String type, int moduleNumber, int offsetId){ return preferences.get(String.format("%s_%02x_%02x", type, moduleNumber, offsetId), ""); } + public void setPatchPattern(String fullTypeSpecifier, String offset){ preferences.put(fullTypeSpecifier, offset); } } diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 7395d3a..6afd267 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -169,7 +169,7 @@ public class PatchesController implements Initializable { continue; if (! lineValues[1].matches("^(([0-9A-Fa-f]{2})|\\.)+?$")) continue; - preferences.setPatchOffset(lineValues[0], lineValues[1]); + preferences.setPatchPattern(lineValues[0], lineValues[1]); System.out.println(pointer[0]+"_"+pointer[1]+"_"+pointer[2]+" = "+lineValues[1]); count++; diff --git a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java index daa483e..5c5aa36 100644 --- a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java +++ b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java @@ -140,7 +140,7 @@ public class DriversInstall { return true; } catch (Exception e){ - runInstallerStatusLabel.setText("Error: "+e.toString()); + runInstallerStatusLabel.setText("Error: "+e); e.printStackTrace(); return false; } diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java index 7130d2e..e32270c 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs1.java @@ -27,7 +27,7 @@ import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; class HeuristicEs1 extends AHeuristic { - private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("ES", 1, 0); + private static final String PATTERN = AppPreferences.getInstance().getPatchPattern("ES", 1, 0); private final List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java index 6ee3ce1..efa710d 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs2.java @@ -28,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicEs2 extends AHeuristic { - private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("ES", 2, 0); + private static final String PATTERN = AppPreferences.getInstance().getPatchPattern("ES", 2, 0); private List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java index ecc8149..e287ef0 100644 --- a/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java +++ b/src/main/java/nsusbloader/Utilities/patches/es/finders/HeuristicEs3.java @@ -27,8 +27,8 @@ import nsusbloader.Utilities.patches.SimplyFind; import java.util.List; class HeuristicEs3 extends AHeuristic { - private static final String PATTERN0 = AppPreferences.getInstance().getPatchOffset("ES", 3, 0); - private static final String PATTERN1 = AppPreferences.getInstance().getPatchOffset("ES", 3, 1); + private static final String PATTERN0 = AppPreferences.getInstance().getPatchPattern("ES", 3, 0); + private static final String PATTERN1 = AppPreferences.getInstance().getPatchPattern("ES", 3, 1); private final List<Integer> findings; private final byte[] where; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java index d5e6f67..a74c5d1 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs1.java @@ -28,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicFs1 extends AHeuristic { - private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("FS", 1, 0); // TBZ + private static final String PATTERN = AppPreferences.getInstance().getPatchPattern("FS", 1, 0); // TBZ private final byte[] where; private final List<Integer> findings; diff --git a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java index bc8f5c8..a6e8ba9 100644 --- a/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java +++ b/src/main/java/nsusbloader/Utilities/patches/fs/finders/HeuristicFs2.java @@ -28,7 +28,7 @@ import java.util.ArrayList; import java.util.List; class HeuristicFs2 extends AHeuristic { - private static final String PATTERN = AppPreferences.getInstance().getPatchOffset("FS", 2, 0); + private static final String PATTERN = AppPreferences.getInstance().getPatchPattern("FS", 2, 0); private final byte[] where; private final List<Integer> findings; From 01a25f071a519209df153e57fc82faeb396b6775 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Fri, 17 Mar 2023 04:27:09 +0300 Subject: [PATCH 098/134] Make CI output a bit more verbose --- .drone.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.drone.yml b/.drone.yml index e35d98e..4606e53 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,6 +16,7 @@ steps: image: alpine:latest commands: - mkdir -p /builds/ns-usbloader + - echo target/ns-usbloader-*jar - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: - name: builds @@ -28,8 +29,10 @@ steps: - misc/windows/update_version.sh - cd misc/windows/NSIS - makensis -V4 ./installer.nsi + - echo Installer-*.exe - cp Installer-*.exe /builds/ns-usbloader/ - rm ./NS-USBloader.exe + - rm ./Installer-*.exe - cd ../../../ volumes: - name: builds @@ -42,6 +45,7 @@ steps: commands: - . ./.make_legacy - mvn -B -DskipTests clean package + - echo target/ns-usbloader-*jar - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: - name: m2 @@ -56,6 +60,7 @@ steps: - misc/windows/update_version.sh legacy - cd misc/windows/NSIS - makensis -V4 ./installer.nsi + - echo Installer-*.exe - cp Installer-*.exe /builds/ns-usbloader/ - cd ../../../ volumes: @@ -69,6 +74,7 @@ steps: commands: - . ./.make_m1 - mvn -B -DskipTests clean package + - echo target/ns-usbloader-*jar - cp target/ns-usbloader-*jar /builds/ns-usbloader/ volumes: - name: m2 From f6b875af0b8cb180498bd16fd3f15c4071a5e6c2 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Wed, 5 Apr 2023 10:58:17 +0800 Subject: [PATCH 099/134] Update locale_zh_CN.properties Update Dialog Tips --- src/main/resources/locale_zh_CN.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 1f02792..ac74a58 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -44,7 +44,7 @@ windowBodyNewVersionNOTAval=\u4F60\u6B63\u5728\u4F7F\u7528\u6700\u65B0\u7248 tab2_Cb_AllowXciNszXcz=Awoo\u6A21\u5F0F\u5141\u8BB8\u9009\u62E9XCI\u6587\u4EF6 tab2_Lbl_AllowXciNszXczDesc=\u7528\u4E8E\u4E00\u4E9B\u652F\u6301XCI/NSZ/XCZ\u548CTinfoil\u4F20\u8F93\u534F\u8BAE\u7684\u7B2C\u4E09\u65B9\u5E94\u7528\u3002\u5982\u679C\u4E0D\u6E05\u695A\u4E0D\u8981\u4FEE\u6539\u3002 tab2_Lbl_Language=\u8BED\u8A00 -windowBodyRestartToApplyLang=\u8BF7\u91CD\u542F\u5E94\u7528\u4EE5\u5E94\u7528\u66F4\u6539\u3002 +windowBodyRestartToApplyLang=\u8bf7\u91cd\u542f\u7a0b\u5e8f\u005c\u006e\u914d\u7f6e\u624d\u4f1a\u751f\u6548\uff01\ua\ua btn_OpenSplitFile=\u9009\u62E9\u5206\u5272\u7684ROM tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8BBE\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 From 6052d385d946fff807a5fe806d3b84684e7afee4 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Sat, 15 Apr 2023 17:00:46 +0800 Subject: [PATCH 100/134] Update locale_zh_CN.properties --- src/main/resources/locale_zh_CN.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index ac74a58..d428871 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -44,7 +44,7 @@ windowBodyNewVersionNOTAval=\u4F60\u6B63\u5728\u4F7F\u7528\u6700\u65B0\u7248 tab2_Cb_AllowXciNszXcz=Awoo\u6A21\u5F0F\u5141\u8BB8\u9009\u62E9XCI\u6587\u4EF6 tab2_Lbl_AllowXciNszXczDesc=\u7528\u4E8E\u4E00\u4E9B\u652F\u6301XCI/NSZ/XCZ\u548CTinfoil\u4F20\u8F93\u534F\u8BAE\u7684\u7B2C\u4E09\u65B9\u5E94\u7528\u3002\u5982\u679C\u4E0D\u6E05\u695A\u4E0D\u8981\u4FEE\u6539\u3002 tab2_Lbl_Language=\u8BED\u8A00 -windowBodyRestartToApplyLang=\u8bf7\u91cd\u542f\u7a0b\u5e8f\u005c\u006e\u914d\u7f6e\u624d\u4f1a\u751f\u6548\uff01\ua\ua +windowBodyRestartToApplyLang=\u8bf7\u91cd\u542f\u7a0b\u5e8f\u005c\u006e\u914d\u7f6e\u624d\u4f1a\u751f\u6548\uff01 btn_OpenSplitFile=\u9009\u62E9\u5206\u5272\u7684ROM tab2_Lbl_ApplicationSettings=\u4E3B\u8981\u8BBE\u5B9A tabSplMrg_Lbl_SplitNMergeTitle=\u5206\u5272&\u5408\u5E76\u6587\u4EF6\u5DE5\u5177 From 4bdc5ffb6329c7190591e482c85630bd1bfde0f5 Mon Sep 17 00:00:00 2001 From: requinDr <75610214+requinDr@users.noreply.github.com> Date: Wed, 17 May 2023 00:14:00 +0200 Subject: [PATCH 101/134] Update locale_fr_FR.properties Add missings translations and missing accents to French (note that a few translations are still missing) --- src/main/resources/locale_fr_FR.properties | 40 +++++++++++++++------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/resources/locale_fr_FR.properties b/src/main/resources/locale_fr_FR.properties index 08048b8..cb9025a 100644 --- a/src/main/resources/locale_fr_FR.properties +++ b/src/main/resources/locale_fr_FR.properties @@ -1,6 +1,8 @@ -btn_OpenFile=Selectionner les fichiers +btn_OpenFile=S\u00E9l\u00E9ctionner les fichiers +btn_OpenFolders=S\u00E9l\u00E9ctionner un dossier btn_Upload=Envoyer vers NS -tab3_Txt_EnteredAsMsg1=Vous etes connect\u00E9 en tant que: +btn_OpenFolders_tooltip=S\u00E9lectionnez un dossier \u00E0 analyser.\NCe dossier et tous ses sous-dossiers seront analys\u00E9s.\NTous les fichiers pertinents seront ajout\u00E9s à la liste. +tab3_Txt_EnteredAsMsg1=Vous \u00EAtes connect\u00E9 en tant que : tab3_Txt_EnteredAsMsg2=Vous devez \u00EAtre root ou avoir configur\u00E9 les r\u00E8gles 'udev' pour cet utilisateur afin d'\u00E9viter tout probl\u00E8me. tab3_Txt_FilesToUploadTitle=Fichiers a envoyer: tab3_Txt_GreetingsMessage=Bienvenue sur NS-USBloader @@ -9,21 +11,21 @@ windowBodyConfirmExit=Le transfert de donn\u00E9es est en cours et la fermeture windowTitleConfirmExit=Non, ne faites pas \u00E7a! btn_Stop=Interrompre tab3_Txt_GreetingsMessage2=--\n\ -Source: https://git.redrise.ru/desu/ns-usbloader\n\ -Mirror: https://github.com/developersu/ns-usbloader/\n\ -Site: https://redrise.ru\n\ +Source : https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror : https://github.com/developersu/ns-usbloader/\n\ +Site : https://redrise.ru\n\ Dmitry Isaenko [developer.su] -tab1_table_Lbl_Upload=Envoyer ? -tab1_table_Lbl_Size=Taille -tab1_table_Lbl_FileName=Nom de fichier tab1_table_Lbl_Status=Statut +tab1_table_Lbl_FileName=Nom de fichier +tab1_table_Lbl_Size=Taille +tab1_table_Lbl_Upload=Envoyer ? tab1_table_contextMenu_Btn_BtnDelete=Supprimer tab1_table_contextMenu_Btn_DeleteAll=Supprimer tout tab2_Lbl_HostIP=IP de l'ordinateur tab1_Lbl_NSIP=IP de NS: tab2_Cb_ValidateNSHostName=Toujours v\u00E9rifier que l'adresse IP de NS entr\u00E9e est correcte -windowTitleBadIp=L'adresse IP de NS est probablement incorrecte windowBodyBadIp=\u00CAtes-vous s\u00FBr que l'adresse IP de NS entr\u00E9e est correcte ? +windowTitleBadIp=L'adresse IP de NS est probablement incorrecte tab2_Cb_ExpertMode=Mode expert tab2_Lbl_HostPort=port tab2_Cb_AutoDetectIp=D\u00E9tection automatique d'IP @@ -37,12 +39,24 @@ tab2_Cb_AutoCheckForUpdates=V\u00E9rifier automatiquement les mises \u00E0 jour windowTitleNewVersionAval=Nouvelle version disponible windowTitleNewVersionNOTAval=Aucune nouvelle version disponible windowTitleNewVersionUnknown=Impossible de v\u00E9rifier les nouvelles versions -windowBodyNewVersionNOTAval=Vous utilisez la derni\u00E8re version windowBodyNewVersionUnknown=Une erreur s'est produite\nPeut-\u00EAtre des probl\u00E8mes de connexion Internet ou GitHub est en panne +windowBodyNewVersionNOTAval=Vous utilisez la derni\u00E8re version tab2_Cb_AllowXciNszXcz=Autoriser la s\u00E9lection de fichiers XCI / NSZ / XCZ pour Awoo tab2_Lbl_AllowXciNszXczDesc=Utilis\u00E9 par certaines applications tierces prenant en charge XCI/NSZ/XCZ et utilisant le protocole de transfert TinFoil. Ne changez pas en cas de doute. -tab2_Lbl_Language=La langue +tab2_Lbl_Language=Langue +windowBodyRestartToApplyLang=Red\u00E9marrez l'application pour appliquer les modifications. +btn_OpenSplitFile=S\u00E9lectionner un NSP divis\u00E9 +tab2_Lbl_ApplicationSettings=Param\u00E8tres principaux +tabSplMrg_RadioBtn_Split=Diviser +tabSplMrg_RadioBtn_Merge=Fusionner +failure_txt=Erreur +btn_Select=S\u00E9lectionner +btn_InjectPayloader=Injecter le payload +tab2_Btn_InstallDrivers=T\u00E9l\u00E9charger et installer les pilotes +windowTitleDownloadDrivers=T\u00E9l\u00E9charger et installer les pilotes btn_Cancel=Annuler - - +btn_Close=Fermer +tab2_Cb_GlVersion=Version de GoldLeaf +tab2_Cb_GLshowNspOnly=N'afficher que les *.nsp dans GoldLeaf. +tab2_Cb_foldersSelectorForRoms=S\u00E9lectionnez un dossier contenant des fichiers ROM plutôt que de les s\u00E9lectionner individuellement. From ad211cd562216dd148e97b51d8adfa55e02dfd56 Mon Sep 17 00:00:00 2001 From: requinDr <75610214+requinDr@users.noreply.github.com> Date: Wed, 17 May 2023 00:16:11 +0200 Subject: [PATCH 102/134] \N -> \n --- src/main/resources/locale_fr_FR.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locale_fr_FR.properties b/src/main/resources/locale_fr_FR.properties index cb9025a..53f47f7 100644 --- a/src/main/resources/locale_fr_FR.properties +++ b/src/main/resources/locale_fr_FR.properties @@ -1,7 +1,7 @@ btn_OpenFile=S\u00E9l\u00E9ctionner les fichiers btn_OpenFolders=S\u00E9l\u00E9ctionner un dossier btn_Upload=Envoyer vers NS -btn_OpenFolders_tooltip=S\u00E9lectionnez un dossier \u00E0 analyser.\NCe dossier et tous ses sous-dossiers seront analys\u00E9s.\NTous les fichiers pertinents seront ajout\u00E9s à la liste. +btn_OpenFolders_tooltip=S\u00E9lectionnez un dossier \u00E0 analyser.\nCe dossier et tous ses sous-dossiers seront analys\u00E9s.\nTous les fichiers pertinents seront ajout\u00E9s à la liste. tab3_Txt_EnteredAsMsg1=Vous \u00EAtes connect\u00E9 en tant que : tab3_Txt_EnteredAsMsg2=Vous devez \u00EAtre root ou avoir configur\u00E9 les r\u00E8gles 'udev' pour cet utilisateur afin d'\u00E9viter tout probl\u00E8me. tab3_Txt_FilesToUploadTitle=Fichiers a envoyer: From d3faa384ae78991e35e8c675b29c890e3d5b6adb Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Tue, 30 May 2023 23:32:29 +0900 Subject: [PATCH 103/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 3a192cf..2a66669 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -1,5 +1,5 @@ btn_OpenFile=\uD30C\uC77C \uC120\uD0DD -btn_OpenFolders=\uD30C\uC77C \uC120\uD0DD +btn_OpenFolders=\uD30C\uC77C \uC120\uD0DD btn_Upload=NS\uC5D0 \uC5C5\uB85C\uB4DC btn_OpenFolders_tooltip=\uC2A4\uCE94\uD560 \uD3F4\uB354\uB97C \uC120\uD0DD\uD569\uB2C8\uB2E4.\n\uC774 \uD3F4\uB354\uC640 \uBAA8\uB4E0 \uD558\uC704 \uD3F4\uB354\uAC00 \uAC80\uC0C9\uB429\uB2C8\uB2E4.\n\uC77C\uCE58\uD558\uB294 \uBAA8\uB4E0 \uD30C\uC77C\uC774 \uBAA9\uB85D\uC5D0 \uCD94\uAC00\uB429\uB2C8\uB2E4. tab3_Txt_EnteredAsMsg1=\uB2E4\uC74C\uACFC \uAC19\uC774 \uC785\uB825\uB418\uC5C8\uC2B5\uB2C8\uB2E4: @@ -79,4 +79,15 @@ windowBodyFilesScanned=\uC2A4\uCE94 \uB41C \uD30C\uC77C: %d\n\uCD94\uAC00 \uB420 tab2_Lbl_AwooBlockTitle=Awoo \uC124\uCE58 \uD504\uB85C\uADF8\uB7A8\uACFC \uD638\uD658\uC131 tabRcm_Lbl_Payload=\uD398\uC774\uB85C\uB4DC: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM - +tabPatches_Lbl_Firmware=\uD38C\uC6E8\uC5B4: +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Btn_fromFolder=\uD3F4\uB354\uC5D0\uC11C +tabPatches_Btn_asZipFile=ZIP \uD30C\uC77C\uB85C +tabPatches_Lbl_Title=\uD328\uCE58 +tabPatches_Lbl_Keys=\uD0A4: +tabPatches_Btn_MakeEs=ES \uB9CC\uB4E4\uAE30 +tabPatches_Btn_MakeFs=FS \uB9CC\uB4E4\uAE30 +tabPatches_Btn_MakeAtmo=\uB85C\uB354 \uB9CC\uB4E4\uAE30 (Atmosphere) +tabPatches_Btn_MakeAll=\uBAA8\uB450 \uB9CC\uB4E4\uAE30 +tabPatches_ServiceWindowMessageEsFs=\uD38C\uC6E8\uC5B4\uC640 \uD0A4 \uBAA8\uB450 \uD328\uCE58\uB97C \uC0DD\uC131\uD558\uB3C4\uB85D \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. \uADF8\uB807\uC9C0 \uC54A\uC73C\uBA74 \uBB34\uC5C7\uC744 \uD328\uCE58\uD560\uC9C0 \uBA85\uD655\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +tabPatches_ServiceWindowMessageLoader='\uB85C\uB354' \uD328\uCE58\uB97C \uC0DD\uC131\uD558\uB824\uBA74 Atmosphere \uD3F4\uB354\uB97C \uC815\uC758\uD574\uC57C \uD569\uB2C8\uB2E4. From 390937177480eb6fb685d5fbb4ed02985bcbf790 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 29 Oct 2023 06:15:43 +0300 Subject: [PATCH 104/134] Add fonts selector for #153 --- pom.xml | 2 +- src/main/java/nsusbloader/AppPreferences.java | 16 ++ .../Controllers/FilesDropHandle.java | 1 + .../nsusbloader/Controllers/FontSelector.java | 56 +++++++ .../Controllers/FontSettingsController.java | 153 ++++++++++++++++++ .../SettingsBlockGenericController.java | 11 ++ .../java/nsusbloader/MediatorControl.java | 4 + src/main/java/nsusbloader/NSLMain.java | 1 + src/main/java/nsusbloader/ServiceWindow.java | 9 +- .../WindowsDrivers/DriversInstall.java | 1 + src/main/resources/FontSettings.fxml | 40 +++++ src/main/resources/NXDTab.fxml | 15 +- src/main/resources/PatchesTab.fxml | 24 +-- src/main/resources/RcmTab.fxml | 39 +---- src/main/resources/SettingsBlockGeneric.fxml | 7 +- src/main/resources/SplitMergeTab.fxml | 9 +- src/main/resources/locale.properties | 4 + src/main/resources/locale_ru_RU.properties | 6 +- ..._UA.properties => locale_uk_RU.properties} | 4 + src/main/resources/res/app_dark.css | 65 +++++++- src/main/resources/res/app_light.css | 35 +++- 21 files changed, 416 insertions(+), 86 deletions(-) create mode 100644 src/main/java/nsusbloader/Controllers/FontSelector.java create mode 100644 src/main/java/nsusbloader/Controllers/FontSettingsController.java create mode 100644 src/main/resources/FontSettings.fxml rename src/main/resources/{locale_uk_UA.properties => locale_uk_RU.properties} (97%) diff --git a/pom.xml b/pom.xml index 21352e0..b90c7c4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>7.0</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers. May have end on '-SNAPSHOT' --> + <version>7.1-SNAPSHOT</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers. May have end on '-SNAPSHOT' --> <url>https://redrise.ru</url> <description>NS multi-tool</description> diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index dfe8e49..9d59e44 100644 --- a/src/main/java/nsusbloader/AppPreferences.java +++ b/src/main/java/nsusbloader/AppPreferences.java @@ -18,6 +18,8 @@ */ package nsusbloader; +import javafx.scene.text.Font; + import java.util.Locale; import java.util.prefs.Preferences; @@ -28,6 +30,7 @@ public class AppPreferences { private final Preferences preferences; private final Locale locale; public static final String[] goldleafSupportedVersions = {"v0.5", "v0.7.x", "v0.8-0.9", "v0.10"}; + private static final Font DEFAULT_FONT = Font.getDefault(); private AppPreferences(){ this.preferences = Preferences.userRoot().node("NS-USBloader"); @@ -152,4 +155,17 @@ public class AppPreferences { public void setPatchesTabInvisible(boolean value){preferences.putBoolean("patches_tab_visible", value);} public String getPatchPattern(String type, int moduleNumber, int offsetId){ return preferences.get(String.format("%s_%02x_%02x", type, moduleNumber, offsetId), ""); } public void setPatchPattern(String fullTypeSpecifier, String offset){ preferences.put(fullTypeSpecifier, offset); } + + public String getFontFamily(){ return preferences.get("font_family", DEFAULT_FONT.getFamily()); } + public double getFontSize(){ return preferences.getDouble("font_size", DEFAULT_FONT.getSize()); } + public String getFontStyle(){ + final String fontFamily = preferences.get("font_family", DEFAULT_FONT.getFamily()); + final double fontSize = preferences.getDouble("font_size", DEFAULT_FONT.getSize()); + + return String.format("-fx-font-family: \"%s\"; -fx-font-size: %.0f;", fontFamily, fontSize); + } + public void setFontStyle(String fontFamily, double size){ + preferences.put("font_family", fontFamily); + preferences.putDouble("font_size", size); + } } diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java index f66be03..29f4482 100644 --- a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java @@ -87,6 +87,7 @@ public class FilesDropHandle { Scene mainScene = new Scene(parentVBox, 310, 185); mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); + parentVBox.setStyle(AppPreferences.getInstance().getFontStyle()); stage.setOnHidden(windowEvent -> filesDropHandleTask.cancel(true ) ); diff --git a/src/main/java/nsusbloader/Controllers/FontSelector.java b/src/main/java/nsusbloader/Controllers/FontSelector.java new file mode 100644 index 0000000..5f4a0dc --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/FontSelector.java @@ -0,0 +1,56 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import nsusbloader.AppPreferences; + +import java.util.ResourceBundle; + +public class FontSelector { + public FontSelector(ResourceBundle resourceBundle) throws Exception{ + Stage stage = new Stage(); + stage.setMinWidth(800); + stage.setMinHeight(800); + + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FontSettings.fxml")); + fxmlLoader.setResources(resourceBundle); + + stage.setTitle(resourceBundle.getString("tab2_Btn_ApplicationFont")); + stage.getIcons().addAll( + new Image("/res/app_icon32x32.png"), + new Image("/res/app_icon48x48.png"), + new Image("/res/app_icon64x64.png"), + new Image("/res/app_icon128x128.png")); + + Parent parent = fxmlLoader.load(); + Scene fontScene = new Scene(parent, 550, 600); + + fontScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); + parent.setStyle(AppPreferences.getInstance().getFontStyle()); + + stage.setAlwaysOnTop(true); + stage.setScene(fontScene); + stage.show(); + } +} diff --git a/src/main/java/nsusbloader/Controllers/FontSettingsController.java b/src/main/java/nsusbloader/Controllers/FontSettingsController.java new file mode 100644 index 0000000..0f7f14c --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/FontSettingsController.java @@ -0,0 +1,153 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of NS-USBloader. + + NS-USBloader is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NS-USBloader is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NS-USBloader. If not, see <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import nsusbloader.AppPreferences; +import nsusbloader.MediatorControl; + +import java.net.URL; +import java.util.ResourceBundle; + +public class FontSettingsController implements Initializable { + private final AppPreferences preferences = AppPreferences.getInstance(); + + @FXML + private Button applyBtn, cancelBtn, resetBtn; + + @FXML + private ListView<String> fontsLv; + + @FXML + private Spinner<Double> fontSizeSpinner; + + @FXML + private Text exampleText; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + applyBtn.setDefaultButton(true); + applyBtn.getStyleClass().add("buttonUp"); + applyBtn.setOnAction(e -> applyChanges()); + + cancelBtn.setCancelButton(true); + cancelBtn.setOnAction(e -> closeWindow()); + + resetBtn.setOnAction(e -> reset()); + + fontsLv.setCellFactory(item -> getCellFactory()); + fontsLv.setItems(getFonts()); + fontsLv.getSelectionModel().select(preferences.getFontFamily()); + fontsLv.getSelectionModel().selectedIndexProperty().addListener( + (observableValue, oldValueNumber, newValueNumber) -> setExampleTextFont()); + fontsLv.setFixedCellSize(40.0); + + fontSizeSpinner.setEditable(false); + fontSizeSpinner.setValueFactory(getValueFactory()); + + exampleText.setText(resourceBundle.getString("fontPreviewText")); + + fontSizeSpinner.getValueFactory().setValue(preferences.getFontSize()); + } + + private ListCell<String> getCellFactory(){ + return new ListCell<>(){ + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) + return; + Font font = Font.font(item); + Text itemText = new Text(item); + itemText.setFont(font); + setGraphic(itemText); + } + }; + } + + private ObservableList<String> getFonts(){ + ObservableList<String> fonts = FXCollections.observableArrayList(); + fonts.addAll(Font.getFamilies()); + + return fonts; + } + + private SpinnerValueFactory<Double> getValueFactory(){ + return new SpinnerValueFactory<>() { + @Override + public void decrement(int i) { + double value = getValue() - i; + if (value < 4) + return; + + setValue(value); + setExampleTextFont(value); + } + + @Override + public void increment(int i) { + double value = getValue() + i; + if (value > 100) + return; + + setValue(value); + setExampleTextFont(value); + } + }; + } + + private void setExampleTextFont(){ + setExampleTextFont(fontsLv.getSelectionModel().getSelectedItem(), fontSizeSpinner.getValue()); + } + private void setExampleTextFont(double size){ + setExampleTextFont(fontsLv.getSelectionModel().getSelectedItem(), size); + } + private void setExampleTextFont(String font, double size){ + exampleText.setFont(Font.font(font, size)); + } + + private void reset(){ + final Font defaultFont = Font.getDefault(); + exampleText.setFont(defaultFont); + + fontsLv.getSelectionModel().select(defaultFont.getFamily()); + fontSizeSpinner.getValueFactory().setValue(defaultFont.getSize()); + } + + private void applyChanges(){ + final String fontFamily = fontsLv.getSelectionModel().getSelectedItem(); + final double fontSize = fontSizeSpinner.getValue().intValue(); + + preferences.setFontStyle(fontFamily, fontSize); + MediatorControl.getInstance().updateApplicationFont(fontFamily, fontSize); + + closeWindow(); + } + + private void closeWindow(){ + ((Stage) cancelBtn.getScene().getWindow()).close(); + } +} diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java index 95ef545..3cf3e0f 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java @@ -43,6 +43,9 @@ import java.util.ResourceBundle; public class SettingsBlockGenericController implements Initializable { @FXML private ChoiceBox<LocaleHolder> languagesChB; + @FXML + private Button fontSelectBtn; + @FXML private Button submitLanguageBtn, driversInstallBtn, @@ -81,6 +84,14 @@ public class SettingsBlockGenericController implements Initializable { newVersionHyperlink.setOnAction(e-> hostServices.showDocument(newVersionHyperlink.getText())); checkForUpdBtn.setOnAction(e->checkForUpdatesAction()); submitLanguageBtn.setOnAction(e->languageButtonAction()); + fontSelectBtn.setOnAction(e -> openFontSettings()); + } + private void openFontSettings() { + try { + new FontSelector(resourceBundle); + } catch (Exception ex) { + throw new RuntimeException(ex); + } } private void setDriversInstallFeature(){ diff --git a/src/main/java/nsusbloader/MediatorControl.java b/src/main/java/nsusbloader/MediatorControl.java index b15b409..a73b239 100644 --- a/src/main/java/nsusbloader/MediatorControl.java +++ b/src/main/java/nsusbloader/MediatorControl.java @@ -60,4 +60,8 @@ public class MediatorControl { getPatchesController().notifyThreadStarted(isActive, appModuleType); } public synchronized boolean getTransferActive() { return this.isTransferActive.get(); } + public void updateApplicationFont(String fontFamily, double fontSize){ + mainController.logArea.getScene().getRoot().setStyle( + String.format("-fx-font-family: \"%s\"; -fx-font-size: %.0f;", fontFamily, fontSize)); + } } diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index 040b5bc..fa40c9a 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -62,6 +62,7 @@ public class NSLMain extends Application { ); mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); + root.setStyle(AppPreferences.getInstance().getFontStyle()); primaryStage.setScene(mainScene); primaryStage.show(); diff --git a/src/main/java/nsusbloader/ServiceWindow.java b/src/main/java/nsusbloader/ServiceWindow.java index 0565448..3355a32 100644 --- a/src/main/java/nsusbloader/ServiceWindow.java +++ b/src/main/java/nsusbloader/ServiceWindow.java @@ -44,7 +44,6 @@ public class ServiceWindow { alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE); alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really. - alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme()); Stage dialogStage = (Stage) alertBox.getDialogPane().getScene().getWindow(); dialogStage.setAlwaysOnTop(true); @@ -54,6 +53,9 @@ public class ServiceWindow { new Image("/res/warn_ico64x64.png"), new Image("/res/warn_ico128x128.png") ); + alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme()); + dialogStage.getScene().getRoot().setStyle(AppPreferences.getInstance().getFontStyle()); + alertBox.show(); dialogStage.toFront(); } @@ -68,7 +70,6 @@ public class ServiceWindow { alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE); alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); alertBox.setResizable(true); // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really. - alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme()); Stage dialogStage = (Stage) alertBox.getDialogPane().getScene().getWindow(); dialogStage.setAlwaysOnTop(true); @@ -78,6 +79,10 @@ public class ServiceWindow { new Image("/res/ask_ico64x64.png"), new Image("/res/ask_ico128x128.png") ); + + alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme()); + dialogStage.getScene().getRoot().setStyle(AppPreferences.getInstance().getFontStyle()); + dialogStage.toFront(); Optional<ButtonType> result = alertBox.showAndWait(); diff --git a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java index 5c5aa36..cdc7cad 100644 --- a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java +++ b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java @@ -103,6 +103,7 @@ public class DriversInstall { Scene mainScene = new Scene(parentVBox, 405, 155); mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); + parentVBox.setStyle(AppPreferences.getInstance().getFontStyle()); stage.setOnHidden(windowEvent -> { downloadTask.cancel(true ); diff --git a/src/main/resources/FontSettings.fxml b/src/main/resources/FontSettings.fxml new file mode 100644 index 0000000..b54ecae --- /dev/null +++ b/src/main/resources/FontSettings.fxml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.FontSettingsController"> + <children> + <VBox layoutX="344.0" layoutY="132.0" prefHeight="200.0" prefWidth="100.0" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <ListView fx:id="fontsLv" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" /> + <Text fx:id="exampleText" strokeType="OUTSIDE" strokeWidth="0.0" /> + <HBox alignment="CENTER_RIGHT" spacing="5.0"> + <children> + <Label text="%fontSize" wrapText="true" /> + <Spinner fx:id="fontSizeSpinner" minWidth="100.0" /> + <Pane HBox.hgrow="ALWAYS" /> + <Button fx:id="resetBtn" mnemonicParsing="false" text="%btn_ResetToDefaults"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Button> + <Button fx:id="cancelBtn" mnemonicParsing="false" text="%btn_Cancel" wrapText="true" /> + <Button fx:id="applyBtn" mnemonicParsing="false" styleClass="buttonUp" text="%btn_Select" wrapText="true" /> + </children> + </HBox> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </VBox> + </children> +</AnchorPane> diff --git a/src/main/resources/NXDTab.fxml b/src/main/resources/NXDTab.fxml index dd720f0..60c1741 100644 --- a/src/main/resources/NXDTab.fxml +++ b/src/main/resources/NXDTab.fxml @@ -12,21 +12,12 @@ <?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.shape.SVGPath?> -<?import javafx.scene.text.Font?> -<VBox spacing="15.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NxdtController"> +<VBox spacing="15.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NxdtController"> <HBox alignment="CENTER"> <children> - <Label styleClass="nxdt" text="nx"> - <font> - <Font name="System Bold" size="18.0" /> - </font> - </Label> - <Label text="dumptool"> - <font> - <Font name="System Bold" size="18.0" /> - </font> - </Label> + <Label styleClass="nxdt" text="nx" /> + <Label styleClass="bold-text" text="dumptool" /> </children> </HBox> <GridPane> diff --git a/src/main/resources/PatchesTab.fxml b/src/main/resources/PatchesTab.fxml index 6daa977..24e1e6e 100644 --- a/src/main/resources/PatchesTab.fxml +++ b/src/main/resources/PatchesTab.fxml @@ -12,18 +12,13 @@ <?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.shape.SVGPath?> -<?import javafx.scene.text.Font?> <ScrollPane fitToWidth="true" onDragDropped="#handleDrop" onDragOver="#handleDragOver" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.PatchesController"> <VBox fx:id="patchesToolPane" spacing="15.0"> <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #2cd882 40%, transparent 45%);" /> <HBox alignment="CENTER"> <children> - <Label text="%tabPatches_Lbl_Title"> - <font> - <Font name="System Bold" size="15.0" /> - </font> - </Label> + <Label styleClass="bold-text" text="%tabPatches_Lbl_Title" /> </children> </HBox> <GridPane> @@ -55,11 +50,7 @@ </Button> </children> </HBox> - <Label fx:id="locationFirmwareLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="locationFirmwareLbl" disable="true" styleClass="italic-text" textOverrun="LEADING_WORD_ELLIPSIS" /> <HBox alignment="CENTER_LEFT" spacing="5.0"> <children> <Label minHeight="-Infinity" minWidth="-Infinity" text="%tabPatches_Lbl_Atmo" wrapText="true" /> @@ -72,11 +63,7 @@ </Button> </children> </HBox> - <Label fx:id="locationAtmosphereLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="locationAtmosphereLbl" disable="true" styleClass="italic-text" textOverrun="LEADING_WORD_ELLIPSIS" /> </children> </VBox> <Separator prefWidth="200.0" /> @@ -94,10 +81,7 @@ </Button> </children> </HBox> - <Label fx:id="locationKeysLbl" disable="true" textOverrun="LEADING_WORD_ELLIPSIS"> - <font> - <Font name="System Italic" size="13.0" /> - </font></Label> + <Label fx:id="locationKeysLbl" disable="true" styleClass="italic-text" textOverrun="LEADING_WORD_ELLIPSIS" /> </children> </VBox> <VBox spacing="5.0"> diff --git a/src/main/resources/RcmTab.fxml b/src/main/resources/RcmTab.fxml index adb7750..85ff20b 100644 --- a/src/main/resources/RcmTab.fxml +++ b/src/main/resources/RcmTab.fxml @@ -14,18 +14,13 @@ <?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.shape.SVGPath?> -<?import javafx.scene.text.Font?> -<ScrollPane fitToWidth="true" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.RcmController"> +<ScrollPane fitToWidth="true" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.RcmController"> <VBox fx:id="rcmToolPane" spacing="15.0"> <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #ff1515 40%, transparent 45%);" /> <HBox alignment="CENTER"> <children> - <Label text="%tabRcm_Lbl_FuseeGelee"> - <font> - <Font name="System Bold" size="15.0" /> - </font> - </Label> + <Label styleClass="bold-text" text="%tabRcm_Lbl_FuseeGelee" /> </children> </HBox> <GridPane> @@ -57,11 +52,7 @@ <Label fx:id="payloadFNameLbl1" /> </children> </HBox> - <Label fx:id="payloadFPathLbl1" disable="true"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="payloadFPathLbl1" disable="true" styleClass="italic-text" /> </children> </VBox> <Button fx:id="selPldBtn1" mnemonicParsing="false" onAction="#bntSelectPayloader" styleClass="buttonSelect"> @@ -86,11 +77,7 @@ <Label fx:id="payloadFNameLbl2" /> </children> </HBox> - <Label fx:id="payloadFPathLbl2" disable="true"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="payloadFPathLbl2" disable="true" styleClass="italic-text" /> </children> </VBox> <Button fx:id="selPldBtn2" mnemonicParsing="false" onAction="#bntSelectPayloader" styleClass="buttonSelect"> @@ -115,11 +102,7 @@ <Label fx:id="payloadFNameLbl3" /> </children> </HBox> - <Label fx:id="payloadFPathLbl3" disable="true"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="payloadFPathLbl3" disable="true" styleClass="italic-text" /> </children> </VBox> <Button fx:id="selPldBtn3" mnemonicParsing="false" onAction="#bntSelectPayloader" styleClass="buttonSelect"> @@ -144,11 +127,7 @@ <Label fx:id="payloadFNameLbl4" /> </children> </HBox> - <Label fx:id="payloadFPathLbl4" disable="true"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="payloadFPathLbl4" disable="true" styleClass="italic-text" /> </children> </VBox> <Button fx:id="selPldBtn4" mnemonicParsing="false" onAction="#bntSelectPayloader" styleClass="buttonSelect"> @@ -173,11 +152,7 @@ <Label fx:id="payloadFNameLbl5" /> </children> </HBox> - <Label fx:id="payloadFPathLbl5" disable="true"> - <font> - <Font name="System Italic" size="13.0" /> - </font> - </Label> + <Label fx:id="payloadFPathLbl5" disable="true" styleClass="italic-text" /> </children> </VBox> <Button fx:id="selPldBtn5" mnemonicParsing="false" onAction="#bntSelectPayloader" styleClass="buttonSelect"> diff --git a/src/main/resources/SettingsBlockGeneric.fxml b/src/main/resources/SettingsBlockGeneric.fxml index 2105d4b..2db10eb 100644 --- a/src/main/resources/SettingsBlockGeneric.fxml +++ b/src/main/resources/SettingsBlockGeneric.fxml @@ -10,7 +10,7 @@ <?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.VBox?> -<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SettingsBlockGenericController"> +<VBox spacing="5.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SettingsBlockGenericController"> <children> <Label text="%tab2_Lbl_ApplicationSettings" /> <HBox alignment="CENTER_LEFT" spacing="5.0"> @@ -28,6 +28,11 @@ <Insets left="5.0" /> </VBox.margin> </HBox> + <Button fx:id="fontSelectBtn" mnemonicParsing="false" text="%tab2_Btn_ApplicationFont"> + <VBox.margin> + <Insets left="5.0" /> + </VBox.margin> + </Button> <HBox> <children> <VBox> diff --git a/src/main/resources/SplitMergeTab.fxml b/src/main/resources/SplitMergeTab.fxml index 6301946..0add1e9 100644 --- a/src/main/resources/SplitMergeTab.fxml +++ b/src/main/resources/SplitMergeTab.fxml @@ -12,19 +12,14 @@ <?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> -<?import javafx.scene.text.Font?> -<VBox fx:id="smToolPane" onDragDropped="#handleDrop" onDragOver="#handleDragOver" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SplitMergeController"> +<VBox fx:id="smToolPane" onDragDropped="#handleDrop" onDragOver="#handleDragOver" spacing="20.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.SplitMergeController"> <VBox spacing="15.0"> <children> <Pane minHeight="-Infinity" prefHeight="10.0" style="-fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #00c8fc 40%, transparent 45%);" /> <HBox alignment="CENTER"> <children> - <Label text="%tabSplMrg_Lbl_SplitNMergeTitle"> - <font> - <Font name="System Bold" size="15.0" /> - </font> - </Label> + <Label styleClass="bold-text" text="%tabSplMrg_Lbl_SplitNMergeTitle" /> </children> </HBox> <GridPane> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index ea87ce7..bcc7298 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -91,3 +91,7 @@ tabPatches_Btn_MakeAtmo=Make Loader (Atmosphere) tabPatches_Btn_MakeAll=Make all tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. +tab2_Btn_ApplicationFont=Change application font +btn_ResetToDefaults=Reset to defaults +fontPreviewText=Text preview +fontSize=Font size: diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index 6674ddd..abdfa80 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -23,7 +23,7 @@ tab2_Lbl_HostIP=IP \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430 tab1_Lbl_NSIP=NS IP: tab2_Cb_ValidateNSHostName=\u0412\u0441\u0435\u0433\u0434\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E\u0441\u0442\u044C NS IP. windowTitleBadIp=IP \u0430\u0434\u0440\u0435\u0441 NS \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0435\u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0439 -windowBodyBadIp=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B \u0447\u0442\u043E IP \u0430\u0434\u0440\u0435\u0441 NS \u0432\u0432\u0435\u0434\u0451\u043D \u0431\u0435\u0437 \u043E\u0448\u0438\u0431\u043E\u043A? +windowBodyBadIp=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B, \u0447\u0442\u043E IP \u0430\u0434\u0440\u0435\u0441 NS \u0432\u0432\u0435\u0434\u0451\u043D \u0431\u0435\u0437 \u043E\u0448\u0438\u0431\u043E\u043A? tab2_Cb_ExpertMode=\u0420\u0435\u0436\u0438\u043C \u044D\u043A\u0441\u043F\u0435\u0440\u0442\u0430 (\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0441\u0435\u0442\u0438) tab2_Lbl_HostPort=\u043F\u043E\u0440\u0442 tab2_Cb_AutoDetectIp=\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u0442\u044C IP @@ -89,5 +89,9 @@ tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0438: tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0430 \u00ABLoader\u00BB \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0443\u0442\u044C \u043A Atmosphere. +tab2_Btn_ApplicationFont=\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0440\u0438\u0444\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F +btn_ResetToDefaults=\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 +fontPreviewText=\u041F\u0440\u0438\u043C\u0435\u0440 \u0442\u0435\u043A\u0441\u0442\u0430 +fontSize=\u0420\u0430\u0437\u043C\u0435\u0440 \u0448\u0440\u0438\u0444\u0442\u043E\u0432: diff --git a/src/main/resources/locale_uk_UA.properties b/src/main/resources/locale_uk_RU.properties similarity index 97% rename from src/main/resources/locale_uk_UA.properties rename to src/main/resources/locale_uk_RU.properties index d38027d..16b5ba7 100644 --- a/src/main/resources/locale_uk_UA.properties +++ b/src/main/resources/locale_uk_RU.properties @@ -89,4 +89,8 @@ tabPatches_Lbl_Keys=\u041A\u043B\u044E\u0447\u0456: tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0456\u0457 "Loader"-\u043F\u0430\u0442\u0447\u0443 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u0448\u043B\u044F\u0445 \u0434\u043E Atmosphere. +tab2_Btn_ApplicationFont=\u0417\u043C\u0456\u043D\u0438\u0442\u0438 \u0448\u0440\u0438\u0444\u0442 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u0438 +btn_ResetToDefaults=C\u043A\u0438\u043D\u0443\u0442\u0438 \u043D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F +fontPreviewText=\u041F\u0440\u0438\u043A\u043B\u0430\u0434 \u0442\u0435\u043A\u0441\u0442\u0443 +fontSize=\u0420\u043E\u0437\u043C\u0456\u0440 \u0448\u0440\u0438\u0444\u0442\u0456\u0432: diff --git a/src/main/resources/res/app_dark.css b/src/main/resources/res/app_dark.css index 75d160c..73ebb49 100644 --- a/src/main/resources/res/app_dark.css +++ b/src/main/resources/res/app_dark.css @@ -5,6 +5,18 @@ -fx-background: #2d2d2d; } +Text { + -fx-fill: white; +} + +.bold-text{ + -fx-font-weight: bold; +} + +.italic-text{ + -fx-font-style: italic; +} + .button, .buttonUp, .buttonStop, .buttonSelect{ -fx-background-color: #4f4f4f; -fx-border-color: #4f4f4f; @@ -235,21 +247,31 @@ -fx-border-width: 2; } -.list-cell, .list-cell:selected, .list-cell:filled:selected{ +.list-cell:even, +.list-cell:even:selected, +.list-cell:even:filled:selected{ -fx-background-color: -fx-table-cell-border-color, #424242; -fx-background-insets: 0, 0 0 1 0; -fx-table-cell-border-color: #6d8484; } -.list-cell:odd, .list-cell:odd:selected, .list-cell:odd:filled:selected{ +.list-cell:odd, +.list-cell:odd:selected, +.list-cell:odd:filled:selected{ -fx-background-color: -fx-table-cell-border-color, #4f4f4f; -fx-background-insets: 0, 0 0 1 0; -fx-table-cell-border-color: #6d8484; } -.list-cell .text, .list-cell:odd .text{ +.list-cell .text, +.list-cell:odd .text, +.list-cell Text, +.list-cell:odd Text{ -fx-fill: #f7fafa; } -.list-cell:filled:selected .text, .list-cell:odd:filled:selected .text{ +.list-cell:filled:selected .text, +.list-cell:odd:filled:selected .text, +.list-cell:filled:selected Text, +.list-cell:odd:filled:selected Text{ -fx-fill: #08f3ff; } /* -========================== Context menu =====================- */ @@ -445,6 +467,7 @@ } .nxdt.label .text { -fx-fill: #cb0010; + -fx-font-weight: bold; } .regionWindows { @@ -468,7 +491,39 @@ -fx-min-height: -size; -fx-min-width: 24; } -// +/********************* Spinner **************************/ +.spinner{ + -fx-background-color: #4f4f4f; + -fx-border-radius: 3; + -fx-border-width: 1; + -fx-border-color: #4f4f4f; + -fx-mark-color: #eea11e; + -fx-effect: none; +} + +.spinner .increment-arrow-button, +.spinner .decrement-arrow-button { + -fx-background-radius: 0; + -fx-background-color: #4f4f4f; +} + +.spinner .increment-arrow-button:hover .increment-arrow, +.spinner .decrement-arrow-button:hover .decrement-arrow { + -fx-background-color: #ff8000; +} + +.spinner .increment-arrow-button:hover:pressed .increment-arrow, +.spinner .decrement-arrow-button:hover:pressed .decrement-arrow, +.spinner .increment-arrow-button:pressed .increment-arrow, +.spinner .decrement-arrow-button:pressed .decrement-arrow { + -fx-background-color: #71e016; +} + +.spinner .increment-arrow-button .increment-arrow, +.spinner .decrement-arrow-button .decrement-arrow { + -fx-background-color: #eea11e; +} + //.lineGradient { // -fx-background-color: linear-gradient(from 41px 34px to 50px 50px, reflect, #00c8fc 30%, transparent 45%); //} \ No newline at end of file diff --git a/src/main/resources/res/app_light.css b/src/main/resources/res/app_light.css index 46fc6cb..fd58212 100644 --- a/src/main/resources/res/app_light.css +++ b/src/main/resources/res/app_light.css @@ -5,6 +5,18 @@ -fx-background: #ebebeb; } +Text { + -fx-fill: black; +} + +.bold-text{ + -fx-font-weight: bold; +} + +.italic-text{ + -fx-font-style: italic; +} + .button, .buttonUp, .buttonStop, .buttonSelect{ -fx-background-color: #fefefe; -fx-background-insets: 0 0 0 0, 0, 1, 2; @@ -253,23 +265,35 @@ -fx-border-width: 2; } -.list-cell, .list-cell:selected, .list-cell:filled:selected{ +.list-cell:even{ -fx-background-color: -fx-table-cell-border-color, #ebfffe; -fx-background-insets: 0, 0 0 1 0; -fx-table-cell-border-color: #b0b0b0; } -.list-cell:odd, .list-cell:odd:selected, .list-cell:odd:filled:selected{ +.list-cell:odd{ -fx-background-color: -fx-table-cell-border-color, #fefefe; -fx-background-insets: 0, 0 0 1 0; -fx-table-cell-border-color: #b0b0b0; } -.list-cell .text, .list-cell:odd .text{ +.list-cell:even:selected, +.list-cell:even:filled:selected, +.list-cell:odd:selected, +.list-cell:odd:filled:selected{ + -fx-background-color: -fx-table-cell-border-color, #e4ffde; +} + +.list-cell .text, +.list-cell:odd .text, +.list-cell Text, +.list-cell:odd Text{ -fx-fill: #2c2c2c; } -.list-cell:filled:selected .text, .list-cell:odd:filled:selected .text{ +.list-cell:filled:selected .text, +.list-cell:odd:filled:selected .text, +.list-cell:filled:selected Text, +.list-cell:odd:filled:selected Text{ -fx-fill: #2c2c2c; - -fx-font-weight: bold } /* -========================= Separator ===================- */ .separator *.line { @@ -363,6 +387,7 @@ } .nxdt.label .text { -fx-fill: #9d010e; + -fx-font-weight: bold; } .regionWindows { -fx-shape: "M3,12V6.75L9,5.43V11.91L3,12M20,3V11.75L10,11.9V5.21L20,3M3,13L9,13.09V19.9L3,18.75V13M20,13.25V22L10,20.09V13.1L20,13.25Z"; From 47045b2aeb2e235f6675d0351dcddcee7d5ca441 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 29 Oct 2023 06:29:33 +0300 Subject: [PATCH 105/134] correct version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b90c7c4..58b1a1e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>7.1-SNAPSHOT</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers. May have end on '-SNAPSHOT' --> + <version>7.1</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers --> <url>https://redrise.ru</url> <description>NS multi-tool</description> From 8348c9f2ab4a24f1f78b9ee28f933f0744a0a220 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 30 Oct 2023 00:09:27 +0300 Subject: [PATCH 106/134] Correct 'Font settings' window size. --- .../{FontSelector.java => FontSettings.java} | 10 +++++----- .../Controllers/SettingsBlockGenericController.java | 2 +- src/main/resources/FontSettings.fxml | 2 +- src/main/resources/locale.properties | 2 +- src/main/resources/locale_ru_RU.properties | 2 +- src/main/resources/locale_uk_RU.properties | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/nsusbloader/Controllers/{FontSelector.java => FontSettings.java} (89%) diff --git a/src/main/java/nsusbloader/Controllers/FontSelector.java b/src/main/java/nsusbloader/Controllers/FontSettings.java similarity index 89% rename from src/main/java/nsusbloader/Controllers/FontSelector.java rename to src/main/java/nsusbloader/Controllers/FontSettings.java index 5f4a0dc..9f5fb15 100644 --- a/src/main/java/nsusbloader/Controllers/FontSelector.java +++ b/src/main/java/nsusbloader/Controllers/FontSettings.java @@ -27,11 +27,11 @@ import nsusbloader.AppPreferences; import java.util.ResourceBundle; -public class FontSelector { - public FontSelector(ResourceBundle resourceBundle) throws Exception{ +public class FontSettings { + public FontSettings(ResourceBundle resourceBundle) throws Exception{ Stage stage = new Stage(); - stage.setMinWidth(800); - stage.setMinHeight(800); + stage.setMinWidth(650); + stage.setMinHeight(450); FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FontSettings.fxml")); fxmlLoader.setResources(resourceBundle); @@ -44,7 +44,7 @@ public class FontSelector { new Image("/res/app_icon128x128.png")); Parent parent = fxmlLoader.load(); - Scene fontScene = new Scene(parent, 550, 600); + Scene fontScene = new Scene(parent, 660, 525); fontScene.getStylesheets().add(AppPreferences.getInstance().getTheme()); parent.setStyle(AppPreferences.getInstance().getFontStyle()); diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java index 3cf3e0f..c90f049 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java @@ -88,7 +88,7 @@ public class SettingsBlockGenericController implements Initializable { } private void openFontSettings() { try { - new FontSelector(resourceBundle); + new FontSettings(resourceBundle); } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/src/main/resources/FontSettings.fxml b/src/main/resources/FontSettings.fxml index b54ecae..0c7f322 100644 --- a/src/main/resources/FontSettings.fxml +++ b/src/main/resources/FontSettings.fxml @@ -11,7 +11,7 @@ <?import javafx.scene.layout.VBox?> <?import javafx.scene.text.Text?> -<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.FontSettingsController"> +<AnchorPane xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.FontSettingsController"> <children> <VBox layoutX="344.0" layoutY="132.0" prefHeight="200.0" prefWidth="100.0" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <children> diff --git a/src/main/resources/locale.properties b/src/main/resources/locale.properties index bcc7298..9622dfe 100644 --- a/src/main/resources/locale.properties +++ b/src/main/resources/locale.properties @@ -92,6 +92,6 @@ tabPatches_Btn_MakeAll=Make all tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. tab2_Btn_ApplicationFont=Change application font -btn_ResetToDefaults=Reset to defaults +btn_ResetToDefaults=Reset fontPreviewText=Text preview fontSize=Font size: diff --git a/src/main/resources/locale_ru_RU.properties b/src/main/resources/locale_ru_RU.properties index abdfa80..abf6fe5 100644 --- a/src/main/resources/locale_ru_RU.properties +++ b/src/main/resources/locale_ru_RU.properties @@ -90,7 +90,7 @@ tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0435\u0439 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u043A \u043F\u0443\u0442\u044C \u043A \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0435, \u0442\u0430\u043A \u0438 \u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0435\u0439. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0447\u0442\u043E \u0436\u0435 \u043F\u0430\u0442\u0447\u0438\u0442\u044C. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u043F\u0430\u0442\u0447\u0430 \u00ABLoader\u00BB \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0443\u0442\u044C \u043A Atmosphere. tab2_Btn_ApplicationFont=\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0440\u0438\u0444\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F -btn_ResetToDefaults=\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 +btn_ResetToDefaults=\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C fontPreviewText=\u041F\u0440\u0438\u043C\u0435\u0440 \u0442\u0435\u043A\u0441\u0442\u0430 fontSize=\u0420\u0430\u0437\u043C\u0435\u0440 \u0448\u0440\u0438\u0444\u0442\u043E\u0432: diff --git a/src/main/resources/locale_uk_RU.properties b/src/main/resources/locale_uk_RU.properties index 16b5ba7..3cf6da2 100644 --- a/src/main/resources/locale_uk_RU.properties +++ b/src/main/resources/locale_uk_RU.properties @@ -90,7 +90,7 @@ tabPatches_Lbl_Title=\u041F\u0430\u0442\u0447\u0438 tabPatches_ServiceWindowMessageEsFs=\u0414\u043B\u044F \u0441\u0442\u0432\u043E\u0440\u0435\u043D\u043D\u044F \u043F\u0430\u0442\u0447\u0456\u0432 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u044F\u043A \u0448\u043B\u044F\u0445 \u0434\u043E \u043F\u0440\u043E\u0448\u0438\u0432\u043A\u0438, \u0442\u0430\u043A \u0456 \u0434\u043E \u0444\u0430\u0439\u043B\u0443 \u043A\u043B\u044E\u0447\u0456\u0432. \u0411\u043E \u0456\u043D\u0430\u043A\u0448\u0435 \u043D\u0435 \u0437\u0440\u043E\u0437\u0443\u043C\u0456\u043B\u043E \u0449\u043E \u0436 \u0442\u0440\u0435\u0431\u0430 \u043F\u0430\u0442\u0447\u0438\u0442\u0438. tabPatches_ServiceWindowMessageLoader=\u0414\u043B\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0456\u0457 "Loader"-\u043F\u0430\u0442\u0447\u0443 \u043D\u0435\u043E\u0431\u0445\u0456\u0434\u043D\u043E \u0432\u043A\u0430\u0437\u0430\u0442\u0438 \u0448\u043B\u044F\u0445 \u0434\u043E Atmosphere. tab2_Btn_ApplicationFont=\u0417\u043C\u0456\u043D\u0438\u0442\u0438 \u0448\u0440\u0438\u0444\u0442 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u0438 -btn_ResetToDefaults=C\u043A\u0438\u043D\u0443\u0442\u0438 \u043D\u0430\u043B\u0430\u0448\u0442\u0443\u0432\u0430\u043D\u043D\u044F +btn_ResetToDefaults=C\u043A\u0438\u043D\u0443\u0442\u0438 fontPreviewText=\u041F\u0440\u0438\u043A\u043B\u0430\u0434 \u0442\u0435\u043A\u0441\u0442\u0443 fontSize=\u0420\u043E\u0437\u043C\u0456\u0440 \u0448\u0440\u0438\u0444\u0442\u0456\u0432: From 474f90a427cc5c1d0535d78bb8ba5239029377f1 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 09:52:55 -0800 Subject: [PATCH 107/134] replaced 404 github link to twitter profile --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1471fb4..6c36c52 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * [agungrbudiman](https://github.com/agungrbudiman) * Perfect algorithms and great examples taken from mrdude project [mrdude2478/IPS_Patch_Creator](https://github.com/mrdude2478/IPS_Patch_Creator/) -* French by [Stephane Meden (JackFromNice)](https://github.com/JackFromNice) +* French by [Stephane Meden (JackFromNice)](https://twitter.com/medenstephane) * Italian by [unbranched](https://github.com/unbranched) * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) From 25910ac94c73f679bb744c76713bf2ef2398dc68 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 09:55:05 -0800 Subject: [PATCH 108/134] fixed broken website link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6c36c52..57149b3 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) * Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), [Kuziel Alejandro](https://github.com/Uzi-Oni) -* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](https://www.xxgame.net) -* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](https://www.xxgame.net) +* Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](http://xxgame.net/) +* Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](http://xxgame.net/) * German by [Swarsele](https://github.com/Swarsele) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) * Czech by [Spenaat](https://github.com/spenaat) From c5688f79df4ffd2942dc4d51ada08bf9cc28ba6a Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 09:56:31 -0800 Subject: [PATCH 109/134] replaced missing github repo link with link to creator --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57149b3..c26a784 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ where '+' means 'any next NS-USBloader version'. ### Awoo Installer and compatible applications support -Awoo Installer uses the same command-set (or 'protocol') to [Adubbz/Tinfoil](https://github.com/Adubbz/Tinfoil/). +Awoo Installer uses the same command-set (or 'protocol') to [Adubbz](https://github.com/Adubbz) Tinfoil (repo does not exist anymore) A lot of other forks/apps uses the same command-set. To stop speculating about the name it's now called 'Awoo'. It WAS called 'TinFoil' before. Not any more. From abf7621f287b88f02ab59e32f6a35bad9904795d Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 09:57:50 -0800 Subject: [PATCH 110/134] github account does not exist anymore, so I replaced the link with instagram --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c26a784..685d705 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), [Kuziel Alejandro](https://github.com/Uzi-Oni) * Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](http://xxgame.net/) * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](http://xxgame.net/) -* German by [Swarsele](https://github.com/Swarsele) +* German by [Swarsele](https://www.instagram.com/swarsele/?hl=en) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) * Czech by [Spenaat](https://github.com/spenaat) * Arabic by [eslamabdel](https://github.com/eslamabdel) From b89bfa806d5aa23a12947b83936f077f48bba988 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 09:59:17 -0800 Subject: [PATCH 111/134] fixed broken link to github user developersu --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 58b1a1e..163c17c 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ <issueManagement> <system>GitHub</system> - <url>https://github.com/developer_su/${project.artifactId}/issues</url> + <url>https://github.com/developersu/${project.artifactId}/issues</url> </issueManagement> <dependencies> <!-- https://mvnrepository.com/artifact/commons-cli/commons-cli --> From 94ad332bf7b5b921c7bcbb4b868c99586f399b61 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 10:05:17 -0800 Subject: [PATCH 112/134] fixed broken link to maven.apache.org/POM/4.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 163c17c..a12525c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + xsi:schemaLocation="https://maven.apache.org/ref/3.9.5/maven-model/maven.html https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>loper</groupId> From 5b120204ef18f7bbc93858ffd3a9be61197ee7b5 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Tue, 7 Nov 2023 10:10:24 -0800 Subject: [PATCH 113/134] fixed broken link --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a12525c..10a222e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ <repository> <id>redrise</id> <name>redrise.ru repository</name> - <url>https://repo.redrise.ru/releases</url> + <url>https://repo.redrise.ru/#/releases</url> </repository> </repositories> From fa236cb1ad6f223dcf42ea118a4b28016c8d01e7 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Nov 2023 14:34:00 +0300 Subject: [PATCH 114/134] Correct Goldleaf supported versions list --- src/main/java/nsusbloader/AppPreferences.java | 10 +++++----- .../Controllers/SettingsBlockGoldleafController.java | 2 +- src/main/java/nsusbloader/cli/GoldLeafCli.java | 4 ++-- .../java/nsusbloader/com/usb/UsbCommunications.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/nsusbloader/AppPreferences.java b/src/main/java/nsusbloader/AppPreferences.java index 9d59e44..ceb9542 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[] goldleafSupportedVersions = {"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+"}; private static final Font DEFAULT_FONT = Font.getDefault(); private AppPreferences(){ @@ -42,13 +42,13 @@ public class AppPreferences { } public String getTheme(){ - String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually + String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user change settings manually if (!theme.matches("(^/res/app_dark.css$)|(^/res/app_light.css$)")) theme = "/res/app_dark.css"; return theme; } public int getProtocol(){ - int protocolIndex = preferences.getInt("protocol_index", 0); // Don't let user to change settings manually + int protocolIndex = preferences.getInt("protocol_index", 0); // Don't let user change settings manually if (protocolIndex < 0 || protocolIndex > 1) protocolIndex = 0; return protocolIndex; @@ -56,7 +56,7 @@ public class AppPreferences { public void setProtocol(int protocolIndex){ preferences.putInt("protocol_index", protocolIndex); } public String getNetUsb(){ - String netUsb = preferences.get("NETUSB", "USB"); // Don't let user to change settings manually + String netUsb = preferences.get("NETUSB", "USB"); // Don't let user change settings manually if (!netUsb.matches("(^USB$)|(^NET$)")) netUsb = "USB"; return netUsb; @@ -120,7 +120,7 @@ public class AppPreferences { public void setNspFileFilterGL(boolean prop){preferences.putBoolean("GL_NSP_FILTER", prop);} public int getGlVersion(){ - return preferences.getInt("gl_ver", goldleafSupportedVersions.length - 1); + return preferences.getInt("gl_ver", GOLDLEAF_SUPPORTED_VERSIONS.length - 1); } public void setGlVersion(int version){ preferences.putInt("gl_ver", version);} diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java index 8be5568..2be959c 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGoldleafController.java @@ -38,7 +38,7 @@ public class SettingsBlockGoldleafController implements Initializable { final AppPreferences preferences = AppPreferences.getInstance(); nspFilesFilterForGLCB.setSelected(preferences.getNspFileFilterGL()); - glVersionChoiceBox.getItems().addAll(AppPreferences.goldleafSupportedVersions); + glVersionChoiceBox.getItems().addAll(AppPreferences.GOLDLEAF_SUPPORTED_VERSIONS); glVersionChoiceBox.getSelectionModel().select(preferences.getGlVersion()); } diff --git a/src/main/java/nsusbloader/cli/GoldLeafCli.java b/src/main/java/nsusbloader/cli/GoldLeafCli.java index 21e2610..27f6f65 100644 --- a/src/main/java/nsusbloader/cli/GoldLeafCli.java +++ b/src/main/java/nsusbloader/cli/GoldLeafCli.java @@ -75,7 +75,7 @@ public class GoldLeafCli { private String getGlSupportedVersions(){ StringBuilder builder = new StringBuilder("Supported versions: \n"); - for (String a : AppPreferences.goldleafSupportedVersions){ + for (String a : AppPreferences.GOLDLEAF_SUPPORTED_VERSIONS){ builder.append("\t"); builder.append(a); builder.append("\n"); @@ -98,7 +98,7 @@ public class GoldLeafCli { "Try 'ns-usbloader -g help' for more information."); } - for (String version : AppPreferences.goldleafSupportedVersions){ + for (String version : AppPreferences.GOLDLEAF_SUPPORTED_VERSIONS){ if (version.equals(goldLeafVersion)) return; } diff --git a/src/main/java/nsusbloader/com/usb/UsbCommunications.java b/src/main/java/nsusbloader/com/usb/UsbCommunications.java index 1222a55..76063d4 100644 --- a/src/main/java/nsusbloader/com/usb/UsbCommunications.java +++ b/src/main/java/nsusbloader/com/usb/UsbCommunications.java @@ -66,7 +66,7 @@ public class UsbCommunications extends CancellableRunnable { case "TinFoil": module = new TinFoil(handler, nspMap, this, logPrinter); break; - case "GoldLeafv0.10": + case "GoldLeafv0.10+": module = new GoldLeaf_010(handler, nspMap, this, logPrinter, nspFilterForGl); break; case "GoldLeafv0.8-0.9": From 66e7338fa5c5076df35ae4f0400d508d3e68561e Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Nov 2023 14:35:15 +0300 Subject: [PATCH 115/134] Save few integration tests --- .gitignore | 2 + src/test/java/integration/Environment.java | 84 +++++++++++++++++++ .../java/integration/EsIntegrationTest.java | 54 ++++++++++++ .../java/integration/FsIntegrationTest.java | 60 +++++++++++++ .../integration/LoaderIntegrationTest.java | 35 ++++++++ 5 files changed, 235 insertions(+) create mode 100644 .gitignore create mode 100644 src/test/java/integration/Environment.java create mode 100644 src/test/java/integration/EsIntegrationTest.java create mode 100644 src/test/java/integration/FsIntegrationTest.java create mode 100644 src/test/java/integration/LoaderIntegrationTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92b14c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +offsets.txt +environment.txt \ No newline at end of file diff --git a/src/test/java/integration/Environment.java b/src/test/java/integration/Environment.java new file mode 100644 index 0000000..d80e7d1 --- /dev/null +++ b/src/test/java/integration/Environment.java @@ -0,0 +1,84 @@ +/* Copyright WTFPL */ +package integration; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; + +public class Environment { + public final String CONTAINER = "environment.txt"; + + private String atmosphere; + private String prodkeys; + private String firmwares; + private String saveTo; + + public Environment() throws Exception{ + if (Files.notExists(Path.of(CONTAINER))) { + boolean createdTemplate = createTemplate(); + throw new Exception("'environment.txt' not found\n" + + "Please "+(createdTemplate?"":"create and ") + + "set values in file"); + } + + read(); + + if (isNotValid()) + throw new Exception("'environment.txt' doesn't contain valid data\n"); + } + private void read() throws Exception{ + HashMap<String, String> rawKeySet = new HashMap<>(); + try (BufferedReader br = new BufferedReader(new FileReader(CONTAINER))) { + String fileLine; + String[] keyValue; + while ((fileLine = br.readLine()) != null) { + keyValue = fileLine.trim().split("\\s+?=\\s+?", 2); + if (keyValue.length == 2) + rawKeySet.put(keyValue[0], keyValue[1]); + } + } + + atmosphere = rawKeySet.get("ATMOSPHERE"); + prodkeys = rawKeySet.get("PRODKEYS"); + firmwares = rawKeySet.get("NS_GLOBAL_FIRMWARES"); + saveTo = rawKeySet.get("SAVE_TO"); + } + private boolean isNotValid(){ + return atmosphere == null || atmosphere.isBlank() || + prodkeys == null || prodkeys.isBlank() || + firmwares == null || firmwares.isBlank(); + } + private boolean createTemplate(){ + try(FileWriter writer = new FileWriter(CONTAINER)){ + writer.write( + "ATMOSPHERE = \n" + + "PRODKEYS = \n" + + "NS_GLOBAL_FIRMWARES = \n" + + "SAVE_TO = /tmp"); + } + catch (Exception e){ + e.printStackTrace(); + return false; + } + return true; + } + + public String getAtmosphereLocation() { + return atmosphere; + } + + public String getProdkeysLocation() { + return prodkeys; + } + + public String getFirmwaresLocation() { + return firmwares; + } + + public String getSaveToLocation() { + return saveTo; + } +} diff --git a/src/test/java/integration/EsIntegrationTest.java b/src/test/java/integration/EsIntegrationTest.java new file mode 100644 index 0000000..c3b9d9c --- /dev/null +++ b/src/test/java/integration/EsIntegrationTest.java @@ -0,0 +1,54 @@ +/* Copyright WTFPL */ +package integration; + +import nsusbloader.NSLMain; +import nsusbloader.Utilities.patches.es.EsPatchMaker; +import org.junit.jupiter.api.*; + +import java.io.File; +import java.util.Arrays; + +public class EsIntegrationTest { + static String pathToFirmware; + static String pathToFirmwares; + static String pathToKeysFile; + static String saveTo; + + @BeforeAll + static void init() throws Exception{ + NSLMain.isCli = true; + Environment environment = new Environment(); + pathToKeysFile = environment.getProdkeysLocation(); + saveTo = environment.getSaveToLocation() + File.separator + "ES_LPR"; + pathToFirmwares = environment.getFirmwaresLocation(); + pathToFirmware = pathToFirmware + File.separator + "Firmware 14.1.0"; + } + + @Disabled + @DisplayName("ES Integration validation - everything") + @Test + void makeEss() throws Exception{ + File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> { + return s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*"); + //return s.matches("^Firmware 10.0.1.*"); + }); + assert fwDirs != null; + Arrays.sort(fwDirs); + for (File dir : fwDirs){ + EsPatchMaker esPatchMaker = new EsPatchMaker(dir.getAbsolutePath(), pathToKeysFile, saveTo); + Thread workThread = new Thread(esPatchMaker); + workThread.start(); + workThread.join(); + } + } + + @Disabled + @DisplayName("ES Integration validation - one particular firmware") + @Test + void makeEs() throws Exception{ + EsPatchMaker esPatchMaker = new EsPatchMaker(pathToFirmware, pathToKeysFile, saveTo); + Thread workThread = new Thread(esPatchMaker); + workThread.start(); + workThread.join(); + } +} diff --git a/src/test/java/integration/FsIntegrationTest.java b/src/test/java/integration/FsIntegrationTest.java new file mode 100644 index 0000000..220bbf7 --- /dev/null +++ b/src/test/java/integration/FsIntegrationTest.java @@ -0,0 +1,60 @@ +/* Copyright WTFPL */ +package integration; + +import nsusbloader.NSLMain; +import nsusbloader.Utilities.patches.fs.FsPatchMaker; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Arrays; + +public class FsIntegrationTest { + static String pathToFirmware; + static String pathToFirmwares; + static String pathToKeysFile; + static String saveTo; + + @BeforeAll + static void init() throws Exception{ + NSLMain.isCli = true; + Environment environment = new Environment(); + pathToKeysFile = environment.getProdkeysLocation(); + saveTo = environment.getSaveToLocation() + File.separator + "FS_LPR"; + pathToFirmwares = environment.getFirmwaresLocation(); + pathToFirmware = pathToFirmware + File.separator + "Firmware 13.0.0"; + } + + @Disabled + @DisplayName("FS Integration validation - everything") + @Test + void makeFss() throws Exception{ + File[] fwDirs = new File(pathToFirmwares).listFiles((file, s) -> { + return (s.matches("^Firmware (9\\.|[0-9][0-9]\\.).*") && ! s.endsWith(".zip")); + //return s.matches("^Firmware 10.0.1.*"); + }); + assert fwDirs != null; + Arrays.sort(fwDirs); + + for (File dir : fwDirs){ + System.out.println("\n\t\t\t"+dir.getName()); + FsPatchMaker fsPatchMaker = new FsPatchMaker(dir.getAbsolutePath(), pathToKeysFile, saveTo); + Thread workThread = new Thread(fsPatchMaker); + workThread.start(); + workThread.join(); + } + } + + @Disabled + @DisplayName("FS Integration validation - one particular firmware") + @Test + void makeFs() throws Exception{ + System.out.println(pathToFirmware); + FsPatchMaker fsPatchMaker = new FsPatchMaker(pathToFirmware, pathToKeysFile, saveTo); + Thread workThread = new Thread(fsPatchMaker); + workThread.start(); + workThread.join(); + } +} diff --git a/src/test/java/integration/LoaderIntegrationTest.java b/src/test/java/integration/LoaderIntegrationTest.java new file mode 100644 index 0000000..d44d6e6 --- /dev/null +++ b/src/test/java/integration/LoaderIntegrationTest.java @@ -0,0 +1,35 @@ +/* Copyright WTFPL */ +package integration; + +import nsusbloader.NSLMain; +import nsusbloader.Utilities.patches.loader.LoaderPatchMaker; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +public class LoaderIntegrationTest { + static String pathToAtmo; + static String saveTo; + + @BeforeAll + static void init() throws Exception{ + NSLMain.isCli = true; + Environment environment = new Environment(); + saveTo = environment.getSaveToLocation() + File.separator + "Loader_LPR"; + pathToAtmo = environment.getAtmosphereLocation(); + } + + @Disabled + @DisplayName("Loader Integration validation") + @Test + void makeLoader() throws Exception{ + System.out.println(pathToAtmo); + LoaderPatchMaker patchMaker = new LoaderPatchMaker(pathToAtmo, saveTo); + Thread workThread = new Thread(patchMaker); + workThread.start(); + workThread.join(); + } +} From 88ca0815edc937a77570d7ce398b450a61adc492 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Wed, 8 Nov 2023 15:08:24 +0300 Subject: [PATCH 116/134] Properly disable integration tests --- src/test/java/integration/EsIntegrationTest.java | 3 +-- src/test/java/integration/FsIntegrationTest.java | 3 +-- src/test/java/integration/LoaderIntegrationTest.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/integration/EsIntegrationTest.java b/src/test/java/integration/EsIntegrationTest.java index c3b9d9c..c8ec24e 100644 --- a/src/test/java/integration/EsIntegrationTest.java +++ b/src/test/java/integration/EsIntegrationTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.*; import java.io.File; import java.util.Arrays; +@Disabled public class EsIntegrationTest { static String pathToFirmware; static String pathToFirmwares; @@ -24,7 +25,6 @@ public class EsIntegrationTest { pathToFirmware = pathToFirmware + File.separator + "Firmware 14.1.0"; } - @Disabled @DisplayName("ES Integration validation - everything") @Test void makeEss() throws Exception{ @@ -42,7 +42,6 @@ public class EsIntegrationTest { } } - @Disabled @DisplayName("ES Integration validation - one particular firmware") @Test void makeEs() throws Exception{ diff --git a/src/test/java/integration/FsIntegrationTest.java b/src/test/java/integration/FsIntegrationTest.java index 220bbf7..5a85acc 100644 --- a/src/test/java/integration/FsIntegrationTest.java +++ b/src/test/java/integration/FsIntegrationTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import java.io.File; import java.util.Arrays; +@Disabled public class FsIntegrationTest { static String pathToFirmware; static String pathToFirmwares; @@ -27,7 +28,6 @@ public class FsIntegrationTest { pathToFirmware = pathToFirmware + File.separator + "Firmware 13.0.0"; } - @Disabled @DisplayName("FS Integration validation - everything") @Test void makeFss() throws Exception{ @@ -47,7 +47,6 @@ public class FsIntegrationTest { } } - @Disabled @DisplayName("FS Integration validation - one particular firmware") @Test void makeFs() throws Exception{ diff --git a/src/test/java/integration/LoaderIntegrationTest.java b/src/test/java/integration/LoaderIntegrationTest.java index d44d6e6..25a448a 100644 --- a/src/test/java/integration/LoaderIntegrationTest.java +++ b/src/test/java/integration/LoaderIntegrationTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +@Disabled public class LoaderIntegrationTest { static String pathToAtmo; static String saveTo; @@ -22,7 +23,6 @@ public class LoaderIntegrationTest { pathToAtmo = environment.getAtmosphereLocation(); } - @Disabled @DisplayName("Loader Integration validation") @Test void makeLoader() throws Exception{ From 1251d39a6b5f07e3c983d794101a165cfeac1ec1 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Wed, 8 Nov 2023 09:11:36 -0800 Subject: [PATCH 117/134] revert Adubbz/Tinfoil --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 685d705..7ccbb9b 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ where '+' means 'any next NS-USBloader version'. ### Awoo Installer and compatible applications support -Awoo Installer uses the same command-set (or 'protocol') to [Adubbz](https://github.com/Adubbz) Tinfoil (repo does not exist anymore) +Awoo Installer uses the same command-set (or 'protocol') to [Adubbz/Tinfoil](https://github.com/Adubbz/Tinfoil/). A lot of other forks/apps uses the same command-set. To stop speculating about the name it's now called 'Awoo'. It WAS called 'TinFoil' before. Not any more. From 5b3ea13b08f721c05d6b1488586ce17796e899a1 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Wed, 8 Nov 2023 09:12:22 -0800 Subject: [PATCH 118/134] revert Stephane Meden (JackFromNice) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ccbb9b..52ce6c5 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * [agungrbudiman](https://github.com/agungrbudiman) * Perfect algorithms and great examples taken from mrdude project [mrdude2478/IPS_Patch_Creator](https://github.com/mrdude2478/IPS_Patch_Creator/) -* French by [Stephane Meden (JackFromNice)](https://twitter.com/medenstephane) +* French by [Stephane Meden (JackFromNice)](https://github.com/JackFromNice) * Italian by [unbranched](https://github.com/unbranched) * Korean by [DDinghoya](https://github.com/DDinghoya) * Portuguese by [almircanella](https://github.com/almircanella) From 353b058388707a884326dd215046b9548da50a40 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Wed, 8 Nov 2023 09:13:44 -0800 Subject: [PATCH 119/134] revert Swarsele --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52ce6c5..52c7904 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Spanish by [/u/cokimaya007](https://www.reddit.com/u/cokimaya007), [Kuziel Alejandro](https://github.com/Uzi-Oni) * Chinese (Simplified) by [Huang YunKun (htynkn)](https://github.com/htynkn), [FFT9 (XXgame Group)](http://xxgame.net/) * Chinese (Traditional) by [qazrfv1234](https://github.com/qazrfv1234), [FFT9 (XXgame Group)](http://xxgame.net/) -* German by [Swarsele](https://www.instagram.com/swarsele/?hl=en) +* German by [Swarsele](https://github.com/Swarsele) * Vietnamese by [Hai Phan Nguyen (pnghai)](https://github.com/pnghai) * Czech by [Spenaat](https://github.com/spenaat) * Arabic by [eslamabdel](https://github.com/eslamabdel) From 2935d4ef559c3a4ea7ad28dcff500f7e4b921073 Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Sat, 11 Nov 2023 12:48:21 -0800 Subject: [PATCH 120/134] revert broken link --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10a222e..a12525c 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ <repository> <id>redrise</id> <name>redrise.ru repository</name> - <url>https://repo.redrise.ru/#/releases</url> + <url>https://repo.redrise.ru/releases</url> </repository> </repositories> From b32986682efa37680b75565eebcc35ed1754ed9a Mon Sep 17 00:00:00 2001 From: Justin Dhillon <justin.singh.dhillon@gmail.com> Date: Sat, 11 Nov 2023 12:49:17 -0800 Subject: [PATCH 121/134] revert maven.apache.org link --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a12525c..163c17c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="https://maven.apache.org/ref/3.9.5/maven-model/maven.html https://maven.apache.org/xsd/maven-4.0.0.xsd"> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>loper</groupId> From c0bf247666d7c2532c3fea597b4798679843fde1 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Tue, 26 Dec 2023 14:39:05 +0300 Subject: [PATCH 122/134] Bundle drivers to windows installer #156 --- .drone.yml | 9 ++++++- misc/windows/NSIS/installer.nsi | 2 ++ .../WindowsDrivers/DownloadDriversTask.java | 8 +++--- .../WindowsDrivers/DriversInstall.java | 26 +++++++++++++++---- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4606e53..2ea37b6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -39,6 +39,8 @@ steps: path: /builds - name: jdk path: /drone/src/misc/windows/NSIS/jdk + - name: drivers + path: /drone/src/misc/windows/NSIS/Drivers_set.exe - name: emerge-legacy-artifact image: maven:3-openjdk-17 @@ -68,6 +70,8 @@ steps: path: /builds - name: jdk path: /drone/src/misc/windows/NSIS/jdk + - name: drivers + path: /drone/src/misc/windows/NSIS/Drivers_set.exe - name: emerge-mac-m1-artifact image: maven:3-openjdk-17 @@ -91,4 +95,7 @@ volumes: path: /home/www/builds - name: jdk host: - path: /home/docker/drone/files/assembly/openjdk-19.0.2 \ No newline at end of file + path: /home/docker/drone/files/assembly/openjdk-19.0.2 + - name: drivers + host: + path: /home/docker/drone/files/assembly/Drivers_set.exe \ No newline at end of file diff --git a/misc/windows/NSIS/installer.nsi b/misc/windows/NSIS/installer.nsi index c5007de..4351b11 100644 --- a/misc/windows/NSIS/installer.nsi +++ b/misc/windows/NSIS/installer.nsi @@ -92,6 +92,7 @@ Section "NS-USBloader" Install SetOutPath "$INSTDIR" file /r jdk + file Drivers_set.exe file NS-USBloader.exe file logo.ico @@ -143,6 +144,7 @@ Section "Uninstall" ;Delete installed files RMDir /r "$INSTDIR\jdk\*" + Delete "$INSTDIR\Drivers_set.exe" Delete "$INSTDIR\NS-USBloader.exe" Delete "$INSTDIR\logo.ico" Delete "$SMPROGRAMS\Uninstall.exe" diff --git a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DownloadDriversTask.java b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DownloadDriversTask.java index 0c023a8..eec25cc 100644 --- a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DownloadDriversTask.java +++ b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DownloadDriversTask.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2023 Dmitry Isaenko This file is part of NS-USBloader. @@ -25,8 +25,8 @@ import java.net.URL; public class DownloadDriversTask extends Task<String> { + public static final long DRIVERS_FILE_SIZE = 3857375; private static final String driverFileLocationURL = "https://github.com/developersu/NS-Drivers/releases/download/v1.0/Drivers_set.exe"; - private static final long driversFileSize = 3857375; private static File driversInstallerFile; @@ -38,7 +38,7 @@ public class DownloadDriversTask extends Task<String> { } private boolean isDriversDownloaded(){ - return driversInstallerFile != null && driversInstallerFile.length() == driversFileSize; + return driversInstallerFile != null && driversInstallerFile.length() == DRIVERS_FILE_SIZE; } private boolean downloadDrivers(){ @@ -64,7 +64,7 @@ public class DownloadDriversTask extends Task<String> { while ((bytesRead = bis.read(dataBuffer, 0, 1024)) != -1) { fos.write(dataBuffer, 0, bytesRead); totalRead += bytesRead; - updateProgress(totalRead, driversFileSize); + updateProgress(totalRead, DRIVERS_FILE_SIZE); if (this.isCancelled()) { bis.close(); fos.close(); diff --git a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java index cdc7cad..f973e75 100644 --- a/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java +++ b/src/main/java/nsusbloader/Utilities/WindowsDrivers/DriversInstall.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2023 Dmitry Isaenko This file is part of NS-USBloader. @@ -32,16 +32,32 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; import nsusbloader.AppPreferences; +import java.io.File; import java.util.ResourceBundle; public class DriversInstall { private static volatile boolean isRunning; + private final ResourceBundle resourceBundle; private Label runInstallerStatusLabel; public DriversInstall(ResourceBundle rb){ + this.resourceBundle = rb; + if (isDriversDistributesWithExecutable()) + runInstaller("Drivers_set.exe"); + else + runDownloadProcess(); + } + + private boolean isDriversDistributesWithExecutable(){ + final File drivers = new File("Drivers_set.exe"); + + return drivers.length() == DownloadDriversTask.DRIVERS_FILE_SIZE; + } + + private void runDownloadProcess(){ if (DriversInstall.isRunning) return; @@ -49,11 +65,11 @@ public class DriversInstall { DownloadDriversTask downloadTask = new DownloadDriversTask(); - Button cancelButton = new Button(rb.getString("btn_Cancel")); + Button cancelButton = new Button(resourceBundle.getString("btn_Cancel")); HBox hBoxInformation = new HBox(); hBoxInformation.setAlignment(Pos.TOP_LEFT); - hBoxInformation.getChildren().add(new Label(rb.getString("windowBodyDownloadDrivers"))); + hBoxInformation.getChildren().add(new Label(resourceBundle.getString("windowBodyDownloadDrivers"))); ProgressBar progressBar = new ProgressBar(); progressBar.setPrefWidth(Double.MAX_VALUE); @@ -90,7 +106,7 @@ public class DriversInstall { Stage stage = new Stage(); - stage.setTitle(rb.getString("windowTitleDownloadDrivers")); + stage.setTitle(resourceBundle.getString("windowTitleDownloadDrivers")); stage.getIcons().addAll( new Image("/res/dwnload_ico32x32.png"), //TODO: REDRAW new Image("/res/dwnload_ico48x48.png"), @@ -115,7 +131,7 @@ public class DriversInstall { stage.toFront(); downloadTask.setOnSucceeded(event -> { - cancelButton.setText(rb.getString("btn_Close")); + cancelButton.setText(resourceBundle.getString("btn_Close")); String returnedValue = downloadTask.getValue(); From d1ac1f540332c91c6b46082815ea0f5bec7608ce Mon Sep 17 00:00:00 2001 From: Kevin | Secret Jupiter <mail@kevinrieger.com> Date: Sun, 28 Jan 2024 22:00:45 +0100 Subject: [PATCH 123/134] Added mandatory info to README.md for Apple Silicon users Added mandatory info for Apple Silicon users --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52c7904..d01cd88 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ Sometimes I add new posts about this project [on my blog page](https://developer ### System requirements -JDK 11 for macOS and Linux +- JDK 11 for macOS and Linux +- libusb, if you have a Mac with Apple Silicon (install via `brew install libusb`) ### Supported Goldleaf versions | Goldleaf version | NS-USBloader version | @@ -127,6 +128,8 @@ Set 'Security & Privacy' settings if needed. Download application with `-m1.jar` postfix. +Manually install libusb with Homebrew by running `brew install libusb` in your Terminal. + ##### Windows: * Once application opens click on 'Gear' icon. From cd9e3ddf303e8d26e3f6f6143a12bad7937a94f6 Mon Sep 17 00:00:00 2001 From: 01ut <115551882+Erimsaholut@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:05:11 +0300 Subject: [PATCH 124/134] Turkish Lang file I just upload the .properties file --- src/main/resources/locale_tr_TR.properties | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/main/resources/locale_tr_TR.properties diff --git a/src/main/resources/locale_tr_TR.properties b/src/main/resources/locale_tr_TR.properties new file mode 100644 index 0000000..2eb1af8 --- /dev/null +++ b/src/main/resources/locale_tr_TR.properties @@ -0,0 +1,97 @@ +btn_OpenFile=Dosya secimi +btn_OpenFolders=Klasor secimi +btn_Upload=NS'e yukle +btn_OpenFolders_tooltip=Taranmasi icin bir klasor secin.\nBu klasor ve butun alt klasorleri taranacaktir.\nButun uyusan dosyalar listeye eklenecektir. +tab3_Txt_EnteredAsMsg1=Olarak giris yaptiniz: +tab3_Txt_EnteredAsMsg2=Bir cok hatadan kacinmak icin root olmaniz ya da bu kullanici icin 'udev' kurallarini ayarlamaniz gerekmektedir. +tab3_Txt_FilesToUploadTitle=Yuklenecek dosyalar: +tab3_Txt_GreetingsMessage= NS-USBloader'a Hos Geldiniz +tab3_Txt_NoFolderOrFileSelected=Hicbir dosya secilmedi: hicbir sey yuklenmedi. +windowBodyConfirmExit=Data transferi devam ediyor ve uygulamayi kapatmak bunu yarida kesecek.\nBu su an yapabileceginiz en kotu sey.\nIslemi yardida kes ve cik ? +windowTitleConfirmExit=Hayir,Bunu yapma! +btn_Stop=Hata +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=Durum +tab1_table_Lbl_FileName=Dosya Ismi +tab1_table_Lbl_Size=Boyut +tab1_table_Lbl_Upload=Yukle? +tab1_table_contextMenu_Btn_BtnDelete=Kaldir +tab1_table_contextMenu_Btn_DeleteAll=Hepsini kaldir +tab2_Lbl_HostIP=Host IP +tab1_Lbl_NSIP=NS IP: +tab2_Cb_ValidateNSHostName= NS IP girislerinizi her zaman dogrulayin. +windowBodyBadIp=Girilen NS IP adresinin dogru olduguna emin misiniz? +windowTitleBadIp=NS'in IP adressi buyuk ihtimalle yanlis +tab2_Cb_ExpertMode=Uzman modu (NET setup) +tab2_Lbl_HostPort=Port +tab2_Cb_AutoDetectIp=Oto-Tespit IP +tab2_Cb_RandSelectPort=Portu rastgele al +tab2_Cb_DontServeRequests=Istekleri yerine getirme +tab2_Lbl_DontServeRequestsDesc=Eger secili ise, bu bilgisayar NS'den (ag uzerinden) gelen NSP dosyalarinin isteklerine yanit vermez ve Awoo Installer'a (veya uyumlu uygulamalara) dosyalari nerede aramasi gerektigini soylemek icin tanimlanmis ana bilgisayar ayarlarini kullanir. +tab2_Lbl_HostExtra=Ekstra +windowTitleErrorPort=Port seti hatali! +windowBodyErrorPort=Port 0 olamaz ve 65535'den buyuk olamaz. +tab2_Cb_AutoCheckForUpdates=Guncellemeleri otomatik kontrol et +windowTitleNewVersionAval=Yeni versiyon mevcut +windowTitleNewVersionNOTAval=Yeni versiyon mevcut degil +windowTitleNewVersionUnknown=Yeni versiyon kontrolu yapilamiyor +windowBodyNewVersionUnknown=Bir sey ters gitti\nBelki internet yoktur ya da GitHub cokmustur +windowBodyNewVersionNOTAval=En son versiyonu kullaniyorsunuz +tab2_Cb_AllowXciNszXcz=XCI / NSZ / XCZ dosyalaninin Awoo icin seciminine izin ver. +tab2_Lbl_AllowXciNszXczDesc=XCI/NSZ/XCZ'yi destekleyen uygulamalar tarafindan kullanilir ve Awoo (Adubbz/TinFoil olarak da bilinir) transfer protokolunu uygular. Emin degilseniz degistirmeyin. Installer icin AWOO'yu etkinlestirin. +tab2_Lbl_Language=Dil +windowBodyRestartToApplyLang=Lutfen degisikliklerin uygulanabilmesi icin uygulamayi yendiden baslatin. +btn_OpenSplitFile=Bolme sec +tab2_Lbl_ApplicationSettings=Ana ayarlar +tabSplMrg_Lbl_SplitNMergeTitle=Dosyalari bolme & birlestirme araci +tabSplMrg_RadioBtn_Split=Ayir +tabSplMrg_RadioBtn_Merge=Birlestir +tabSplMrg_Txt_File=Dosya: +tabSplMrg_Txt_Folder=Dosyayi ayir (klasor): +tabSplMrg_Btn_SelectFile=Dosya sec +tabSplMrg_Btn_SelectFolder=Klasor Sec +tabSplMrg_Lbl_SaveToLocation=Buraya kaydet: +tabSplMrg_Btn_ChangeSaveToLocation=Degistir +tabSplMrg_Btn_Convert=Cevir +windowTitleError=Hata +windowBodyPleaseFinishTransfersFirst=Bolme/Birlestirme islemleri USB/Network transfer sureci aktifken yapilamaz. Lutfen once aktif transfer isleminizi bitiriniz. +done_txt=Hazir! +failure_txt=Basarisiz +btn_Select=Secim yap +btn_InjectPayloader=payload yukle (enject) +tabNXDT_Btn_Start=Basla! +tab2_Btn_InstallDrivers=Suruculeri indir ve yukle +windowTitleDownloadDrivers=Suruculeri indir ve yukle +windowBodyDownloadDrivers=Suruculer indiriliyor (libusbK v3.0.7.0)... +btn_Cancel=Iptal +btn_Close=Kapat +tab2_Cb_GlVersion=GoldLeaf versiyonu +tab2_Cb_GLshowNspOnly=Goldleaf'ta sadece *.nsp goster. +windowBodyPleaseStopOtherProcessFirst=Lutfen devam etmeden once butun diger aktif islemleri durdurun. +tab2_Cb_foldersSelectorForRoms=Direkt ROM dosyalini secmek yerine ROM klasorunu sec +tab2_Cb_foldersSelectorForRomsDesc=Oyunlar' sekmesinde 'Dosyalari Sec' dugmesi davranisini degistirir: Tek tek ROM dosyalarini secmek yerine, desteklenen her dosyayi bir klasor secerek ekleyebilirsiniz. +windowTitleAddingFiles=Dosyalar araniyor... +windowBodyFilesScanned=Dosyalar tarandi: %d\nEklenecek: %d +tab2_Lbl_AwooBlockTitle=Awoo Yukleyicisi ve Uyumu +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Lbl_Firmware=Firmware: +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Btn_fromFolder=Klasorden +tabPatches_Btn_asZipFile=ZIP dosyasi olarak +tabPatches_Lbl_Title=Yamalar +tabPatches_Lbl_Keys=Anahtarlar: +tabPatches_Btn_MakeEs=ES yap +tabPatches_Btn_MakeFs=FS yap +tabPatches_Btn_MakeAtmo=Loader yap (Atmosphere) +tabPatches_Btn_MakeAll=Hepsini yap +tabPatches_ServiceWindowMessageEsFs=Firmware ve anahtarlar, yamalari olusturmak icin ayarlanmalidir. Aksi takdirde, neyi duzeltecegi belirsiz olacaktir. +tabPatches_ServiceWindowMessageLoader='Loader' yamasi olusturmak icin Atmosphere klasoru tanimlanmalidir. +tab2_Btn_ApplicationFont=Uygulama yazi tipini degistir +btn_ResetToDefaults=Reset +fontPreviewText=Yazi Onizlemesi +fontSize=Yazi boyutu: From d382181700162d5812034185c0b256075f1c5590 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Mon, 27 May 2024 19:15:04 +0300 Subject: [PATCH 125/134] Update architecture. Change mediator, add simple observer to track active 'transfers' --- .../Controllers/FilesDropHandle.java | 11 +- .../Controllers/FilesDropHandleTask.java | 8 +- .../Controllers/FontSettingsController.java | 6 +- .../Controllers/GamesController.java | 173 +++++++++--------- .../nsusbloader/Controllers/ISubscriber.java | 25 +++ .../Controllers/NSLMainController.java | 58 ++---- .../Controllers/NSTableViewController.java | 23 ++- .../Controllers/NxdtController.java | 29 ++- .../Controllers/PatchesController.java | 26 +-- .../java/nsusbloader/Controllers/Payload.java | 48 +++++ .../Controllers/RcmController.java | 45 ++--- .../SettingsBlockGenericController.java | 11 +- .../Controllers/SplitMergeController.java | 164 ++++++++--------- .../java/nsusbloader/MediatorControl.java | 77 ++++---- .../ModelControllers/LogPrinterGui.java | 10 +- .../ModelControllers/MessagesConsumer.java | 52 ++---- src/main/java/nsusbloader/NSLMain.java | 7 +- .../java/nsusbloader/TransfersPublisher.java | 47 +++++ .../nsusbloader/com/usb/GoldLeaf_010.java | 4 +- .../java/nsusbloader/com/usb/GoldLeaf_07.java | 4 +- .../java/nsusbloader/com/usb/GoldLeaf_08.java | 4 +- .../java/integration/EsIntegrationTest.java | 2 +- .../java/integration/FsIntegrationTest.java | 2 +- 23 files changed, 462 insertions(+), 374 deletions(-) create mode 100644 src/main/java/nsusbloader/Controllers/ISubscriber.java create mode 100644 src/main/java/nsusbloader/Controllers/Payload.java create mode 100644 src/main/java/nsusbloader/TransfersPublisher.java diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java index 29f4482..d2042cc 100644 --- a/src/main/java/nsusbloader/Controllers/FilesDropHandle.java +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandle.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -39,10 +39,13 @@ import java.util.ResourceBundle; public class FilesDropHandle { - public FilesDropHandle(List<File> files, String filesRegex, String foldersRegex){ + public FilesDropHandle(List<File> files, + String filesRegex, + String foldersRegex, + NSTableViewController tableController){ FilesDropHandleTask filesDropHandleTask = new FilesDropHandleTask(files, filesRegex, foldersRegex); - ResourceBundle resourceBundle = MediatorControl.getInstance().getResourceBundle(); + ResourceBundle resourceBundle = MediatorControl.INSTANCE.getResourceBundle(); Button cancelButton = new Button(resourceBundle.getString("btn_Cancel")); ProgressIndicator progressIndicator = new ProgressIndicator(); @@ -101,7 +104,7 @@ public class FilesDropHandle { List<File> allFiles = filesDropHandleTask.getValue(); if (! allFiles.isEmpty()) { - MediatorControl.getInstance().getGamesController().tableFilesListController.setFiles(allFiles); + tableController.setFiles(allFiles); } stage.close(); }); diff --git a/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java b/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java index 93bc8ad..598bc41 100644 --- a/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java +++ b/src/main/java/nsusbloader/Controllers/FilesDropHandleTask.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -32,7 +32,7 @@ public class FilesDropHandleTask extends Task<List<File>> { private final List<File> filesDropped; private final List<File> allFiles; - private String messageTemplate; + private final String messageTemplate; private long filesScanned = 0; private long filesAdded = 0; @@ -43,12 +43,12 @@ public class FilesDropHandleTask extends Task<List<File>> { this.filesRegex = filesRegex; this.foldersRegex = foldersRegex; this.allFiles = new ArrayList<>(); - this.messageTemplate = MediatorControl.getInstance().getResourceBundle().getString("windowBodyFilesScanned"); + this.messageTemplate = MediatorControl.INSTANCE.getResourceBundle().getString("windowBodyFilesScanned"); } @Override protected List<File> call() { - if (filesDropped == null || filesDropped.size() == 0) + if (filesDropped == null || filesDropped.isEmpty()) return allFiles; for (File file : filesDropped){ diff --git a/src/main/java/nsusbloader/Controllers/FontSettingsController.java b/src/main/java/nsusbloader/Controllers/FontSettingsController.java index 0f7f14c..98c20a6 100644 --- a/src/main/java/nsusbloader/Controllers/FontSettingsController.java +++ b/src/main/java/nsusbloader/Controllers/FontSettingsController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2023 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -142,7 +142,9 @@ public class FontSettingsController implements Initializable { final double fontSize = fontSizeSpinner.getValue().intValue(); preferences.setFontStyle(fontFamily, fontSize); - MediatorControl.getInstance().updateApplicationFont(fontFamily, fontSize); + + MediatorControl.INSTANCE.getLogArea().getScene().getRoot().setStyle( + String.format("-fx-font-family: \"%s\"; -fx-font-size: %.0f;", fontFamily, fontSize)); closeWindow(); } diff --git a/src/main/java/nsusbloader/Controllers/GamesController.java b/src/main/java/nsusbloader/Controllers/GamesController.java index 3c78b99..364cb08 100644 --- a/src/main/java/nsusbloader/Controllers/GamesController.java +++ b/src/main/java/nsusbloader/Controllers/GamesController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko, wolfposd + Copyright 2019-2024 Dmitry Isaenko, wolfposd This file is part of NS-USBloader. @@ -31,6 +31,7 @@ import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import nsusbloader.AppPreferences; +import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.com.net.NETCommunications; import nsusbloader.com.usb.UsbCommunications; import nsusbloader.FilesHelper; @@ -45,12 +46,14 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; -public class GamesController implements Initializable { +public class GamesController implements Initializable, ISubscriber { private static final String REGEX_ONLY_NSP = ".*\\.nsp$"; private static final String REGEX_ALLFILES_TINFOIL = ".*\\.(nsp$|xci$|nsz$|xcz$)"; private static final String REGEX_ALLFILES = ".*"; + private static final MediatorControl mediator = MediatorControl.INSTANCE; + @FXML private AnchorPane usbNetPane; @@ -63,7 +66,7 @@ public class GamesController implements Initializable { @FXML private Button switchThemeBtn; @FXML - public NSTableViewController tableFilesListController; // Accessible from Mediator (for drag-n-drop support) + private NSTableViewController tableFilesListController; @FXML private Button selectNspBtn, selectSplitBtn, uploadStopBtn; @@ -101,6 +104,7 @@ public class GamesController implements Initializable { disableUploadStopBtn(tableFilesListController.isFilesForUploadListEmpty()); }); // Add listener to notify tableView controller tableFilesListController.setNewProtocol(getSelectedProtocolByName()); // Notify tableView controller + tableFilesListController.setGamesController(this); ObservableList<String> choiceNetUsbList = FXCollections.observableArrayList("USB", "NET"); choiceNetUsb.setItems(choiceNetUsbList); @@ -204,11 +208,11 @@ public class GamesController implements Initializable { } private boolean isAllFiletypesAllowedForGL() { - return ! MediatorControl.getInstance().getSettingsController().getGoldleafSettings().getNSPFileFilterForGL(); + return ! mediator.getSettingsController().getGoldleafSettings().getNSPFileFilterForGL(); } private boolean isXciNszXczSupport() { - return MediatorControl.getInstance().getSettingsController().getTinfoilSettings().isXciNszXczSupport(); + return mediator.getSettingsController().getTinfoilSettings().isXciNszXczSupport(); } /** @@ -216,7 +220,7 @@ public class GamesController implements Initializable { * tinfoil + xcinszxcz </br> * tinfoil + nsponly </br> * goldleaf </br> - * etc.. + * etc... */ private String getRegexForFiles() { if (isTinfoil() && isXciNszXczSupport()) @@ -368,56 +372,58 @@ public class GamesController implements Initializable { if (workThread != null && workThread.isAlive()) return; - // Collect files - List<File> nspToUpload; - - TextArea logArea = MediatorControl.getInstance().getContoller().logArea; - if (isTinfoil() && tableFilesListController.getFilesForUpload() == null) { - logArea.setText(resourceBundle.getString("tab3_Txt_NoFolderOrFileSelected")); + ServiceWindow.getInfoNotification("(o_o\")", resourceBundle.getString("tab3_Txt_NoFolderOrFileSelected")); return; } - if ((nspToUpload = tableFilesListController.getFilesForUpload()) != null){ + // Collect files + List<File> nspToUpload = tableFilesListController.getFilesForUpload(); + + if (nspToUpload == null) + nspToUpload = new ArrayList<>(); + //todo: add to make it visible + /* + else { + TextArea logArea = mediator.getLogArea(); logArea.setText(resourceBundle.getString("tab3_Txt_FilesToUploadTitle")+"\n"); nspToUpload.forEach(item -> logArea.appendText(" "+item.getAbsolutePath()+"\n")); } - else { - logArea.clear(); - nspToUpload = new LinkedList<>(); - } + */ - SettingsController settings = MediatorControl.getInstance().getSettingsController(); + SettingsController settings = mediator.getSettingsController(); // If USB selected if (isGoldLeaf()){ final SettingsBlockGoldleafController goldleafSettings = settings.getGoldleafSettings(); usbNetCommunications = new UsbCommunications(nspToUpload, "GoldLeaf" + goldleafSettings.getGlVer(), goldleafSettings.getNSPFileFilterForGL()); } - else if (( isTinfoil() && getSelectedNetUsb().equals("USB") )){ - usbNetCommunications = new UsbCommunications(nspToUpload, "TinFoil", false); - } - else { // NET INSTALL OVER TINFOIL - final String ipValidationPattern = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; - final SettingsBlockTinfoilController tinfoilSettings = settings.getTinfoilSettings(); - - if (tinfoilSettings.isValidateNSHostName() && ! getNsIp().matches(ipValidationPattern)) { - if (!ServiceWindow.getConfirmationWindow(resourceBundle.getString("windowTitleBadIp"), resourceBundle.getString("windowBodyBadIp"))) - return; + else { + if (getSelectedNetUsb().equals("USB")){ + usbNetCommunications = new UsbCommunications(nspToUpload, "TinFoil", false); } + else { // NET INSTALL OVER TINFOIL + final String ipValidationPattern = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; + final SettingsBlockTinfoilController tinfoilSettings = settings.getTinfoilSettings(); - String nsIP = getNsIp(); + if (tinfoilSettings.isValidateNSHostName() && ! getNsIp().matches(ipValidationPattern)) { + if (!ServiceWindow.getConfirmationWindow(resourceBundle.getString("windowTitleBadIp"), resourceBundle.getString("windowBodyBadIp"))) + return; + } - if (! tinfoilSettings.isExpertModeSelected()) - usbNetCommunications = new NETCommunications(nspToUpload, nsIP, false, "", "", ""); - else { - usbNetCommunications = new NETCommunications( - nspToUpload, - nsIP, - tinfoilSettings.isNoRequestsServe(), - tinfoilSettings.isAutoDetectIp()?"":tinfoilSettings.getHostIp(), - tinfoilSettings.isRandomlySelectPort()?"":tinfoilSettings.getHostPort(), - tinfoilSettings.isNoRequestsServe()?tinfoilSettings.getHostExtra():"" - ); + String nsIP = getNsIp(); + + if (! tinfoilSettings.isExpertModeSelected()) + usbNetCommunications = new NETCommunications(nspToUpload, nsIP, false, "", "", ""); + else { + usbNetCommunications = new NETCommunications( + nspToUpload, + nsIP, + tinfoilSettings.isNoRequestsServe(), + tinfoilSettings.isAutoDetectIp()?"":tinfoilSettings.getHostIp(), + tinfoilSettings.isRandomlySelectPort()?"":tinfoilSettings.getHostPort(), + tinfoilSettings.isNoRequestsServe()?tinfoilSettings.getHostExtra():"" + ); + } } } workThread = new Thread(usbNetCommunications); @@ -446,7 +452,7 @@ public class GamesController implements Initializable { * */ @FXML private void handleDragOver(DragEvent event){ - if (event.getDragboard().hasFiles() && ! MediatorControl.getInstance().getTransferActive()) + if (event.getDragboard().hasFiles() && ! mediator.getTransferActive()) event.acceptTransferModes(TransferMode.ANY); event.consume(); } @@ -456,47 +462,15 @@ public class GamesController implements Initializable { @FXML private void handleDrop(DragEvent event) { List<File> files = event.getDragboard().getFiles(); - new FilesDropHandle(files, getRegexForFiles(), getRegexForFolders()); + new FilesDropHandle(files, getRegexForFiles(), getRegexForFolders(), tableFilesListController); event.setDropCompleted(true); event.consume(); } - + /** - * This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission" - * Called from mediator - * TODO: remove shitcoding practices + * This function called from NSTableViewController * */ - public void notifyThreadStarted(boolean isActive, EModule type){ - if (! type.equals(EModule.USB_NET_TRANSFERS)){ - usbNetPane.setDisable(isActive); - return; - } - - selectNspBtn.setDisable(isActive); - selectSplitBtn.setDisable(isActive); - btnUpStopImage.getStyleClass().clear(); - - if (isActive) { - btnUpStopImage.getStyleClass().add("regionStop"); - - uploadStopBtn.setOnAction(e-> stopBtnAction()); - uploadStopBtn.setText(resourceBundle.getString("btn_Stop")); - uploadStopBtn.getStyleClass().remove("buttonUp"); - uploadStopBtn.getStyleClass().add("buttonStop"); - } - else { - btnUpStopImage.getStyleClass().add("regionUpload"); - - uploadStopBtn.setOnAction(e-> uploadBtnAction()); - uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); - uploadStopBtn.getStyleClass().remove("buttonStop"); - uploadStopBtn.getStyleClass().add("buttonUp"); - } - } - /** - * Crunch. This function called from NSTableViewController - * */ - public void disableUploadStopBtn(boolean disable){ + void disableUploadStopBtn(boolean disable){ if (isTinfoil()) uploadStopBtn.setDisable(disable); else @@ -515,11 +489,8 @@ public class GamesController implements Initializable { }).start(); } - public void updateFilesSelectorButtonBehaviour(boolean isDirectoryChooser){ + void setFilesSelectorButtonBehaviour(boolean isDirectoryChooser){ btnSelectImage.getStyleClass().clear(); - setFilesSelectorButtonBehaviour(isDirectoryChooser); - } - private void setFilesSelectorButtonBehaviour(boolean isDirectoryChooser){ if (isDirectoryChooser){ selectNspBtn.setOnAction(e -> selectFoldersBtnAction()); btnSelectImage.getStyleClass().add("regionScanFolders"); @@ -535,7 +506,7 @@ public class GamesController implements Initializable { /** * Get 'Recent' path */ - public String getRecentPath(){ + private String getRecentPath(){ return previouslyOpenedPath; } @@ -547,4 +518,42 @@ public class GamesController implements Initializable { preferences.setNetUsb(getSelectedNetUsb()); preferences.setNsIp(getNsIp()); } + + /** + * This thing modifies UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission" + * */ + @Override + public void notify(EModule type, boolean isActive, Payload payload) { + if (! type.equals(EModule.USB_NET_TRANSFERS)){ + usbNetPane.setDisable(isActive); + return; + } + + selectNspBtn.setDisable(isActive); + selectSplitBtn.setDisable(isActive); + btnUpStopImage.getStyleClass().clear(); + + if (isActive) { + btnUpStopImage.getStyleClass().add("regionStop"); + + uploadStopBtn.setOnAction(e-> stopBtnAction()); + uploadStopBtn.setText(resourceBundle.getString("btn_Stop")); + uploadStopBtn.getStyleClass().remove("buttonUp"); + uploadStopBtn.getStyleClass().add("buttonStop"); + return; + } + btnUpStopImage.getStyleClass().add("regionUpload"); + + uploadStopBtn.setOnAction(e-> uploadBtnAction()); + uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); + uploadStopBtn.getStyleClass().remove("buttonStop"); + uploadStopBtn.getStyleClass().add("buttonUp"); + + Map<String, EFileStatus> statusMap = payload.getStatusMap(); + + if (! statusMap.isEmpty()) { + for (String key : statusMap.keySet()) + tableFilesListController.setFileStatus(key, statusMap.get(key)); + } + } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/Controllers/ISubscriber.java b/src/main/java/nsusbloader/Controllers/ISubscriber.java new file mode 100644 index 0000000..1e1ba59 --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/ISubscriber.java @@ -0,0 +1,25 @@ +/* + 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 <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import nsusbloader.NSLDataTypes.EModule; + +public interface ISubscriber { + void notify(EModule type, boolean status, Payload payload); +} diff --git a/src/main/java/nsusbloader/Controllers/NSLMainController.java b/src/main/java/nsusbloader/Controllers/NSLMainController.java index 533c450..a159c9e 100644 --- a/src/main/java/nsusbloader/Controllers/NSLMainController.java +++ b/src/main/java/nsusbloader/Controllers/NSLMainController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -18,7 +18,6 @@ */ package nsusbloader.Controllers; -import javafx.application.HostServices; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -35,10 +34,10 @@ public class NSLMainController implements Initializable { private ResourceBundle resourceBundle; @FXML - public TextArea logArea; // Accessible from Mediator + private TextArea logArea; @FXML - public ProgressBar progressBar; // Accessible from Mediator + private ProgressBar progressBar; @FXML private TabPane mainTabPane; @@ -68,8 +67,6 @@ public class NSLMainController implements Initializable { logArea.appendText(rb.getString("tab3_Txt_GreetingsMessage2")+"\n"); - MediatorControl.getInstance().setController(this); - AppPreferences preferences = AppPreferences.getInstance(); if (preferences.getAutoCheckUpdates()) @@ -79,6 +76,22 @@ public class NSLMainController implements Initializable { mainTabPane.getTabs().remove(3); openLastOpenedTab(); + + TransfersPublisher transfersPublisher = new TransfersPublisher( + GamesTabController, + SplitMergeTabController, + RcmTabController, + NXDTabController, + PatchesTabController); + + MediatorControl.INSTANCE.configure( + resourceBundle, + SettingsTabController, + logArea, + progressBar, + GamesTabController, + transfersPublisher); + } private void checkForUpdates(){ Task<List<String>> updTask = new UpdatesChecker(); @@ -101,40 +114,7 @@ public class NSLMainController implements Initializable { updates.setDaemon(true); updates.start(); } - /** - * Get resources - * TODO: Find better solution; used in UsbCommunications() -> GL -> SelectFile command - * @return ResourceBundle - */ - public ResourceBundle getResourceBundle() { - return resourceBundle; - } - /** - * Provide hostServices to Settings tab - * */ - public void setHostServices(HostServices hs ){ SettingsTabController.getGenericSettings().registerHostServices(hs);} - /** - * Get 'Settings' controller - * Used by FrontController - * */ - public SettingsController getSettingsCtrlr(){ - return SettingsTabController; - } - - public GamesController getGamesCtrlr(){ - return GamesTabController; - } - - public SplitMergeController getSmCtrlr(){ - return SplitMergeTabController; - } - - public RcmController getRcmCtrlr(){ return RcmTabController; } - - public NxdtController getNXDTabController(){ return NXDTabController; } - - public PatchesController getPatchesTabController(){ return PatchesTabController; } /** * Save preferences before exit * */ diff --git a/src/main/java/nsusbloader/Controllers/NSTableViewController.java b/src/main/java/nsusbloader/Controllers/NSTableViewController.java index 267da43..379ba79 100644 --- a/src/main/java/nsusbloader/Controllers/NSTableViewController.java +++ b/src/main/java/nsusbloader/Controllers/NSTableViewController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko, wolfposd + Copyright 2019-2024 Dmitry Isaenko, wolfposd This file is part of NS-USBloader. @@ -42,6 +42,8 @@ public class NSTableViewController implements Initializable { private TableView<NSLRowModel> table; private ObservableList<NSLRowModel> rowsObsLst; + private GamesController gamesController; + @Override public void initialize(URL url, ResourceBundle resourceBundle) { rowsObsLst = FXCollections.observableArrayList(); @@ -52,10 +54,10 @@ public class NSTableViewController implements Initializable { table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); table.setOnKeyPressed(keyEvent -> { if (!rowsObsLst.isEmpty()) { - if (keyEvent.getCode() == KeyCode.DELETE && !MediatorControl.getInstance().getTransferActive()) { + if (keyEvent.getCode() == KeyCode.DELETE && !MediatorControl.INSTANCE.getTransferActive()) { rowsObsLst.removeAll(table.getSelectionModel().getSelectedItems()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better + gamesController.disableUploadStopBtn(true); table.refresh(); } else if (keyEvent.getCode() == KeyCode.SPACE) { for (NSLRowModel item : table.getSelectionModel().getSelectedItems()) { @@ -173,13 +175,13 @@ public class NSTableViewController implements Initializable { deleteMenuItem.setOnAction(actionEvent -> { rowsObsLst.remove(row.getItem()); if (rowsObsLst.isEmpty()) - MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better + gamesController.disableUploadStopBtn(true); table.refresh(); }); MenuItem deleteAllMenuItem = new MenuItem(resourceBundle.getString("tab1_table_contextMenu_Btn_DeleteAll")); deleteAllMenuItem.setOnAction(actionEvent -> { rowsObsLst.clear(); - MediatorControl.getInstance().getGamesController().disableUploadStopBtn(true); // TODO: change to something better + gamesController.disableUploadStopBtn(true); table.refresh(); }); contextMenu.getItems().addAll(deleteMenuItem, deleteAllMenuItem); @@ -189,7 +191,7 @@ public class NSTableViewController implements Initializable { Bindings.when( Bindings.isNotNull( row.itemProperty())) - .then(MediatorControl.getInstance().getTransferActive()?null:contextMenu) + .then(MediatorControl.INSTANCE.getTransferActive()?null:contextMenu) .otherwise((ContextMenu) null) ); // Just.. don't ask.. @@ -210,6 +212,11 @@ public class NSTableViewController implements Initializable { table.getColumns().add(fileSizeColumn); table.getColumns().add(uploadColumn); } + + public void setGamesController(GamesController gamesController) { + this.gamesController = gamesController; + } + /** * Add single file when user selected it (Split file usually) * */ @@ -224,7 +231,7 @@ public class NSTableViewController implements Initializable { } else { rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getGamesController().disableUploadStopBtn(false); // TODO: change to something better + gamesController.disableUploadStopBtn(false); } table.refresh(); } @@ -244,7 +251,7 @@ public class NSTableViewController implements Initializable { else { for (File file: newFiles) rowsObsLst.add(new NSLRowModel(file, true)); - MediatorControl.getInstance().getGamesController().disableUploadStopBtn(false); // TODO: change to something better + gamesController.disableUploadStopBtn(false); } //rowsObsLst.get(0).setMarkForUpload(true); table.refresh(); diff --git a/src/main/java/nsusbloader/Controllers/NxdtController.java b/src/main/java/nsusbloader/Controllers/NxdtController.java index c824cc9..a6439c6 100644 --- a/src/main/java/nsusbloader/Controllers/NxdtController.java +++ b/src/main/java/nsusbloader/Controllers/NxdtController.java @@ -25,8 +25,6 @@ import javafx.scene.control.Label; import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; import nsusbloader.AppPreferences; -import nsusbloader.FilesHelper; -import nsusbloader.MediatorControl; import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.Utilities.nxdumptool.NxdtTask; @@ -35,7 +33,7 @@ import java.io.File; import java.net.URL; import java.util.ResourceBundle; -public class NxdtController implements Initializable { +public class NxdtController implements Initializable, ISubscriber { @FXML private Label saveToLocationLbl, statusLbl; @@ -79,7 +77,6 @@ public class NxdtController implements Initializable { * */ private void startDumpProcess(){ if ((workThread == null || ! workThread.isAlive())){ - MediatorControl.getInstance().getContoller().logArea.clear(); nxdtTask = new NxdtTask(saveToLocationLbl.getText()); workThread = new Thread(nxdtTask); @@ -97,12 +94,22 @@ public class NxdtController implements Initializable { } } - public void notifyThreadStarted(boolean isActive, EModule type){ + /** + * Save application settings on exit + * */ + public void updatePreferencesOnExit(){ + AppPreferences.getInstance().setNXDTSaveToLocation(saveToLocationLbl.getText()); + } + + @Override + public void notify(EModule type, boolean isActive, Payload payload) { if (! type.equals(EModule.NXDT)){ injectPldBtn.setDisable(isActive); return; } + statusLbl.setText(payload.getMessage()); + if (isActive) { btnDumpStopImage.getStyleClass().clear(); btnDumpStopImage.getStyleClass().add("regionStop"); @@ -121,16 +128,4 @@ public class NxdtController implements Initializable { injectPldBtn.getStyleClass().remove("buttonStop"); injectPldBtn.getStyleClass().add("buttonUp"); } - public void setOneLineStatus(boolean status){ - if (status) - statusLbl.setText(rb.getString("done_txt")); - else - statusLbl.setText(rb.getString("failure_txt")); - } - /** - * Save application settings on exit - * */ - public void updatePreferencesOnExit(){ - AppPreferences.getInstance().setNXDTSaveToLocation(saveToLocationLbl.getText()); - } } diff --git a/src/main/java/nsusbloader/Controllers/PatchesController.java b/src/main/java/nsusbloader/Controllers/PatchesController.java index 6afd267..a2f0b8c 100644 --- a/src/main/java/nsusbloader/Controllers/PatchesController.java +++ b/src/main/java/nsusbloader/Controllers/PatchesController.java @@ -1,5 +1,5 @@ /* - Copyright 2018-2022 Dmitry Isaenko + Copyright 2018-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -46,7 +46,7 @@ import nsusbloader.Utilities.patches.fs.FsPatchMaker; import nsusbloader.Utilities.patches.loader.LoaderPatchMaker; // TODO: CLI SUPPORT -public class PatchesController implements Initializable { +public class PatchesController implements Initializable, ISubscriber { @FXML private VBox patchesToolPane; @FXML @@ -237,9 +237,8 @@ public class PatchesController implements Initializable { if (workThread != null && workThread.isAlive()) return; - statusLbl.setText(""); - if (MediatorControl.getInstance().getTransferActive()) { + if (MediatorControl.INSTANCE.getTransferActive()) { ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); return; @@ -261,9 +260,8 @@ public class PatchesController implements Initializable { if (workThread != null && workThread.isAlive()) return; - statusLbl.setText(""); - if (MediatorControl.getInstance().getTransferActive()) { + if (MediatorControl.INSTANCE.getTransferActive()) { ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); return; @@ -285,9 +283,8 @@ public class PatchesController implements Initializable { if (workThread != null && workThread.isAlive()) return; - statusLbl.setText(""); - if (MediatorControl.getInstance().getTransferActive()) { + if (MediatorControl.INSTANCE.getTransferActive()) { ServiceWindow.getErrorNotification(resourceBundle.getString("windowTitleError"), resourceBundle.getString("windowBodyPleaseStopOtherProcessFirst")); return; @@ -306,18 +303,20 @@ public class PatchesController implements Initializable { workThread.interrupt(); } - public void notifyThreadStarted(boolean isActive, EModule type) { + @Override + public void notify(EModule type, boolean isActive, Payload payload) { if (! type.equals(EModule.PATCHES)) { patchesToolPane.setDisable(isActive); return; } + statusLbl.setText(payload.getMessage()); + convertRegionEs.getStyleClass().clear(); makeFsBtn.setVisible(! isActive); makeLoaderBtn.setVisible(! isActive); if (isActive) { - MediatorControl.getInstance().getContoller().logArea.clear(); convertRegionEs.getStyleClass().add("regionStop"); makeEsBtn.setOnAction(e-> interruptProcessOfPatchMaking()); @@ -334,13 +333,6 @@ public class PatchesController implements Initializable { makeEsBtn.getStyleClass().add("buttonUp"); } - public void setOneLineStatus(boolean statusSuccess){ - if (statusSuccess) - statusLbl.setText(resourceBundle.getString("done_txt")); - else - statusLbl.setText(resourceBundle.getString("failure_txt")); - } - void updatePreferencesOnExit(){ AppPreferences.getInstance().setPatchesSaveToLocation(saveToLbl.getText()); if (locationKeysLbl.getText().isEmpty()) diff --git a/src/main/java/nsusbloader/Controllers/Payload.java b/src/main/java/nsusbloader/Controllers/Payload.java new file mode 100644 index 0000000..3b37fc1 --- /dev/null +++ b/src/main/java/nsusbloader/Controllers/Payload.java @@ -0,0 +1,48 @@ +/* + 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 <https://www.gnu.org/licenses/>. + */ +package nsusbloader.Controllers; + +import nsusbloader.NSLDataTypes.EFileStatus; + +import java.util.Collections; +import java.util.Map; + +public class Payload { + private final String message; + private final Map<String, EFileStatus> statusMap; + + public Payload(){ + this(""); + } + public Payload(String message){ + this(message, Collections.emptyMap()); + } + public Payload(String message, Map<String, EFileStatus> statusMap){ + this.message = message; + this.statusMap = statusMap; + } + + public String getMessage() { + return message; + } + + public Map<String, EFileStatus> getStatusMap() { + return statusMap; + } +} diff --git a/src/main/java/nsusbloader/Controllers/RcmController.java b/src/main/java/nsusbloader/Controllers/RcmController.java index 3fc6b5e..de29650 100644 --- a/src/main/java/nsusbloader/Controllers/RcmController.java +++ b/src/main/java/nsusbloader/Controllers/RcmController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -41,7 +41,7 @@ import java.io.File; import java.net.URL; import java.util.ResourceBundle; -public class RcmController implements Initializable { +public class RcmController implements Initializable, ISubscriber { @FXML private ToggleGroup rcmToggleGrp; @@ -68,12 +68,14 @@ public class RcmController implements Initializable { @FXML private Label statusLbl; + private AppPreferences preferences; private ResourceBundle rb; private String myRegexp; + @Override public void initialize(URL url, ResourceBundle resourceBundle) { this.rb = resourceBundle; - final AppPreferences preferences = AppPreferences.getInstance(); + this.preferences = AppPreferences.getInstance(); rcmToggleGrp.selectToggle(pldrRadio1); pldrRadio1.setOnAction(e -> statusLbl.setText("")); @@ -193,8 +195,7 @@ public class RcmController implements Initializable { } private void smash(){ - statusLbl.setText(""); - if (MediatorControl.getInstance().getTransferActive()) { + if (MediatorControl.INSTANCE.getTransferActive()) { ServiceWindow.getErrorNotification(rb.getString("windowTitleError"), rb.getString("windowBodyPleaseStopOtherProcessFirst")); return; @@ -273,31 +274,28 @@ public class RcmController implements Initializable { private void bntResetPayloader(ActionEvent event){ final Node btn = (Node)event.getSource(); + statusLbl.setText(""); + switch (btn.getId()){ case "resPldBtn1": payloadFNameLbl1.setText(""); payloadFPathLbl1.setText(""); - statusLbl.setText(""); break; case "resPldBtn2": payloadFNameLbl2.setText(""); payloadFPathLbl2.setText(""); - statusLbl.setText(""); break; case "resPldBtn3": payloadFNameLbl3.setText(""); payloadFPathLbl3.setText(""); - statusLbl.setText(""); break; case "resPldBtn4": payloadFNameLbl4.setText(""); payloadFPathLbl4.setText(""); - statusLbl.setText(""); break; case "resPldBtn5": payloadFNameLbl5.setText(""); payloadFPathLbl5.setText(""); - statusLbl.setText(""); } } @@ -324,27 +322,20 @@ public class RcmController implements Initializable { } } - public void setOneLineStatus(boolean statusSuccess){ - if (statusSuccess) - statusLbl.setText(rb.getString("done_txt")); - else - statusLbl.setText(rb.getString("failure_txt")); - } - - public void notifyThreadStarted(boolean isStart, EModule type){ - rcmToolPane.setDisable(isStart); - if (type.equals(EModule.RCM) && isStart){ - MediatorControl.getInstance().getContoller().logArea.clear(); - } + @Override + public void notify(EModule type, boolean isActive, Payload payload) { + rcmToolPane.setDisable(isActive); + if (type.equals(EModule.RCM)) + statusLbl.setText(payload.getMessage()); } /** * Save application settings on exit * */ public void updatePreferencesOnExit(){ - AppPreferences.getInstance().setRecentRcm(1, payloadFPathLbl1.getText()); - AppPreferences.getInstance().setRecentRcm(2, payloadFPathLbl2.getText()); - AppPreferences.getInstance().setRecentRcm(3, payloadFPathLbl3.getText()); - AppPreferences.getInstance().setRecentRcm(4, payloadFPathLbl4.getText()); - AppPreferences.getInstance().setRecentRcm(5, payloadFPathLbl5.getText()); + preferences.setRecentRcm(1, payloadFPathLbl1.getText()); + preferences.setRecentRcm(2, payloadFPathLbl2.getText()); + preferences.setRecentRcm(3, payloadFPathLbl3.getText()); + preferences.setRecentRcm(4, payloadFPathLbl4.getText()); + preferences.setRecentRcm(5, payloadFPathLbl5.getText()); } } diff --git a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java index c90f049..fc4b584 100644 --- a/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java +++ b/src/main/java/nsusbloader/Controllers/SettingsBlockGenericController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -55,9 +55,7 @@ public class SettingsBlockGenericController implements Initializable { direcroriesChooserForRomsCB; @FXML private Hyperlink newVersionHyperlink; - private ResourceBundle resourceBundle; - private HostServices hostServices; @Override @@ -68,8 +66,8 @@ public class SettingsBlockGenericController implements Initializable { autoCheckForUpdatesCB.setSelected(preferences.getAutoCheckUpdates()); direcroriesChooserForRomsCB.setSelected(preferences.getDirectoriesChooserForRoms()); direcroriesChooserForRomsCB.setOnAction(actionEvent -> - MediatorControl.getInstance().getGamesController().updateFilesSelectorButtonBehaviour(direcroriesChooserForRomsCB.isSelected()) - ); + MediatorControl.INSTANCE.getGamesController().setFilesSelectorButtonBehaviour(direcroriesChooserForRomsCB.isSelected()) + ); Region btnSwitchImage = new Region(); btnSwitchImage.getStyleClass().add("regionUpdatesCheck"); @@ -81,6 +79,7 @@ public class SettingsBlockGenericController implements Initializable { languagesChB.setItems(settingsLanguagesSetup.getLanguages()); languagesChB.getSelectionModel().select(settingsLanguagesSetup.getRecentLanguage()); + hostServices = MediatorControl.INSTANCE.getHostServices(); newVersionHyperlink.setOnAction(e-> hostServices.showDocument(newVersionHyperlink.getText())); checkForUpdBtn.setOnAction(e->checkForUpdatesAction()); submitLanguageBtn.setOnAction(e->languageButtonAction()); @@ -149,8 +148,6 @@ public class SettingsBlockGenericController implements Initializable { return direcroriesChooserForRomsCB.isSelected(); } - protected void registerHostServices(HostServices hostServices){ this.hostServices = hostServices;} - void setNewVersionLink(String newVer){ newVersionHyperlink.setVisible(true); newVersionHyperlink.setText("https://github.com/developersu/ns-usbloader/releases/tag/"+newVer); diff --git a/src/main/java/nsusbloader/Controllers/SplitMergeController.java b/src/main/java/nsusbloader/Controllers/SplitMergeController.java index 57de95d..10e1de3 100644 --- a/src/main/java/nsusbloader/Controllers/SplitMergeController.java +++ b/src/main/java/nsusbloader/Controllers/SplitMergeController.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -31,7 +31,6 @@ import javafx.stage.FileChooser; import nsusbloader.AppPreferences; import nsusbloader.FilesHelper; import nsusbloader.MediatorControl; -import nsusbloader.ModelControllers.CancellableRunnable; import nsusbloader.NSLDataTypes.EModule; import nsusbloader.ServiceWindow; import nsusbloader.Utilities.splitmerge.SplitMergeTaskExecutor; @@ -41,7 +40,7 @@ import java.net.URL; import java.util.List; import java.util.ResourceBundle; -public class SplitMergeController implements Initializable { +public class SplitMergeController implements Initializable, ISubscriber { @FXML private ToggleGroup splitMergeTogGrp; @FXML @@ -147,13 +146,87 @@ public class SplitMergeController implements Initializable { convertBtn.setOnAction(actionEvent -> setConvertBtnAction()); } - public void notifyThreadStarted(boolean isStart, EModule type){ // todo: refactor: remove everything, place to separate container and just disable. - if (! type.equals(EModule.SPLIT_MERGE_TOOL)){ - smToolPane.setDisable(isStart); + /** + * It's button listener when convert-process in progress + * */ + private void stopBtnAction(){ + if (smThread != null && smThread.isAlive()) { + smThread.interrupt(); + } + } + /** + * It's button listener when convert-process NOT in progress + * */ + private void setConvertBtnAction(){ + if (MediatorControl.INSTANCE.getTransferActive()) { + ServiceWindow.getErrorNotification( + resourceBundle.getString("windowTitleError"), + resourceBundle.getString("windowBodyPleaseFinishTransfersFirst") + ); return; } - if (isStart){ - MediatorControl.getInstance().getContoller().logArea.clear(); + + if (splitRad.isSelected()) + smTask = new SplitMergeTaskExecutor(true, BlockListViewController.getItems(), saveToPathLbl.getText()); + else + smTask = new SplitMergeTaskExecutor(false, BlockListViewController.getItems(), saveToPathLbl.getText()); + smThread = new Thread(smTask); + smThread.setDaemon(true); + smThread.start(); + } + /** + * Drag-n-drop support (dragOver consumer) + * */ + @FXML + private void handleDragOver(DragEvent event){ + if (event.getDragboard().hasFiles() && ! MediatorControl.INSTANCE.getTransferActive()) + event.acceptTransferModes(TransferMode.ANY); + event.consume(); + } + /** + * Drag-n-drop support (drop consumer) + * */ + @FXML + private void handleDrop(DragEvent event) { + List<File> files = event.getDragboard().getFiles(); + File firstFile = files.get(0); + + if (firstFile.isDirectory()) + mergeRad.fire(); + else + splitRad.fire(); + + this.BlockListViewController.addAll(files); + + event.setDropCompleted(true); + event.consume(); + } + + + /** + * Save application settings on exit + * */ + public void updatePreferencesOnExit(){ + if (splitRad.isSelected()) + AppPreferences.getInstance().setSplitMergeType(0); + else + AppPreferences.getInstance().setSplitMergeType(1); + + AppPreferences.getInstance().setSplitMergeRecent(saveToPathLbl.getText()); + } + + @Override + public void notify(EModule type, boolean isActive, Payload payload) { + // todo: refactor: remove everything, place to separate container and just disable. + + if (! type.equals(EModule.SPLIT_MERGE_TOOL)){ + smToolPane.setDisable(isActive); + return; + } + + statusLbl.setText(payload.getMessage()); + + if (isActive){ splitRad.setDisable(true); mergeRad.setDisable(true); selectFileFolderBtn.setDisable(true); @@ -182,79 +255,4 @@ public class SplitMergeController implements Initializable { else convertRegion.getStyleClass().add("regionOneToSplit"); } - - /** - * It's button listener when convert-process in progress - * */ - private void stopBtnAction(){ - if (smThread != null && smThread.isAlive()) { - smThread.interrupt(); - } - } - /** - * It's button listener when convert-process NOT in progress - * */ - private void setConvertBtnAction(){ - statusLbl.setText(""); - if (MediatorControl.getInstance().getTransferActive()) { - ServiceWindow.getErrorNotification( - resourceBundle.getString("windowTitleError"), - resourceBundle.getString("windowBodyPleaseFinishTransfersFirst") - ); - return; - } - - if (splitRad.isSelected()) - smTask = new SplitMergeTaskExecutor(true, BlockListViewController.getItems(), saveToPathLbl.getText()); - else - smTask = new SplitMergeTaskExecutor(false, BlockListViewController.getItems(), saveToPathLbl.getText()); - smThread = new Thread(smTask); - smThread.setDaemon(true); - smThread.start(); - } - /** - * Drag-n-drop support (dragOver consumer) - * */ - @FXML - private void handleDragOver(DragEvent event){ - if (event.getDragboard().hasFiles() && ! MediatorControl.getInstance().getTransferActive()) - event.acceptTransferModes(TransferMode.ANY); - event.consume(); - } - /** - * Drag-n-drop support (drop consumer) - * */ - @FXML - private void handleDrop(DragEvent event) { - List<File> files = event.getDragboard().getFiles(); - File firstFile = files.get(0); - - if (firstFile.isDirectory()) - mergeRad.fire(); - else - splitRad.fire(); - - this.BlockListViewController.addAll(files); - - event.setDropCompleted(true); - event.consume(); - } - - public void setOneLineStatus(boolean status){ - if (status) - statusLbl.setText(resourceBundle.getString("done_txt")); - else - statusLbl.setText(resourceBundle.getString("failure_txt")); - } - /** - * Save application settings on exit - * */ - public void updatePreferencesOnExit(){ - if (splitRad.isSelected()) - AppPreferences.getInstance().setSplitMergeType(0); - else - AppPreferences.getInstance().setSplitMergeType(1); - - AppPreferences.getInstance().setSplitMergeRecent(saveToPathLbl.getText()); - } } \ No newline at end of file diff --git a/src/main/java/nsusbloader/MediatorControl.java b/src/main/java/nsusbloader/MediatorControl.java index a73b239..af2cb27 100644 --- a/src/main/java/nsusbloader/MediatorControl.java +++ b/src/main/java/nsusbloader/MediatorControl.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -18,50 +18,57 @@ */ package nsusbloader; +import javafx.application.HostServices; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.TextArea; import nsusbloader.Controllers.*; import nsusbloader.NSLDataTypes.EModule; import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicBoolean; public class MediatorControl { - private final AtomicBoolean isTransferActive = new AtomicBoolean(false); // Overcoded just for sure - private NSLMainController mainController; + public static final MediatorControl INSTANCE = new MediatorControl(); - public static MediatorControl getInstance(){ - return MediatorControlHold.INSTANCE; + private ResourceBundle resourceBundle; + private TransfersPublisher transfersPublisher; + private HostServices hostServices; + private GamesController gamesController; + private SettingsController settingsController; + + private TextArea logArea; + private ProgressBar progressBar; + + private MediatorControl(){} + + public void configure(ResourceBundle resourceBundle, + SettingsController settingsController, + TextArea logArea, + ProgressBar progressBar, + GamesController gamesController, + TransfersPublisher transfersPublisher) { + this.resourceBundle = resourceBundle; + this.settingsController = settingsController; + this.gamesController = gamesController; + this.logArea = logArea; + this.progressBar = progressBar; + this.transfersPublisher = transfersPublisher; + } + public void setHostServices(HostServices hostServices) { + this.hostServices = hostServices; } - private static class MediatorControlHold { - private static final MediatorControl INSTANCE = new MediatorControl(); - } - public void setController(NSLMainController controller){ - this.mainController = controller; + public HostServices getHostServices() { return hostServices; } + public ResourceBundle getResourceBundle(){ return resourceBundle; } + public SettingsController getSettingsController() { return settingsController; } + public GamesController getGamesController() { return gamesController; } + public TextArea getLogArea() { return logArea; } + public ProgressBar getProgressBar() { return progressBar; } + + public synchronized void setTransferActive(EModule appModuleType, boolean isActive, Payload payload) { + transfersPublisher.setTransferActive(appModuleType, isActive, payload); } - public NSLMainController getContoller(){ return mainController; } - public GamesController getGamesController(){ return mainController.getGamesCtrlr(); } - public SettingsController getSettingsController(){ return mainController.getSettingsCtrlr(); } - public SplitMergeController getSplitMergeController(){ return mainController.getSmCtrlr(); } - public RcmController getRcmController(){ return mainController.getRcmCtrlr(); } - public NxdtController getNxdtController(){ return mainController.getNXDTabController(); } - public PatchesController getPatchesController(){ return mainController.getPatchesTabController(); } - - public ResourceBundle getResourceBundle(){ - return mainController.getResourceBundle(); - } - - public synchronized void setBgThreadActive(boolean isActive, EModule appModuleType) { - isTransferActive.set(isActive); - getGamesController().notifyThreadStarted(isActive, appModuleType); - getSplitMergeController().notifyThreadStarted(isActive, appModuleType); - getRcmController().notifyThreadStarted(isActive, appModuleType); - getNxdtController().notifyThreadStarted(isActive, appModuleType); - getPatchesController().notifyThreadStarted(isActive, appModuleType); - } - public synchronized boolean getTransferActive() { return this.isTransferActive.get(); } - public void updateApplicationFont(String fontFamily, double fontSize){ - mainController.logArea.getScene().getRoot().setStyle( - String.format("-fx-font-family: \"%s\"; -fx-font-size: %.0f;", fontFamily, fontSize)); + public synchronized boolean getTransferActive() { + return transfersPublisher.getTransferActive(); } } diff --git a/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java b/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java index d557964..606771d 100644 --- a/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java +++ b/src/main/java/nsusbloader/ModelControllers/LogPrinterGui.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -40,9 +40,13 @@ public class LogPrinterGui implements ILogPrinter { LogPrinterGui(EModule whoIsAsking){ this.msgQueue = new LinkedBlockingQueue<>(); this.progressQueue = new LinkedBlockingQueue<>(); - this.statusMap = new HashMap<>(); + this.statusMap = new HashMap<>(); this.oneLinerStatus = new AtomicBoolean(); - this.msgConsumer = new MessagesConsumer(whoIsAsking, this.msgQueue, this.progressQueue, this.statusMap, this.oneLinerStatus); + this.msgConsumer = new MessagesConsumer(whoIsAsking, + this.msgQueue, + this.progressQueue, + this.statusMap, + this.oneLinerStatus); this.msgConsumer.start(); } /** diff --git a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java index 08add56..491d5ad 100644 --- a/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java +++ b/src/main/java/nsusbloader/ModelControllers/MessagesConsumer.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -22,24 +22,26 @@ import javafx.animation.AnimationTimer; import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.TextArea; -import nsusbloader.Controllers.NSTableViewController; +import nsusbloader.Controllers.Payload; import nsusbloader.MediatorControl; import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EModule; import java.util.ArrayList; import java.util.HashMap; +import java.util.ResourceBundle; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; public class MessagesConsumer extends AnimationTimer { - private final BlockingQueue<String> msgQueue; - private final TextArea logsArea; + private static final MediatorControl mediator = MediatorControl.INSTANCE; + private static final TextArea logsArea = mediator.getLogArea(); + private static final ProgressBar progressBar = mediator.getProgressBar();; + private static final ResourceBundle resourceBundle = mediator.getResourceBundle(); + private final BlockingQueue<String> msgQueue; private final BlockingQueue<Double> progressQueue; - private final ProgressBar progressBar; private final HashMap<String, EFileStatus> statusMap; - private final NSTableViewController tableViewController; private final EModule appModuleType; private final AtomicBoolean oneLinerStatus; @@ -53,22 +55,16 @@ public class MessagesConsumer extends AnimationTimer { AtomicBoolean oneLinerStatus){ this.appModuleType = appModuleType; this.isInterrupted = false; - this.msgQueue = msgQueue; - this.logsArea = MediatorControl.getInstance().getContoller().logArea; - this.progressQueue = progressQueue; - this.progressBar = MediatorControl.getInstance().getContoller().progressBar; - this.statusMap = statusMap; - this.tableViewController = MediatorControl.getInstance().getGamesController().tableFilesListController; - this.oneLinerStatus = oneLinerStatus; progressBar.setProgress(0.0); - progressBar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS); - MediatorControl.getInstance().setBgThreadActive(true, appModuleType); + + logsArea.clear(); + mediator.setTransferActive(appModuleType, true, new Payload()); } @Override @@ -89,32 +85,18 @@ public class MessagesConsumer extends AnimationTimer { }); } - if (isInterrupted) // It's safe 'cuz it's could't be interrupted while HashMap populating + if (isInterrupted) // safe, could not be interrupted while HashMap populating updateElementsAndStop(); } private void updateElementsAndStop(){ - MediatorControl.getInstance().setBgThreadActive(false, appModuleType); + Payload payload = new Payload( + resourceBundle.getString(oneLinerStatus.get() ? "done_txt" : "failure_txt"), + statusMap); + + mediator.setTransferActive(appModuleType, false, payload); progressBar.setProgress(0.0); - if (statusMap.size() > 0){ - for (String key : statusMap.keySet()) - tableViewController.setFileStatus(key, statusMap.get(key)); - } - - switch (appModuleType){ - case RCM: - MediatorControl.getInstance().getRcmController().setOneLineStatus(oneLinerStatus.get()); - break; - case NXDT: - MediatorControl.getInstance().getNxdtController().setOneLineStatus(oneLinerStatus.get()); - break; - case SPLIT_MERGE_TOOL: - MediatorControl.getInstance().getSplitMergeController().setOneLineStatus(oneLinerStatus.get()); - break; - case PATCHES: - MediatorControl.getInstance().getPatchesController().setOneLineStatus(oneLinerStatus.get()); - } this.stop(); } diff --git a/src/main/java/nsusbloader/NSLMain.java b/src/main/java/nsusbloader/NSLMain.java index fa40c9a..ce98308 100644 --- a/src/main/java/nsusbloader/NSLMain.java +++ b/src/main/java/nsusbloader/NSLMain.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -68,14 +68,15 @@ public class NSLMain extends Application { primaryStage.show(); primaryStage.setOnCloseRequest(e->{ - if (MediatorControl.getInstance().getTransferActive()) + if (MediatorControl.INSTANCE.getTransferActive()) if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit"))) e.consume(); }); NSLMainController controller = loader.getController(); - controller.setHostServices(getHostServices()); + MediatorControl.INSTANCE.setHostServices(getHostServices()); + primaryStage.setOnHidden(e-> { AppPreferences.getInstance().setSceneHeight(mainScene.getHeight()); AppPreferences.getInstance().setSceneWidth(mainScene.getWidth()); diff --git a/src/main/java/nsusbloader/TransfersPublisher.java b/src/main/java/nsusbloader/TransfersPublisher.java new file mode 100644 index 0000000..246cbb8 --- /dev/null +++ b/src/main/java/nsusbloader/TransfersPublisher.java @@ -0,0 +1,47 @@ +/* + 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 <https://www.gnu.org/licenses/>. + */ +package nsusbloader; + +import nsusbloader.Controllers.ISubscriber; +import nsusbloader.Controllers.Payload; +import nsusbloader.NSLDataTypes.EModule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class TransfersPublisher { + private final AtomicBoolean isTransferActive = new AtomicBoolean(false); + + private final List<ISubscriber> subscribers = new ArrayList<>(); + + public TransfersPublisher(ISubscriber... subscriber){ + subscribers.addAll(Arrays.asList(subscriber)); + } + + public void setTransferActive(EModule appModuleType, boolean isActive, Payload payload) { + isTransferActive.set(isActive); + subscribers.forEach(s->s.notify(appModuleType, isActive, payload)); + } + + public boolean getTransferActive() { + return isTransferActive.get(); + } +} diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java index a5669d2..3f420a0 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_010.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2022 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -933,7 +933,7 @@ class GoldLeaf_010 extends TransferModule { private boolean selectFile(){ File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + 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. diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java index 38adb7e..640032b 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_07.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -918,7 +918,7 @@ class GoldLeaf_07 extends TransferModule { private boolean selectFile(){ File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + 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 prio. fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); return fChooser.showOpenDialog(null); // Leave as is for now. diff --git a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java index f8bf657..c367fd8 100644 --- a/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java +++ b/src/main/java/nsusbloader/com/usb/GoldLeaf_08.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Dmitry Isaenko + Copyright 2019-2024 Dmitry Isaenko This file is part of NS-USBloader. @@ -941,7 +941,7 @@ class GoldLeaf_08 extends TransferModule { private boolean selectFile(){ File selectedFile = CompletableFuture.supplyAsync(() -> { FileChooser fChooser = new FileChooser(); - fChooser.setTitle(MediatorControl.getInstance().getResourceBundle().getString("btn_OpenFile")); // TODO: FIX BAD IMPLEMENTATION + 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 prio. fChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("*", "*")); return fChooser.showOpenDialog(null); // Leave as is for now. diff --git a/src/test/java/integration/EsIntegrationTest.java b/src/test/java/integration/EsIntegrationTest.java index c8ec24e..8fb868c 100644 --- a/src/test/java/integration/EsIntegrationTest.java +++ b/src/test/java/integration/EsIntegrationTest.java @@ -22,7 +22,7 @@ public class EsIntegrationTest { pathToKeysFile = environment.getProdkeysLocation(); saveTo = environment.getSaveToLocation() + File.separator + "ES_LPR"; pathToFirmwares = environment.getFirmwaresLocation(); - pathToFirmware = pathToFirmware + File.separator + "Firmware 14.1.0"; + pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 17.0.0"; } @DisplayName("ES Integration validation - everything") diff --git a/src/test/java/integration/FsIntegrationTest.java b/src/test/java/integration/FsIntegrationTest.java index 5a85acc..f56544c 100644 --- a/src/test/java/integration/FsIntegrationTest.java +++ b/src/test/java/integration/FsIntegrationTest.java @@ -25,7 +25,7 @@ public class FsIntegrationTest { pathToKeysFile = environment.getProdkeysLocation(); saveTo = environment.getSaveToLocation() + File.separator + "FS_LPR"; pathToFirmwares = environment.getFirmwaresLocation(); - pathToFirmware = pathToFirmware + File.separator + "Firmware 13.0.0"; + pathToFirmware = environment.getFirmwaresLocation() + File.separator + "Firmware 17.0.0"; } @DisplayName("FS Integration validation - everything") From 7bf48c6e51a31b24c0f6b95f446a3cf109308ba2 Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Mon, 2 Sep 2024 10:18:11 +0800 Subject: [PATCH 126/134] Update locale_zh_CN.properties --- src/main/resources/locale_zh_CN.properties | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index d428871..525573e 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -79,4 +79,20 @@ windowBodyFilesScanned=\u626B\u63CF\u6587\u4EF6: %25d\n\u88AB\u6DFB\u52A0: %25d tab2_Lbl_AwooBlockTitle=awoo installer \u5B8C\u6210 tabRcm_Lbl_Payload=Payload: tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Lbl_Firmware=Firmware: +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Btn_fromFolder=From folder +tabPatches_Btn_asZipFile=as ZIP file +tabPatches_Lbl_Title=Patches +tabPatches_Lbl_Keys=Keys: +tabPatches_Btn_MakeEs=Make ES +tabPatches_Btn_MakeFs=Make FS +tabPatches_Btn_MakeAtmo=Make Loader (Atmosphere) +tabPatches_Btn_MakeAll=Make all +tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. +tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. +tab2_Btn_ApplicationFont=Change application font +btn_ResetToDefaults=Reset +fontPreviewText=Text preview +fontSize=Font size: From 0429b4dc453cbe78b567be74f0bb61fdab1ecbca Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Mon, 2 Sep 2024 10:22:08 +0800 Subject: [PATCH 127/134] Update locale_zh_CN.properties --- src/main/resources/locale_zh_CN.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 525573e..3930d52 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -91,8 +91,8 @@ tabPatches_Btn_MakeAtmo=Make Loader (Atmosphere) tabPatches_Btn_MakeAll=Make all tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. -tab2_Btn_ApplicationFont=Change application font +tab2_Btn_ApplicationFont=修改程序字体 btn_ResetToDefaults=Reset fontPreviewText=Text preview -fontSize=Font size: +fontSize=字体大小: From 0b3ac9513014974589a56009fac426776ae5da4c Mon Sep 17 00:00:00 2001 From: exiori <exiori@qq.com> Date: Mon, 2 Sep 2024 10:35:55 +0800 Subject: [PATCH 128/134] Sync locale.properties and Update --- src/main/resources/locale_zh_CN.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/locale_zh_CN.properties b/src/main/resources/locale_zh_CN.properties index 3930d52..2ad405d 100644 --- a/src/main/resources/locale_zh_CN.properties +++ b/src/main/resources/locale_zh_CN.properties @@ -91,8 +91,8 @@ tabPatches_Btn_MakeAtmo=Make Loader (Atmosphere) tabPatches_Btn_MakeAll=Make all tabPatches_ServiceWindowMessageEsFs=Both firmware and keys should be set to generate patches. Otherwise, it's not clear what to patch. tabPatches_ServiceWindowMessageLoader=Atmosphere folder should be defined to generate 'Loader' patch. -tab2_Btn_ApplicationFont=修改程序字体 -btn_ResetToDefaults=Reset -fontPreviewText=Text preview -fontSize=字体大小: +tab2_Btn_ApplicationFont=\u4fee\u6539\u7a0b\u5e8f\u5b57\u4f53 +btn_ResetToDefaults=\u91cd\u7f6e +fontPreviewText=\u6587\u5b57\u9884\u89c8 +fontSize=\u5b57\u53f7: From 2ef3ccd44238432eb5aa65d36c321f1a0ce57d97 Mon Sep 17 00:00:00 2001 From: DDinghoya <ddinghoya@gmail.com> Date: Sat, 28 Sep 2024 23:46:44 +0900 Subject: [PATCH 129/134] Update locale_ko_KR.properties --- src/main/resources/locale_ko_KR.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/locale_ko_KR.properties b/src/main/resources/locale_ko_KR.properties index 2a66669..71e5ba4 100644 --- a/src/main/resources/locale_ko_KR.properties +++ b/src/main/resources/locale_ko_KR.properties @@ -91,3 +91,7 @@ tabPatches_Btn_MakeAtmo=\uB85C\uB354 \uB9CC\uB4E4\uAE30 (Atmosphere) tabPatches_Btn_MakeAll=\uBAA8\uB450 \uB9CC\uB4E4\uAE30 tabPatches_ServiceWindowMessageEsFs=\uD38C\uC6E8\uC5B4\uC640 \uD0A4 \uBAA8\uB450 \uD328\uCE58\uB97C \uC0DD\uC131\uD558\uB3C4\uB85D \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. \uADF8\uB807\uC9C0 \uC54A\uC73C\uBA74 \uBB34\uC5C7\uC744 \uD328\uCE58\uD560\uC9C0 \uBA85\uD655\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. tabPatches_ServiceWindowMessageLoader='\uB85C\uB354' \uD328\uCE58\uB97C \uC0DD\uC131\uD558\uB824\uBA74 Atmosphere \uD3F4\uB354\uB97C \uC815\uC758\uD574\uC57C \uD569\uB2C8\uB2E4. +tab2_Btn_ApplicationFont=\uC751\uC6A9 \uD504\uB85C\uADF8\uB7A8 \uAE00\uAF34 \uBCC0\uACBD +btn_ResetToDefaults=\uC7AC\uC124\uC815 +fontPreviewText=\uD14D\uC2A4\uD2B8 \uBBF8\uB9AC\uBCF4\uAE30 +fontSize=\uAE00\uAF34 \uC0AC\uC774\uC988: From 6cac53df7ca6b1a35faec7e0081aa712b9b193d5 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 6 Oct 2024 16:10:06 +0300 Subject: [PATCH 130/134] remove no longer valid info --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index d01cd88..b84d0eb 100644 --- a/README.md +++ b/README.md @@ -256,17 +256,6 @@ If you like this app, just give a star (@ GitHub). This is non-commercial project. -Nevertheless, I'll be more than happy if you find a chance to make a donation for charity to people I trust: - -* BTC → 1BnErE3n6LEdEjvvFrt4FMdXd1UGa5L7Ge -* ETH → 0x9c29418129553bE171181bb6245151aa0576A3b7 -* DOT → 15BWSwmA4xEHZdq3gGftWg7dctMQk9vXwqA92Pg22gsxDweF -* LTC → ltc1qfjvzxm04tax077ra9rvmxdnsum8alws2n20fag -* ETC → 0xe9064De288C8454942533a005AB72515e689226E -* USDT (TRC20) → TKgp5SvJGiqYNFtvJfEDGLFbezFEHq1tBy -* USDT (ERC20) → 0x9c29418129553bE171181bb6245151aa0576A3b7 -* XRP → rGmGaLsKmSUbxWfyi4mujtVamTzj3Nqxbw. - Thanks! Appreciate assistance and support of both [Vitaliy](https://github.com/SebastianUA) and [Konstantin](https://github.com/konstantin-kelemen). Without you all this magic would not have happened. From 0ca162be1ebbea70ba6e7376d710b1659ad846b9 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 6 Oct 2024 16:32:38 +0300 Subject: [PATCH 131/134] increment version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 163c17c..82ee300 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>7.1</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers --> + <version>7.2</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers --> <url>https://redrise.ru</url> <description>NS multi-tool</description> From e3c22390a9edb85f37e3f62e7875b027ccfc0b49 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 31 Oct 2024 00:45:51 +0300 Subject: [PATCH 132/134] Correct text --- README.md | 1 + src/main/java/nsusbloader/com/usb/UsbConnect.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b84d0eb..d8aa487 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Sometimes I add new posts about this project [on my blog page](https://developer * Swedish by [Daniel Nylander](https://github.com/yeager) * Japanese by [kuragehime](https://github.com/kuragehimekurara1) * Ryukyuan languages by [kuragehime](https://github.com/kuragehimekurara1) +* Turkish language by [Erimsaholut](https://github.com/Erimsaholut) * Angelo Elias Dalzotto makes packages in AUR * Phoenix[Msc] provides his shiny Mac M1 for debug diff --git a/src/main/java/nsusbloader/com/usb/UsbConnect.java b/src/main/java/nsusbloader/com/usb/UsbConnect.java index f4e5ad3..99e0c75 100644 --- a/src/main/java/nsusbloader/com/usb/UsbConnect.java +++ b/src/main/java/nsusbloader/com/usb/UsbConnect.java @@ -169,7 +169,7 @@ public class UsbConnect { "Double check that you have administrator privileges (you're 'root') or check 'udev' rules set for this user (linux only)!\n\n" + "Steps to set 'udev' rules:\n" + "root # vim /etc/udev/rules.d/99-NS" + ((RCM_VID == VENDOR_ID) ? "RCM" : "") + ".rules\n" + - "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%04x\", ATTRS{idProduct}==\"%04x\", GROUP=\"plugdev\"\n" + + "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%04x\", ATTRS{idProduct}==\"%04x\", MODE=\"0666\"\n" + "root # udevadm control --reload-rules && udevadm trigger\n", UsbErrorCodes.getErrCode(returningValue), VENDOR_ID, PRODUCT_ID)); } else From ff31494f50eb432b040cb733613e903ac36e90a8 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Thu, 21 Nov 2024 01:42:57 +0300 Subject: [PATCH 133/134] Switch to Woodpecker CI --- .woodpecker/woodpecker.yml | 82 +++++++++++++++++++++++++++++++++ README.md | 2 +- misc/windows/NSIS/installer.nsi | 4 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 .woodpecker/woodpecker.yml diff --git a/.woodpecker/woodpecker.yml b/.woodpecker/woodpecker.yml new file mode 100644 index 0000000..11eea07 --- /dev/null +++ b/.woodpecker/woodpecker.yml @@ -0,0 +1,82 @@ +steps: + - name: test-standard + when: + event: [tag, push] + image: maven:3-openjdk-17 + commands: + - mvn -B -DskipTests clean package + - mvn test -B + - echo target/ns-usbloader-*jar + - mkdir artifacts + - cp target/ns-usbloader-*jar artifacts + volumes: + - /home/docker/woodpecker/files/m2:/root/.m2 + + - name: make-windows-installer + when: + event: [tag, push] + image: wheatstalk/makensis:3 + commands: + - cp target/NS-USBloader.exe misc/windows/NSIS/ + - misc/windows/update_version.sh + - cd misc/windows/NSIS + - makensis -V4 ./installer.nsi + - echo Installer-*.exe + - cp Installer-*.exe "../../../artifacts" + - rm ./NS-USBloader.exe + - rm ./Installer-*.exe + - cd ../../../ + volumes: + - /home/docker/woodpecker/files/assembly/openjdk-19.0.2:/assembly/jdk + - /home/docker/woodpecker/files/assembly/Drivers_set.exe:/assembly/Drivers_set.exe + + - name: emerge-legacy-artifact + when: + event: [tag, push] + image: maven:3-openjdk-17 + commands: + - . ./.make_legacy + - mvn -B -DskipTests clean package + - echo target/ns-usbloader-*jar + - cp target/ns-usbloader-*jar artifacts + volumes: + - /home/docker/woodpecker/files/m2:/root/.m2 + + - name: make-legacy-windows-installer + when: + event: [tag, push] + image: wheatstalk/makensis:3 + commands: + - cp target/NS-USBloader.exe misc/windows/NSIS/ + - misc/windows/update_version.sh legacy + - cd misc/windows/NSIS + - makensis -V4 ./installer.nsi + - echo Installer-*.exe + - cp Installer-*.exe "../../../artifacts" + - cd ../../../ + volumes: + - /home/docker/woodpecker/files/assembly/openjdk-19.0.2:/assembly/jdk + - /home/docker/woodpecker/files/assembly/Drivers_set.exe:/assembly/Drivers_set.exe + + - name: emerge-mac-m1-artifact + when: + event: [tag, push] + image: maven:3-openjdk-17 + commands: + - . ./.make_m1 + - mvn -B -DskipTests clean package + - echo target/ns-usbloader-*jar + - cp target/ns-usbloader-*jar artifacts + volumes: + - /home/docker/woodpecker/files/m2:/root/.m2 + + - name: save-artifacts + when: + event: [tag, push] + image: alpine:latest + commands: + - export ARTIFACTS_DIR="$(date -d @$CI_PIPELINE_CREATED +'%Y-%m-%d %H:%m %Z')" + - mkdir -p /builds/ns-usbloader/ + - mv artifacts "/builds/ns-usbloader/$ARTIFACTS_DIR" + volumes: + - /home/www/builds:/builds \ No newline at end of file diff --git a/README.md b/README.md index d8aa487..91f8aa2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ <h1 align="center"><img src="screenshots/ApplicationLogo.svg" alt="NS-USBloader" width="450px"/></h1> -   [](https://ci.redrise.ru/desu/ns-usbloader) +   [](https://ci.redrise.ru/repos/12) NS-USBloader is: * A PC-side installer for **[Huntereb/Awoo-Installer](https://github.com/Huntereb/Awoo-Installer)** / other compatible installers (USB and Network supported) and **[XorTroll/Goldleaf](https://github.com/XorTroll/Goldleaf)** (USB) NSP installer. diff --git a/misc/windows/NSIS/installer.nsi b/misc/windows/NSIS/installer.nsi index 4351b11..67c1073 100644 --- a/misc/windows/NSIS/installer.nsi +++ b/misc/windows/NSIS/installer.nsi @@ -91,8 +91,8 @@ Section "NS-USBloader" Install SetOutPath "$INSTDIR" - file /r jdk - file Drivers_set.exe + file /r \assembly\jdk + file \assembly\Drivers_set.exe file NS-USBloader.exe file logo.ico From 6d243a2be1a015cfc4b6cd38bb0dd4fe47903c95 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko <developer.su@gmail.com> Date: Sun, 26 Jan 2025 23:05:24 +0300 Subject: [PATCH 134/134] Close #171. BlytheScythe contribution --- README.md | 3 +- pom.xml | 2 +- src/main/resources/SettingsBlockGeneric.fxml | 4 +- .../resources/locale_sr_RS_#LATN.properties | 97 +++++++++++++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/locale_sr_RS_#LATN.properties diff --git a/README.md b/README.md index 91f8aa2..8c6161e 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ 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 @@ -247,7 +248,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..d39d2f5 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <name>NS-USBloader</name> <artifactId>ns-usbloader</artifactId> - <version>7.2</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers --> + <version>7.3</version> <!-- linked via script to NSIS system. Should have format of 2 blocks of numbers --> <url>https://redrise.ru</url> <description>NS multi-tool</description> 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 @@ <Label text="%tab2_Lbl_ApplicationSettings" /> <HBox alignment="CENTER_LEFT" spacing="5.0"> <children> - <Label text="%tab2_Lbl_Language" /> - <ChoiceBox fx:id="languagesChB" prefWidth="180.0" /> + <Label minWidth="35.0" text="%tab2_Lbl_Language" /> + <ChoiceBox fx:id="languagesChB" prefWidth="240.0" /> <Button fx:id="submitLanguageBtn" mnemonicParsing="false" text="OK" /> <VBox alignment="CENTER_RIGHT" HBox.hgrow="ALWAYS"> <children> diff --git a/src/main/resources/locale_sr_RS_#LATN.properties b/src/main/resources/locale_sr_RS_#LATN.properties new file mode 100644 index 0000000..6df3ac0 --- /dev/null +++ b/src/main/resources/locale_sr_RS_#LATN.properties @@ -0,0 +1,97 @@ +btn_OpenFile=Izaberite datoteke +btn_OpenFolders=Izaberite fasciklu +btn_Upload=Otpremi na NS +btn_OpenFolders_tooltip=Izaberite fasciklu koju \u017Eelite da se skenira.\nIzabrana fascikla i sve podfascikle \u0107e biti skenirane.\nSve odgovaraju\u0107e datoteke \u0107e biti dodane na listu. +tab3_Txt_EnteredAsMsg1=Upisani ste kao: +tab3_Txt_EnteredAsMsg2=Potrebno je imati root privilegije ili podesiti "udev" pravila za ovog korisnika kako bi se izbegli bilo kakvi problemi. +tab3_Txt_FilesToUploadTitle=Datoteke za otpremanje: +tab3_Txt_GreetingsMessage=Dobrodo\u0161li u NS-USBloader +tab3_Txt_NoFolderOrFileSelected=Nije izabrana nijedna datoteka: Nema ni\u0161ta za otpremanje. +windowBodyConfirmExit=Prenos podataka je u toku i zatvaranje ove aplikacije \u0107e je prekinuti.\nTo je najgore \u0161to mo\u017Eete u\u010Diniti u ovom trenutku.\Da li \u017Eelite da prekinete proces i iza\u0111ete? +windowTitleConfirmExit=Ne, nemoj to uraditi! +btn_Stop=Prekini +tab3_Txt_GreetingsMessage2=--\n\ +Source: https://git.redrise.ru/desu/ns-usbloader\n\ +Mirror: https://github.com/developersu/ns-usbloader/\n\ +Site: https://redrise.ru\n\ +Dmitry Isaenko [developer.su] +tab1_table_Lbl_Status=Status +tab1_table_Lbl_FileName=Naziv datoteke +tab1_table_Lbl_Size=Veli\u010Dina +tab1_table_Lbl_Upload=Otpremi? +tab1_table_contextMenu_Btn_BtnDelete=Ukloni +tab1_table_contextMenu_Btn_DeleteAll=Ukloni sve +tab2_Lbl_HostIP=Host IP +tab1_Lbl_NSIP=NS IP: +tab2_Cb_ValidateNSHostName=Uvijek proverite uneseni NS IP. +windowBodyBadIp=Da li ste sigurni da ste ispravno uneli NS IP adresu? +windowTitleBadIp=NS IP adresa je verovatno neta\u010Dna. +tab2_Cb_ExpertMode=Napredni re\u017Eim (NET pode\u0161avanje) +tab2_Lbl_HostPort=port +tab2_Cb_AutoDetectIp=Automatski prona\u0111i IP +tab2_Cb_RandSelectPort=Nasumi\u010Dno dodeli port +tab2_Cb_DontServeRequests=Ne uslu\u017Euj zahteve +tab2_Lbl_DontServeRequestsDesc=Ako je opcija izabrana, ovaj ra\u010Dunar nec\u0301e odgovarati na zahteve NSP datoteka koje dolaze od NS-a (preko mre\u017Ee) i koristiti definisana pode\u0161avanja host-a da ka\u017Ee Awoo Installer-u (ili kompatibilnim aplikacijama) gde treba da tra\u017Ei datoteke. +tab2_Lbl_HostExtra=dodatno +windowTitleErrorPort=Port je pogre\u0161no pode\u0161en! +windowBodyErrorPort=Port ne mo\u017Ee biti 0 ili vec\u0301i od 65535. +tab2_Cb_AutoCheckForUpdates=Automatski proveri a\u017Euriranja +windowTitleNewVersionAval=Nova verzija dostupna +windowTitleNewVersionNOTAval=Nema dostupnih novih verzija +windowTitleNewVersionUnknown=Nije moguc\u0301e proveriti da li postoje nove verzije +windowBodyNewVersionUnknown=Ne\u0161to nije u redu\nMo\u017Eda Internet konekcija nije dostupna ili GitHub ne radi +windowBodyNewVersionNOTAval=Koristite najnoviju verziju +tab2_Cb_AllowXciNszXcz=Dozvolite izbor XCI / NSZ / XCZ datoteka za Awoo +tab2_Lbl_AllowXciNszXczDesc=Kori\u0161teno od strane aplikacija koje podr\u017Eavaju XCI/NSZ/XCZ i koriste Awoo (poznatiji kao Adubbz/TinFoil) protokol za prenos. Ne menjajte ako niste sigurni. Omoguc\u0301i za Awoo Installer. +tab2_Lbl_Language=Jezik +windowBodyRestartToApplyLang=Ponovo pokrenite aplikaciju da biste primenili promene. +btn_OpenSplitFile=Izaberite razdvojene +tab2_Lbl_ApplicationSettings=Glavna pode\u0161avanja +tabSplMrg_Lbl_SplitNMergeTitle=Alat za razdvajanje i spajanje datoteka +tabSplMrg_RadioBtn_Split=Razdvoji +tabSplMrg_RadioBtn_Merge=Spoji +tabSplMrg_Txt_File=Datoteka: +tabSplMrg_Txt_Folder=Razdvoji datoteku (fasciklu): +tabSplMrg_Btn_SelectFile=Izaberite datoteku +tabSplMrg_Btn_SelectFolder=Izaberite fasciklu +tabSplMrg_Lbl_SaveToLocation=Sa\u010Duvaj na: +tabSplMrg_Btn_ChangeSaveToLocation=Promeni +tabSplMrg_Btn_Convert=Pretvori +windowTitleError=Gre\u0161ka +windowBodyPleaseFinishTransfersFirst=Nije moguc\u0301e razdvojiti/spojiti datoteke kada je aktivan USB/mre\u017Eni proces aplikacije. Najpre prekinite aktivne transfere. +done_txt=Gotovo! +failure_txt=Nije uspelo +btn_Select=Izaberi +btn_InjectPayloader=Po\u0161alji payloadbtn_OpenSplitFile +tabNXDT_Btn_Start=Po\u010Dni! +tab2_Btn_InstallDrivers=Preuzmi i instaliraj drajvere +windowTitleDownloadDrivers=Preuzmi i instaliraj drajvere +windowBodyDownloadDrivers=Preuzimanje drajvera (libusbK v3.0.7.0)... +btn_Cancel=Otka\u017Ei +btn_Close=Zatvori +tab2_Cb_GlVersion=GoldLeaf verzija +tab2_Cb_GLshowNspOnly=Prika\u017Ei samo *.nsp u GoldLeaf-u. +windowBodyPleaseStopOtherProcessFirst=Zaustavite druge aktivne procese pre nego \u0161to nastavite. +tab2_Cb_foldersSelectorForRoms=Izaberite fasciklu sa ROM datotekama umesto da birate ROM datoteke pojedina\u010Dno. +tab2_Cb_foldersSelectorForRomsDesc=Menja pona\u0161anje dugmeta "Izaberi datoteke" na kartici "Igre": umesto da birate ROM datoteke jednu po jednu, mo\u017Eete izabrati fasciklu da biste dodali sve podr\u017Eane datoteke odjednom. +windowTitleAddingFiles=Tra\u017Eenje datoteka... +windowBodyFilesScanned=Skenirano datoteka: %d\nBi\u0107e dodano: %d +tab2_Lbl_AwooBlockTitle=Awoo Installer i kompatibilni +tabRcm_Lbl_Payload=Payload: +tabRcm_Lbl_FuseeGelee=Fus\u00E9e Gel\u00E9e RCM +tabPatches_Lbl_Firmware=Firmware: +tabPatches_Lbl_Atmo=Atmosphere: +tabPatches_Btn_fromFolder=Iz fascikle +tabPatches_Btn_asZipFile=kao ZIP datoteka +tabPatches_Lbl_Title=Zakrpe +tabPatches_Lbl_Keys=Klju\u010Devi: +tabPatches_Btn_MakeEs=Napravi ES +tabPatches_Btn_MakeFs=Napravi FS +tabPatches_Btn_MakeAtmo=Napravi Loader (Atmosphere) +tabPatches_Btn_MakeAll=Napravi sve +tabPatches_ServiceWindowMessageEsFs=I firmware i klju\u010Devi treba da budu pode\u0161eni da bi se generisale zakrpe. U suprotnom, nije jasno \u0161ta treba zakrpiti. +tabPatches_ServiceWindowMessageLoader=Fascikla Atmosphere treba da bude definisana da bi se generisala "Loader" zakrpa. +tab2_Btn_ApplicationFont=Promenite font aplikacije +btn_ResetToDefaults=Resetuj +fontPreviewText=Pregled teksta +fontSize=Veli\u010Dina fonta: \ No newline at end of file