Files
FastBoot/FastBootTransport.cs

245 lines
7.9 KiB
C#

/*
* MIT License
*
* Copyright (c) 2024 The DuoWOA authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using MadWizard.WinUSBNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace FastBoot
{
public class FastBootTransport : IDisposable
{
private bool Disposed = false;
private readonly USBDevice USBDevice = null;
private USBPipe InputPipe = null;
private USBPipe OutputPipe = null;
public FastBootTransport(string DevicePath)
{
USBDevice = new USBDevice(DevicePath);
foreach (USBPipe Pipe in USBDevice.Pipes)
{
if (Pipe.IsIn)
{
InputPipe = Pipe;
}
if (Pipe.IsOut)
{
OutputPipe = Pipe;
}
}
if (InputPipe == null || OutputPipe == null)
{
USBDevice.Dispose();
throw new Exception("Invalid USB device!");
}
int ControlPipeTimeout = USBDevice.ControlPipeTimeout;
int InputPipeTransferTimeout = InputPipe.Policy.PipeTransferTimeout;
int OutputPipeTransferTimeout = OutputPipe.Policy.PipeTransferTimeout;
// Allow 1s of response time.
USBDevice.ControlPipeTimeout = 1000;
InputPipe.Policy.PipeTransferTimeout = 1000;
OutputPipe.Policy.PipeTransferTimeout = 1000;
// Test if the device is a FastBoot device right here.
try
{
FastBootCommands.GetAllVariables(this, out string[] vars);
}
catch
{
USBDevice.Dispose();
throw new Exception("Incompatible Device!");
}
USBDevice.ControlPipeTimeout = ControlPipeTimeout;
InputPipe.Policy.PipeTransferTimeout = InputPipeTransferTimeout;
OutputPipe.Policy.PipeTransferTimeout= OutputPipeTransferTimeout;
}
private (FastBootStatus status, string response, byte[] rawResponse) ReadResponse()
{
byte[] responseBuffer = new byte[0x1000];
int readByteCount = InputPipe.Read(responseBuffer);
string responseBufferAsString = Encoding.ASCII.GetString(responseBuffer);
if (responseBufferAsString.Length < 4)
{
throw new Exception($"Invalid response: {responseBufferAsString}");
}
string statusAsString = responseBufferAsString[0..4];
FastBootStatus status;
switch (statusAsString)
{
case "INFO":
{
status = FastBootStatus.INFO;
break;
}
case "OKAY":
{
status = FastBootStatus.OKAY;
break;
}
case "DATA":
{
status = FastBootStatus.DATA;
if (readByteCount > 12)
{
readByteCount = 12;
responseBufferAsString = responseBufferAsString[..12];
}
break;
}
case "FAIL":
{
status = FastBootStatus.FAIL;
break;
}
default:
{
throw new Exception($"Unknown response: {statusAsString} - {responseBufferAsString}");
}
};
string responseBufferDataAsString = responseBufferAsString[4..readByteCount];
byte[] responseBufferData = responseBuffer[4..readByteCount];
// To view the response data for debugging purposes
// Console.WriteLine($"[{status}] {responseBufferDataAsString}");
return (status, responseBufferDataAsString, responseBufferData);
}
public (FastBootStatus status, string response, byte[] rawResponse)[] SendCommand(byte[] command)
{
OutputPipe.Write(command);
List<(FastBootStatus, string, byte[])> responseList = new();
FastBootStatus status;
do
{
(status, string response, byte[] rawResponse) = ReadResponse();
responseList.Add((status, response, rawResponse));
}
while (status == FastBootStatus.INFO);
return responseList.ToArray();
}
public (FastBootStatus status, string response, byte[] rawResponse)[] SendCommand(string command)
{
return SendCommand(Encoding.ASCII.GetBytes(command));
}
public (FastBootStatus status, string response, byte[] rawResponse) SendData(Stream stream)
{
long length = stream.Length;
byte[] buffer = new byte[0x80000];
(FastBootStatus status, string response, byte[] rawResponse)[] responses = SendCommand($"download:{length:X8}");
(FastBootStatus status, string response, byte[] _) = responses.Last();
if (status != FastBootStatus.DATA)
{
throw new InvalidDataException($"Invalid response: {status} - {response}");
}
while (length >= 0x80000)
{
stream.Read(buffer, 0, 0x80000);
OutputPipe.Write(buffer);
length -= 0x80000;
}
if (length > 0)
{
buffer = new byte[length];
stream.Read(buffer, 0, (int)length);
OutputPipe.Write(buffer);
}
(status, response, byte[] rawResponse) = ReadResponse();
if (status != FastBootStatus.OKAY)
{
throw new InvalidDataException($"Invalid response: {status} - {response}");
}
return (status, response, rawResponse);
}
public (FastBootStatus status, string response, byte[] rawResponse) SendData(string filePath)
{
using FileStream fileStream = File.OpenRead(filePath);
return SendData(fileStream);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~FastBootTransport()
{
Dispose(false);
}
public void Close()
{
USBDevice?.Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (Disposed)
{
return;
}
if (disposing)
{
// Other disposables
}
// Clean unmanaged resources here.
Close();
Disposed = true;
}
}
}