Add split-files support for every 'protocol' supported

This commit is contained in:
Dmitry Isaenko 2019-10-27 00:02:40 +03:00
parent 077aa9b0d8
commit 049c07fe8d
28 changed files with 618 additions and 265 deletions

View file

@ -12,7 +12,7 @@ With GUI and cookies. Works on Windows, macOS and Linux.
Sometimes I add new posts about this project [on my home page](https://developersu.blogspot.com/search/label/NS-USBloader). Sometimes I add new posts about this project [on my home page](https://developersu.blogspot.com/search/label/NS-USBloader).
![Screenshot](https://farm8.staticflickr.com/7809/46703921964_53f60f04ed_o.png) ![Screenshot](https://live.staticflickr.com/65535/48962978677_4c3913e8a9_o.png)
#### License #### License
@ -99,7 +99,7 @@ There are three tabs. First one is main.
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 folder with NSPs OR files 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. 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. Table.
@ -140,13 +140,16 @@ usb4java since NS-USBloader-v0.2.3 switched to 1.2.0 instead of 1.3.0. This shou
### Translators! ### 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. 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. 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): #### TODO (maybe):
- [x] [Android support](https://github.com/developersu/ns-usbloader-mobile) - [x] [Android support](https://github.com/developersu/ns-usbloader-mobile)
- [ ] File order sort (non-critical) - [ ] File order sort (non-critical)
- [ ] More deep file analyze before uploading. - [ ] More deep file analyze before uploading.
- [ ] XCI support
## Support this app ## Support this app

View file

@ -8,7 +8,7 @@
<name>NS-USBloader</name> <name>NS-USBloader</name>
<artifactId>ns-usbloader</artifactId> <artifactId>ns-usbloader</artifactId>
<version>0.8.2-SNAPSHOT</version> <version>0.9-SNAPSHOT</version>
<url>https://github.com/developersu/ns-usbloader/</url> <url>https://github.com/developersu/ns-usbloader/</url>
<description> <description>

View file

@ -0,0 +1,91 @@
package nsusbloader.COM.Helpers;
import java.io.*;
/**
* Handle Split files
* */
public class NSSplitReader implements Closeable {
private final String splitFileDir;
private final long referenceSplitChunkSize;
private byte subFileNum;
private long curPosition;
private BufferedInputStream biStream;
public NSSplitReader(File file, long seekToPosition) throws IOException, NullPointerException {
this.splitFileDir = file.getAbsolutePath()+File.separator;
File subFile = new File(file.getAbsolutePath()+File.separator+"00");
if (! file.exists())
throw new FileNotFoundException("File not found on "+file.getAbsolutePath()+File.separator+"00");
this.referenceSplitChunkSize = subFile.length();
this.subFileNum = (byte) (seekToPosition / referenceSplitChunkSize);
this.biStream = new BufferedInputStream(new FileInputStream(splitFileDir + String.format("%02d", subFileNum)));
this.curPosition = seekToPosition;
seekToPosition -= referenceSplitChunkSize * subFileNum;
if (seekToPosition != biStream.skip(seekToPosition))
throw new IOException("Unable to seek to requested position of "+seekToPosition+" for file "+splitFileDir+String.format("%02d", subFileNum));
}
public long seek(long position) throws IOException{
byte subFileRequested = (byte) (position / referenceSplitChunkSize);
if ((subFileRequested != this.subFileNum) || (curPosition > position)) {
biStream.close();
biStream = new BufferedInputStream(new FileInputStream(splitFileDir + String.format("%02d", subFileRequested)));
this.subFileNum = subFileRequested;
this.curPosition = referenceSplitChunkSize * subFileRequested;
}
long retVal = biStream.skip(position - curPosition);
retVal += curPosition;
this.curPosition = position;
return retVal;
}
public int read(byte[] readBuffer) throws IOException, NullPointerException {
final int requested = readBuffer.length;
int readPrtOne;
if ( (curPosition + requested) <= (referenceSplitChunkSize * (subFileNum+1))) {
if ((readPrtOne = biStream.read(readBuffer)) < 0 )
return readPrtOne;
curPosition += readPrtOne;
return readPrtOne;
}
int partOne = (int) (referenceSplitChunkSize * (subFileNum+1) - curPosition);
int partTwo = requested - partOne;
int readPrtTwo;
if ( (readPrtOne = biStream.read(readBuffer, 0, partOne)) < 0)
return readPrtOne;
curPosition += readPrtOne;
if (readPrtOne != partOne)
return readPrtOne;
biStream.close();
subFileNum += 1;
biStream = new BufferedInputStream(new FileInputStream(splitFileDir + String.format("%02d", subFileNum)));
if ( (readPrtTwo = biStream.read(readBuffer, partOne, partTwo) ) < 0)
return readPrtTwo;
curPosition += readPrtTwo;
return readPrtOne + readPrtTwo;
}
@Override
public void close() throws IOException {
if (biStream != null)
biStream.close();
}
}

View file

@ -1,9 +1,10 @@
package nsusbloader.NET; package nsusbloader.COM.NET;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@ -22,6 +23,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
private String switchIP; private String switchIP;
private HashMap<String, File> nspMap; private HashMap<String, File> nspMap;
private HashMap<String, Long> nspFileSizes;
private ServerSocket serverSocket; private ServerSocket serverSocket;
@ -42,6 +44,29 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
this.switchIP = switchIP; this.switchIP = switchIP;
this.logPrinter = new LogPrinter(); this.logPrinter = new LogPrinter();
this.nspMap = new HashMap<>(); this.nspMap = new HashMap<>();
this.nspFileSizes = new HashMap<>();
// Filter and remove empty/incorrect split-files
filesList.removeIf(f -> {
if (f.isDirectory()){
File[] subFiles = f.listFiles((file, name) -> name.matches("[0-9]{2}"));
if (subFiles == null || subFiles.length == 0) {
logPrinter.print("NET: Removing empty folder: " + f.getName(), EMsgType.WARNING);
return true;
}
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("NET: Removing strange 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;
});
// Collect and encode NSP files list // Collect and encode NSP files list
try { try {
for (File nspFile : filesList) for (File nspFile : filesList)
@ -50,11 +75,26 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
catch (UnsupportedEncodingException uee){ catch (UnsupportedEncodingException uee){
isValid = false; isValid = false;
logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t"+uee.getMessage(), EMsgType.FAIL); logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t"+uee.getMessage(), EMsgType.FAIL);
for (File nspFile : filesList) //for (File nspFile : filesList)
nspMap.put(nspFile.getName(), nspFile); // nspMap.put(nspFile.getName(), nspFile);
close(EFileStatus.FAILED); close(EFileStatus.FAILED);
return; return;
} }
// Collect sizes since now we can have split-files support
for (Map.Entry<String, File> entry : nspMap.entrySet()){
File inFile = entry.getValue();
long fSize = 0;
if (inFile.isDirectory()){
File[] subFiles = inFile.listFiles((file, name) -> name.matches("[0-9]{2}"));
for (File subFile : subFiles)
fSize += subFile.length();
nspFileSizes.put(entry.getKey(), fSize);
}
else
nspFileSizes.put(entry.getKey(), inFile.length());
}
// Resolve IP // Resolve IP
if (hostIPaddr.isEmpty()) { if (hostIPaddr.isEmpty()) {
DatagramSocket socket; DatagramSocket socket;
@ -242,7 +282,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
LinkedList<String> tcpPacket = new LinkedList<>(); LinkedList<String> tcpPacket = new LinkedList<>();
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
//System.out.println(line); // TODO: remove DBG //System.out.println(line); // Debug
if (line.trim().isEmpty()) { // If TCP packet is ended if (line.trim().isEmpty()) { // If TCP packet is ended
if (handleRequest(tcpPacket)) // Proceed required things if (handleRequest(tcpPacket)) // Proceed required things
break work_routine; break work_routine;
@ -271,8 +311,11 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
private boolean handleRequest(LinkedList<String> packet){ private boolean handleRequest(LinkedList<String> packet){
//private boolean handleRequest(LinkedList<String> packet, OutputStreamWriter pw){ //private boolean handleRequest(LinkedList<String> packet, OutputStreamWriter pw){
File requestedFile; File requestedFile;
requestedFile = nspMap.get(packet.get(0).replaceAll("(^[A-z\\s]+/)|(\\s+?.*$)", "")); String reqFileName = packet.get(0).replaceAll("(^[A-z\\s]+/)|(\\s+?.*$)", "");
if (!requestedFile.exists() || requestedFile.length() == 0){ // well.. tell 404 if file exists with 0 length is against standard, but saves time long reqFileSize = nspFileSizes.get(reqFileName);
requestedFile = nspMap.get(reqFileName);
if (!requestedFile.exists() || reqFileSize == 0){ // well.. tell 404 if file exists with 0 length is against standard, but saves time
currSockPW.write(NETPacket.getCode404()); currSockPW.write(NETPacket.getCode404());
currSockPW.flush(); currSockPW.flush();
logPrinter.print("NET: File "+requestedFile.getName()+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL); logPrinter.print("NET: File "+requestedFile.getName()+" doesn't exists or have 0 size. Returning 404", EMsgType.FAIL);
@ -280,7 +323,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
return true; return true;
} }
if (packet.get(0).startsWith("HEAD")){ if (packet.get(0).startsWith("HEAD")){
currSockPW.write(NETPacket.getCode200(requestedFile.length())); currSockPW.write(NETPacket.getCode200(reqFileSize));
currSockPW.flush(); currSockPW.flush();
logPrinter.print("NET: Replying for requested file: "+requestedFile.getName(), EMsgType.INFO); logPrinter.print("NET: Replying for requested file: "+requestedFile.getName(), EMsgType.INFO);
return false; return false;
@ -298,23 +341,23 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
logPrinter.update(requestedFile, EFileStatus.FAILED); logPrinter.update(requestedFile, EFileStatus.FAILED);
return true; return true;
} }
if (writeToSocket(requestedFile, Long.parseLong(rangeStr[0]), Long.parseLong(rangeStr[1]))) // DO WRITE if (writeToSocket(reqFileName, Long.parseLong(rangeStr[0]), Long.parseLong(rangeStr[1]))) // DO WRITE
return true; return true;
} }
else if (!rangeStr[0].isEmpty()) { // If only START defined: Read all else if (!rangeStr[0].isEmpty()) { // If only START defined: Read all
if (writeToSocket(requestedFile, Long.parseLong(rangeStr[0]), requestedFile.length())) // DO WRITE if (writeToSocket(reqFileName, Long.parseLong(rangeStr[0]), reqFileSize)) // DO WRITE
return true; return true;
} }
else if (!rangeStr[1].isEmpty()) { // If only END defined: Try to read last 500 bytes else if (!rangeStr[1].isEmpty()) { // If only END defined: Try to read last 500 bytes
if (requestedFile.length() > 500){ if (reqFileSize > 500){
if (writeToSocket(requestedFile, requestedFile.length()-500, requestedFile.length())) // DO WRITE if (writeToSocket(reqFileName, reqFileSize-500, reqFileSize)) // DO WRITE
return true; return true;
} }
else { // If file smaller than 500 bytes else { // If file smaller than 500 bytes
currSockPW.write(NETPacket.getCode416()); currSockPW.write(NETPacket.getCode416());
currSockPW.flush(); currSockPW.flush();
logPrinter.print("NET: File size requested for "+requestedFile.getName()+" while actual size of it: "+requestedFile.length()+". Returning 416", EMsgType.FAIL); logPrinter.print("NET: File size requested for "+requestedFile.getName()+" while actual size of it: "+reqFileSize+". Returning 416", EMsgType.FAIL);
logPrinter.update(requestedFile, EFileStatus.FAILED); logPrinter.update(requestedFile, EFileStatus.FAILED);
return true; return true;
} }
@ -343,23 +386,56 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
/** /**
* Send files. * Send files.
* */ * */
private boolean writeToSocket(File file, long start, long end){ private boolean writeToSocket(String fileName, long start, long end){
File reqFile = nspMap.get(fileName);
// Inform
logPrinter.print("NET: Responding to requested range: "+start+"-"+end, EMsgType.INFO); logPrinter.print("NET: Responding to requested range: "+start+"-"+end, EMsgType.INFO);
currSockPW.write(NETPacket.getCode206(file.length(), start, end)); // Reply
currSockPW.write(NETPacket.getCode206(nspFileSizes.get(fileName), start, end));
currSockPW.flush(); currSockPW.flush();
try{ // Prepare transfer
long count = end - start + 1; long count = end - start + 1;
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
int readPice = 8388608; // = 8Mb int readPice = 8388608; // = 8Mb
byte[] byteBuf; byte[] byteBuf;
long currentOffset = 0;
try{
//================================= SPLIT FILE ====================================
if (reqFile.isDirectory()){
NSSplitReader nsr = new NSSplitReader(reqFile, start);
while (currentOffset < count){
if (isCancelled())
return true;
if ((currentOffset + readPice) >= count){
readPice = Math.toIntExact(count - currentOffset);
}
byteBuf = new byte[readPice];
if (nsr.read(byteBuf) != readPice){
logPrinter.print("NET: Reading of file stream suddenly ended.", EMsgType.FAIL);
return true;
}
currSockOS.write(byteBuf);
//-------/
logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0);
//-------/
currentOffset += readPice;
}
currSockOS.flush(); // TODO: check if this really needed.
nsr.close();
}
//================================= REGULAR FILE ====================================
else {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(reqFile));
if (bis.skip(start) != start){ if (bis.skip(start) != start){
logPrinter.print("NET: Unable to skip requested range.", EMsgType.FAIL); logPrinter.print("NET: Unable to skip requested range.", EMsgType.FAIL);
logPrinter.update(file, EFileStatus.FAILED); logPrinter.update(reqFile, EFileStatus.FAILED);
return true; return true;
} }
long currentOffset = 0;
while (currentOffset < count){ while (currentOffset < count){
if (isCancelled()) if (isCancelled())
return true; return true;
@ -373,20 +449,21 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
return true; return true;
} }
currSockOS.write(byteBuf); currSockOS.write(byteBuf);
//-----------------------------------------/ //-------/
logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0); logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0);
//-----------------------------------------/ //-------/
currentOffset += readPice; currentOffset += readPice;
} }
currSockOS.flush(); // TODO: check if this really needed. currSockOS.flush(); // TODO: check if this really needed.
bis.close(); bis.close();
//-----------------------------------------/
logPrinter.updateProgress(1.0);
//-----------------------------------------/
} }
catch (IOException ioe){ //-------/
logPrinter.print("NET: File transmission failed. Returned:\n\t"+ioe.getMessage(), EMsgType.FAIL); logPrinter.updateProgress(1.0);
logPrinter.update(file, EFileStatus.FAILED); //-------/
}
catch (IOException | NullPointerException e){
logPrinter.print("NET: File transmission failed. Returned:\n\t"+e.getMessage(), EMsgType.FAIL);
logPrinter.update(reqFile, EFileStatus.FAILED);
return true; return true;
} }
return false; return false;
@ -403,7 +480,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
logPrinter.print("NET: Closing server socket.", EMsgType.PASS); logPrinter.print("NET: Closing server socket.", EMsgType.PASS);
} }
} }
catch (IOException | NullPointerException ioe){ catch (IOException ioe){
logPrinter.print("NET: Closing server socket failed. Sometimes it's not an issue.", EMsgType.WARNING); logPrinter.print("NET: Closing server socket failed. Sometimes it's not an issue.", EMsgType.WARNING);
} }
if (status != null) { if (status != null) {

View file

@ -1,4 +1,4 @@
package nsusbloader.NET; package nsusbloader.COM.NET;
import nsusbloader.NSLMain; import nsusbloader.NSLMain;

View file

@ -1,12 +1,12 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import nsusbloader.MediatorControl; import nsusbloader.MediatorControl;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
@ -19,14 +19,10 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* GoldLeaf processing * GoldLeaf 0.7 - 0.7.3 processing
*/ */
class GoldLeaf implements ITransferModule { class GoldLeaf extends TransferModule {
private LogPrinter logPrinter;
private DeviceHandle handlerNS;
private LinkedHashMap<String, File> nspMap;
private boolean nspFilterForGl; private boolean nspFilterForGl;
private Task<Void> task;
// CMD // 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_SUCCESS = new byte[]{0x47, 0x4c, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00}; // used @ writeToUsb_GLCMD
@ -44,8 +40,11 @@ class GoldLeaf implements ITransferModule {
private String openReadFileNameAndPath; private String openReadFileNameAndPath;
private RandomAccessFile randAccessFile; private RandomAccessFile randAccessFile;
private NSSplitReader splitReader;
private HashMap<String, BufferedOutputStream> writeFilesMap; private HashMap<String, BufferedOutputStream> writeFilesMap;
private long virtDriveSize;
private HashMap<String, Long> splitFileSize;
private boolean isWindows; private boolean isWindows;
private String homePath; private String homePath;
@ -53,6 +52,8 @@ class GoldLeaf implements ITransferModule {
private File selectedFile; private File selectedFile;
GoldLeaf(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter, boolean nspFilter){ GoldLeaf(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter, boolean nspFilter){
super(handler, nspMap, task, logPrinter);
final byte CMD_GetDriveCount = 0x00; final byte CMD_GetDriveCount = 0x00;
final byte CMD_GetDriveInfo = 0x01; final byte CMD_GetDriveInfo = 0x01;
final byte CMD_StatPath = 0x02; // proxy done [proxy: in case if folder contains ENG+RUS+UKR file names works incorrect] final byte CMD_StatPath = 0x02; // proxy done [proxy: in case if folder contains ENG+RUS+UKR file names works incorrect]
@ -72,11 +73,7 @@ class GoldLeaf implements ITransferModule {
final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49}; final byte[] CMD_GLCI = new byte[]{0x47, 0x4c, 0x43, 0x49};
this.handlerNS = handler;
this.nspMap = nspMap;
this.logPrinter = logPrinter;
this.nspFilterForGl = nspFilter; this.nspFilterForGl = nspFilter;
this.task = task;
logPrinter.print("============= GoldLeaf =============\n\tVIRT:/ equals files added into the application\n\tHOME:/ equals " logPrinter.print("============= GoldLeaf =============\n\tVIRT:/ equals files added into the application\n\tHOME:/ equals "
+System.getProperty("user.home"), EMsgType.INFO); +System.getProperty("user.home"), EMsgType.INFO);
@ -92,6 +89,22 @@ class GoldLeaf implements ITransferModule {
homePath = System.getProperty("user.home")+File.separator; 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 // Go parse commands
byte[] readByte; byte[] readByte;
int someLength1, int someLength1,
@ -193,7 +206,7 @@ class GoldLeaf implements ITransferModule {
for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){ for (BufferedOutputStream fBufOutStream: writeFilesMap.values()){
try{ try{
fBufOutStream.close(); fBufOutStream.close();
}catch (IOException ignored){} }catch (IOException | NullPointerException ignored){}
} }
} }
closeOpenedReadFilesGl(); closeOpenedReadFilesGl();
@ -207,9 +220,14 @@ class GoldLeaf implements ITransferModule {
try{ try{
randAccessFile.close(); randAccessFile.close();
} }
catch (IOException ignored){} catch (IOException | NullPointerException ignored){}
try{
splitReader.close();
}
catch (IOException | NullPointerException ignored){}
openReadFileNameAndPath = null; openReadFileNameAndPath = null;
randAccessFile = null; randAccessFile = null;
splitReader = null;
} }
} }
/** /**
@ -243,7 +261,7 @@ class GoldLeaf implements ITransferModule {
driveLetterLen, driveLetterLen,
totalFreeSpace, totalFreeSpace,
totalSize; totalSize;
long totalSizeLong = 0; long totalSizeLong;
// 0 == VIRTUAL DRIVE // 0 == VIRTUAL DRIVE
if (driveNo == 0){ if (driveNo == 0){
@ -252,9 +270,7 @@ class GoldLeaf implements ITransferModule {
driveLetter = "VIRT".getBytes(StandardCharsets.UTF_16LE); // TODO: Consider moving to class field declaration driveLetter = "VIRT".getBytes(StandardCharsets.UTF_16LE); // TODO: Consider moving to class field declaration
driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7 driveLetterLen = intToArrLE(driveLetter.length / 2);// since GL 0.7
totalFreeSpace = new byte[4]; totalFreeSpace = new byte[4];
for (File nspFile : nspMap.values()){ totalSizeLong = virtDriveSize;
totalSizeLong += nspFile.length();
}
totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL! totalSize = Arrays.copyOfRange(longToArrLE(totalSizeLong), 0, 4); // Dirty hack; now for GL!
} }
else { //1 == User home dir else { //1 == User home dir
@ -567,7 +583,12 @@ class GoldLeaf implements ITransferModule {
filePath = filePath.replaceFirst("VIRT:/", ""); filePath = filePath.replaceFirst("VIRT:/", "");
if (nspMap.containsKey(filePath)){ if (nspMap.containsKey(filePath)){
command.add(GL_OBJ_TYPE_FILE); // THIS IS INT 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! command.add(longToArrLE(nspMap.get(filePath).length())); // YES, THIS IS LONG!
if (writeGL_PASS(command)) { if (writeGL_PASS(command)) {
logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL); logPrinter.print("GL Handle 'StatPath' command.", EMsgType.FAIL);
return true; return true;
@ -693,7 +714,7 @@ class GoldLeaf implements ITransferModule {
* false if everything is ok * false if everything is ok
* */ * */
private boolean readFile(String fileName, long offset, long size) { private boolean readFile(String fileName, long offset, long size) {
//System.out.println("readFile "+fileName+" "+offset+" "+size); //System.out.println("readFile "+fileName+" "+offset+" "+size+"\n");
if (fileName.startsWith("VIRT:/")){ if (fileName.startsWith("VIRT:/")){
// Let's find out which file requested // Let's find out which file requested
String fNamePath = nspMap.get(fileName.substring(6)).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length String fNamePath = nspMap.get(fileName.substring(6)).getAbsolutePath(); // NOTE: 6 = "VIRT:/".length
@ -703,14 +724,25 @@ class GoldLeaf implements ITransferModule {
if (openReadFileNameAndPath != null){ if (openReadFileNameAndPath != null){
try{ try{
randAccessFile.close(); randAccessFile.close();
}catch (IOException ignored){} }catch (Exception ignored){}
try{
splitReader.close();
}catch (Exception ignored){}
} }
// Open what has to be opened // Open what has to be opened
try{ try{
randAccessFile = new RandomAccessFile(nspMap.get(fileName.substring(6)), "r"); 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; openReadFileNameAndPath = fNamePath;
} }
catch (IOException ioe){ catch (IOException | NullPointerException ioe){
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
} }
} }
@ -724,19 +756,41 @@ class GoldLeaf implements ITransferModule {
if (openReadFileNameAndPath != null){ if (openReadFileNameAndPath != null){
try{ try{
randAccessFile.close(); randAccessFile.close();
}catch (IOException ignored){} }catch (IOException | NullPointerException ignored){}
} }
// Open what has to be opened // Open what has to be opened
try{ try{
randAccessFile = new RandomAccessFile(fileName, "r"); randAccessFile = new RandomAccessFile(fileName, "r");
openReadFileNameAndPath = fileName; openReadFileNameAndPath = fileName;
}catch (IOException ioe){ }catch (IOException | NullPointerException ioe){
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
} }
} }
} }
//----------------------- Actual transfer chain ------------------------ //----------------------- Actual transfer chain ------------------------
try{ 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))) {
logPrinter.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);
return true;
}
return false;
}
else {
randAccessFile.seek(offset); randAccessFile.seek(offset);
byte[] chunk = new byte[(int)size]; // WTF MAN? byte[] chunk = new byte[(int)size]; // WTF MAN?
// Let's find out how much bytes we got // Let's find out how much bytes we got
@ -756,15 +810,25 @@ class GoldLeaf implements ITransferModule {
} }
return false; return false;
} }
catch (IOException ioe){ }
catch (Exception ioe){
try{ try{
randAccessFile.close(); randAccessFile.close();
} }
catch (IOException ioee){ catch (NullPointerException ignored){}
logPrinter.print("GL Handle 'ReadFile' command: unable to close: "+openReadFileNameAndPath+"\n\t"+ioee.getMessage(), EMsgType.WARNING); catch (IOException ioe_){
logPrinter.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);
} }
openReadFileNameAndPath = null; openReadFileNameAndPath = null;
randAccessFile = null; randAccessFile = null;
splitReader = null;
return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage()); return writeGL_FAIL("GL Handle 'ReadFile' command\n\t"+ioe.getMessage());
} }
} }
@ -852,10 +916,6 @@ class GoldLeaf implements ITransferModule {
return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected"); return writeGL_FAIL("GL Handle 'SelectFile' command: Nothing selected");
} }
@Override
public EFileStatus getStatus() {
return EFileStatus.UNKNOWN;
}
/*----------------------------------------------------*/ /*----------------------------------------------------*/
/* GL HELPERS */ /* GL HELPERS */
/*----------------------------------------------------*/ /*----------------------------------------------------*/

View file

@ -1,10 +1,11 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.USB.PFS.PFSProvider; import nsusbloader.COM.Helpers.NSSplitReader;
import nsusbloader.COM.USB.PFS.PFSProvider;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
@ -18,7 +19,7 @@ import java.util.LinkedHashMap;
/** /**
* GoldLeaf processing * GoldLeaf processing
* */ * */
public class GoldLeaf_05 implements ITransferModule{ public class GoldLeaf_05 extends TransferModule {
// CMD G L U C // CMD G L U C
private static final byte[] CMD_GLUC = new byte[]{0x47, 0x4c, 0x55, 0x43}; private static final byte[] CMD_GLUC = new byte[]{0x47, 0x4c, 0x55, 0x43};
private static final byte[] CMD_ConnectionRequest = new byte[]{0x00, 0x00, 0x00, 0x00}; // Write-only command private static final byte[] CMD_ConnectionRequest = new byte[]{0x00, 0x00, 0x00, 0x00}; // Write-only command
@ -31,13 +32,13 @@ public class GoldLeaf_05 implements ITransferModule{
private static final byte[] CMD_NSPTicket = new byte[]{0x06, 0x00, 0x00, 0x00}; private static final byte[] CMD_NSPTicket = new byte[]{0x06, 0x00, 0x00, 0x00};
private static final byte[] CMD_Finish = new byte[]{0x07, 0x00, 0x00, 0x00}; private static final byte[] CMD_Finish = new byte[]{0x07, 0x00, 0x00, 0x00};
private DeviceHandle handlerNS;
private Task<Void> task;
private LogPrinter logPrinter;
private EFileStatus status = EFileStatus.FAILED;
private RandomAccessFile raf; // NSP File private RandomAccessFile raf; // NSP File
private NSSplitReader nsr; // It'a also NSP File
GoldLeaf_05(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){ GoldLeaf_05(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){
super(handler, nspMap, task, logPrinter);
status = EFileStatus.FAILED;
logPrinter.print("============= GoldLeaf v0.5 =============\n" + logPrinter.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); " Only one file per time could be sent. In case you selected more the first one would be picked.", EMsgType.INFO);
if (nspMap.isEmpty()){ if (nspMap.isEmpty()){
@ -62,14 +63,14 @@ public class GoldLeaf_05 implements ITransferModule{
} }
logPrinter.print("GL File structure validated and it will be uploaded", EMsgType.PASS); logPrinter.print("GL File structure validated and it will be uploaded", EMsgType.PASS);
this.handlerNS = handler;
this.task = task;
this.logPrinter = logPrinter;
try{ try{
if (nspFile.isDirectory())
this.nsr = new NSSplitReader(nspFile, 0);
else
this.raf = new RandomAccessFile(nspFile, "r"); this.raf = new RandomAccessFile(nspFile, "r");
} }
catch (FileNotFoundException fnfe){ catch (IOException ioe){
logPrinter.print("GL File not found\n\t"+fnfe.getMessage(), EMsgType.FAIL); logPrinter.print("GL File not found\n\t"+ioe.getMessage(), EMsgType.FAIL);
return; return;
} }
@ -131,9 +132,11 @@ public class GoldLeaf_05 implements ITransferModule{
try { try {
raf.close(); raf.close();
} }
catch (IOException ioe){ catch (IOException | NullPointerException ignored){}
logPrinter.print("GL Failed to close file.", EMsgType.INFO); try {
nsr.close();
} }
catch (IOException | NullPointerException ignored){}
} }
/** /**
* ConnectionResponse command handler * ConnectionResponse command handler
@ -254,6 +257,25 @@ public class GoldLeaf_05 implements ITransferModule{
byte[] readBuf; byte[] readBuf;
try{ try{
if (raf == null){
nsr.seek(realNcaOffset);
while (readFrom < realNcaSize){
if (realNcaSize - readFrom < readPice)
readPice = Math.toIntExact(realNcaSize - readFrom); // it's safe, I guarantee
readBuf = new byte[readPice];
if (nsr.read(readBuf) != readPice)
return true;
//System.out.println("S: "+readFrom+" T: "+realNcaSize+" P: "+readPice); // DEBUG
if (writeUsb(readBuf))
return true;
//-----------------------------------------/
logPrinter.updateProgress((readFrom+readPice)/(realNcaSize/100.0) / 100.0);
//-----------------------------------------/
readFrom += readPice;
}
}
else {
raf.seek(realNcaOffset); raf.seek(realNcaOffset);
while (readFrom < realNcaSize){ while (readFrom < realNcaSize){
@ -270,6 +292,7 @@ public class GoldLeaf_05 implements ITransferModule{
//-----------------------------------------/ //-----------------------------------------/
readFrom += readPice; readFrom += readPice;
} }
}
//-----------------------------------------/ //-----------------------------------------/
logPrinter.updateProgress(1.0); logPrinter.updateProgress(1.0);
//-----------------------------------------/ //-----------------------------------------/
@ -282,8 +305,6 @@ public class GoldLeaf_05 implements ITransferModule{
return false; return false;
} }
@Override
public EFileStatus getStatus() { return status; }
/** /**
* Sending any byte array to USB device * Sending any byte array to USB device

View file

@ -1,4 +1,4 @@
package nsusbloader.USB.PFS; package nsusbloader.COM.USB.PFS;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;

View file

@ -1,8 +1,7 @@
package nsusbloader.USB.PFS; package nsusbloader.COM.USB.PFS;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.ServiceWindow;
import java.io.*; import java.io.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -22,9 +21,14 @@ public class PFSProvider {
private int ticketID = -1; private int ticketID = -1;
public PFSProvider(File nspFile, LogPrinter logPrinter) throws Exception{ public PFSProvider(File nspFile, LogPrinter logPrinter) throws Exception{
if (nspFile.isDirectory()) {
nspFileName = nspFile.getName();
nspFile = new File(nspFile.getAbsolutePath() + File.separator + "00");
}
else
nspFileName = nspFile.getName();
RandomAccessFile randAccessFile = new RandomAccessFile(nspFile, "r"); RandomAccessFile randAccessFile = new RandomAccessFile(nspFile, "r");
nspFileName = nspFile.getName();
int filesCount; int filesCount;
int header; int header;

View file

@ -1,9 +1,10 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
@ -18,18 +19,9 @@ import java.util.LinkedHashMap;
/** /**
* Tinfoil processing * Tinfoil processing
* */ * */
class TinFoil implements ITransferModule { class TinFoil extends TransferModule {
private LogPrinter logPrinter;
private DeviceHandle handlerNS;
private LinkedHashMap<String, File> nspMap;
private EFileStatus status = EFileStatus.FAILED;
private Task<Void> task;
TinFoil(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){ TinFoil(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){
this.handlerNS = handler; super(handler, nspMap, task, logPrinter);
this.nspMap = nspMap;
this.task = task;
this.logPrinter = logPrinter;
logPrinter.print("============= TinFoil =============", EMsgType.INFO); logPrinter.print("============= TinFoil =============", EMsgType.INFO);
@ -162,27 +154,64 @@ class TinFoil implements ITransferModule {
return false; return false;
try { try {
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor?
byte[] bufferCurrent; //= new byte[1048576]; // eq. Allocate 1mb byte[] bufferCurrent; //= new byte[1048576]; // eq. Allocate 1mb
if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){
logPrinter.print("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL);
return false;
}
long currentOffset = 0; long currentOffset = 0;
// 'End Offset' equal to receivedRangeSize. // 'End Offset' equal to receivedRangeSize.
int readPice = 8388608; // = 8Mb int readPice = 8388608; // = 8Mb
//---------------! Split files start !---------------
if (nspMap.get(receivedRequestedNSP).isDirectory()){
NSSplitReader nsSplitReader = new NSSplitReader(nspMap.get(receivedRequestedNSP), receivedRangeSize);
if (nsSplitReader.seek(receivedRangeOffset) != receivedRangeOffset){
logPrinter.print("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL);
return false;
}
while (currentOffset < receivedRangeSize){ while (currentOffset < receivedRangeSize){
if ((currentOffset + readPice) >= receivedRangeSize ) if ((currentOffset + readPice) >= receivedRangeSize )
readPice = Math.toIntExact(receivedRangeSize - currentOffset); readPice = Math.toIntExact(receivedRangeSize - currentOffset);
//System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // NOTE: DEBUG //System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // NOTE: DEBUG
// updating progress bar (if a lot of data requested) START BLOCK // updating progress bar (if a lot of data requested) START BLOCK
//-----------------------------------------/ //---Tell progress to UI---/
logPrinter.updateProgress((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0); logPrinter.updateProgress((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0);
//-----------------------------------------/ //------------------------/
bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring.
if (nsSplitReader.read(bufferCurrent) != readPice) { // changed since @ v0.3.2
logPrinter.print("TF Reading of stream suddenly ended.", EMsgType.WARNING);
return false;
}
//write to USB
if (writeUsb(bufferCurrent)) {
logPrinter.print("TF Failure during NSP transmission.", EMsgType.FAIL);
return false;
}
currentOffset += readPice;
}
nsSplitReader.close();
//---Tell progress to UI---/
logPrinter.updateProgress(1.0);
//------------------------/
}
//---------------! Split files end !---------------
//---------------! Regular files start !---------------
else {
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor?
if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset) {
logPrinter.print("TF Requested skip is out of file size. Nothing to transmit.", EMsgType.FAIL);
return false;
}
while (currentOffset < receivedRangeSize) {
if ((currentOffset + readPice) >= receivedRangeSize)
readPice = Math.toIntExact(receivedRangeSize - currentOffset);
//System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+readPice); // NOTE: DEBUG
// updating progress bar (if a lot of data requested) START BLOCK
//---Tell progress to UI---/
logPrinter.updateProgress((currentOffset + readPice) / (receivedRangeSize / 100.0) / 100.0);
//------------------------/
bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring. bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring.
if (bufferedInStream.read(bufferCurrent) != readPice) { // changed since @ v0.3.2 if (bufferedInStream.read(bufferCurrent) != readPice) { // changed since @ v0.3.2
@ -197,9 +226,11 @@ class TinFoil implements ITransferModule {
currentOffset += readPice; currentOffset += readPice;
} }
bufferedInStream.close(); bufferedInStream.close();
//-----------------------------------------/ //---Tell progress to UI---/
logPrinter.updateProgress(1.0); logPrinter.updateProgress(1.0);
//-----------------------------------------/ //------------------------/
}
//---------------! Regular files end !---------------
} catch (FileNotFoundException fnfe){ } catch (FileNotFoundException fnfe){
logPrinter.print("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL); logPrinter.print("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL);
fnfe.printStackTrace(); fnfe.printStackTrace();
@ -212,6 +243,10 @@ class TinFoil implements ITransferModule {
logPrinter.print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):\n "+ae.getMessage(), EMsgType.FAIL); logPrinter.print("TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'):\n "+ae.getMessage(), EMsgType.FAIL);
ae.printStackTrace(); ae.printStackTrace();
return false; return false;
} catch (NullPointerException npe){
logPrinter.print("TF NullPointerException (in some moment application didn't find something. Something important.):\n "+npe.getMessage(), EMsgType.FAIL);
npe.printStackTrace();
return false;
} }
return true; return true;
@ -309,13 +344,4 @@ class TinFoil implements ITransferModule {
logPrinter.print("TF Execution interrupted", EMsgType.INFO); logPrinter.print("TF Execution interrupted", EMsgType.INFO);
return null; return null;
} }
/**
* Status getter
* @return status
*/
@Override
public EFileStatus getStatus() {
return status;
}
} }

View file

@ -0,0 +1,54 @@
package nsusbloader.COM.USB;
import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType;
import org.usb4java.DeviceHandle;
import java.io.File;
import java.util.*;
public abstract class TransferModule {
EFileStatus status = EFileStatus.UNKNOWN;
LinkedHashMap<String, File> nspMap;
LogPrinter logPrinter;
DeviceHandle handlerNS;
Task<Void> task;
TransferModule(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter printer){
this.handlerNS = handler;
this.nspMap = nspMap;
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);
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);
}
public EFileStatus getStatus(){ return status; }
}

View file

@ -1,4 +1,4 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
@ -50,7 +50,7 @@ public class UsbCommunications extends Task<Void> {
DeviceHandle handler = usbConnect.getHandlerNS(); DeviceHandle handler = usbConnect.getHandlerNS();
ITransferModule module; TransferModule module;
if (protocol.equals("TinFoil")) if (protocol.equals("TinFoil"))
module = new TinFoil(handler, nspMap, this, logPrinter); module = new TinFoil(handler, nspMap, this, logPrinter);

View file

@ -1,4 +1,4 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import nsusbloader.ModelControllers.LogPrinter; import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EMsgType; import nsusbloader.NSLDataTypes.EMsgType;

View file

@ -1,4 +1,4 @@
package nsusbloader.USB; package nsusbloader.COM.USB;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;

View file

@ -8,15 +8,15 @@ import javafx.scene.control.*;
import javafx.scene.input.DragEvent; import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import nsusbloader.*; import nsusbloader.*;
import nsusbloader.ModelControllers.UpdatesChecker; import nsusbloader.ModelControllers.UpdatesChecker;
import nsusbloader.NET.NETCommunications; import nsusbloader.COM.NET.NETCommunications;
import nsusbloader.USB.UsbCommunications; import nsusbloader.COM.USB.UsbCommunications;
import java.io.File; import java.io.File;
import java.net.*; import java.net.*;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@ -28,9 +28,8 @@ public class NSLMainController implements Initializable {
@FXML @FXML
public TextArea logArea; // Accessible from Mediator public TextArea logArea; // Accessible from Mediator
@FXML @FXML
private Button selectNspBtn; private Button selectNspBtn, selectSplitNspBtn, uploadStopBtn;
@FXML
private Button uploadStopBtn;
private Region btnUpStopImage; private Region btnUpStopImage;
@FXML @FXML
public ProgressBar progressBar; // Accessible from Mediator public ProgressBar progressBar; // Accessible from Mediator
@ -62,6 +61,10 @@ public class NSLMainController implements Initializable {
else else
uploadStopBtn.setDisable(false); uploadStopBtn.setDisable(false);
selectNspBtn.setOnAction(e-> selectFilesBtnAction()); selectNspBtn.setOnAction(e-> selectFilesBtnAction());
selectSplitNspBtn.setOnAction(e-> selectSplitBtnAction());
selectSplitNspBtn.getStyleClass().add("buttonSelect");
uploadStopBtn.setOnAction(e-> uploadBtnAction()); uploadStopBtn.setOnAction(e-> uploadBtnAction());
selectNspBtn.getStyleClass().add("buttonSelect"); selectNspBtn.getStyleClass().add("buttonSelect");
@ -101,7 +104,6 @@ public class NSLMainController implements Initializable {
public ResourceBundle getResourceBundle() { public ResourceBundle getResourceBundle() {
return resourceBundle; return resourceBundle;
} }
/** /**
* Provide hostServices to Settings tab * Provide hostServices to Settings tab
* */ * */
@ -109,7 +111,6 @@ public class NSLMainController implements Initializable {
/** /**
* Functionality for selecting NSP button. * Functionality for selecting NSP button.
* Uses setReady and setNotReady to simplify code readability.
* */ * */
private void selectFilesBtnAction(){ private void selectFilesBtnAction(){
List<File> filesList; List<File> filesList;
@ -138,6 +139,28 @@ public class NSLMainController implements Initializable {
previouslyOpenedPath = filesList.get(0).getParent(); previouslyOpenedPath = filesList.get(0).getParent();
} }
} }
/**
* Functionality for selecting Split NSP button.
* */
private void selectSplitBtnAction(){
File splitFile;
DirectoryChooser dirChooser = new DirectoryChooser();
dirChooser.setTitle(resourceBundle.getString("btn_OpenFile"));
File validator = new File(previouslyOpenedPath);
if (validator.exists())
dirChooser.setInitialDirectory(validator);
else
dirChooser.setInitialDirectory(new File(System.getProperty("user.home")));
splitFile = dirChooser.showDialog(logArea.getScene().getWindow());
if (splitFile != null && splitFile.getName().toLowerCase().endsWith(".nsp")) {
FrontTabController.tableFilesListController.setFile(splitFile);
uploadStopBtn.setDisable(false); // Is it useful?
previouslyOpenedPath = splitFile.getParent();
}
}
/** /**
* It's button listener when no transmission executes * It's button listener when no transmission executes
* */ * */
@ -210,6 +233,7 @@ public class NSLMainController implements Initializable {
public void notifyTransmissionStarted(boolean isTransmissionStarted){ public void notifyTransmissionStarted(boolean isTransmissionStarted){
if (isTransmissionStarted) { if (isTransmissionStarted) {
selectNspBtn.setDisable(true); selectNspBtn.setDisable(true);
selectSplitNspBtn.setDisable(true);
uploadStopBtn.setOnAction(e-> stopBtnAction()); uploadStopBtn.setOnAction(e-> stopBtnAction());
uploadStopBtn.setText(resourceBundle.getString("btn_Stop")); uploadStopBtn.setText(resourceBundle.getString("btn_Stop"));
@ -222,6 +246,7 @@ public class NSLMainController implements Initializable {
} }
else { else {
selectNspBtn.setDisable(false); selectNspBtn.setDisable(false);
selectSplitNspBtn.setDisable(false);
uploadStopBtn.setOnAction(e-> uploadBtnAction()); uploadStopBtn.setOnAction(e-> uploadBtnAction());
uploadStopBtn.setText(resourceBundle.getString("btn_Upload")); uploadStopBtn.setText(resourceBundle.getString("btn_Upload"));
@ -253,57 +278,21 @@ public class NSLMainController implements Initializable {
/** /**
* Drag-n-drop support (drop consumer) * Drag-n-drop support (drop consumer)
* */ * */
// TODO: DO SOMETHING WITH THIS
@FXML @FXML
private void handleDrop(DragEvent event){ private void handleDrop(DragEvent event){
if (MediatorControl.getInstance().getTransferActive()) { if (MediatorControl.getInstance().getTransferActive()) {
event.setDropCompleted(true); event.setDropCompleted(true);
return; return;
} }
List<File> filesDropped = new ArrayList<>(); List<File> filesDropped = event.getDragboard().getFiles();
try {
if (FrontTabController.getSelectedProtocol().equals("TinFoil") && SettingsTabController.getTfXciNszXczSupport()){
for (File fileOrDir : event.getDragboard().getFiles()) {
if (fileOrDir.isDirectory()) {
for (File file : fileOrDir.listFiles())
if ((! file.isDirectory()) && (file.getName().toLowerCase().endsWith(".nsp") ||
file.getName().toLowerCase().endsWith(".xci") ||
file.getName().toLowerCase().endsWith(".nsz") ||
file.getName().toLowerCase().endsWith(".xcz")))
filesDropped.add(file);
}
else if (fileOrDir.getName().toLowerCase().endsWith(".nsp") || fileOrDir.getName().toLowerCase().endsWith(".xci") ||
fileOrDir.getName().toLowerCase().endsWith(".nsz") || fileOrDir.getName().toLowerCase().endsWith(".xcz") )
filesDropped.add(fileOrDir);
} if (FrontTabController.getSelectedProtocol().equals("TinFoil") && SettingsTabController.getTfXciNszXczSupport())
}// TODO: Somehow improve this double-action function in settings. filesDropped.removeIf(file -> ! file.getName().toLowerCase().matches("(.*\\.nsp$)|(.*\\.xci$)|(.*\\.nsz$)|(.*\\.xcz$)"));
else if (FrontTabController.getSelectedProtocol().equals("GoldLeaf") && (! SettingsTabController.getNSPFileFilterForGL())){ else if (FrontTabController.getSelectedProtocol().equals("GoldLeaf") && (! SettingsTabController.getNSPFileFilterForGL()))
for (File fileOrDir : event.getDragboard().getFiles()) { filesDropped.removeIf(file -> (file.isDirectory() && ! file.getName().toLowerCase().matches(".*\\.nsp$")));
if (fileOrDir.isDirectory()) {
for (File file : fileOrDir.listFiles())
if ((! file.isDirectory()) && (! file.isHidden()))
filesDropped.add(file);
}
else else
filesDropped.add(fileOrDir); filesDropped.removeIf(file -> ! file.getName().toLowerCase().matches(".*\\.nsp$"));
}
}
else {
for (File fileOrDir : event.getDragboard().getFiles()) {
if (fileOrDir.isDirectory()){
for (File file : fileOrDir.listFiles())
if (file.getName().toLowerCase().endsWith(".nsp") && (! file.isDirectory()))
filesDropped.add(file);
}
else if (fileOrDir.getName().toLowerCase().endsWith(".nsp"))
filesDropped.add(fileOrDir);
}
}
}
catch (SecurityException se){
se.printStackTrace();
}
if ( ! filesDropped.isEmpty() ) if ( ! filesDropped.isEmpty() )
FrontTabController.tableFilesListController.setFiles(filesDropped); FrontTabController.tableFilesListController.setFiles(filesDropped);

View file

@ -3,6 +3,7 @@ package nsusbloader.Controllers;
import nsusbloader.NSLDataTypes.EFileStatus; import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File; import java.io.File;
import java.io.FilenameFilter;
public class NSLRowModel { public class NSLRowModel {
@ -16,7 +17,15 @@ public class NSLRowModel {
this.nspFile = nspFile; this.nspFile = nspFile;
this.markForUpload = checkBoxValue; this.markForUpload = checkBoxValue;
this.nspFileName = nspFile.getName(); this.nspFileName = nspFile.getName();
if (nspFile.isFile())
this.nspFileSize = nspFile.length(); this.nspFileSize = nspFile.length();
else {
File[] subFilesArr = nspFile.listFiles((file, name) -> name.matches("[0-9]{2}"));
if (subFilesArr != null) {
for (File subFile : subFilesArr)
this.nspFileSize += subFile.length();
}
}
this.status = ""; this.status = "";
} }
// Model methods start // Model methods start

View file

@ -149,6 +149,24 @@ public class NSTableViewController implements Initializable {
table.getColumns().add(fileSizeColumn); table.getColumns().add(fileSizeColumn);
table.getColumns().add(uploadColumn); table.getColumns().add(uploadColumn);
} }
/**
* Add single file when user selected it (Split file usually)
* */
public void setFile(File file){
if ( ! rowsObsLst.isEmpty()){
List<String> filesAlreayInList = new ArrayList<>();
for (NSLRowModel model : rowsObsLst)
filesAlreayInList.add(model.getNspFileName());
if ( ! filesAlreayInList.contains(file.getName()))
rowsObsLst.add(new NSLRowModel(file, true));
}
else {
rowsObsLst.add(new NSLRowModel(file, true));
MediatorControl.getInstance().getContoller().disableUploadStopBtn(false);
}
table.refresh();
}
/** /**
* Add files when user selected them * Add files when user selected them
* */ * */

View file

@ -12,7 +12,7 @@ import java.util.Locale;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class NSLMain extends Application { public class NSLMain extends Application {
public static final String appVersion = "v0.8.2"; public static final String appVersion = "v0.9";
@Override @Override
public void start(Stage primaryStage) throws Exception{ public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));

View file

@ -24,6 +24,7 @@ public class RainbowHexDump {
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
for (byte b: byteArray) for (byte b: byteArray)
System.out.print(String.format("%02x ", b)); System.out.print(String.format("%02x ", b));
//System.out.println();
System.out.print("\t\t\t" System.out.print("\t\t\t"
+ new String(byteArray, StandardCharsets.UTF_8) + new String(byteArray, StandardCharsets.UTF_8)
+ "\n"); + "\n");

View file

@ -1,7 +0,0 @@
package nsusbloader.USB;
import nsusbloader.NSLDataTypes.EFileStatus;
public interface ITransferModule {
EFileStatus getStatus();
}

View file

@ -57,7 +57,7 @@
<Insets left="5.0" right="5.0" top="2.0" /> <Insets left="5.0" right="5.0" top="2.0" />
</VBox.margin> </VBox.margin>
</ProgressBar> </ProgressBar>
<HBox alignment="TOP_CENTER" VBox.vgrow="NEVER"> <HBox alignment="TOP_CENTER" spacing="3.0" VBox.vgrow="NEVER">
<children> <children>
<Button fx:id="selectNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenFile"> <Button fx:id="selectNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenFile">
<HBox.margin> <HBox.margin>
@ -67,6 +67,10 @@
<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" /> <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> </graphic>
</Button> </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" />
</graphic></Button>
<Pane HBox.hgrow="ALWAYS" /> <Pane HBox.hgrow="ALWAYS" />
<Button fx:id="uploadStopBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_Upload"> <Button fx:id="uploadStopBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_Upload">
<HBox.margin> <HBox.margin>

View file

@ -44,3 +44,4 @@ tab2_Lbl_Language=Language
windowBodyRestartToApplyLang=Please restart application to apply changes. windowBodyRestartToApplyLang=Please restart application to apply changes.
tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf. tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf.
tab2_Cb_UseOldGlVersion=Use old GoldLeaf version tab2_Cb_UseOldGlVersion=Use old GoldLeaf version
btn_OpenSplitFile=Select split NSP

View file

@ -38,7 +38,7 @@ windowTitleNewVersionNOTAval=Keine neue Version verf\u00FCgbar
windowTitleNewVersionUnknown=Nicht in der Lage nach Updates zu suchen windowTitleNewVersionUnknown=Nicht in der Lage nach Updates zu suchen
windowBodyNewVersionUnknown=Etwas ist schiefgelaufen\nInternet vielleicht nicht verf\u00FCgbar, oder GitHub nicht verf\u00FCgbar windowBodyNewVersionUnknown=Etwas ist schiefgelaufen\nInternet vielleicht nicht verf\u00FCgbar, oder GitHub nicht verf\u00FCgbar
windowBodyNewVersionNOTAval=Du benutzt die neueste Version windowBodyNewVersionNOTAval=Du benutzt die neueste Version
tab2_Cb_AllowXciNszXcz=Erlaube XCI/NSZ/XCZ-Dateien-Verwendung f\u00FCr Tinfoil 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_AllowXciNszXczDesc=Von einigen Drittanbietern verwendet, welche XCI/NSZ/XCZ unterst\u00FCtzen, nutzt Tinfoil Transfer Protocol. Nicht \u00E4ndern, wenn unsicher.
tab2_Lbl_Language=Sprache tab2_Lbl_Language=Sprache
windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen. windowBodyRestartToApplyLang=Bitte die Applikation neustarten um die Einstellungen zu \u00FCbernehmen.

View file

@ -44,4 +44,5 @@ 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. 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. 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.
tab2_Cb_UseOldGlVersion=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E GoldLeaf tab2_Cb_UseOldGlVersion=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E GoldLeaf
btn_OpenSplitFile=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0437\u0431\u0438\u0442\u044B\u0439 NSP

View file

@ -44,3 +44,4 @@ 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. 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. 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.
tab2_Cb_UseOldGlVersion=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0432\u0430\u0442\u0438 \u0441\u0442\u0430\u0440\u0443 \u0432\u0435\u0440\u0441\u0456\u044E GoldLeaf tab2_Cb_UseOldGlVersion=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0432\u0430\u0442\u0438 \u0441\u0442\u0430\u0440\u0443 \u0432\u0435\u0440\u0441\u0456\u044E GoldLeaf
btn_OpenSplitFile=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0440\u043E\u0437\u0431\u0438\u0442\u0438\u0439 NSP