352 lines
		
	
	
		
			No EOL
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			No EOL
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package nsusbloader.USB;
 | |
| 
 | |
| import javafx.concurrent.Task;
 | |
| import nsusbloader.ModelControllers.LogPrinter;
 | |
| import nsusbloader.NSLDataTypes.EFileStatus;
 | |
| import nsusbloader.NSLDataTypes.EMsgType;
 | |
| import nsusbloader.USB.PFS.PFSProvider;
 | |
| 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.util.Arrays;
 | |
| import java.util.LinkedHashMap;
 | |
| 
 | |
| /**
 | |
|  * GoldLeaf processing
 | |
|  * */
 | |
| public class GoldLeaf_05 implements ITransferModule{
 | |
|     //                            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
 | |
|     private static final byte[] CMD_NSPName =            new byte[]{0x02, 0x00, 0x00, 0x00};    // Write-only command
 | |
|     private static final byte[] CMD_NSPData =            new byte[]{0x04, 0x00, 0x00, 0x00};    // Write-only command
 | |
| 
 | |
|     private static final byte[] CMD_ConnectionResponse = new byte[]{0x01, 0x00, 0x00, 0x00};
 | |
|     private static final byte[] CMD_Start =              new byte[]{0x03, 0x00, 0x00, 0x00};
 | |
|     private static final byte[] CMD_NSPContent =         new byte[]{0x05, 0x00, 0x00, 0x00};
 | |
|     private static final byte[] CMD_NSPTicket =          new byte[]{0x06, 0x00, 0x00, 0x00};
 | |
|     private static final byte[] CMD_Finish =             new byte[]{0x07, 0x00, 0x00, 0x00};
 | |
| 
 | |
|     private DeviceHandle handlerNS;
 | |
|     private Task<Void> task;
 | |
|     private LogPrinter logPrinter;
 | |
|     private EFileStatus status = EFileStatus.FAILED;
 | |
|     private RandomAccessFile raf;   // NSP File
 | |
| 
 | |
|     GoldLeaf_05(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){
 | |
|         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()){
 | |
|             logPrinter.print("For using this GoldLeaf version you have to add file to the table and select it for upload", EMsgType.INFO);
 | |
|             return;
 | |
|         }
 | |
|         File nspFile = (File) nspMap.values().toArray()[0];
 | |
|         logPrinter.print("File for upload: "+nspFile.getAbsolutePath(), EMsgType.INFO);
 | |
| 
 | |
|         if (!nspFile.getName().toLowerCase().endsWith(".nsp")) {
 | |
|             logPrinter.print("GL This file doesn't look like NSP", EMsgType.FAIL);
 | |
|             return;
 | |
|         }
 | |
|         PFSProvider pfsElement;
 | |
|         try{
 | |
|             pfsElement = new PFSProvider(nspFile, logPrinter);
 | |
|         }
 | |
|         catch (Exception e){
 | |
|             logPrinter.print("GL File provided has incorrect structure and won't be uploaded\n\t"+e.getMessage(), EMsgType.FAIL);
 | |
|             status = EFileStatus.INCORRECT_FILE_FAILED;
 | |
|             return;
 | |
|         }
 | |
|         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");
 | |
|         }
 | |
|         catch (FileNotFoundException fnfe){
 | |
|             logPrinter.print("GL File not found\n\t"+fnfe.getMessage(), EMsgType.FAIL);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Go parse commands
 | |
|         byte[] readByte;
 | |
| 
 | |
|         // Go connect to GoldLeaf
 | |
|         if (writeUsb(CMD_GLUC)) {
 | |
|             logPrinter.print("GL Initiating GoldLeaf connection [1/2]", EMsgType.FAIL);
 | |
|             return;
 | |
|         }
 | |
|         logPrinter.print("GL Initiating GoldLeaf connection: [1/2]", EMsgType.PASS);
 | |
|         if (writeUsb(CMD_ConnectionRequest)){
 | |
|             logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.FAIL);
 | |
|             return;
 | |
|         }
 | |
|         logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.PASS);
 | |
| 
 | |
|         while (true) {
 | |
|             readByte = readUsb();
 | |
|             if (readByte == null)
 | |
|                 return;
 | |
| 
 | |
|             if (Arrays.equals(readByte, CMD_GLUC)) {
 | |
|                 if ((readByte = readUsb()) == null)
 | |
|                     return;
 | |
| 
 | |
|                 if (Arrays.equals(readByte, CMD_ConnectionResponse)) {
 | |
|                     if (handleConnectionResponse(pfsElement))
 | |
|                         return;
 | |
|                     else
 | |
|                         continue;
 | |
|                 }
 | |
|                 if (Arrays.equals(readByte, CMD_Start)) {
 | |
|                     if (handleStart(pfsElement))
 | |
|                         return;
 | |
|                     else
 | |
|                         continue;
 | |
|                 }
 | |
|                 if (Arrays.equals(readByte, CMD_NSPContent)) {
 | |
|                     if (handleNSPContent(pfsElement, true))
 | |
|                         return;
 | |
|                     else
 | |
|                         continue;
 | |
|                 }
 | |
|                 if (Arrays.equals(readByte, CMD_NSPTicket)) {
 | |
|                     if (handleNSPContent(pfsElement, false))
 | |
|                         return;
 | |
|                     else
 | |
|                         continue;
 | |
|                 }
 | |
|                 if (Arrays.equals(readByte, CMD_Finish)) {
 | |
|                     logPrinter.print("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS);
 | |
|                     status = EFileStatus.UPLOADED;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         try {
 | |
|             raf.close();
 | |
|         }
 | |
|         catch (IOException ioe){
 | |
|             logPrinter.print("GL Failed to close file.", EMsgType.INFO);
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * ConnectionResponse command handler
 | |
|      * @return true if failed
 | |
|      *         false if no issues
 | |
|      * */
 | |
|     private boolean handleConnectionResponse(PFSProvider pfsElement){
 | |
|         logPrinter.print("GL 'ConnectionResponse' command:", EMsgType.INFO);
 | |
|         if (writeUsb(CMD_GLUC)) {
 | |
|             logPrinter.print("  [1/4]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [1/4]", EMsgType.PASS);
 | |
|         if (writeUsb(CMD_NSPName)) {
 | |
|             logPrinter.print("  [2/4]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [2/4]", EMsgType.PASS);
 | |
| 
 | |
|         if (writeUsb(pfsElement.getBytesNspFileNameLength())) {
 | |
|             logPrinter.print("  [3/4]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [3/4]", EMsgType.PASS);
 | |
| 
 | |
|         if (writeUsb(pfsElement.getBytesNspFileName())) {
 | |
|             logPrinter.print("  [4/4]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [4/4]", EMsgType.PASS);
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
|     /**
 | |
|      * Start command handler
 | |
|      * @return true if failed
 | |
|      *         false if no issues
 | |
|      * */
 | |
|     private boolean handleStart(PFSProvider pfsElement){
 | |
|         logPrinter.print("GL Handle 'Start' command:", EMsgType.INFO);
 | |
|         if (writeUsb(CMD_GLUC)) {
 | |
|             logPrinter.print("  [Prefix]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [Prefix]", EMsgType.PASS);
 | |
| 
 | |
|         if (writeUsb(CMD_NSPData)) {
 | |
|             logPrinter.print("  [Command]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [Command]", EMsgType.PASS);
 | |
| 
 | |
|         if (writeUsb(pfsElement.getBytesCountOfNca())) {
 | |
|             logPrinter.print("  [Sub-files count]", EMsgType.FAIL);
 | |
|             return true;
 | |
|         }
 | |
|         logPrinter.print("  [Sub-files count]", EMsgType.PASS);
 | |
| 
 | |
|         int ncaCount = pfsElement.getIntCountOfNca();
 | |
|         logPrinter.print("  [Information for "+ncaCount+" sub-files]", EMsgType.INFO);
 | |
|         for (int i = 0; i < ncaCount; i++){
 | |
|             logPrinter.print("File #"+i, EMsgType.INFO);
 | |
|             if (writeUsb(pfsElement.getNca(i).getNcaFileNameLength())) {
 | |
|                 logPrinter.print("  [1/4] Name length", EMsgType.FAIL);
 | |
|                 return true;
 | |
|             }
 | |
|             logPrinter.print("  [1/4] Name length", EMsgType.PASS);
 | |
| 
 | |
|             if (writeUsb(pfsElement.getNca(i).getNcaFileName())) {
 | |
|                 logPrinter.print("  [2/4] Name", EMsgType.FAIL);
 | |
|                 return true;
 | |
|             }
 | |
|             logPrinter.print("  [2/4] Name", EMsgType.PASS);
 | |
|             if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) {   // offset. real.
 | |
|                 logPrinter.print("  [3/4] Offset", EMsgType.FAIL);
 | |
|                 return true;
 | |
|             }
 | |
|             logPrinter.print("  [3/4] Offset", EMsgType.PASS);
 | |
|             if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) {  // size
 | |
|                 logPrinter.print("  [4/4] Size", EMsgType.FAIL);
 | |
|                 return true;
 | |
|             }
 | |
|             logPrinter.print("  [4/4] Size", EMsgType.PASS);
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
|     /**
 | |
|      * NSPContent command handler
 | |
|      * @param isItRawRequest true: just ask NS what's needed
 | |
|      *                       false: send ticket
 | |
|      * @return true if failed
 | |
|      *         false if no issues
 | |
|      * */
 | |
|     private boolean handleNSPContent(PFSProvider pfsElement, boolean isItRawRequest){
 | |
|         int requestedNcaID;
 | |
| 
 | |
|         if (isItRawRequest) {
 | |
|             logPrinter.print("GL Handle 'Content' command", EMsgType.INFO);
 | |
|             byte[] readByte = readUsb();
 | |
|             if (readByte == null || readByte.length != 4) {
 | |
|                 logPrinter.print("  [Read requested ID]", EMsgType.FAIL);
 | |
|                 return true;
 | |
|             }
 | |
|             requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt();
 | |
|             logPrinter.print("  [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS);
 | |
|         }
 | |
|         else {
 | |
|             requestedNcaID = pfsElement.getNcaTicketID();
 | |
|             logPrinter.print("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO);
 | |
|         }
 | |
| 
 | |
|         long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize();
 | |
|         long realNcaSize = pfsElement.getNca(requestedNcaID).getNcaSize();
 | |
| 
 | |
|         long readFrom = 0;
 | |
| 
 | |
|         int readPice = 8388608; // 8mb
 | |
|         byte[] readBuf;
 | |
| 
 | |
|         try{
 | |
|             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);
 | |
|             //-----------------------------------------/
 | |
|         }
 | |
|         catch (IOException ioe){
 | |
|             logPrinter.print("GL Failed to read NCA ID "+requestedNcaID+". IO Exception:\n  "+ioe.getMessage(), EMsgType.FAIL);
 | |
|             ioe.printStackTrace();
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public EFileStatus getStatus() { return status; }
 | |
| 
 | |
|     /**
 | |
|      * 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;
 | |
| 
 | |
|         while (! task.isCancelled()) {
 | |
|             result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 1000);  // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01
 | |
| 
 | |
|             switch (result){
 | |
|                 case LibUsb.SUCCESS:
 | |
|                     if (writeBufTransferred.get() == message.length)
 | |
|                         return false;
 | |
|                     else {
 | |
|                         logPrinter.print("GL Data transfer issue [write]\n  Requested: "+message.length+"\n  Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
 | |
|                         return true;
 | |
|                     }
 | |
|                 case LibUsb.ERROR_TIMEOUT:
 | |
|                     continue;
 | |
|                 default:
 | |
|                     logPrinter.print("GL Data transfer issue [write]\n  Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL);
 | |
|                     logPrinter.print("GL Execution stopped", EMsgType.FAIL);
 | |
|                     return true;
 | |
|             }
 | |
|         }
 | |
|         logPrinter.print("GL 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:
 | |
|                     logPrinter.print("GL Data transfer issue [read]\n  Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL);
 | |
|                     logPrinter.print("GL Execution stopped", EMsgType.FAIL);
 | |
|                     return null;
 | |
|             }
 | |
|         }
 | |
|         logPrinter.print("GL Execution interrupted", EMsgType.INFO);
 | |
|         return null;
 | |
|     }
 | |
| } | 
