Add split-files support for every 'protocol' supported

master v0.9
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).
![Screenshot](https://farm8.staticflickr.com/7809/46703921964_53f60f04ed_o.png)
![Screenshot](https://live.staticflickr.com/65535/48962978677_4c3913e8a9_o.png)
#### 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).
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.
@ -140,13 +140,16 @@ usb4java since NS-USBloader-v0.2.3 switched to 1.2.0 instead of 1.3.0. This shou
### Translators!
If you want to see this app translated to your language, go grab [this file](https://github.com/developersu/ns-usbloader/blob/master/src/main/resources/locale.properties) and translate it.
Upload somewhere (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):
- [x] [Android support](https://github.com/developersu/ns-usbloader-mobile)
- [ ] File order sort (non-critical)
- [ ] More deep file analyze before uploading.
- [ ] XCI support
## Support this app

View File

@ -8,7 +8,7 @@
<name>NS-USBloader</name>
<artifactId>ns-usbloader</artifactId>
<version>0.8.2-SNAPSHOT</version>
<version>0.9-SNAPSHOT</version>
<url>https://github.com/developersu/ns-usbloader/</url>
<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 nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
import java.io.*;
import java.net.*;
@ -22,6 +23,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
private String switchIP;
private HashMap<String, File> nspMap;
private HashMap<String, Long> nspFileSizes;
private ServerSocket serverSocket;
@ -42,6 +44,29 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
this.switchIP = switchIP;
this.logPrinter = new LogPrinter();
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
try {
for (File nspFile : filesList)
@ -49,12 +74,27 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
}
catch (UnsupportedEncodingException uee){
isValid = false;
logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t"+uee.getMessage(), EMsgType.FAIL);
for (File nspFile : filesList)
nspMap.put(nspFile.getName(), nspFile);
logPrinter.print("NET: Unsupported encoding for 'URLEncoder'. Internal issue you can't fix. Please report. Returned:\n\t"+uee.getMessage(), EMsgType.FAIL);
//for (File nspFile : filesList)
// nspMap.put(nspFile.getName(), nspFile);
close(EFileStatus.FAILED);
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
if (hostIPaddr.isEmpty()) {
DatagramSocket socket;
@ -242,23 +282,23 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
LinkedList<String> tcpPacket = new LinkedList<>();
while ((line = br.readLine()) != null) {
//System.out.println(line); // TODO: remove DBG
if (line.trim().isEmpty()) { // If TCP packet is ended
//System.out.println(line); // Debug
if (line.trim().isEmpty()) { // If TCP packet is ended
if (handleRequest(tcpPacket)) // Proceed required things
break work_routine;
tcpPacket.clear(); // Clear data and wait for next TCP packet
}
else
tcpPacket.add(line); // Otherwise collect data
tcpPacket.add(line); // Otherwise collect data
}
// and reopen client sock
clientSocket.close();
}
catch (IOException ioe){ // If server socket closed, then client socket also closed.
catch (IOException ioe){ // If server socket closed, then client socket also closed.
break;
}
}
if (!isCancelled())
if ( ! isCancelled() )
close(EFileStatus.UNKNOWN);
return null;
}
@ -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, OutputStreamWriter pw){
File requestedFile;
requestedFile = nspMap.get(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
String reqFileName = packet.get(0).replaceAll("(^[A-z\\s]+/)|(\\s+?.*$)", "");
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.flush();
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;
}
if (packet.get(0).startsWith("HEAD")){
currSockPW.write(NETPacket.getCode200(requestedFile.length()));
currSockPW.write(NETPacket.getCode200(reqFileSize));
currSockPW.flush();
logPrinter.print("NET: Replying for requested file: "+requestedFile.getName(), EMsgType.INFO);
return false;
@ -298,23 +341,23 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
logPrinter.update(requestedFile, EFileStatus.FAILED);
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;
}
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;
}
else if (!rangeStr[1].isEmpty()) { // If only END defined: Try to read last 500 bytes
if (requestedFile.length() > 500){
if (writeToSocket(requestedFile, requestedFile.length()-500, requestedFile.length())) // DO WRITE
if (reqFileSize > 500){
if (writeToSocket(reqFileName, reqFileSize-500, reqFileSize)) // DO WRITE
return true;
}
else { // If file smaller than 500 bytes
currSockPW.write(NETPacket.getCode416());
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);
return true;
}
@ -343,50 +386,84 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
/**
* 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);
currSockPW.write(NETPacket.getCode206(file.length(), start, end));
// Reply
currSockPW.write(NETPacket.getCode206(nspFileSizes.get(fileName), start, end));
currSockPW.flush();
// Prepare transfer
long count = end - start + 1;
int readPice = 8388608; // = 8Mb
byte[] byteBuf;
long currentOffset = 0;
try{
long count = end - start + 1;
//================================= SPLIT FILE ====================================
if (reqFile.isDirectory()){
NSSplitReader nsr = new NSSplitReader(reqFile, start);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
int readPice = 8388608; // = 8Mb
byte[] byteBuf;
while (currentOffset < count){
if (isCancelled())
return true;
if ((currentOffset + readPice) >= count){
readPice = Math.toIntExact(count - currentOffset);
}
byteBuf = new byte[readPice];
if (bis.skip(start) != start){
logPrinter.print("NET: Unable to skip requested range.", EMsgType.FAIL);
logPrinter.update(file, EFileStatus.FAILED);
return true;
}
long currentOffset = 0;
while (currentOffset < count){
if (isCancelled())
return true;
if ((currentOffset+readPice) >= count){
readPice = Math.toIntExact(count - currentOffset);
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;
}
byteBuf = new byte[readPice];
currSockOS.flush(); // TODO: check if this really needed.
nsr.close();
}
//================================= REGULAR FILE ====================================
else {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(reqFile));
if (bis.read(byteBuf) != readPice){
logPrinter.print("NET: Reading of file stream suddenly ended.", EMsgType.FAIL);
if (bis.skip(start) != start){
logPrinter.print("NET: Unable to skip requested range.", EMsgType.FAIL);
logPrinter.update(reqFile, EFileStatus.FAILED);
return true;
}
currSockOS.write(byteBuf);
//-----------------------------------------/
logPrinter.updateProgress((currentOffset+readPice)/(count/100.0) / 100.0);
//-----------------------------------------/
currentOffset += readPice;
while (currentOffset < count){
if (isCancelled())
return true;
if ((currentOffset + readPice) >= count){
readPice = Math.toIntExact(count - currentOffset);
}
byteBuf = new byte[readPice];
if (bis.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.
bis.close();
}
currSockOS.flush(); // TODO: check if this really needed.
bis.close();
//-----------------------------------------/
//-------/
logPrinter.updateProgress(1.0);
//-----------------------------------------/
//-------/
}
catch (IOException ioe){
logPrinter.print("NET: File transmission failed. Returned:\n\t"+ioe.getMessage(), EMsgType.FAIL);
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 false;
@ -403,7 +480,7 @@ public class NETCommunications extends Task<Void> { // todo: thows IOException?
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);
}
if (status != null) {

View File

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

View File

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

View File

@ -1,10 +1,11 @@
package nsusbloader.USB;
package nsusbloader.COM.USB;
import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus;
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.LibUsb;
@ -18,7 +19,7 @@ import java.util.LinkedHashMap;
/**
* GoldLeaf processing
* */
public class GoldLeaf_05 implements ITransferModule{
public class GoldLeaf_05 extends TransferModule {
// CMD G L U C
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
@ -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_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 NSSplitReader nsr; // It'a also NSP File
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" +
" Only one file per time could be sent. In case you selected more the first one would be picked.", EMsgType.INFO);
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);
this.handlerNS = handler;
this.task = task;
this.logPrinter = logPrinter;
try{
this.raf = new RandomAccessFile(nspFile, "r");
if (nspFile.isDirectory())
this.nsr = new NSSplitReader(nspFile, 0);
else
this.raf = new RandomAccessFile(nspFile, "r");
}
catch (FileNotFoundException fnfe){
logPrinter.print("GL File not found\n\t"+fnfe.getMessage(), EMsgType.FAIL);
catch (IOException ioe){
logPrinter.print("GL File not found\n\t"+ioe.getMessage(), EMsgType.FAIL);
return;
}
@ -131,9 +132,11 @@ public class GoldLeaf_05 implements ITransferModule{
try {
raf.close();
}
catch (IOException ioe){
logPrinter.print("GL Failed to close file.", EMsgType.INFO);
catch (IOException | NullPointerException ignored){}
try {
nsr.close();
}
catch (IOException | NullPointerException ignored){}
}
/**
* ConnectionResponse command handler
@ -254,21 +257,41 @@ public class GoldLeaf_05 implements ITransferModule{
byte[] readBuf;
try{
raf.seek(realNcaOffset);
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 (raf.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;
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);
while (readFrom < realNcaSize){
if (realNcaSize - readFrom < readPice)
readPice = Math.toIntExact(realNcaSize - readFrom); // it's safe, I guarantee
readBuf = new byte[readPice];
if (raf.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;
}
}
//-----------------------------------------/
logPrinter.updateProgress(1.0);
@ -282,8 +305,6 @@ public class GoldLeaf_05 implements ITransferModule{
return false;
}
@Override
public EFileStatus getStatus() { return status; }
/**
* 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.ByteOrder;

View File

@ -1,8 +1,7 @@
package nsusbloader.USB.PFS;
package nsusbloader.COM.USB.PFS;
import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.ServiceWindow;
import java.io.*;
import java.nio.ByteBuffer;
@ -22,9 +21,14 @@ public class PFSProvider {
private int ticketID = -1;
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");
nspFileName = nspFile.getName();
int filesCount;
int header;

View File

@ -1,9 +1,10 @@
package nsusbloader.USB;
package nsusbloader.COM.USB;
import javafx.concurrent.Task;
import nsusbloader.ModelControllers.LogPrinter;
import nsusbloader.NSLDataTypes.EFileStatus;
import nsusbloader.NSLDataTypes.EMsgType;
import nsusbloader.COM.Helpers.NSSplitReader;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
@ -18,18 +19,9 @@ import java.util.LinkedHashMap;
/**
* Tinfoil processing
* */
class TinFoil implements ITransferModule {
private LogPrinter logPrinter;
private DeviceHandle handlerNS;
private LinkedHashMap<String, File> nspMap;
private EFileStatus status = EFileStatus.FAILED;
private Task<Void> task;
class TinFoil extends TransferModule {
TinFoil(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){
this.handlerNS = handler;
this.nspMap = nspMap;
this.task = task;
this.logPrinter = logPrinter;
super(handler, nspMap, task, logPrinter);
logPrinter.print("============= TinFoil =============", EMsgType.INFO);
@ -162,44 +154,83 @@ class TinFoil implements ITransferModule {
return false;
try {
BufferedInputStream bufferedInStream = new BufferedInputStream(new FileInputStream(nspMap.get(receivedRequestedNSP))); // TODO: refactor?
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;
}
byte[] bufferCurrent; //= new byte[1048576]; // eq. Allocate 1mb
long currentOffset = 0;
// 'End Offset' equal to receivedRangeSize.
int readPice = 8388608; // = 8Mb
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
//-----------------------------------------/
logPrinter.updateProgress((currentOffset+readPice)/(receivedRangeSize/100.0) / 100.0);
//-----------------------------------------/
bufferCurrent = new byte[readPice]; // TODO: not perfect moment, consider refactoring.
//---------------! 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;
}
if (bufferedInStream.read(bufferCurrent) != readPice) { // changed since @ v0.3.2
logPrinter.print("TF Reading of stream suddenly ended.", EMsgType.WARNING);
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.
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;
}
//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);
//------------------------/
}
bufferedInStream.close();
//-----------------------------------------/
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.
if (bufferedInStream.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;
}
bufferedInStream.close();
//---Tell progress to UI---/
logPrinter.updateProgress(1.0);
//------------------------/
}
//---------------! Regular files end !---------------
} catch (FileNotFoundException fnfe){
logPrinter.print("TF FileNotFoundException:\n "+fnfe.getMessage(), EMsgType.FAIL);
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);
ae.printStackTrace();
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;
@ -309,13 +344,4 @@ class TinFoil implements ITransferModule {
logPrinter.print("TF Execution interrupted", EMsgType.INFO);
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 nsusbloader.ModelControllers.LogPrinter;
@ -50,7 +50,7 @@ public class UsbCommunications extends Task<Void> {
DeviceHandle handler = usbConnect.getHandlerNS();
ITransferModule module;
TransferModule module;
if (protocol.equals("TinFoil"))
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.NSLDataTypes.EMsgType;

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package nsusbloader.Controllers;
import nsusbloader.NSLDataTypes.EFileStatus;
import java.io.File;
import java.io.FilenameFilter;
public class NSLRowModel {
@ -16,7 +17,15 @@ public class NSLRowModel {
this.nspFile = nspFile;
this.markForUpload = checkBoxValue;
this.nspFileName = nspFile.getName();
this.nspFileSize = nspFile.length();
if (nspFile.isFile())
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 = "";
}
// Model methods start

View File

@ -149,6 +149,24 @@ public class NSTableViewController implements Initializable {
table.getColumns().add(fileSizeColumn);
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
* */

View File

@ -12,7 +12,7 @@ import java.util.Locale;
import java.util.ResourceBundle;
public class NSLMain extends Application {
public static final String appVersion = "v0.8.2";
public static final String appVersion = "v0.9";
@Override
public void start(Stage primaryStage) throws Exception{
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);
for (byte b: byteArray)
System.out.print(String.format("%02x ", b));
//System.out.println();
System.out.print("\t\t\t"
+ new String(byteArray, StandardCharsets.UTF_8)
+ "\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" />
</VBox.margin>
</ProgressBar>
<HBox alignment="TOP_CENTER" VBox.vgrow="NEVER">
<HBox alignment="TOP_CENTER" spacing="3.0" VBox.vgrow="NEVER">
<children>
<Button fx:id="selectNspBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_OpenFile">
<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" />
</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" />
</graphic></Button>
<Pane HBox.hgrow="ALWAYS" />
<Button fx:id="uploadStopBtn" contentDisplay="TOP" mnemonicParsing="false" prefHeight="60.0" text="%btn_Upload">
<HBox.margin>

View File

@ -38,9 +38,10 @@ 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_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.
tab2_Lbl_Language=Language
windowBodyRestartToApplyLang=Please restart application to apply changes.
tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf.
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
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_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.

View File

@ -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 TinFoil
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

View File

@ -38,6 +38,6 @@ windowTitleNewVersionNOTAval=\uC0C8\uB85C\uC6B4 \uBC84\uC804\uC774 \uC5C6\uC2B5\
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_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

View File

@ -38,10 +38,11 @@ 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/XCZ \u0444\u0430\u0439\u043B\u043E\u0432 \u0434\u043B\u044F TinFoil
tab2_Cb_AllowXciNszXcz=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440 XCI / NSZ / 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.
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.
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

@ -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 Tinfoil
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.

View File

@ -38,9 +38,10 @@ 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/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_Cb_AllowXciNszXcz=\u0414\u043E\u0437\u0432\u043E\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0456\u0440 XCI / NSZ / 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.
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.
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