2020-03-30 04:58:39 +03:00
/ *
Copyright 2019 - 2020 Dmitry Isaenko
This file is part of NS - USBloader .
NS - USBloader is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
NS - USBloader is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with NS - USBloader . If not , see < https : //www.gnu.org/licenses/>.
* /
2019-10-27 00:02:40 +03:00
package nsusbloader.COM.USB ;
2019-09-25 05:43:47 +03:00
2020-07-07 15:02:37 +03:00
import nsusbloader.COM.INSTask ;
2020-07-03 04:01:20 +03:00
import nsusbloader.ModelControllers.ILogPrinter ;
2019-09-25 05:43:47 +03:00
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)
2020-03-30 04:58:39 +03:00
private static final byte [ ] TUL0 = new byte [ ] { ( byte ) 0x54 , ( byte ) 0x55 , ( byte ) 0x4c , ( byte ) 0x30 } ;
private static final byte [ ] MAGIC = new byte [ ] { ( byte ) 0x54 , ( byte ) 0x55 , ( byte ) 0x43 , ( byte ) 0x30 } ; // aka 'TUC0' ASCII
private static final byte CMD_EXIT = 0x00 ;
private static final byte CMD_FILE_RANGE_DEFAULT = 0x01 ;
private static final byte CMD_FILE_RANGE_ALTERNATIVE = 0x02 ;
/ * byte [ ] magic = new byte [ 4 ] ;
ByteBuffer bb = StandardCharsets . UTF_8 . encode ( " TUC0 " ) . rewind ( ) . get ( magic ) ; // Let's rephrase this 'string' */
2019-12-18 06:29:49 +03:00
2020-07-07 15:02:37 +03:00
TinFoil ( DeviceHandle handler , LinkedHashMap < String , File > nspMap , INSTask task , ILogPrinter logPrinter ) {
2019-10-27 00:02:40 +03:00
super ( handler , nspMap , task , logPrinter ) ;
2020-03-30 04:58:39 +03:00
logPrinter . print ( " ============= Tinfoil ============= " , EMsgType . INFO ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
if ( ! sendListOfFiles ( ) )
2019-09-25 05:43:47 +03:00
return ;
if ( proceedCommands ( ) ) // REPORT SUCCESS
status = EFileStatus . UPLOADED ; // Don't change status that is already set to FAILED
}
/ * *
* Send what NSP will be transferred
* * /
2020-03-30 04:58:39 +03:00
private boolean sendListOfFiles ( ) {
final String fileNamesListToSend = getFileNamesToSend ( ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
byte [ ] nspListNames = getFileNamesToSendAsBytes ( fileNamesListToSend ) ;
byte [ ] nspListNamesSize = getFileNamesLengthToSendAsBytes ( nspListNames ) ;
byte [ ] padding = new byte [ 8 ] ;
2019-12-18 06:29:49 +03:00
if ( writeUsb ( TUL0 ) ) {
2020-03-30 04:58:39 +03:00
logPrinter . print ( " TF Send list of files: handshake [1/4] " , EMsgType . FAIL ) ;
2019-12-18 06:29:49 +03:00
return false ;
}
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
if ( writeUsb ( nspListNamesSize ) ) { // size of the list we can transfer
logPrinter . print ( " TF Send list of files: list length [2/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2020-03-30 04:58:39 +03:00
if ( writeUsb ( padding ) ) {
logPrinter . print ( " TF Send list of files: padding [3/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2020-03-30 04:58:39 +03:00
if ( writeUsb ( nspListNames ) ) {
logPrinter . print ( " TF Send list of files: list itself [4/4] " , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
return false ;
}
2020-03-30 04:58:39 +03:00
logPrinter . print ( " TF Send list of files complete. " , EMsgType . PASS ) ;
2019-09-25 05:43:47 +03:00
return true ;
}
2020-03-30 04:58:39 +03:00
private String getFileNamesToSend ( ) {
StringBuilder fileNamesListBuilder = new StringBuilder ( ) ;
for ( String nspFileName : nspMap . keySet ( ) ) {
fileNamesListBuilder . append ( nspFileName ) ; // And here we come with java string default encoding (UTF-16)
fileNamesListBuilder . append ( '\n' ) ;
}
return fileNamesListBuilder . toString ( ) ;
}
private byte [ ] getFileNamesToSendAsBytes ( String fileNamesListToSend ) {
return fileNamesListToSend . getBytes ( StandardCharsets . UTF_8 ) ;
}
private byte [ ] getFileNamesLengthToSendAsBytes ( byte [ ] fileNamesListToSendAsBytes ) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( Integer . BYTES ) . order ( ByteOrder . LITTLE_ENDIAN ) ; // integer = 4 bytes; BTW Java is stored in big-endian format
byteBuffer . putInt ( fileNamesListToSendAsBytes . length ) ; // This way we obtain length in int converted to byte array in correct Big-endian order. Trust me.
return byteBuffer . array ( ) ;
}
2019-09-25 05:43:47 +03:00
/ * *
* After we sent commands to NS , this chain starts
* * /
private boolean proceedCommands ( ) {
logPrinter . print ( " TF Awaiting for NS commands. " , EMsgType . INFO ) ;
2020-03-30 04:58:39 +03:00
try {
byte [ ] deviceReply ;
byte command ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
while ( true ) {
deviceReply = readUsb ( ) ;
if ( ! isReplyValid ( deviceReply ) )
continue ;
command = getCommandFromReply ( deviceReply ) ;
switch ( command ) {
case CMD_EXIT :
logPrinter . print ( " TF Transfer complete. " , EMsgType . PASS ) ;
return true ;
case CMD_FILE_RANGE_DEFAULT :
case CMD_FILE_RANGE_ALTERNATIVE :
//logPrinter.print("TF Received 'FILE RANGE' command [0x0"+command+"].", EMsgType.PASS);
if ( fileRangeCmd ( ) )
return false ; // catches exception
}
2019-09-25 05:43:47 +03:00
}
}
2020-03-30 04:58:39 +03:00
catch ( Exception e ) {
logPrinter . print ( e . getMessage ( ) , EMsgType . INFO ) ;
return false ;
}
}
private boolean isReplyValid ( byte [ ] reply ) {
return Arrays . equals ( Arrays . copyOfRange ( reply , 0 , 4 ) , MAGIC ) ;
}
private byte getCommandFromReply ( byte [ ] reply ) {
return reply [ 8 ] ;
2019-09-25 05:43:47 +03:00
}
/ * *
* This is what returns requested file ( files )
* Executes multiple times
2020-03-30 04:58:39 +03:00
* @return ' false ' if everything is ok
* ' true ' is error / exception occurs
2019-09-25 05:43:47 +03:00
* * /
private boolean fileRangeCmd ( ) {
2020-03-30 04:58:39 +03:00
try {
byte [ ] receivedArray = readUsb ( ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
byte [ ] sizeAsBytes = Arrays . copyOfRange ( receivedArray , 0 , 8 ) ;
long size = ByteBuffer . wrap ( sizeAsBytes ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ; // could be unsigned long. This app won't support files greater then 8796093022208 Gb
long offset = ByteBuffer . wrap ( Arrays . copyOfRange ( receivedArray , 8 , 16 ) ) . order ( ByteOrder . LITTLE_ENDIAN ) . getLong ( ) ; // could be unsigned long. This app doesn't support files greater then 8796093022208 Gb
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
// Requesting UTF-8 file name required:
receivedArray = readUsb ( ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
String nspFileName = new String ( receivedArray , StandardCharsets . UTF_8 ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
logPrinter . print ( String . format ( " TF Reply to: %s " +
" \ n Offset: %-20d 0x%x " +
" \ n Size: %-20d 0x%x " ,
nspFileName ,
offset , offset ,
size , size ) , EMsgType . INFO ) ;
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
File nspFile = nspMap . get ( nspFileName ) ;
boolean isSplitFile = nspFile . isDirectory ( ) ;
2019-10-27 00:02:40 +03:00
2020-03-30 04:58:39 +03:00
// Sending response 'header'
if ( sendMetaInfoForFile ( sizeAsBytes ) ) // Get size in 'RAW' format exactly as it has been received to simplify the process.
return true ;
2019-12-18 06:29:49 +03:00
2020-03-30 04:58:39 +03:00
if ( isSplitFile )
sendSplitFile ( nspFile , size , offset ) ;
else
sendNormalFile ( nspFile , size , offset ) ;
2019-09-25 05:43:47 +03:00
} catch ( IOException ioe ) {
2020-03-30 04:58:39 +03:00
logPrinter . print ( " TF IOException: \ n " + ioe . getMessage ( ) , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
ioe . printStackTrace ( ) ;
2020-03-30 04:58:39 +03:00
return true ;
2019-09-25 05:43:47 +03:00
} catch ( ArithmeticException ae ) {
2020-03-30 04:58:39 +03:00
logPrinter . print ( " TF ArithmeticException (can't cast 'offset end' - 'offsets current' to 'integer'): " +
" \ n " + ae . getMessage ( ) , EMsgType . FAIL ) ;
2019-09-25 05:43:47 +03:00
ae . printStackTrace ( ) ;
2020-03-30 04:58:39 +03:00
return true ;
2019-10-27 00:02:40 +03:00
} catch ( NullPointerException npe ) {
2020-03-30 04:58:39 +03:00
logPrinter . print ( " TF NullPointerException (in some moment application didn't find something. Something important.): " +
" \ n " + npe . getMessage ( ) , EMsgType . FAIL ) ;
2019-10-27 00:02:40 +03:00
npe . printStackTrace ( ) ;
2020-03-30 04:58:39 +03:00
return true ;
}
catch ( Exception defe ) {
logPrinter . print ( defe . getMessage ( ) , EMsgType . FAIL ) ;
return true ;
2019-09-25 05:43:47 +03:00
}
2020-03-30 04:58:39 +03:00
return false ;
}
2019-09-25 05:43:47 +03:00
2020-03-30 04:58:39 +03:00
void sendSplitFile ( File nspFile , long size , long offset ) throws IOException , NullPointerException , ArithmeticException {
byte [ ] readBuffer ;
long currentOffset = 0 ;
int chunk = 8388608 ; // = 8Mb;
NSSplitReader nsSplitReader = new NSSplitReader ( nspFile , size ) ;
if ( nsSplitReader . seek ( offset ) ! = offset )
throw new IOException ( " TF Requested offset is out of file size. Nothing to transmit. " ) ;
while ( currentOffset < size ) {
if ( ( currentOffset + chunk ) > = size )
chunk = Math . toIntExact ( size - currentOffset ) ;
//System.out.println("CO: "+currentOffset+"\t\tEO: "+size+"\t\tRP: "+chunk); // NOTE: DEBUG
logPrinter . updateProgress ( ( currentOffset + chunk ) / ( size / 100 . 0 ) / 100 . 0 ) ;
readBuffer = new byte [ chunk ] ; // TODO: not perfect moment, consider refactoring.
if ( nsSplitReader . read ( readBuffer ) ! = chunk )
throw new IOException ( " TF Reading from stream suddenly ended. " ) ;
if ( writeUsb ( readBuffer ) )
throw new IOException ( " TF Failure during file transfer. " ) ;
currentOffset + = chunk ;
}
nsSplitReader . close ( ) ;
logPrinter . updateProgress ( 1 . 0 ) ;
}
void sendNormalFile ( File nspFile , long size , long offset ) throws IOException , NullPointerException , ArithmeticException {
byte [ ] readBuffer ;
long currentOffset = 0 ;
int chunk = 8388608 ;
BufferedInputStream bufferedInStream = new BufferedInputStream ( new FileInputStream ( nspFile ) ) ;
if ( bufferedInStream . skip ( offset ) ! = offset )
throw new IOException ( " TF Requested offset is out of file size. Nothing to transmit. " ) ;
while ( currentOffset < size ) {
if ( ( currentOffset + chunk ) > = size )
chunk = Math . toIntExact ( size - currentOffset ) ;
//System.out.println("CO: "+currentOffset+"\t\tEO: "+receivedRangeSize+"\t\tRP: "+chunk); // NOTE: DEBUG
logPrinter . updateProgress ( ( currentOffset + chunk ) / ( size / 100 . 0 ) / 100 . 0 ) ;
readBuffer = new byte [ chunk ] ;
if ( bufferedInStream . read ( readBuffer ) ! = chunk )
throw new IOException ( " TF Reading from stream suddenly ended. " ) ;
if ( writeUsb ( readBuffer ) )
throw new IOException ( " TF Failure during file transfer. " ) ;
currentOffset + = chunk ;
}
bufferedInStream . close ( ) ;
logPrinter . updateProgress ( 1 . 0 ) ;
2019-09-25 05:43:47 +03:00
}
/ * *
* Send response header .
2020-03-30 04:58:39 +03:00
* @return false if everything OK
* true if failed
2019-09-25 05:43:47 +03:00
* * /
2020-03-30 04:58:39 +03:00
private boolean sendMetaInfoForFile ( byte [ ] sizeAsBytes ) {
2019-12-18 06:29:49 +03:00
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 ] ;
2020-03-30 04:58:39 +03:00
if ( writeUsb ( standardReplyBytes ) ) { // Send integer value of '1' in Little-endian format.
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Sending response failed [1/3] " , EMsgType . FAIL ) ;
2020-03-30 04:58:39 +03:00
return true ;
2019-09-25 05:43:47 +03:00
}
2019-12-18 06:29:49 +03:00
2020-03-30 04:58:39 +03:00
if ( writeUsb ( sizeAsBytes ) ) { // Send EXACTLY what has been received
2019-12-18 06:29:49 +03:00
logPrinter . print ( " TF Sending response failed [2/3] " , EMsgType . FAIL ) ;
2020-03-30 04:58:39 +03:00
return true ;
2019-09-25 05:43:47 +03:00
}
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 ) ;
2020-03-30 04:58:39 +03:00
return true ;
2019-09-25 05:43:47 +03:00
}
2020-03-30 04:58:39 +03:00
return false ;
2019-09-25 05:43:47 +03:00
}
/ * *
* Sending any byte array to USB device
* @return ' false ' if no issues
* ' true ' if errors happened
* * /
2020-03-30 04:58:39 +03:00
private boolean writeUsb ( byte [ ] message ) {
2019-09-25 05:43:47 +03:00
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 )
2020-03-30 04:58:39 +03:00
logPrinter . print ( " writeUsb() retry cnt: " + varVar , EMsgType . INFO ) ; //NOTE: DEBUG
2019-12-18 06:29:49 +03:00
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
* * /
2020-03-30 04:58:39 +03:00
private byte [ ] readUsb ( ) throws Exception {
2019-09-25 05:43:47 +03:00
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 :
2020-03-30 04:58:39 +03:00
throw new Exception ( " TF Data transfer issue [read] " +
2019-12-18 06:29:49 +03:00
" \ n Returned: " + UsbErrorCodes . getErrCode ( result ) +
2020-03-30 04:58:39 +03:00
" \ n (execution stopped) " ) ;
2019-09-25 05:43:47 +03:00
}
}
2020-03-30 04:58:39 +03:00
throw new InterruptedException ( " TF Execution interrupted " ) ;
2019-09-25 05:43:47 +03:00
}
}