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;
|
|
}
|
|
} |