// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Diagnostics; using System.Net.NetworkInformation; using System.Threading; using AutomationTool; using UnrealBuildTool; using Ionic.Zip; using EpicGames.Core; using UnrealBuildBase; using System.Text.RegularExpressions; using AutomationScripts; using System.Net; using System.Net.Sockets; using System.IO.Compression; namespace AutomationTool { public class AndroidFileClient { private const bool TimeBatching = false; private const int DefaultPort = 57099; private const int ServerVersion = 100; private const int FILE_BUFFERSIZE = 65536 * 16; private const int SOCKET_READBUFFER = 625 * 1024; private const int SOCKET_SENDBUFFER = 625 * 1024; private const int BATCH_BUFFERSIZE = !TimeBatching ? 65536 * 16 : 200000000; private const int BATCH_SENDSIZE = 65536 * 16; private string Device; private int ServerPort; private int HostPort; private static OptimalADB adb = new OptimalADB(); static string LastListenDevice = ""; static bool bHave_ss = true; static bool bHave_netstat = true; private Socket ClientSocket = null; private FileStream RecordFile = null; private BufferedStream RecordStream = null; private bool bBlockSend = false; private byte[] BatchBuffer = null; private int BatchBufferIndex = 0; private string LastBaseDir = ""; private static int Command_Terminate = 0; private static int Command_Close = 1; private static int Command_Info = 2; private static int Command_Query = 3; private static int Command_GetProp = 4; private static int Command_SetBaseDir = 5; private static int Command_DirExists = 10; private static int Command_DirList = 11; private static int Command_DirListFlat = 12; private static int Command_DirCreate = 13; private static int Command_DirDelete = 14; private static int Command_DirDeleteRecurse = 15; private static int Command_FileExists = 20; private static int Command_FileDelete = 21; private static int Command_FileCopy = 22; private static int Command_FileMove = 23; private static int Command_FileRead = 24; private static int Command_FileWrite = 25; private static int Command_FileWriteCompressed = 26; // ------------------ Helpers to deal with ADB ------------------ // optional way to talk directly with the adbd instead of shelling out to adb.exe public class OptimalADB { private const int DefaultPort = 5037; private string Executable; private int Port = 6037; private Socket ClientSocket = null; public OptimalADB(string ADBExecutable = "", int inPort = DefaultPort) { Executable = (ADBExecutable != "") ? ADBExecutable : Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/platform-tools/adb" + (RuntimePlatform.IsWindows ? ".exe" : "")); Port = inPort; } ~OptimalADB() { CloseConnection(); } private bool OpenConnection() { if (ClientSocket != null) { return true; } // create TCP/IP socket try { IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName()); IPAddress ipAddr = IPAddress.Parse("127.0.0.1"); IPEndPoint localEndPoint = new IPEndPoint(ipAddr, Port); // create TCP/IP socket ClientSocket = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); ClientSocket.Connect(localEndPoint); } catch (Exception) { // adb server not running so start it IProcessResult Result = CommandUtils.Run(Executable, "start-server", null, CommandUtils.ERunOptions.NoLoggingOfRunCommand); if (Result.ExitCode != 0) { Log.TraceError("Unable to execute adb to start adb server"); return false; } // try again return OpenConnection(); } return true; } private void CloseConnection() { if (ClientSocket != null) { try { ClientSocket.Shutdown(SocketShutdown.Both); ClientSocket.Close(); ClientSocket.Dispose(); ClientSocket = null; } catch (SocketException se) { Log.TraceWarning("SocketException: {0}", se.ToString()); } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); } } } private bool sendData(string Data) { if (!OpenConnection()) { return false; } byte[] buffer = Encoding.UTF8.GetBytes(Data.Length.ToString("X4") + Data); int sentBytes = ClientSocket.Send(buffer); return true; } private bool receiveData(out string Output, bool bOnce = false) { Output = ""; if (ClientSocket == null) { return false; } byte[] buffer = new byte[65536]; int bytesRecv = ClientSocket.Receive(buffer); string WorkBuffer = Encoding.UTF8.GetString(buffer, 0, bytesRecv); if (WorkBuffer.Length >= 4) { if (WorkBuffer.Substring(0, 4) == "OKAY") { WorkBuffer = WorkBuffer.Substring(4); if (bOnce) { while (ClientSocket.Available > 0) { bytesRecv = ClientSocket.Receive(buffer); WorkBuffer += Encoding.UTF8.GetString(buffer, 0, bytesRecv); } Output += WorkBuffer; return true; } int remaining = 0; while (true) { while (ClientSocket.Available > 0 || WorkBuffer.Length < 4) { bytesRecv = ClientSocket.Receive(buffer); WorkBuffer += Encoding.UTF8.GetString(buffer, 0, bytesRecv); } if (WorkBuffer.Length >= 4) { if (WorkBuffer.Substring(0, 4) == "OKAY") { WorkBuffer = WorkBuffer.Substring(4); continue; } try { remaining = int.Parse(WorkBuffer.Substring(0, 4), System.Globalization.NumberStyles.HexNumber); WorkBuffer = WorkBuffer.Substring(4); remaining -= WorkBuffer.Length; } catch (Exception) { while (ClientSocket.Available > 0) { bytesRecv = ClientSocket.Receive(buffer); WorkBuffer += Encoding.UTF8.GetString(buffer, 0, bytesRecv); } Output = WorkBuffer; return true; } break; } } while (remaining > 0) { bytesRecv = ClientSocket.Receive(buffer); WorkBuffer += Encoding.UTF8.GetString(buffer, 0, bytesRecv); remaining -= bytesRecv; } Output = WorkBuffer; return true; } } // fail Output = WorkBuffer; return false; } private bool execute(string Command, out string Output, bool bOnce = false) { bool Result = true; Output = ""; if (sendData(Command)) { Result = receiveData(out Output, bOnce); } return Result; } private bool executeDevice(string Device, string Command, out string Output, bool bOnce = false) { if (!execute("host:transport" + (Device == "" ? "-any" : ":" + Device), out Output, true)) { return false; } return execute(Command, out Output, bOnce); } public string GetVersion() { string Result; execute("host:version", out Result); CloseConnection(); return Result; } public string GetDevices() { string Result; execute("host:devices", out Result); CloseConnection(); return Result; } private string shellEscape(string source) { // wraps whole thing in ' ' after escaping it with \' return "'" + source.Replace("'", "\'") + "'"; } public string Shell(string Device, string Command) { string Result = "FAIL"; if (!execute("host:transport" + (Device == "" ? "-any" : ":" + Device), out Result, true)) { CloseConnection(); return Result; } Result = "FAIL"; //if (!sendData("shell:" + shellEscape(Command))) if (!sendData("shell:" + Command)) { CloseConnection(); return Result; } byte[] buffer = new byte[65536]; Result = ""; while (Result.Length < 4) { int bytesRecv = ClientSocket.Receive(buffer); Result += Encoding.UTF8.GetString(buffer, 0, bytesRecv); } if (Result.Substring(0, 4) != "OKAY") { CloseConnection(); return Result; } Result = Result.Substring(4); // there can be a delay before any more results (and may not be any) if (ClientSocket.Available == 0) { Thread.Sleep(50); } while (ClientSocket.Available > 0) { int bytesRecv = ClientSocket.Receive(buffer); Result += Encoding.UTF8.GetString(buffer, 0, bytesRecv); if (ClientSocket.Available == 0) { Thread.Sleep(50); } } CloseConnection(); return Result; } public string ListForward(string Device) { string Result; executeDevice(Device, "host:list-forward", out Result); CloseConnection(); return Result; } public string AddForward(string Device, string Local, string Remote) { string Result; executeDevice(Device, "host:forward:" + Local + ";" + Remote, out Result); CloseConnection(); return Result; } public string RemoveForward(string Device, string Local) { string Result; executeDevice(Device, "host:killforward:" + Local, out Result, true); CloseConnection(); return Result; } } private static string GetAdbCommandLine(string SerialNumber, string Args) { if (string.IsNullOrEmpty(SerialNumber) == false) { SerialNumber = "-s " + SerialNumber; } return string.Format("{0} {1}", SerialNumber, Args); } static string LastSpewFilename = ""; public static string ADBSpewFilter(string Message) { if (Message.StartsWith("[") && Message.Contains("%]")) { int LastIndex = Message.IndexOf(":"); LastIndex = LastIndex == -1 ? Message.Length : LastIndex; if (Message.Length > 7) { string Filename = Message.Substring(7, LastIndex - 7); if (Filename == LastSpewFilename) { return null; } LastSpewFilename = Filename; } return Message; } return Message; } public static IProcessResult RunAdbCommand(string SerialNumber, string Args, string Input = null, CommandUtils.ERunOptions Options = CommandUtils.ERunOptions.NoLoggingOfRunCommand, bool bShouldLogCommand = false) { string AdbCommand = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/platform-tools/adb" + (RuntimePlatform.IsWindows ? ".exe" : "")); if (Options.HasFlag(CommandUtils.ERunOptions.AllowSpew) || Options.HasFlag(CommandUtils.ERunOptions.SpewIsVerbose)) { LastSpewFilename = ""; return CommandUtils.Run(AdbCommand, GetAdbCommandLine(SerialNumber, Args), Input, Options, SpewFilterCallback: new ProcessResult.SpewFilterCallbackType(ADBSpewFilter)); } return CommandUtils.Run(AdbCommand, GetAdbCommandLine(SerialNumber, Args), Input, Options); } public static void GetConnectedDevices(out List Devices) { Devices = new List(); string Result = adb.GetDevices(); if (Result.Length > 0) { string[] LogLines = Result.Split(new char[] { '\n', '\r' }); for (int i = 0; i < LogLines.Length; ++i) { string[] DeviceLine = LogLines[i].Split(new char[] { '\t' }); if (DeviceLine.Length == 2) { // the second param should be "device" // if it's not setup correctly it might be "unattached" or "powered off" or something like that // warning in that case if (DeviceLine[1] == "device") { Devices.Add("@" + DeviceLine[0]); } else { CommandUtils.LogWarning("Device attached but in bad state {0}:{1}", DeviceLine[0], DeviceLine[1]); } } } } } // Checks for existing forward of device port and returns local host port if found, otherwise sets one public static int ForwardPort_FindOrAdd(string Device, int DevicePort) { int HostPort = -1; // Check if port already forwarded to this device string Result = adb.ListForward(Device); if (Result.Length > 0) { string[] LogLines = Result.Split(new char[] { '\n', '\r' }); for (int i = 0; i < LogLines.Length; ++i) { string[] DeviceLine = LogLines[i].Split(new char[] { '\t', ' ' }); if (DeviceLine.Length == 3) { if (DeviceLine[1].StartsWith("tcp:") && DeviceLine[2].StartsWith("tcp:")) { int ListDevicePort = -1; if (int.TryParse(DeviceLine[2].Substring(4), out ListDevicePort)) { if (ListDevicePort == DevicePort) { if (int.TryParse(DeviceLine[1].Substring(4), out HostPort)) { return HostPort; } } } } } } } // request forwarding from an available host port Result = adb.AddForward(Device, "tcp:0", "tcp:" + DevicePort.ToString()); if (Result.Length > 0) { string[] LogLines = Result.Split(new char[] { '\n', '\r' }); for (int i = 0; i < LogLines.Length; ++i) { if (int.TryParse(LogLines[i], out HostPort)) { return HostPort; } } } // did not work, return -1 as error return -1; } // Checks for existing forwards of device port and removes them then requests a new one to return // This deals better with stale binds and leftover forward requests public static int ForwardPort_Add(string Device, int DevicePort) { int HostPort = -1; // Check if port already forwarded to this device string Result = adb.ListForward(Device); if (Result.Length > 0) { string[] LogLines = Result.Split(new char[] { '\n', '\r' }); for (int i = 0; i < LogLines.Length; ++i) { string[] DeviceLine = LogLines[i].Split(new char[] { '\t', ' ' }); if (DeviceLine.Length == 3) { if (DeviceLine[1].StartsWith("tcp:") && DeviceLine[2].StartsWith("tcp:")) { int ListDevicePort = -1; if (int.TryParse(DeviceLine[2].Substring(4), out ListDevicePort)) { if (ListDevicePort == DevicePort) { adb.RemoveForward(Device, DeviceLine[1]); } } } } } } // request forwarding from an available host port Result = adb.AddForward(Device, "tcp:0", "tcp:" + DevicePort.ToString()); if (Result.Length > 0) { string[] LogLines = Result.Split(new char[] { '\n', '\r' }); for (int i = 0; i < LogLines.Length; ++i) { if (int.TryParse(LogLines[i], out HostPort)) { return HostPort; } } } // did not work, return -1 as error return -1; } public static void ForwardPort_Remove(string Device, int HostPort) { // request removal of forwarding from host port adb.RemoveForward(Device, "tcp:" + HostPort.ToString()); } public static void ReversePort_Add(string Device, int DevicePort, int HostPort) { // request reversing from device port to local host port IProcessResult Result = RunAdbCommand(Device, "reverse tcp:" + DevicePort.ToString() + " tcp:" + HostPort.ToString()); Result.DisposeProcess(); } public static void ReversePort_Remove(string Device, int DevicePort) { // request removal of forwarding from host port IProcessResult Result = RunAdbCommand(Device, "reverse --remove tcp:" + DevicePort.ToString()); Result.DisposeProcess(); } // ------------------ Helpers to deal with packets ------------------ private long Stats_FilesRead = 0; private long Stats_FilesWrite = 0; private long Stats_TotalBytesReceived = 0; private long Stats_TotalBytesSent = 0; private long Stats_PayloadBytesReceived = 0; private long Stats_PayloadBytesSent = 0; private long Stats_CompressedBytesReceived = 0; private long Stats_CompressedBytesSent = 0; private Stopwatch Stats_Stopwatch = null; public void Stats_Clear(bool bStartTimer = true) { Stats_FilesRead = 0; Stats_FilesWrite = 0; Stats_TotalBytesReceived = 0; Stats_TotalBytesSent = 0; Stats_PayloadBytesReceived = 0; Stats_PayloadBytesSent = 0; Stats_CompressedBytesReceived = 0; Stats_CompressedBytesSent = 0; if (bStartTimer) { Stats_StartStopwatch(); } } public void Stats_StartStopwatch() { Stats_Stopwatch = System.Diagnostics.Stopwatch.StartNew(); } public void Stats_Combine(AndroidFileClient client) { Stats_FilesRead += client.Stats_FilesRead; Stats_FilesWrite += client.Stats_FilesWrite; Stats_TotalBytesReceived += client.Stats_TotalBytesReceived; Stats_TotalBytesSent += client.Stats_TotalBytesSent; Stats_PayloadBytesReceived += client.Stats_PayloadBytesReceived; Stats_PayloadBytesSent += client.Stats_PayloadBytesSent; Stats_CompressedBytesReceived += client.Stats_CompressedBytesReceived; Stats_CompressedBytesSent += client.Stats_CompressedBytesSent; client.Stats_Clear(false); } public void Stats_Report() { Log.TraceInformation("Total bytes received: {0}", Stats_TotalBytesReceived); Log.TraceInformation("Payload bytes received: {0}", Stats_PayloadBytesReceived); Log.TraceInformation("Total files read: {0}", Stats_FilesRead); Log.TraceInformation("."); Log.TraceInformation("Total bytes sent: {0}", Stats_TotalBytesSent); Log.TraceInformation("Payload bytes sent: {0}", Stats_PayloadBytesSent); Log.TraceInformation("Total files written: {0}", Stats_FilesWrite); Log.TraceInformation("."); Log.TraceInformation("Uncompressed bytes recv: {0}", Stats_CompressedBytesReceived); Log.TraceInformation("Uncompressed bytes sent: {0}", Stats_CompressedBytesSent); Log.TraceInformation("."); if (Stats_Stopwatch != null) { Stats_Stopwatch.Stop(); var ElapsedMs = Stats_Stopwatch.ElapsedMilliseconds; double MBPerSec = (double)Stats_TotalBytesSent / ((double)ElapsedMs / 1000.0) / 1024.0 / 1024.0; Log.TraceInformation("Actual sent: {0:N1} MB/s ({1} in {2:N3}s)", MBPerSec, Stats_TotalBytesSent, (float)(ElapsedMs / 1000.0)); MBPerSec = (double)Stats_CompressedBytesSent / ((double)ElapsedMs / 1000.0) / 1024.0 / 1024.0; Log.TraceInformation("Uncompressed: {0:N1} MB/s ({1} in {2:N3}s)", MBPerSec, Stats_CompressedBytesSent, (float)(ElapsedMs / 1000.0)); } } public void Record_Stop() { bBlockSend = false; if (RecordStream != null) { RecordStream.Close(); RecordStream.Dispose(); RecordStream = null; } if (RecordFile != null) { RecordFile.Close(); RecordFile.Dispose(); RecordFile = null; } } public bool Record_Begin(string Filename, bool BlockSend = false) { bBlockSend = BlockSend; Record_Stop(); try { RecordFile = File.Open(Filename, FileMode.Create, FileAccess.Write); try { RecordStream = new BufferedStream(RecordFile); } catch (Exception) { RecordFile.Close(); RecordFile.Dispose(); RecordFile = null; return false; } } catch (Exception) { RecordFile = null; return false; } return true; } public bool Record_Play(string Filepath) { Boolean Result = false; if (ClientSocket == null) { return Result; } if (!File.Exists(Filepath)) { return Result; } using (FileStream fileStream = File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read)) { try { int bytesRead; byte[] buffer = new byte[FILE_BUFFERSIZE]; while ((bytesRead = fileStream.Read(buffer, 0, FILE_BUFFERSIZE)) != 0) { SocketSend(true, buffer, bytesRead); } fileStream.Close(); Result = true; } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); CloseConnection(); } } return Result; } private int SocketReceive(bool bPayload, byte[] packet) { int received = 0; try { received = ClientSocket.Receive(packet); Stats_TotalBytesReceived += received; if (bPayload) { Stats_PayloadBytesReceived += received; } return received; } catch (Exception) { } return received; } private int SocketReceive(bool bPayload, byte[] packet, int readSize) { int received = ClientSocket.Receive(packet, readSize, SocketFlags.None); Stats_TotalBytesReceived += received; if (bPayload) { Stats_PayloadBytesReceived += received; } return received; } public bool Batch_Start() { if (BatchBuffer != null) { return false; } BatchBuffer = new byte[BATCH_BUFFERSIZE]; BatchBufferIndex = 0; return true; } public bool Batch_Flush() { if (BatchBuffer == null) { return false; } if (BatchBufferIndex > 0) { int index = 0; int remaining = BatchBufferIndex; while (remaining > 0) { int chunk = remaining < BATCH_SENDSIZE ? remaining : BATCH_SENDSIZE; int sent = ClientSocket.Send(BatchBuffer, index, chunk, SocketFlags.None); remaining -= sent; index += sent; } BatchBufferIndex = 0; } return true; } public void Batch_Stop() { Batch_Flush(); BatchBuffer = null; BatchBufferIndex = 0; } private void Batch_Append(byte[] packet, int writeSize) { int readOffset = 0; while (writeSize > 0) { int copySize = (BatchBufferIndex + writeSize > BATCH_BUFFERSIZE) ? BATCH_BUFFERSIZE - BatchBufferIndex : writeSize; Buffer.BlockCopy(packet, readOffset, BatchBuffer, BatchBufferIndex, copySize); BatchBufferIndex += copySize; readOffset += copySize; writeSize -= copySize; if (BatchBufferIndex == BATCH_BUFFERSIZE) { Batch_Flush(); } } } private int SocketSend(bool bPayload, byte[] packet) { int writeSize = packet.Length; int sent = writeSize; if (BatchBuffer != null) { Batch_Append(packet, writeSize); } else if (!bBlockSend) { sent = ClientSocket.Send(packet); } if (RecordStream != null) { RecordStream.Write(packet); } Stats_TotalBytesSent += sent; if (bPayload) { Stats_PayloadBytesSent += sent; } if (sent < writeSize) { // did not send all } return sent; } private int SocketSend(bool bPayload, byte[] packet, int writeSize) { int sent = writeSize; if (BatchBuffer != null) { Batch_Append(packet, writeSize); } else if (!bBlockSend) { sent = ClientSocket.Send(packet, writeSize, SocketFlags.None); } if (RecordStream != null) { RecordStream.Write(packet, 0, writeSize); } Stats_TotalBytesSent += sent; if (bPayload) { Stats_PayloadBytesSent += sent; } if (sent < writeSize) { // did not send all } return sent; } private byte[] CommandPacket(int Command, long Size) { byte[] Result = { (byte)(Command & 255), (byte)((Command >> 8) & 255), (byte)(Size & 255), (byte)((Size >> 8) & 255), (byte)((Size >> 16) & 255), (byte)((Size >> 24) & 255), (byte)((Size >> 32) & 255), (byte)((Size >> 40) & 255) }; return Result; } private long ResultSize() { long Result = -1; try { byte[] sizePacket = new byte[8]; int byteRecv = SocketReceive(false, sizePacket); if (byteRecv == 8) { Result = sizePacket[0] + (sizePacket[1] << 8) + (sizePacket[2] << 16) + (sizePacket[3] << 24) + (sizePacket[4] << 32) + ((long)sizePacket[5] << 40) + ((long)sizePacket[6] << 48) + ((long)sizePacket[7] << 56); } } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); CloseConnection(); } return Result; } private string GetStringResult(int Command, string Params) { string Result = null; if (ClientSocket == null) { return Result; } byte[] paramMessage = Encoding.UTF8.GetBytes(Params + "\0"); long paramSize = paramMessage.Length; byte[] commandMessage = CommandPacket(Command, paramSize); try { int bytesSent = SocketSend(false, commandMessage); bytesSent = SocketSend(false, paramMessage); int resultSize = (int)ResultSize(); if (resultSize > 0) { byte[] buffer = new byte[resultSize < FILE_BUFFERSIZE ? resultSize : FILE_BUFFERSIZE ]; Result = ""; long remaining = resultSize; while (remaining > 0) { int readSize = remaining > FILE_BUFFERSIZE ? FILE_BUFFERSIZE : (int)remaining; int bytesRecv = SocketReceive(true, buffer, readSize); remaining -= bytesRecv; Result += Encoding.UTF8.GetString(buffer, 0, bytesRecv); } } } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); CloseConnection(); } return Result; } private bool GetBoolResult(int Command, string Params) { string Result = GetStringResult(Command, Params); if (Result == null) { return false; } return (Result == "true"); } // ------------------ Optimizes destination paths ----------------- private string OptimizePath(string inPath) { string inDir = Path.GetDirectoryName(inPath).Replace("\\", "/") + "/"; string inFilename = Path.GetFileName(inPath); if (inDir == "/") { return inPath; } if (LastBaseDir == "") { LastBaseDir = inDir; return inPath; } // trivial case of same as last base if (LastBaseDir == inDir) { return "^^/" + inFilename; } // basedir matches start of new path if (inDir.StartsWith(LastBaseDir)) { int BaseDirLength = LastBaseDir.Length; LastBaseDir = inDir; return "^^/" + inDir.Substring(BaseDirLength) + inFilename; } int startIndex; int nextIndex; int dropCount; // if indir is below basedir, figure out how many directories to drop if (LastBaseDir.StartsWith(inDir)) { startIndex = inDir.Length; dropCount = 0; while ((nextIndex = LastBaseDir.Substring(startIndex).IndexOf('/')) >= 0) { dropCount++; startIndex += nextIndex + 1; } LastBaseDir = LastBaseDir.Substring(0, inDir.Length); return "^-" + dropCount + "/" + inFilename; } // lastly, see what portion of the two paths match (if any) int searchIndex = 0; int lastSlash = -1; int maxIndex = inDir.Length < LastBaseDir.Length ? inDir.Length : LastBaseDir.Length; while (searchIndex < maxIndex && inDir[searchIndex] == LastBaseDir[searchIndex]) { if (inDir[searchIndex] == '/') { lastSlash = searchIndex; } searchIndex++; } if (lastSlash < 1) { LastBaseDir = inDir; return inPath; } startIndex = lastSlash + 1; dropCount = 0; while ((nextIndex = LastBaseDir.Substring(startIndex).IndexOf('/')) >= 0) { dropCount++; startIndex += nextIndex + 1; } LastBaseDir = inDir; return "^-" + dropCount + inDir.Substring(lastSlash) + inFilename; } // ------------------ Interace ------------------ public AndroidFileClient(string inDevice, int inPort = DefaultPort) { Device = inDevice; ServerPort = inPort != 0 ? inPort : DefaultPort; HostPort = -1; } ~AndroidFileClient() { CloseConnection(); } public static int GetDefaultPort() { return DefaultPort; } public string GetDevice() { return Device; } public int GetServerPort() { return ServerPort; } public bool OpenConnection(String Address = "127.0.0.1") { int UsePort = ServerPort; if (Address == null || Address == "") { Address = "127.0.0.1"; } if (Address == "127.0.0.1") { HostPort = ForwardPort_Add(Device, ServerPort); if (HostPort == -1) { return false; } UsePort = HostPort; } // create TCP/IP socket try { IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName()); IPAddress ipAddr = IPAddress.Parse(Address); IPEndPoint localEndPoint = new IPEndPoint(ipAddr, UsePort); // create TCP/IP socket ClientSocket = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); ClientSocket.Connect(localEndPoint); ClientSocket.NoDelay = false; ClientSocket.SendBufferSize = SOCKET_SENDBUFFER; ClientSocket.ReceiveBufferSize = SOCKET_READBUFFER; Stats_Clear(); LastBaseDir = ""; int bytesSent = SocketSend(false, CommandPacket(Command_Info, 0)); int defaultTimeout = ClientSocket.ReceiveTimeout; ClientSocket.ReceiveTimeout = 1000; byte[] sizePacket = new byte[8]; int bytesRecv = SocketReceive(false, sizePacket); if (bytesRecv == 8) { ClientSocket.ReceiveTimeout = defaultTimeout; int resultSize = sizePacket[0] + (sizePacket[1] << 8) + (sizePacket[2] << 8) + (sizePacket[3] << 16) + (sizePacket[4] << 24) + (sizePacket[5] << 32) + (sizePacket[6] << 40) + (sizePacket[7] << 48); byte[] message = new byte[resultSize]; bytesRecv = SocketReceive(false, message); if (bytesRecv == resultSize) { int version = message[0] + (message[1] << 8); if (version == ServerVersion) { Log.TraceInformation("Connected to RemoteFileManager"); return true; } } } else { //Log.TraceInformation("Did not get response from RemoteFileManager"); } CloseConnection(); return false; } catch (ArgumentNullException ane) { Log.TraceWarning("ArgumentNullException: {0}", ane.ToString()); } catch (SocketException se) { string message = se.ToString(); if (message.Contains("No connection could be made because the target machine actively refused it")) { // ok not to display this one } else { Log.TraceWarning("SocketException: {0}", message); } } catch (Exception e) { Log.TraceWarning("OpenConnection failed: {0}", e.ToString()); } if (ClientSocket != null) { ClientSocket.Dispose(); ClientSocket = null; } if (HostPort != -1) { ForwardPort_Remove(Device, HostPort); HostPort = -1; } return false; } public static List GetInstalledReceivers(string Device) { List Result = new List(); String InstalledResult = adb.Shell(Device, "cmd package query-receivers --components -a com.epicgames.unreal.RemoteFileManager.intent.COMMAND"); foreach (string Line in InstalledResult.Split('\n')) { int SlashIndex = Line.IndexOf('/'); if (SlashIndex > 0 && (Line.Substring(SlashIndex + 1) == "com.epicgames.unreal.RemoteFileManagerReceiver")) { Result.Add(Line.Substring(0, SlashIndex)); } } return Result; } public static bool GetListenStatus_netstat(string Device, int Port, out bool bUSB, out bool bWifi, out string WifiAddress) { bUSB = false; bWifi = false; WifiAddress = ""; string Search = ":" + Port; string netstatResult = adb.Shell(Device, "netstat -atn"); if (netstatResult.Contains("not found") || netstatResult.Contains("FAIL")) { return false; } foreach (string Line in netstatResult.Split('\n')) { if (Line.Contains("LISTEN") && Line.Contains(Search)) { String[] parts = Line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); if (parts.Length > 3 && parts[3].EndsWith(Search)) { int SearchIndex = parts[3].LastIndexOf(Search); string LineIP = parts[3].Substring(0, SearchIndex); if (LineIP.Contains("127.0.0.1")) { bUSB = true; } else { bWifi = true; // ip address is partial so don't try to parse it } } } } return true; } public static bool GetListenStatus_ss(string Device, int Port, out bool bUSB, out bool bWifi, out string WifiAddress) { bUSB = false; bWifi = false; WifiAddress = ""; string Search = ":" + Port; string ssResult = adb.Shell(Device, "ss -ta"); if (ssResult.Contains("not found") || ssResult.Contains("FAIL")) { return false; } foreach (string Line in ssResult.Split('\n')) { if (Line.Contains("LISTEN") && Line.Contains(Search)) { String[] parts = Line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); if (parts.Length > 3 && parts[3].EndsWith(Search)) { // either ip:port or [*]:port int SearchIndex = parts[3].LastIndexOf(Search); string LineIP = parts[3].Substring(0, SearchIndex); if (LineIP.StartsWith("[")) { // handle [*:ip]:port or [ip]:port SearchIndex = LineIP.LastIndexOf(":"); if (SearchIndex > 0) { LineIP = LineIP.Substring(SearchIndex + 1, LineIP.Length - SearchIndex - 2); } else { LineIP = LineIP.Substring(1, LineIP.Length - 2); } } if (LineIP == "127.0.0.1") { bUSB = true; } else { WifiAddress = LineIP; bWifi = true; } } } } return true; } public static bool GetListenStatus(string Device, int Port, out bool bUSB, out bool bWifi, out string WifiAddress) { bUSB = false; bWifi = false; WifiAddress = ""; if (LastListenDevice != Device) { // reset availability of commands if different device LastListenDevice = Device; bHave_ss = true; bHave_netstat = true; } // try ss first and remember if available for next time if (bHave_ss) { bHave_ss = GetListenStatus_ss(Device, Port == 0 ? DefaultPort : Port, out bUSB, out bWifi, out WifiAddress); if (bHave_ss) { return true; } } // next fall back to netstat and remember if available for next time if (bHave_netstat) { bHave_netstat = GetListenStatus_netstat(Device, Port == 0 ? DefaultPort : Port, out bUSB, out bWifi, out WifiAddress); if (bHave_netstat) { return true; } } // neither available so cannot determine listen status return false; } public static bool StopAnyServers(string Device, int Port, bool bWaitForStop = true) { // get a list of installed receivers and verify package is available first List InstalledReceivers = GetInstalledReceivers(Device); // deal with stopping all client servers if requested bool bDidSendStops = false; foreach (string Receiver in InstalledReceivers) { bDidSendStops = true; adb.Shell(Device, "am broadcast -a com.epicgames.unreal.RemoteFileManager.intent.COMMAND -n " + Receiver + "/com.epicgames.unreal.RemoteFileManagerReceiver -e cmd 'stop'"); } // it can take up to 2 seconds for running servers to terminate so check if any binds to port are still active if (bDidSendStops && bWaitForStop) { // we will try ss first, but it may not be available bool bUSB; bool bWifi; string WifiAddress; if (GetListenStatus(Device, Port, out bUSB, out bWifi, out WifiAddress)) { // wait for both to terminate (up to 10 seconds, then give up) long StartTime = DateTimeOffset.Now.ToUnixTimeSeconds(); while (bUSB || bWifi) { GetListenStatus(Device, Port, out bUSB, out bWifi, out WifiAddress); if (DateTimeOffset.Now.ToUnixTimeSeconds() - StartTime > 9) { // there are still binds active that won't terminate return false; } Thread.Sleep(100); } } else { // command not available so will just need to wait 2 seconds to be sure Thread.Sleep(2000); } } return true; } public bool StartServer(string PackageName, string Token = "", string IPAddress = "127.0.0.1", bool bStopAnyServers = true) { // already connected? if (ClientSocket != null) { return true; } bool bIsUSB = (IPAddress == null || IPAddress == "" || IPAddress == "127.0.0.1"); // deal with stopping all servers if requested if (bStopAnyServers) { if (!StopAnyServers(Device, ServerPort, true)) { // there are still binds active that won't terminate so can't start a new one return false; } } int tries = 10; // get a list of installed clients and verify package is available List InstalledReceivers = GetInstalledReceivers(Device); if (!InstalledReceivers.Contains(PackageName)) { // if this was called right after installed the receiver may not be registered yet, wait and try again bool bFound = false; while (tries-- > 0) { Thread.Sleep(200); InstalledReceivers = GetInstalledReceivers(Device); if (InstalledReceivers.Contains(PackageName)) { bFound = true; break; } } if (!bFound) { Log.TraceInformation("Did not find package with receiver"); return false; } } tries = 15; bool bUSB; bool bWifi; string WifiAddress; // sent start request (won't do anything if already started) string StartCommand = "am broadcast -a com.epicgames.unreal.RemoteFileManager.intent.COMMAND -n " + PackageName + "/com.epicgames.unreal.RemoteFileManagerReceiver -e cmd 'start' -e token '" + Token + "' -ei port " + ServerPort; adb.Shell(Device, StartCommand); // see if we can check listen status if (GetListenStatus(Device, ServerPort, out bUSB, out bWifi, out WifiAddress)) { while (tries-- > 0) { // try to connect if requested server is listening if ((bUSB && bIsUSB) || (bWifi && !bIsUSB)) { if (OpenConnection(IPAddress)) { return true; } } // wait before trying to again Thread.Sleep(250); // sent start request again (won't do anything if already started) adb.Shell(Device, StartCommand); GetListenStatus(Device, ServerPort, out bUSB, out bWifi, out WifiAddress); } Log.TraceInformation("Did not find a bind listener"); return false; } // try 5 times, this is faster than looking response in logcat while (tries-- > 0) { // wait before trying to connect Thread.Sleep(250); if (OpenConnection(IPAddress)) { return true; } // sent start request again (won't do anything if already started) adb.Shell(Device, StartCommand); } Log.TraceInformation("Timed out on connection attempts"); return false; } public void TerminateServer() { // try to connect if not already connected if (ClientSocket == null) { if (!OpenConnection()) { return; } } if (ClientSocket != null) { try { int bytesSent = SocketSend(false, CommandPacket(Command_Terminate, 0)); // close socket ClientSocket.Shutdown(SocketShutdown.Both); ClientSocket.Close(); ClientSocket.Dispose(); ClientSocket = null; } catch (SocketException se) { Log.TraceWarning("SocketException: {0}", se.ToString()); } catch (ObjectDisposedException) { // allow it } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); } } } public void CloseConnection() { Stats_Stopwatch = null; if (ClientSocket != null) { try { int bytesSent = SocketSend(false, CommandPacket(Command_Close, 0)); // close socket ClientSocket.Shutdown(SocketShutdown.Both); ClientSocket.Close(); ClientSocket.Dispose(); ClientSocket = null; } catch (SocketException se) { Log.TraceWarning("SocketException: {0}", se.ToString()); } catch (ObjectDisposedException) { // allow it } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); } } if (HostPort != -1) { ForwardPort_Remove(Device, HostPort); HostPort = -1; } } public string Query(string Filepath, bool bStripEndingSlash = false) { string result = GetStringResult(Command_Query, Filepath); if (result != null && bStripEndingSlash && result.EndsWith("/")) { return result.Substring(0, result.Length - 1); } return result; } public string GetProp(string Filepath) { return GetStringResult(Command_GetProp, Filepath); } public bool SetBaseDir(string Filepath) { if (Filepath.EndsWith("/")) { Filepath = Filepath.Substring(0, Filepath.Length - 1); } LastBaseDir = Filepath; return GetBoolResult(Command_SetBaseDir, Filepath); } public bool DirExists(string Filepath) { return GetBoolResult(Command_DirExists, Filepath); } public string DirList(string Filepath) { return GetStringResult(Command_DirList, Filepath); } public string DirListFlat(string Filepath) { return GetStringResult(Command_DirListFlat, Filepath); } public bool DirCreate(string Filepath) { return GetBoolResult(Command_DirCreate, Filepath); } public bool DirDelete(string Filepath) { return GetBoolResult(Command_DirDelete, Filepath); } public bool DirDeleteRecurse(string Filepath) { return GetBoolResult(Command_DirDeleteRecurse, Filepath); } public bool FileExists(string Filepath) { return GetBoolResult(Command_FileExists, Filepath); } public bool FileDelete(string Filepath) { return GetBoolResult(Command_FileDelete, Filepath); } public bool FileCopy(string SourcePath, string DestPath) { return GetBoolResult(Command_FileCopy, SourcePath + "\0" + DestPath); } public bool FileMove(string SourcePath, string DestPath) { return GetBoolResult(Command_FileMove, SourcePath + "\0" + DestPath); } public bool FileRead(string SourcePath, string DestPath) { Boolean Result = false; Log.TraceInformation("FileRead {0}", SourcePath); if (ClientSocket == null) { return Result; } if (File.Exists(DestPath)) { File.Delete(DestPath); } try { // deal with LastBaseDir substitution SourcePath = OptimizePath(SourcePath); byte[] paramMessage = Encoding.UTF8.GetBytes(SourcePath + "\0"); long paramSize = paramMessage.Length; byte[] commandMessage = CommandPacket(Command_FileRead, paramSize); int bytesSent = SocketSend(false, commandMessage); bytesSent = SocketSend(false, paramMessage); long resultSize = ResultSize(); long startSize = resultSize; if (resultSize >= 0) { int bytesRead; byte[] buffer = new byte[FILE_BUFFERSIZE]; Stats_FilesRead++; try { using (FileStream fileStream = File.Open(DestPath, FileMode.Create, FileAccess.Write)) using (BufferedStream bufferedStream = new BufferedStream(fileStream)) { long remaining = resultSize; while (remaining > 0) { int readSize = remaining > FILE_BUFFERSIZE ? FILE_BUFFERSIZE : (int)remaining; bytesRead = SocketReceive(true, buffer, readSize); remaining -= bytesRead; bufferedStream.Write(buffer, 0, bytesRead); Stats_CompressedBytesReceived += bytesRead; } bufferedStream.Close(); fileStream.Close(); } } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); // ignore received data long remainingSkip = resultSize; while (remainingSkip > 0) { int readSize = remainingSkip > FILE_BUFFERSIZE ? FILE_BUFFERSIZE : (int)remainingSkip; bytesRead = SocketReceive(true, buffer, readSize); remainingSkip -= bytesRead; } return Result; } Result = true; } } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); CloseConnection(); } return Result; } public bool FileWrite(string SourcePath, string DestPath, int bLog = 0) { Boolean Result = false; if (ClientSocket == null) { return Result; } try { using (FileStream fileStream = new FileStream(SourcePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.SequentialScan)) { // deal with LastBaseDir substitution DestPath = OptimizePath(DestPath); if (bLog > 0) { Log.TraceInformation("{0}> Writing {1}", bLog, DestPath); } long fileSize = fileStream.Length; Stats_FilesWrite++; int bytesSent; int bytesRead; long remaining = fileSize; byte[] paramMessage = Encoding.UTF8.GetBytes(DestPath + "\0"); int headerSize = paramMessage.Length; long paramSize = headerSize + fileSize; if (BatchBuffer != null) { byte[] commandMessage = CommandPacket(Command_FileWrite, paramSize); bytesSent = SocketSend(false, commandMessage); bytesSent = SocketSend(false, paramMessage); // directly read into batch buffer for minimal copies while (remaining > 0) { int batchRemaining = BATCH_BUFFERSIZE - BatchBufferIndex; int chunkSize = (int)(BatchBufferIndex + remaining > BATCH_BUFFERSIZE ? BATCH_BUFFERSIZE - BatchBufferIndex : remaining); bytesRead = fileStream.Read(BatchBuffer, BatchBufferIndex, chunkSize); // need to do this here since SocketSend not called if (RecordStream != null) { RecordStream.Write(BatchBuffer, BatchBufferIndex, bytesRead); } Stats_CompressedBytesSent += bytesRead; Stats_TotalBytesSent += bytesRead; Stats_PayloadBytesSent += bytesRead; BatchBufferIndex += bytesRead; remaining -= bytesRead; if (BatchBufferIndex == BATCH_BUFFERSIZE) { Batch_Flush(); } } } else { // reduce the number of SocketSends if not batching byte[] commandMessage = new byte[8 + headerSize]; commandMessage[0] = (byte)(Command_FileWrite & 255); commandMessage[1] = (byte)((Command_FileWrite >> 8) & 255); commandMessage[2] = (byte)(paramSize & 255); commandMessage[3] = (byte)((paramSize >> 8) & 255); commandMessage[4] = (byte)((paramSize >> 16) & 255); commandMessage[5] = (byte)((paramSize >> 24) & 255); commandMessage[6] = (byte)((paramSize >> 32) & 255); commandMessage[7] = (byte)((paramSize >> 40) & 255); Buffer.BlockCopy(paramMessage, 0, commandMessage, 8, headerSize); bytesSent = SocketSend(false, commandMessage); byte[] buffer = new byte[FILE_BUFFERSIZE]; while ((bytesRead = fileStream.Read(buffer, 0, FILE_BUFFERSIZE)) != 0) { bytesSent = SocketSend(true, buffer, bytesRead); Stats_CompressedBytesSent += bytesSent; } } fileStream.Close(); } Result = true; } catch (IOException) { // file not found return false; } catch (Exception e) { Log.TraceError("{0}> Unexpected Exception: {1}", bLog, e.ToString()); CloseConnection(); } return Result; } public bool FileWriteCompressed(string SourcePath, string DestPath, int bLog = 0) { Boolean Result = false; if (ClientSocket == null) { return Result; } try { using (FileStream fileStream = File.Open(SourcePath, FileMode.Open, FileAccess.Read)) { long fileSize = fileStream.Length; if (fileSize < 1024) { fileStream.Close(); return FileWrite(SourcePath, DestPath, bLog); } // deal with LastBaseDir substitution DestPath = OptimizePath(DestPath); if (bLog > 0) { Log.TraceInformation("{0}> Writing Compressed {1}", bLog, DestPath); } Stats_FilesWrite++; int COMPRESS_BUFFERSIZE = 1024 * 1024; int bytesSent; int bytesRead; long remaining = fileSize; byte[] paramMessage = Encoding.UTF8.GetBytes("Z" + DestPath + "\0"); int headerSize = paramMessage.Length; long paramSize = headerSize + fileSize; if (BatchBuffer != null) { byte[] commandMessage = CommandPacket(Command_FileWriteCompressed, paramSize); bytesSent = SocketSend(false, commandMessage); bytesSent = SocketSend(false, paramMessage); } else { // reduce the number of SocketSends if not batching byte[] commandMessage = new byte[8 + headerSize]; commandMessage[0] = (byte)(Command_FileWriteCompressed & 255); commandMessage[1] = (byte)((Command_FileWriteCompressed >> 8) & 255); commandMessage[2] = (byte)(paramSize & 255); commandMessage[3] = (byte)((paramSize >> 8) & 255); commandMessage[4] = (byte)((paramSize >> 16) & 255); commandMessage[5] = (byte)((paramSize >> 24) & 255); commandMessage[6] = (byte)((paramSize >> 32) & 255); commandMessage[7] = (byte)((paramSize >> 40) & 255); Buffer.BlockCopy(paramMessage, 0, commandMessage, 8, headerSize); bytesSent = SocketSend(false, commandMessage); } byte[] buffer = new byte[COMPRESS_BUFFERSIZE + 3]; byte[] header = new byte[3]; // first part of buffer contains zero which flags COMPRESS_BUFFERSIZE uncompressed buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; while ((bytesRead = fileStream.Read(buffer, 3, COMPRESS_BUFFERSIZE)) != 0) { Stats_CompressedBytesSent += bytesRead; var compressedStream = new MemoryStream(); var zipStream = new GZipStream(compressedStream, CompressionLevel.Fastest); zipStream.Write(buffer, 3, bytesRead); zipStream.Close(); byte[] compressedArray = compressedStream.ToArray(); int compsize = compressedArray.Length; if (compsize < COMPRESS_BUFFERSIZE) { if (BatchBuffer != null) { // batching will do a block copy so better to do two calls header[0] = (byte)(compsize & 255); header[1] = (byte)((compsize >> 8) & 255); header[2] = (byte)((compsize >> 16) & 255); bytesSent = SocketSend(true, header); bytesSent = SocketSend(true, compressedArray); } else { // more efficient to send one packet byte[] compPacket = new byte[compsize + 3]; compPacket[0] = (byte)(compsize & 255); compPacket[1] = (byte)((compsize >> 8) & 255); compPacket[2] = (byte)((compsize >> 16) & 255); Buffer.BlockCopy(compressedArray, 0, compPacket, 3, compsize); bytesSent = SocketSend(true, compPacket); } } else { bytesSent = SocketSend(true, buffer); } } fileStream.Close(); /* int resultSize = (int)ResultSize(); if (resultSize > 0) { byte[] message = new byte[resultSize]; int bytesRecv = SocketReceive(false, message); string Outcome = Encoding.UTF8.GetString(message, 0, bytesRecv); Result = Outcome == "true"; } */ Result = true; } } catch (IOException) { // file not found Result = false; } catch (Exception e) { Log.TraceError("{0}> Unexpected Exception: {1}", bLog, e.ToString()); CloseConnection(); } return Result; } public bool FileWriteString(string Contents, string DestPath) { Boolean Result = false; if (ClientSocket == null) { return Result; } try { // deal with LastBaseDir substitution DestPath = OptimizePath(DestPath); byte[] paramMessage = Encoding.UTF8.GetBytes(DestPath + "\0"); byte[] contentsMessage = Encoding.UTF8.GetBytes(Contents); long paramSize = paramMessage.Length + contentsMessage.Length; byte[] commandMessage = CommandPacket(Command_FileWrite, paramSize); Stats_FilesWrite++; int bytesSent = SocketSend(false, commandMessage); bytesSent = SocketSend(false, paramMessage); Stats_CompressedBytesSent += bytesSent; bytesSent = SocketSend(true, contentsMessage); /* int resultSize = (int)ResultSize(); if (resultSize > 0) { byte[] message = new byte[resultSize]; int bytesRecv = SocketReceive(false, message); string Outcome = Encoding.UTF8.GetString(message, 0, bytesRecv); Result = Outcome == "true"; }*/ Result = true; } catch (Exception e) { Log.TraceError("Unexpected Exception: {0}", e.ToString()); CloseConnection(); } return Result; } public bool PushFile(string InSource, string InDest, bool bCompress = false, int bLog = 0) { InSource = InSource.Replace("\\", "/"); InDest = InDest.Replace("\\", "/"); bool Result = false; if (bCompress) { Result = FileWriteCompressed(InSource, InDest, bLog); } else { Result = FileWrite(InSource, InDest, bLog); } if (!Result && bLog > 0) { Log.TraceInformation("{0}> Failed to copy {1} to {2}", bLog, InSource, InDest); return false; } /* if (bLog > 0) { Log.TraceInformation("{0}> Copied: {1}", bLog, InSource); } */ return true; } public bool PushDirectory(string InSource, string InDest, bool bCompress = false, int bLog = 0) { if (Directory.Exists(InSource)) { foreach (string File in Directory.EnumerateFiles(InSource)) { if (!PushFile(File, File.Replace(InSource, InDest), bCompress, bLog)) { return false; } } } return true; } public bool PushDirectories(string InSource, string InDest, bool bCompress = false, int bLog = 0) { if (Directory.Exists(InSource)) { foreach (string File in Directory.EnumerateFiles(InSource)) { if (!PushFile(File, File.Replace(InSource, InDest), bCompress, bLog)) { return false; } } foreach (string Directory in Directory.EnumerateDirectories(InSource)) { if (!PushDirectories(Directory, Directory.Replace(InSource, InDest), bCompress, bLog)) { return false; } } } return true; } private void AddDeployDirectories(ref List> USBClientFiles, ref List> WiFiClientFiles, ref bool bAlternate, string InSource, string InDest) { if (Directory.Exists(InSource)) { foreach (string File in Directory.EnumerateFiles(InSource)) { if (bAlternate) { WiFiClientFiles.Add(new KeyValuePair(File, File.Replace(InSource, InDest))); } else { USBClientFiles.Add(new KeyValuePair(File, File.Replace(InSource, InDest))); } bAlternate = !bAlternate; } foreach (string Directory in Directory.EnumerateDirectories(InSource)) { AddDeployDirectories(ref USBClientFiles, ref WiFiClientFiles, ref bAlternate, Directory, Directory.Replace(InSource, InDest)); } } } private bool bDeployThreadInterruptRequested = false; private bool bDeployThreadError = false; private void DeployThread(AndroidFileClient Client, ref List> Files, bool bCompress, int bLog) { // Push all the WiFi client files foreach (KeyValuePair Entry in Files) { if (!Client.PushFile(Entry.Key, Entry.Value, bCompress, bLog)) { Volatile.Write(ref bDeployThreadError, true); break; } if (Volatile.Read(ref bDeployThreadInterruptRequested)) { break; } } } private void AddSourceDirectories(ref List Directories, string InSource, string InDest) { if (Directory.Exists(InSource)) { foreach (string Directory in Directory.EnumerateDirectories(InSource)) { AddSourceDirectories(ref Directories, Directory, Directory.Replace(InSource, InDest)); } string Entry = InDest.Replace("\\", "/"); int LastIndex = Directories.Count - 1; if (LastIndex < 0 || !Directories[LastIndex].StartsWith(Entry)) { Directories.Add(Entry); } } } public bool Deploy(HashSet EntriesToDeploy, string SourceDir, string DestDir, bool bCompress = false, bool bLog = false, bool bReportStats = true, AndroidFileClient WiFiClient = null) { bool Result = true; int logId = bLog ? 1 : 0; Stopwatch totalwatch = System.Diagnostics.Stopwatch.StartNew(); Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); // pre-create the directories (send only deepest) List Directories = new List(); foreach (string Entry in EntriesToDeploy) { string RemotePath = Entry.Replace(SourceDir, DestDir).Replace("\\", "/"); FileAttributes attributes = File.GetAttributes(Entry); if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { AddSourceDirectories(ref Directories, Entry, RemotePath); } else { Directories.Add(Path.GetDirectoryName(RemotePath).Replace("\\", "/")); } } foreach (string Entry in Directories) { DirCreate(Entry); } stopwatch.Stop(); var ElapsedMs = stopwatch.ElapsedMilliseconds; if (bReportStats) { Log.TraceInformation("Time to create directories: {0:N3}s", (float)(ElapsedMs / 1000.0)); } Stats_Clear(); Batch_Start(); stopwatch.Restart(); if (WiFiClient != null) { WiFiClient.Stats_Clear(false); WiFiClient.Batch_Start(); // collect all the files into two lists, one for each client List> USBClientFiles = new List>(); List> WiFiClientFiles = new List>(); bool bAlternate = false; foreach (string Entry in EntriesToDeploy) { string RemotePath = Entry.Replace(SourceDir, DestDir).Replace("\\", "/"); FileAttributes attributes = File.GetAttributes(Entry); if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { AddDeployDirectories(ref USBClientFiles, ref WiFiClientFiles, ref bAlternate, Entry, RemotePath); } else { if (bAlternate) { WiFiClientFiles.Add(new KeyValuePair(Entry, RemotePath)); } else { USBClientFiles.Add(new KeyValuePair(Entry, RemotePath)); } bAlternate = !bAlternate; } } Thread WiFiDeployThread = new Thread(() => DeployThread(WiFiClient, ref WiFiClientFiles, bCompress, bLog ? 2 : 0)) { Name = "WiFiDeployThread" }; bDeployThreadInterruptRequested = false; bDeployThreadError = false; WiFiDeployThread.Start(); // Push all the USB client files foreach (KeyValuePair Entry in USBClientFiles) { if (!PushFile(Entry.Key, Entry.Value, bCompress, logId)) { Volatile.Write(ref bDeployThreadInterruptRequested, true); Result = false; break; } if (Volatile.Read(ref bDeployThreadError)) if (bDeployThreadError) { Result = false; break; } } // Wait for WiFi thread to finish WiFiDeployThread.Join(); WiFiClient.Batch_Stop(); Stats_Combine(WiFiClient); } else { // push all files on USB foreach (string Entry in EntriesToDeploy) { string RemotePath = Entry.Replace(SourceDir, DestDir).Replace("\\", "/"); FileAttributes attributes = File.GetAttributes(Entry); if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { if (!PushDirectories(Entry, RemotePath, bCompress, logId)) { Result = false; break; } } else { if (!PushFile(Entry, RemotePath, bCompress, logId)) { Result = false; break; } } } } // time to batch (if buffer wasn't flushed) stopwatch.Stop(); // this code will be removed if TimeBatching is false but will give an unreachable code warning #pragma warning disable if (TimeBatching) { if (bReportStats) { ElapsedMs = stopwatch.ElapsedMilliseconds; Log.TraceInformation("Time to build batch: {0:N3}s", (float)(ElapsedMs / 1000.0)); } Stats_Stopwatch.Stop(); Stats_StartStopwatch(); } #pragma warning restore // stop and flush batch buffer Batch_Stop(); // this causes a sync so totalwatch is valid Query("^^"); totalwatch.Stop(); if (bReportStats) { Stats_Report(); ElapsedMs = totalwatch.ElapsedMilliseconds; Log.TraceInformation("Total time to Deploy AFS: {0:N3}s", (float)(ElapsedMs / 1000.0)); } return Result; } /* public void TestOptimizePath() { Log.TraceInformation("{0}", OptimizePath("/storage/emulated/0/Android/data/com.epicgames.SaveTest/files/UE4Game/SaveTest/Manifest_DebugFiles_Android.txt")); Log.TraceInformation("{0}", OptimizePath("/storage/emulated/0/Android/data/com.epicgames.SaveTest/files/UE4Game/SaveTest/DataDrivenPlatformInfo.ini")); Log.TraceInformation("{0}", OptimizePath("/storage/emulated/0/Android/data/com.epicgames.SaveTest/files/UE4Game/SaveTest/Engine/Config/Layouts/DefaultLayout.ini")); Log.TraceInformation("{0}", OptimizePath("/storage/emulated/0/Android/data/com.epicgames.SaveTest/files/UE4Game/SaveTest/Engine/Content/ArtTools/RenderToTexture/Materials/Debug/M_Emissive_Color.uasset")); Log.TraceInformation("{0}", OptimizePath("/storage/0/emulated/commandfile.txt")); Log.TraceInformation("{0}", OptimizePath("/storage/0/emulated/123.txt")); Log.TraceInformation("{0}", OptimizePath("/storage/0/emulated/game/123.txt")); Log.TraceInformation("{0}", OptimizePath("/storage/0/emulated/every.txt")); Log.TraceInformation("{0}", OptimizePath("^commandfile")); Log.TraceInformation("{0}", OptimizePath("^commandfile")); Log.TraceInformation("{0}", OptimizePath("^ext/z.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/abc.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/cde.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/1/abc.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/1/2/abc.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/xyz.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/1/2/stu.txt")); Log.TraceInformation("{0}", OptimizePath("^ext/1/wer.txt")); Log.TraceInformation("{0}", OptimizePath("^int/1/2/stu.txt")); Log.TraceInformation("{0}", OptimizePath("^int/1/abc.txt")); } */ } }