/** * Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.Win32; using Manzana; using System.Linq; using iPhonePackager; using System.Net.Sockets; namespace DeploymentServer { class DummyDeployTimeReporter : DeployTimeReportingInterface { public void Log(string Line) { Console.WriteLine("[DD] " + Line); } public void Error(string Line) { Console.WriteLine("[DD] Error: " + Line); } public void Warning(string Line) { Console.WriteLine("[DD] Warning: " + Line); } public void SetProgressIndex(int Progress) { } public int GetTransferProgressDivider() { return 25; } } class DeploymentProxy : MarshalByRefObject, DeploymentInterface { static public DeploymentImplementation Deployer; public DeploymentProxy() { } public string DeviceId { get { return Deployer.DeviceId; } set { Deployer.DeviceId = value; } } public void SetReportingInterface(DeployTimeReportingInterface InReporter) { Deployer.SetReportingInterface(InReporter); } public bool UninstallIPAOnDevice(string ApplicationIdentifier) { return Deployer.UninstallIPAOnDevice(ApplicationIdentifier); } public ConnectedDeviceInfo[] EnumerateConnectedDevices() { return Deployer.EnumerateConnectedDevices(); } public bool InstallIPAOnDevice(string IPAPath) { return Deployer.InstallIPAOnDevice(IPAPath); } public bool InstallFilesOnDevice(string BundleIdentifier, string ManifestFile) { return Deployer.InstallFilesOnDevice(BundleIdentifier, ManifestFile); } public bool CopyFileToDevice(string BundleIdentifier, string SourceFile, string DestFile) { return Deployer.CopyFileToDevice(BundleIdentifier, SourceFile, DestFile); } public bool BackupDocumentsDirectory(string BundleIdentifier, string DestinationDocumentsDirectory) { return Deployer.BackupDocumentsDirectory(BundleIdentifier, DestinationDocumentsDirectory); } public bool BackupFiles(string BundleIdentifier, string[] Files) { return Deployer.BackupFiles(BundleIdentifier, Files); } public bool ListApplications() { return Deployer.ListApplications(); } }; class DeploymentImplementation : DeploymentInterface { bool bHaveRegisteredHandlers = false; /// /// Delay for 1 second after finding the first connected device, to wait for others to enumerate. /// const uint StandardEnumerationDelayMS = 1000; const uint SleepAfterFailedDeviceCallMS = 500; /// /// Mappings from device type string to a more friendly device name /// Dictionary DeviceTypeMapping = new Dictionary(); /// /// The reporting interface used to talk back to iPhonePackager /// static DeployTimeReportingInterface ReportIF = new DummyDeployTimeReporter(); public string DeviceId { get; set; } public DeploymentImplementation() { // Setup the device type mapping DeviceTypeMapping.Add("iPhone1,1", "iPhone 1G"); DeviceTypeMapping.Add("iPhone1,2", "iPhone 3G"); DeviceTypeMapping.Add("iPhone2,1", "iPhone 3GS"); DeviceTypeMapping.Add("iPhone3,1", "iPhone 4"); // AT&T DeviceTypeMapping.Add("iPhone3,3", "iPhone 4"); // CDMA/Verizon DeviceTypeMapping.Add("iPhone4,1", "iPhone 4S"); DeviceTypeMapping.Add("iPod1,1", "iPod Touch 1G"); DeviceTypeMapping.Add("iPod2,1", "iPod Touch 2G"); DeviceTypeMapping.Add("iPod3,1", "iPod Touch 3G"); DeviceTypeMapping.Add("iPod4,1", "iPod Touch 4G"); DeviceTypeMapping.Add("iPad1,1", "iPad"); DeviceTypeMapping.Add("iPad2,1", "iPad 2 (Wifi)"); // Wifi only DeviceTypeMapping.Add("iPad2,2", "iPad 2 (with 3G)"); // AT&T DeviceTypeMapping.Add("iPad2,3", "iPad 2 (with 3G)"); // CDMA/Verizon Console.WriteLine("[deploy] Created deployment server."); // Initialize the mobile device manager if (!bHaveRegisteredHandlers) { Manzana.MobileDeviceInstanceManager.Initialize(MobileDeviceConnected, MobileDeviceDisconnected); bHaveRegisteredHandlers = true; } } /// /// Returns a pretty device type name /// string GetPrettyDeviceType(string DeviceType) { string OutName; if (!DeviceTypeMapping.TryGetValue(DeviceType, out OutName)) { OutName = DeviceType; } return OutName; } /// /// Tries to determine if the device will not be able to run a UE3 application /// bool CanRunUE3Applications(string DeviceType) { // Not perfect, but it will give it a go if (DeviceType.StartsWith("iPhone1,") || DeviceType.StartsWith("iPod1,") || DeviceType.StartsWith("iPod2,")) { return false; } else { return true; } } void MobileDeviceConnected(object sender, Manzana.ConnectEventArgs args) { string DeviceName = "(unknown name)"; MobileDeviceInstance Inst = MobileDeviceInstanceManager.ConnectedDevices[args.Device]; if (Inst != null) { DeviceName = Inst.DeviceName; } ReportIF.Log(String.Format("Mobile Device '{0}' connected", DeviceName)); Inst.OnGenericProgress = MobileDeviceProgressCallback; Inst.TransferProgressDivisor = ReportIF.GetTransferProgressDivider(); } void MobileDeviceDisconnected(object sender, Manzana.ConnectEventArgs args) { ReportIF.Error("Mobile Device disconnected during run!"); } void MobileDeviceProgressCallback(string Msg, int PercentDone) { ReportIF.SetProgressIndex(PercentDone); ReportIF.Log(String.Format(" ... {0}", Msg)); } /// /// Tries to connect to all devices /// /// void ConnectToDevices(uint DelayAfterFirstDeviceMS) { { ReportIF.Log("Trying to connect to mobile device running iOS ..."); try { // Initialize the mobile device manager if (!bHaveRegisteredHandlers) { Manzana.MobileDeviceInstanceManager.Initialize(MobileDeviceConnected, MobileDeviceDisconnected); bHaveRegisteredHandlers = true; } // Wait for connections to roll in int SleepDurationMS = 100; int TotalDurationMS = 5000; while (!MobileDeviceInstanceManager.AreAnyDevicesConnected() && (TotalDurationMS > 0)) { System.Threading.Thread.Sleep(SleepDurationMS); TotalDurationMS -= SleepDurationMS; } // Wait one additional tick in case any new devices come online //@TODO: Is there a better way to determine if all devices have been enumerated? System.Threading.Thread.Sleep((int)DelayAfterFirstDeviceMS); if (!MobileDeviceInstanceManager.AreAnyDevicesConnected()) { ReportIF.Error("Timed out while trying to connect to a mobile device. Make sure one is connected."); } } catch (ThreadAbortException) { // we expect this to happen so we don't log it } catch (Exception ex) { ReportIF.Error(String.Format("Error encountered ('{0}') while trying to connect to a mobile device. Please verify that iTunes is installed", ex.Message)); } } } /// /// A delegate to do work on a device, that knows if it has succeeded or failed /// delegate bool PerformDeviceActionDelegate(MobileDeviceInstance Device); /// /// Calls a delegate on all connected devices. This re-evaluates the currently connected devices /// after each go, so if some devices take a while to enumerate, they will probably still be caught /// after the first device work finishes (since the work is typically long). /// /// The initial number of ms to wait after the first enumerated device is found /// The delegate to perform on each connected device /// True if all devices had the work successfully performed, and false if any failed or none were found bool PerformActionOnAllDevices(uint DelayEnumerationPeriodMS, PerformDeviceActionDelegate PerDeviceWork) { // Start enumerating the devices ConnectToDevices(DelayEnumerationPeriodMS); // Keep looking at the device list and executing on new ones as long as we keep finding them Dictionary Runs = new Dictionary(); bool bFoundNewDevices = true; while (bFoundNewDevices) { IEnumerable Devices = MobileDeviceInstanceManager.GetSnapshotInstanceList(); bFoundNewDevices = false; foreach (MobileDeviceInstance PotentialDevice in Devices) { PotentialDevice.Reconnect(); string DeviceId = PotentialDevice.DeviceId; if (DeviceId == String.Empty) { System.Threading.Thread.Sleep((int)SleepAfterFailedDeviceCallMS); DeviceId = PotentialDevice.DeviceId; } if (!Runs.ContainsKey(DeviceId) && PotentialDevice.IsConnected) { // New device, do the work on it Runs.Add(DeviceId, PerDeviceWork(PotentialDevice)); bFoundNewDevices = true; } } } // Determine if all succeeded bool bAllSucceeded = true; bool bAnySucceeded = Runs.Count > 0; foreach (var KVP in Runs) { bAllSucceeded = bAllSucceeded && KVP.Value; } return bAllSucceeded && bAnySucceeded; } /// /// Uninstalls all application bundles with the specified application bundle ID on all connected devices /// public bool UninstallIPAOnDevice(string ApplicationIdentifier) { ReportIF.Log("Uninstalling IPA on device ... "); // Connect to each device and issue the uninstall return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { ReportIF.Log(String.Format(" ... Uninstalling application with bundle identifier '{0}' on device '{1}'", ApplicationIdentifier, Device.DeviceName)); return Device.TryUninstall(ApplicationIdentifier); }); } /// /// Makes a dictionary of UDID->DeviceName for all connected devices /// public ConnectedDeviceInfo [] EnumerateConnectedDevices() { List Results = new List(); PerformActionOnAllDevices(2 * StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { string DeviceName = Device.DeviceName; string UDID = Device.DeviceId; string DeviceType = Device.ProductType; if (UDID != "") { Results.Add(new ConnectedDeviceInfo(DeviceName, UDID, GetPrettyDeviceType(DeviceType))); ReportIF.Log(String.Format(" Connected device '{0}' has UDID '{1}' and type '{2}'... ", DeviceName, UDID, DeviceType)); } else { ReportIF.Warning(String.Format(" Failed to query device for it's name or UDID")); } return true; }); return Results.ToArray(); } /// /// return the list of devices to stdout /// public void ListDevices() { PerformActionOnAllDevices(2 * StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { string ProductType = Device.ProductType; string DeviceName = Device.DeviceName; string UDID = Device.DeviceId; ReportIF.Log(String.Format("FOUND: TYPE: {0} ID: {1} NAME: {2}", ProductType, UDID, DeviceName)); return true; }); } public void ListenToDevice(string inDeviceID, TextWriter Writer) { MobileDeviceInstance targetDevice = null; PerformActionOnAllDevices(2 * StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { if(inDeviceID == Device.DeviceId) { targetDevice = Device; } return true; }); if(targetDevice != null) { targetDevice.StartSyslogService(); // This never returns, the process must be killed to stop logging while(true) { string curLog = targetDevice.GetSyslogData(); if(curLog.Trim().Length > 0) { Writer.Write(curLog); } System.Threading.Thread.Sleep(50); } } else { ReportIF.Error("Could not find device " + inDeviceID); } } public void TunnelToDevice(string inDeviceID, String Command) { MobileDeviceInstance TargetDevice = null; PerformActionOnAllDevices(2 * StandardEnumerationDelayMS, delegate (MobileDeviceInstance Device) { if (inDeviceID == Device.DeviceId) { TargetDevice = Device; } return true; }); IntPtr TCPService = new IntPtr(); if (TargetDevice != null) { TargetDevice.StartTCPRelayService(ref TCPService); int SentDat = TargetDevice.TunnelData(Command, TCPService); TargetDevice.StopSyslogService(); } else { ReportIF.Error("Could not find device " + inDeviceID); } } public MobileDeviceInstance StartTCPTunnel(string inDeviceID, ref IntPtr TCPService, short Port = 8888) { MobileDeviceInstance targetDevice = null; PerformActionOnAllDevices(2 * StandardEnumerationDelayMS, delegate (MobileDeviceInstance Device) { if (inDeviceID == Device.DeviceId) { targetDevice = Device; } return true; }); if (targetDevice != null) { if (targetDevice.StartTCPRelayService(ref TCPService)) { return targetDevice; } ReportIF.Error("Could not start TCP relay to " + inDeviceID); } else { ReportIF.Error("Could not find device " + inDeviceID); } return null; } /// /// Installs an IPA to all connected devices /// public bool InstallIPAOnDevice(string IPAPath) { if ((IPAPath == null) || (IPAPath.Length == 0)) { return false; } // Transfer to all connected devices return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { // Transfer the file to the device string DeviceName = Device.DeviceName; string UDID = Device.DeviceId; string DeviceType = Device.ProductType; // Check to see what kind of device it is if (!CanRunUE3Applications(DeviceType)) { ReportIF.Warning(String.Format("Device '{0}' is a {1} model, which does not support OpenGL ES2.0. The installation is likely to fail.", DeviceName, GetPrettyDeviceType(DeviceType))); } Console.WriteLine("Device '{0}' with id {1} of type {3} is being checked against {2}.", Device.DeviceName, Device.DeviceId, DeviceId, Device.ProductType); if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { ReportIF.Log(String.Format("Transferring IPA to device '{0}' ... ", DeviceName)); Device.CopyFileToPublicStaging(IPAPath); // Request that the device install it ReportIF.Log(String.Format("Installing IPA on device '{0}' ... ", DeviceName)); // Upgrade will function as install if the app isn't already installed, and has the added benefit of killing a running // app rather than failing if the user is still running the app to upgrade //return ConnectedDevice.TryInstall(IPAPath); bool bResult = Device.TryUpgrade(IPAPath); ReportIF.Log(""); return bResult; } return true; }); } /// /// Installs an IPA to all connected devices /// public bool InstallFilesOnDevice(string BundleIdentifier, string Manifest) { // Transfer to all connected devices return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { bool bResult = true; string DeviceType = Device.ProductType; if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { bResult = Device.TryCopy(BundleIdentifier, Manifest); ReportIF.Log(""); } return bResult; }); } /// /// Copies a single file to a specified location on the device for the given bundle /// public bool CopyFileToDevice(string BundleIdentifier, string SourceFile, string DestFile) { return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate (MobileDeviceInstance Device) { bool bResult = true; string DeviceType = Device.ProductType; if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { bResult = Device.TryCopyFile(BundleIdentifier, SourceFile, DestFile); ReportIF.Log(""); } return bResult; }); } /// /// Copies a single file from a specified location on the device for the given bundle /// public bool CopyFileFromDevice(string BundleIdentifier, string SourceFile, string DestFile) { return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate (MobileDeviceInstance Device) { bool bResult = true; string DeviceType = Device.ProductType; if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { bResult = Device.TryCopyFileOut(BundleIdentifier, SourceFile, DestFile); ReportIF.Log(""); } return bResult; }); } public bool BackupDocumentsDirectory(string BundleIdentifier, string DestinationDocumentsDirectory) { return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { bool bResult = true; string DeviceType = Device.ProductType; if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { string SafeDeviceName = MobileDeviceInstance.SanitizePathNoFilename(Device.DeviceName); // Destination folder string TargetFolder = Path.Combine(DestinationDocumentsDirectory, SafeDeviceName); // Source folder string SourceFolder = "/Documents/"; bResult = Device.TryBackup(BundleIdentifier, SourceFolder, TargetFolder + SourceFolder); SourceFolder = "/Library/Caches/"; bResult = Device.TryBackup(BundleIdentifier, SourceFolder, TargetFolder + SourceFolder); ReportIF.Log(""); } return bResult; }); } public bool BackupFiles(string BundleIdentifier, string[] DestionationFiles) { return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { bool bResult = true; string DeviceType = Device.ProductType; if (String.IsNullOrEmpty(DeviceId) || Device.DeviceId == DeviceId || (DeviceId.Contains("All_tvOS_On") && DeviceType.Contains("TV")) || (DeviceId.Contains("All_iOS_On") && !DeviceType.Contains("TV"))) { bResult = Device.TryBackup(BundleIdentifier, DestionationFiles); ReportIF.Log(""); } return bResult; }); } public bool ListApplications() { return PerformActionOnAllDevices(StandardEnumerationDelayMS, delegate(MobileDeviceInstance Device) { Console.WriteLine("Device '{0}' has the following applications:", Device.DeviceName); Device.DumpInstalledApplications(); ReportIF.Log(""); return true; }); } public void SetReportingInterface(DeployTimeReportingInterface InReporter) { // System.Threading.Thread.Sleep(10000); ReportIF = InReporter; } } }