2019-10-27 00:02:40 +03:00
package nsusbloader.COM.USB ;
2019-09-25 05:43:47 +03:00
import javafx.concurrent.Task ;
import nsusbloader.ModelControllers.LogPrinter ;
import nsusbloader.NSLDataTypes.EFileStatus ;
import nsusbloader.NSLDataTypes.EMsgType ;
2019-10-27 00:02:40 +03:00
import nsusbloader.COM.Helpers.NSSplitReader ;
2019-09-25 05:43:47 +03:00
import org.usb4java.DeviceHandle ;
import org.usb4java.LibUsb ;
import java.io.* ;
import java.nio.ByteBuffer ;
import java.nio.ByteOrder ;
import java.nio.IntBuffer ;
import java.nio.charset.StandardCharsets ;
import java.util.Arrays ;
import java.util.LinkedHashMap ;
/ * *
* Tinfoil processing
* * /
2019-10-27 00:02:40 +03:00
class TinFoil extends TransferModule {
2019-12-18 06:29:49 +03:00
// "TUL0".getBytes(StandardCharsets.US_ASCII)
private static final byte [ ] TUL0 = new byte [ ] { ( byte ) 0x54 , ( byte ) 0x55 , ( byte ) 0x4c , ( byte ) 0x30 } ;
2019-09-25 05:43:47 +03:00
TinFoil ( DeviceHandle handler , LinkedHashMap < String , File > nspMap , Task < Void > task , LogPrinter logPrinter ) {
2019-10-27 00:02:40 +03:00
super ( handler , nspMap , task , logPrinter ) ;
2019-09-25 05:43:47 +03:00
logPrinter . print ( " ============= TinFoil ============= " , EMsgType . INFO ) ;
if ( ! sendListOfNSP ( ) )
return ;
if ( proceedCommands ( ) ) // REPORT SUCCESS
status = EFileStatus . UPLOADED ; // Don't change status that is already set to FAILED
}
/ * *
* Send what NSP will be transferred
* * /
private boolean sendListOfNSP ( ) {
//Collect file names
StringBuilder nspListNamesBuilder = new StringBuilder ( ) ; // Add every title to one stringBuilder
for ( String nspFileName : nspMap . keySet ( ) ) {
nspListNamesBuilder . append ( nspFileName ) ; // And here we come with java string default encoding (UTF-16)
nspListNamesBuilder . append ( '\n' ) ;
}
byte [ ] nspListNames = nspListNamesBuilder . toString ( ) . getBytes ( StandardCharsets . UTF_8 ) ;
ByteBuffer byteBuffer = ByteBuffer . allocate ( Integer . BYTES ) . order ( ByteOrder . LITTLE_ENDIAN ) ; // integer = 4 bytes; BTW Java is stored in big-endian format
byteBuffer . putInt ( nspListNames . length ) ; // This way we obtain length in int converted to byte array in correct Big-endian order. Trust me.
2019-12-18 06:29:49 +03:00
byte [ ] nspListSize = byteBuffer . array ( ) ;
logPrinter . print ( " TF Send list of files: " , EMsgType . INFO ) ;
// Proceed "TUL0"
if ( writeUsb ( TUL0 ) ) {
logPrinter . print ( " handshake [1/4] " , EMsgType . FAIL ) ;
return false ;
}
logPrinter . print ( " handshake [1/4] " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
// Sending NSP list
if ( writeUsb ( nspListSize ) ) { // size of the list we're going to transfer goes...
2019-12-18 06:29:49 +03:00
logPrinter . print ( " list length [2/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
logPrinter . print ( " list length [2/4] " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
if ( writeUsb ( new byte [ 8 ] ) ) { // 8 zero bytes goes...
2019-12-18 06:29:49 +03:00
logPrinter . print ( " padding [3/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
logPrinter . print ( " padding [3/4] " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
if ( writeUsb ( nspListNames ) ) { // list of the names goes...
2019-12-18 06:29:49 +03:00
logPrinter . print ( " list itself [4/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
logPrinter . print ( " list itself [4/4] " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
return true ;
}
/ * *
* After we sent commands to NS , this chain starts
* * /
private boolean proceedCommands ( ) {
logPrinter . print ( " TF Awaiting for NS commands. " , EMsgType . INFO ) ;
/ * byte [ ] magic = new byte [ 4 ] ;
ByteBuffer bb = StandardCharsets . UTF_8 . encode ( " TUC0 " ) . rewind ( ) . get ( magic ) ; // Let's rephrase this 'string'
* /
final byte [ ] magic = new byte [ ] { ( byte ) 0x54 , ( byte ) 0x55 , ( byte ) 0x43 , ( byte ) 0x30 } ; // eq. 'TUC0' @ UTF-8 (actually ASCII lol, u know what I mean)
byte [ ] receivedArray ;
while ( true ) { // Check if user interrupted process.
receivedArray = readUsb ( ) ;
if ( receivedArray = = null ) // catches error
return false ;
if ( ! Arrays . equals ( Arrays . copyOfRange ( receivedArray , 0 , 4 ) , magic ) ) // Bytes from 0 to 3 should contain 'magic' TUC0, so must be verified like this
continue ;
// 8th to 12th(explicits) bytes in returned data stands for command ID as unsigned integer (Little-endian). Actually, we have to compare arrays here, but in real world it can't be greater then 0/1/2, thus:
// BTW also protocol specifies 4th byte to be 0x00 kinda indicating that that this command is valid. But, as you may see, never happens other situation when it's not = 0.
if ( receivedArray [ 8 ] = = 0x00 ) { //0x00 - exit
logPrinter . print ( " TF Received 'EXIT' command. Terminating. " , EMsgType . PASS ) ;
return true ; // All interaction with USB device should be ended (expected);
}
else if ( ( receivedArray [ 8 ] = = 0x01 ) | | ( receivedArray [ 8 ] = = 0x02 ) ) { //0x01 - file range; 0x02 unknown bug on backend side (dirty hack).
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Received 'FILE RANGE' command [0x0 " + receivedArray [ 8 ] + " ]. " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
/ * // We can get in this pocket a length of file name (+32). Why +32? I dunno man.. Do we need this? Definitely not. This app can live without it.
long receivedSize = ByteBuffer . wrap ( Arrays . copyOfRange ( receivedArray , 12 , 20 ) ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ;
logsArea . appendText ( " [V] Received FILE_RANGE command. Size: " + Long . toUnsignedString ( receivedSize ) + " \ n " ) ; // this shit returns string that will be chosen next '+32'. And, BTW, can't be greater then 512
* /
if ( ! fileRangeCmd ( ) )
return false ; // catches exception
}
}
}
/ * *
* This is what returns requested file ( files )
* Executes multiple times
* @return ' true ' if everything is ok
* ' false ' is error / exception occurs
* * /
private boolean fileRangeCmd ( ) {
byte [ ] receivedArray ;
// Here we take information of what other side wants
2019-12-18 06:29:49 +03:00
if ( ( receivedArray = readUsb ( ) ) = = null )
2019-09-25 05:43:47 +03:00
return false ;
2019-12-18 06:29:49 +03:00
// range_offset of the requested file. In the beginning it will be 0x10.
2019-09-25 05:43:47 +03:00
long receivedRangeSize = ByteBuffer . wrap ( Arrays . copyOfRange ( receivedArray , 0 , 8 ) ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ; // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb
2019-12-18 06:29:49 +03:00
byte [ ] receivedRangeSizeRAW = Arrays . copyOfRange ( receivedArray , 0 , 8 ) ; // used (only) when we use sendResponse().
2019-09-25 05:43:47 +03:00
long receivedRangeOffset = ByteBuffer . wrap ( Arrays . copyOfRange ( receivedArray , 8 , 16 ) ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ; // Note - it could be unsigned long. Unfortunately, this app won't support files greater then 8796093022208 Gb
/ * Below , it ' s REAL NSP file name length that we sent before among others ( WITHOUT + 32 byes ) . It can ' t be greater then . . . see what is written in the beginning of this code .
We don ' t need this since in next pocket we ' ll get name itself UTF - 8 encoded . Could be used to double - checks or something like that .
long receivedNspNameLen = ByteBuffer . wrap ( Arrays . copyOfRange ( receivedArray , 16 , 24 ) ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ; * /
// Requesting UTF-8 file name required:
2019-12-18 06:29:49 +03:00
if ( ( receivedArray = readUsb ( ) ) = = null )
2019-09-25 05:43:47 +03:00
return false ;
String receivedRequestedNSP = new String ( receivedArray , StandardCharsets . UTF_8 ) ;
2019-12-18 06:29:49 +03:00
logPrinter . print ( String . format ( " TF Reply for: %s " +
" \ n Offset: %-20d 0x%x " +
" \ n Size: %-20d 0x%x " ,
receivedRequestedNSP ,
receivedRangeOffset , receivedRangeOffset ,
receivedRangeSize , receivedRangeSize ) , EMsgType . INFO ) ;
2019-09-25 05:43:47 +03:00
// Sending response header
2019-12-18 06:29:49 +03:00
if ( ! sendResponse ( receivedRangeSizeRAW ) ) // Get receivedRangeSize in 'RAW' format exactly as it has been received to simplify the process.
2019-09-25 05:43:47 +03:00
return false ;
try {
2019-10-27 00:02:40 +03:00
byte [ ] bufferCurrent ; //= new byte[1048576]; // eq. Allocate 1mb
2019-09-25 05:43:47 +03:00
long currentOffset = 0 ;
// 'End Offset' equal to receivedRangeSize.
int readPice = 8388608 ; // = 8Mb
2019-10-27 00:02:40 +03:00
//---------------! Split files start !---------------
if ( nspMap . get ( receivedRequestedNSP ) . isDirectory ( ) ) {
NSSplitReader nsSplitReader = new NSSplitReader ( nspMap . get ( receivedRequestedNSP ) , receivedRangeSize ) ;
if ( nsSplitReader . seek ( receivedRangeOffset ) ! = receivedRangeOffset ) {
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Requested offset is out of file size. Nothing to transmit. " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-10-27 00:02:40 +03:00
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
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Reading from stream suddenly ended. " , EMsgType . WARNING ) ;
2019-10-27 00:02:40 +03:00
return false ;
}
//write to USB
if ( writeUsb ( bufferCurrent ) ) {
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Failure during file transfer. " , EMsgType . FAIL ) ;
2019-10-27 00:02:40 +03:00
return false ;
}
currentOffset + = readPice ;
}
nsSplitReader . close ( ) ;
}
//---------------! Split files end !---------------
//---------------! Regular files start !---------------
else {
BufferedInputStream bufferedInStream = new BufferedInputStream ( new FileInputStream ( nspMap . get ( receivedRequestedNSP ) ) ) ; // TODO: refactor?
if ( bufferedInStream . skip ( receivedRangeOffset ) ! = receivedRangeOffset ) {
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Requested offset is out of file size. Nothing to transmit. " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
/ *
File dir = new File ( System . getProperty ( " user.home " ) + File . separator + " DBG_ " + receivedRequestedNSP ) ; //todo: remove
dir . mkdirs ( ) ; //todo: remove
2019-10-27 00:02:40 +03:00
2019-12-18 06:29:49 +03:00
File chunkFile = new File ( System . getProperty ( " user.home " ) + File . separator + " DBG_ " + receivedRequestedNSP + File . separator + receivedRangeSize + " _ " + receivedRangeOffset + " .part " ) ; //todo: remove
BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ( chunkFile ) ) ; //todo: remove
* /
2019-10-27 00:02:40 +03:00
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
2019-12-18 06:29:49 +03:00
//logPrinter.print(String.format("CO: %-20d EO: %-20d RP: %d\n", currentOffset, receivedRangeSize, readPice), EMsgType.NULL); // NOTE: better DEBUG
2019-10-27 00:02:40 +03:00
// 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
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Reading from stream suddenly ended. " , EMsgType . WARNING ) ;
2019-10-27 00:02:40 +03:00
return false ;
}
//write to USB
2019-12-18 06:29:49 +03:00
//bos.write(bufferCurrent); //todo: remove
2019-10-27 00:02:40 +03:00
if ( writeUsb ( bufferCurrent ) ) {
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Failure during file transfer. " , EMsgType . FAIL ) ;
2019-10-27 00:02:40 +03:00
return false ;
}
currentOffset + = readPice ;
}
bufferedInStream . close ( ) ;
2019-12-18 06:29:49 +03:00
//bos.close(); //todo: remove
2019-09-25 05:43:47 +03:00
}
2019-10-27 00:02:40 +03:00
//---------------! Regular files end !---------------
2019-12-18 06:29:49 +03:00
//---Tell progress to UI---/
logPrinter . updateProgress ( 1 . 0 ) ;
2019-09-25 05:43:47 +03:00
} catch ( FileNotFoundException fnfe ) {
logPrinter . print ( " TF FileNotFoundException: \ n " + fnfe . getMessage ( ) , EMsgType . FAIL ) ;
fnfe . printStackTrace ( ) ;
return false ;
} catch ( IOException ioe ) {
logPrinter . print ( " TF IOException: \ n " + ioe . getMessage ( ) , EMsgType . FAIL ) ;
ioe . printStackTrace ( ) ;
return false ;
} catch ( ArithmeticException ae ) {
logPrinter . print ( " TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'): \ n " + ae . getMessage ( ) , EMsgType . FAIL ) ;
ae . printStackTrace ( ) ;
return false ;
2019-10-27 00:02:40 +03:00
} 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 ;
2019-09-25 05:43:47 +03:00
}
return true ;
}
/ * *
* Send response header .
* @return true if everything OK
* false if failed
* * /
2019-12-18 06:29:49 +03:00
private boolean sendResponse ( byte [ ] rangeSize ) { // This method as separate function itself for application needed as a cookie in the middle of desert.
final byte [ ] standardReplyBytes = new byte [ ] { 0x54 , 0x55 , 0x43 , 0x30 , // 'TUC0'
0x01 , 0x00 , 0x00 , 0x00 , // CMD_TYPE_RESPONSE = 1
0x01 , 0x00 , 0x00 , 0x00 } ;
final byte [ ] twelveZeroBytes = new byte [ 12 ] ;
//logPrinter.print("TF Sending response", EMsgType.INFO);
if ( writeUsb ( standardReplyBytes ) // Send integer value of '1' in Little-endian format.
2019-09-25 05:43:47 +03:00
) {
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Sending response failed [1/3] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
2019-09-25 05:43:47 +03:00
if ( writeUsb ( rangeSize ) ) { // Send EXACTLY what has been received
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Sending response failed [2/3] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
if ( writeUsb ( twelveZeroBytes ) ) { // kinda another one padding
logPrinter . print ( " TF Sending response failed [3/3] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Sending response complete (3/3) " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
return true ;
}
/ * *
* Sending any byte array to USB device
* @return ' false ' if no issues
* ' true ' if errors happened
* * /
private boolean writeUsb ( byte [ ] message ) {
ByteBuffer writeBuffer = ByteBuffer . allocateDirect ( message . length ) ; //writeBuffer.order() equals BIG_ENDIAN;
writeBuffer . put ( message ) ; // Don't do writeBuffer.rewind();
IntBuffer writeBufTransferred = IntBuffer . allocate ( 1 ) ;
int result ;
2019-12-18 06:29:49 +03:00
//int varVar = 0; //todo:remove
2019-09-25 05:43:47 +03:00
while ( ! task . isCancelled ( ) ) {
2019-12-18 06:29:49 +03:00
/ *
if ( varVar ! = 0 )
logPrinter . print ( " writeUsb() retry cnt: " + varVar , EMsgType . INFO ) ; //todo:remove
varVar + + ;
* /
result = LibUsb . bulkTransfer ( handlerNS , ( byte ) 0x01 , writeBuffer , writeBufTransferred , 5050 ) ; // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01
2019-09-25 05:43:47 +03:00
switch ( result ) {
case LibUsb . SUCCESS :
if ( writeBufTransferred . get ( ) = = message . length )
return false ;
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Data transfer issue [write] " +
" \ n Requested: " + message . length +
" \ n Transferred: " + writeBufTransferred . get ( ) , EMsgType . FAIL ) ;
return true ;
2019-09-25 05:43:47 +03:00
case LibUsb . ERROR_TIMEOUT :
2019-12-18 06:29:49 +03:00
//System.out.println("writeBuffer position: "+writeBuffer.position()+" "+writeBufTransferred.get());
//writeBufTransferred.clear(); // MUST BE HERE IF WE 'GET()' IT
2019-09-25 05:43:47 +03:00
continue ;
default :
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Data transfer issue [write] " +
" \ n Returned: " + UsbErrorCodes . getErrCode ( result ) +
" \ n (execution stopped) " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return true ;
}
}
logPrinter . print ( " TF Execution interrupted " , EMsgType . INFO ) ;
return true ;
}
/ * *
* Reading what USB device responded .
* @return byte array if data read successful
* ' null ' if read failed
* * /
private byte [ ] readUsb ( ) {
ByteBuffer readBuffer = ByteBuffer . allocateDirect ( 512 ) ;
// We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb.
IntBuffer readBufTransferred = IntBuffer . allocate ( 1 ) ;
int result ;
while ( ! task . isCancelled ( ) ) {
result = LibUsb . bulkTransfer ( handlerNS , ( byte ) 0x81 , readBuffer , readBufTransferred , 1000 ) ; // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
switch ( result ) {
case LibUsb . SUCCESS :
int trans = readBufTransferred . get ( ) ;
byte [ ] receivedBytes = new byte [ trans ] ;
readBuffer . get ( receivedBytes ) ;
return receivedBytes ;
case LibUsb . ERROR_TIMEOUT :
continue ;
default :
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Data transfer issue [read] " +
" \ n Returned: " + UsbErrorCodes . getErrCode ( result ) +
" \ n (execution stopped) " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return null ;
}
}
logPrinter . print ( " TF Execution interrupted " , EMsgType . INFO ) ;
return null ;
}
}