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

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