2019-01-08 09:35:52 -05:00
/ * *
2019-12-26 23:01:54 -05:00
* Copyright Epic Games , Inc . All Rights Reserved .
2014-03-14 14:13:41 -04:00
* /
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 System.Windows.Forms ;
using Microsoft.Win32 ;
using System.Linq ;
using System.Runtime.Remoting.Channels.Ipc ;
using System.Runtime.Remoting.Channels ;
using System.Runtime.Remoting ;
namespace iPhonePackager
{
2021-03-22 22:24:38 -04:00
[Serializable]
public struct ConnectedDeviceInformation
{
public string DeviceName ;
public string UDID ;
public string DeviceType ;
public string DeviceInterface ;
public ConnectedDeviceInformation ( string DeviceName , string UDID , string DeviceType , string DeviceInterface )
{
this . DeviceName = DeviceName ;
this . UDID = UDID ;
this . DeviceType = DeviceType ;
this . DeviceInterface = DeviceInterface ;
}
}
2014-03-14 14:13:41 -04:00
[Serializable]
2021-03-22 22:24:38 -04:00
class DeployTimeReporter
2014-03-14 14:13:41 -04:00
{
public void Log ( string Line )
{
Program . Log ( Line ) ;
}
public void Error ( string Line )
{
Program . Error ( Line ) ;
}
public void Warning ( string Line )
{
Program . Warning ( Line ) ;
}
public void SetProgressIndex ( int Progress )
{
Program . ProgressIndex = Progress ;
}
public int GetTransferProgressDivider ( )
{
return ( Program . BGWorker ! = null ) ? 1000 : 25 ;
}
}
class DeploymentHelper
{
public static void InstallIPAOnConnectedDevices ( string IPAPath )
{
// Read the mobile provision to check for issues
FileOperations . ReadOnlyZipFileSystem Zip = new FileOperations . ReadOnlyZipFileSystem ( IPAPath ) ;
MobileProvision Provision = null ;
try
{
MobileProvisionParser . ParseFile ( Zip . ReadAllBytes ( "embedded.mobileprovision" ) ) ;
}
catch ( System . Exception ex )
{
Program . Warning ( String . Format ( "Couldn't find an embedded mobile provision ({0})" , ex . Message ) ) ;
Provision = null ;
}
2014-08-06 15:37:27 -04:00
Zip . Close ( ) ;
2014-03-14 14:13:41 -04:00
2021-03-22 22:24:38 -04:00
2014-03-14 14:13:41 -04:00
if ( Provision ! = null )
{
2021-03-22 22:24:38 -04:00
var DevicesList = GetAllConnectedDevices ( ) ;
2014-03-14 14:13:41 -04:00
2021-03-22 22:24:38 -04:00
foreach ( var DeviceInfo in DevicesList )
2014-03-14 14:13:41 -04:00
{
string UDID = DeviceInfo . UDID ;
string DeviceName = DeviceInfo . DeviceName ;
// Check the IPA's mobile provision against the connected device to make sure this device is authorized
2022-10-21 13:25:02 -04:00
// We'll still try installing anyways, but this message is more friendly than the failure we got back from MobileDeviceInterface when we used it
2014-03-14 14:13:41 -04:00
if ( UDID ! = String . Empty )
{
if ( ! Provision . ContainsUDID ( UDID ) )
{
Program . Warning ( String . Format ( "Embedded provision in IPA does not include the UDID {0} of device '{1}'. The installation is likely to fail." , UDID , DeviceName ) ) ;
}
}
else
{
Program . Warning ( String . Format ( "Unable to query device for UDID, and therefore unable to verify IPA embedded mobile provision contains this device." ) ) ;
}
}
}
2021-03-22 22:24:38 -04:00
InstallIPAOnDevices ( IPAPath ) ;
2014-03-14 14:13:41 -04:00
}
public static bool ExecuteDeployCommand ( string Command , string GamePath , string RPCCommand )
{
switch ( Command . ToLowerInvariant ( ) )
{
case "backup" :
{
string ApplicationIdentifier = RPCCommand ;
if ( ApplicationIdentifier = = null )
{
ApplicationIdentifier = Utilities . GetStringFromPList ( "CFBundleIdentifier" ) ;
}
2014-12-11 16:20:07 -05:00
if ( Config . FilesForBackup . Count > 0 )
{
2021-03-22 22:24:38 -04:00
if ( ! BackupFiles ( ApplicationIdentifier , Config . FilesForBackup . ToArray ( ) ) )
2014-12-11 16:20:07 -05:00
{
Program . Error ( "Failed to transfer manifest file from device to PC" ) ;
Program . ReturnCode = ( int ) ErrorCodes . Error_DeviceBackupFailed ;
}
}
2021-03-22 22:24:38 -04:00
else if ( ! DeploymentHelper . BackupDocumentsDirectory ( ApplicationIdentifier , Config . GetRootBackedUpDocumentsDirectory ( ) ) )
2014-03-14 14:13:41 -04:00
{
Program . Error ( "Failed to transfer documents directory from device to PC" ) ;
2014-08-01 20:30:13 -04:00
Program . ReturnCode = ( int ) ErrorCodes . Error_DeviceBackupFailed ;
2014-03-14 14:13:41 -04:00
}
}
break ;
case "uninstall" :
{
string ApplicationIdentifier = RPCCommand ;
if ( ApplicationIdentifier = = null )
{
ApplicationIdentifier = Utilities . GetStringFromPList ( "CFBundleIdentifier" ) ;
}
2021-03-22 22:24:38 -04:00
if ( ! UninstallIPAOnDevices ( ApplicationIdentifier ) )
2014-03-14 14:13:41 -04:00
{
Program . Error ( "Failed to uninstall IPA on device" ) ;
2014-09-12 06:23:49 -04:00
Program . ReturnCode = ( int ) ErrorCodes . Error_AppUninstallFailed ;
2014-03-14 14:13:41 -04:00
}
}
break ;
case "deploy" :
case "install" :
{
string IPAPath = GamePath ;
2014-10-02 14:08:18 -04:00
string AdditionalCommandline = Program . AdditionalCommandline ;
2014-03-14 14:13:41 -04:00
2014-12-11 16:20:07 -05:00
if ( ! String . IsNullOrEmpty ( AdditionalCommandline ) & & ! Config . bIterate )
2014-10-03 12:05:32 -04:00
{
// Read the mobile provision to check for issues
FileOperations . ReadOnlyZipFileSystem Zip = new FileOperations . ReadOnlyZipFileSystem ( IPAPath ) ;
try
{
// Compare the commandline embedded to prevent us from any unnecessary writing.
2021-06-02 14:48:26 -04:00
byte [ ] CommandlineBytes = Zip . ReadAllBytes ( "uecommandline.txt" ) ;
2014-10-03 12:05:32 -04:00
string ExistingCommandline = Encoding . UTF8 . GetString ( CommandlineBytes , 0 , CommandlineBytes . Length ) ;
2014-10-02 14:08:18 -04:00
if ( ExistingCommandline ! = AdditionalCommandline )
2014-10-03 12:05:32 -04:00
{
// Ensure we have a temp dir to stage our temporary ipa
if ( ! Directory . Exists ( Config . PCStagingRootDir ) )
{
Directory . CreateDirectory ( Config . PCStagingRootDir ) ;
}
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
string TmpFilePath = Path . Combine ( Path . GetDirectoryName ( Config . PCStagingRootDir ) , Path . GetFileNameWithoutExtension ( IPAPath ) + ".tmp.ipa" ) ;
if ( File . Exists ( TmpFilePath ) )
{
File . Delete ( TmpFilePath ) ;
}
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
File . Copy ( IPAPath , TmpFilePath ) ;
2014-10-02 14:08:18 -04:00
// Get the project name:
string ProjectFile = ExistingCommandline . Split ( ' ' ) . FirstOrDefault ( ) ;
2014-10-03 12:05:32 -04:00
// Write out the new commandline.
FileOperations . ZipFileSystem WritableZip = new FileOperations . ZipFileSystem ( TmpFilePath ) ;
2014-10-02 14:08:18 -04:00
byte [ ] NewCommandline = Encoding . UTF8 . GetBytes ( ProjectFile + " " + AdditionalCommandline ) ;
2021-06-02 14:48:26 -04:00
WritableZip . WriteAllBytes ( "uecommandline.txt" , NewCommandline ) ;
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
// We need to residn the application after the commandline file has changed.
CodeSignatureBuilder CodeSigner = new CodeSignatureBuilder ( ) ;
CodeSigner . FileSystem = WritableZip ;
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
CodeSigner . PrepareForSigning ( ) ;
CodeSigner . PerformSigning ( ) ;
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
WritableZip . Close ( ) ;
2014-10-02 14:08:18 -04:00
2014-10-03 12:05:32 -04:00
// Set the deploying ipa path to our new ipa
IPAPath = TmpFilePath ;
}
}
catch ( System . Exception ex )
{
Program . Warning ( String . Format ( "Failed to override the commandline.txt file: ({0})" , ex . Message ) ) ;
}
Zip . Close ( ) ;
}
2014-10-02 14:08:18 -04:00
2014-12-11 16:20:07 -05:00
if ( Config . bIterate )
{
string ApplicationIdentifier = RPCCommand ;
if ( String . IsNullOrEmpty ( ApplicationIdentifier ) )
{
ApplicationIdentifier = Utilities . GetStringFromPList ( "CFBundleIdentifier" ) ;
}
2021-03-22 22:24:38 -04:00
if ( ! DeploymentHelper . PushFiles ( ApplicationIdentifier , Config . DeltaManifest ) )
2014-12-11 16:20:07 -05:00
{
Program . Error ( "Failed to install Files on device" ) ;
Program . ReturnCode = ( int ) ErrorCodes . Error_FilesInstallFailed ;
}
}
else if ( File . Exists ( IPAPath ) )
2014-03-14 14:13:41 -04:00
{
2021-03-22 22:24:38 -04:00
if ( ! InstallIPAOnDevices ( IPAPath ) )
2014-03-14 14:13:41 -04:00
{
Program . Error ( "Failed to install IPA on device" ) ;
2014-09-12 06:23:49 -04:00
Program . ReturnCode = ( int ) ErrorCodes . Error_AppInstallFailed ;
2014-03-14 14:13:41 -04:00
}
}
else
{
Program . Error ( String . Format ( "Failed to find IPA file: '{0}'" , IPAPath ) ) ;
2014-09-12 06:23:49 -04:00
Program . ReturnCode = ( int ) ErrorCodes . Error_AppNotFound ;
2014-03-14 14:13:41 -04:00
}
}
break ;
default :
return false ;
}
return true ;
}
static DeployTimeReporter Reporter = new DeployTimeReporter ( ) ;
2022-03-02 23:55:13 -05:00
static string ExecuteLibimobileProcess ( string ProcessName , string ProcessArguments = "" , bool bTreatOutputAsUTF8 = true )
2014-03-14 14:13:41 -04:00
{
2021-03-22 22:24:38 -04:00
string ExePath = Directory . GetCurrentDirectory ( ) + "/../../../Extras/ThirdPartyNotUE/libimobiledevice/" ;
if ( Environment . OSVersion . Platform = = PlatformID . Win32NT )
2014-03-14 14:13:41 -04:00
{
2021-03-22 22:24:38 -04:00
ExePath + = "x64/" ;
ProcessName + = ".exe" ;
2014-03-14 14:13:41 -04:00
}
2021-03-22 22:24:38 -04:00
else if ( Environment . OSVersion . Platform = = PlatformID . MacOSX )
2014-03-14 14:13:41 -04:00
{
2021-03-22 22:24:38 -04:00
ExePath + = "Mac/" ;
2014-12-11 16:20:07 -05:00
}
else
{
2021-03-22 22:24:38 -04:00
Program . LogVerbose ( "Unsupported Platform." ) ;
return "" ;
2014-03-14 14:13:41 -04:00
}
2021-03-22 22:24:38 -04:00
ProcessStartInfo StartInfo = new ProcessStartInfo ( ExePath + ProcessName , ProcessArguments ) ;
StartInfo . UseShellExecute = false ;
StartInfo . RedirectStandardOutput = true ;
StartInfo . RedirectStandardError = true ;
StartInfo . CreateNoWindow = true ;
2022-03-02 23:55:13 -05:00
if ( bTreatOutputAsUTF8 )
{
StartInfo . StandardOutputEncoding = Encoding . UTF8 ;
StartInfo . StandardErrorEncoding = Encoding . UTF8 ;
}
2021-03-22 22:24:38 -04:00
string FullOutput = "" ;
string ErrorOutput = "" ;
using ( Process LocalProcess = Process . Start ( StartInfo ) )
{
StreamReader OutputReader = LocalProcess . StandardOutput ;
FullOutput = OutputReader . ReadToEnd ( ) . Trim ( ) ;
StreamReader ErrorReader = LocalProcess . StandardError ;
ErrorOutput = ErrorReader . ReadToEnd ( ) . Trim ( ) ;
if ( FullOutput . Length > 0 )
{
Program . LogVerbose ( FullOutput ) ;
}
if ( ErrorOutput . Length > 0 )
{
Program . LogVerbose ( ErrorOutput ) ;
}
LocalProcess . WaitForExit ( ) ;
}
if ( ErrorOutput . Length > 0 )
{
if ( FullOutput . Length > 0 )
{
FullOutput + = Environment . NewLine ;
}
FullOutput + = ErrorOutput ;
}
return FullOutput ;
}
public static bool PushFiles ( string BundleIdentifier , string ManifestFile )
{
string FullOutput = "" ;
var DeviceList = GetAllConnectedDevices ( ) ;
foreach ( var Device in DeviceList )
{
string IdeviceFSArgs = "" ;
if ( Device . DeviceInterface = = "Network" )
{
IdeviceFSArgs = "-n" ;
continue ;
}
string [ ] FileList = ManifestFile . Split ( '\n' ) ;
string AllCommandsToPush = "" ;
foreach ( string Filename in FileList )
{
if ( ! string . IsNullOrEmpty ( Filename ) & & ! string . IsNullOrWhiteSpace ( Filename ) )
{
string Trimmed = Filename . Trim ( ) ;
string BaseFolder = Path . GetDirectoryName ( ManifestFile ) ;
string SourceFilename = BaseFolder + "/" + Trimmed ;
string DestFilename = "/Library/Caches/" + Trimmed . Replace ( "cookeddata/" , "" ) ;
DestFilename = DestFilename . Replace ( '\\' , '/' ) ;
DestFilename = "\"" + DestFilename + "\"" ;
string CommandToPush = "push -p \"" + SourceFilename + "\" " + DestFilename + "\n" ;
AllCommandsToPush + = CommandToPush ;
}
}
System . IO . File . WriteAllText ( Directory . GetCurrentDirectory ( ) + "/CommandsToPush.txt" , AllCommandsToPush ) ;
IdeviceFSArgs = IdeviceFSArgs + " -u " + Device . UDID + " -b " + BundleIdentifier + " -x \"" + Directory . GetCurrentDirectory ( ) + "/CommandsToPush.txt" + "\"" ;
2022-03-02 23:55:13 -05:00
FullOutput = ExecuteLibimobileProcess ( "idevicefs" , IdeviceFSArgs , false ) ;
2021-03-22 22:24:38 -04:00
File . Delete ( Directory . GetCurrentDirectory ( ) + "/CommandsToPush.txt" ) ;
if ( FullOutput ! = "0" )
{
return false ;
}
}
return true ;
}
static bool BackupFiles ( string BundleIdentifier , string [ ] DestinationFiles )
{
string FullOutput = "" ;
var DeviceList = GetAllConnectedDevices ( ) ;
foreach ( var Device in DeviceList )
{
if ( Device . DeviceInterface = = "Network" )
{
continue ;
}
string AllCommands = "" ;
foreach ( var DestinationFile in DestinationFiles )
{
string Command = "pull " + DestinationFile ;
AllCommands + = Command ;
}
System . IO . File . WriteAllText ( Directory . GetCurrentDirectory ( ) + "/CommandsToPull.txt" , AllCommands ) ;
string arguments = "\"" + "-u " + Device . UDID + " -b " + BundleIdentifier + " -x " + Directory . GetCurrentDirectory ( ) + "/CommandsToPull.txt" + "\"" ;
2022-03-02 23:55:13 -05:00
FullOutput = ExecuteLibimobileProcess ( "idevicefs" , arguments , false ) ;
2021-03-22 22:24:38 -04:00
}
File . Delete ( Directory . GetCurrentDirectory ( ) + "/CommandsToPull.txt" ) ;
if ( FullOutput ! = "0" )
{
return false ;
}
return true ;
}
public static bool BackupDocumentsDirectory ( string BundleIdentifier , string DestinationDocumentsDirectory )
{
string FullOutput = "" ;
var DeviceList = GetAllConnectedDevices ( ) ;
foreach ( var Device in DeviceList )
{
if ( Device . DeviceInterface = = "Network" )
{
continue ;
}
// Destination folder
string TargetFolder = Path . Combine ( DestinationDocumentsDirectory , Device . DeviceName ) ;
2022-03-02 23:55:13 -05:00
if ( ! System . IO . Directory . Exists ( TargetFolder ) )
2022-01-20 04:04:49 -05:00
{
System . IO . Directory . CreateDirectory ( TargetFolder ) ;
}
2022-03-02 23:55:13 -05:00
// Pull /Documents
string SourceFolder = "/Documents/ " ;
string Command = " pull " + SourceFolder + "\"" + TargetFolder + "\"" ;
FullOutput = ExecuteLibimobileProcess ( "idevicefs" , "-u " + Device . UDID + " -b " + BundleIdentifier + Command , false ) ;
2021-03-22 22:24:38 -04:00
if ( FullOutput ! = "0" )
{
Program . LogVerbose ( FullOutput ) ;
}
2022-03-02 23:55:13 -05:00
// Pull /Library/Caches
SourceFolder = "/Library/Caches/ " ;
Command = " pull " + SourceFolder + "\"" + TargetFolder + "\"" ;
FullOutput = ExecuteLibimobileProcess ( "idevicefs" , "-u " + Device . UDID + " -b " + BundleIdentifier + Command , false ) ;
2021-03-22 22:24:38 -04:00
if ( FullOutput ! = "0" )
{
Program . LogVerbose ( FullOutput ) ;
}
}
return true ;
}
static bool ActionOnIPAOnDevices ( string args , string IPAPath )
{
List < ConnectedDeviceInformation > DevicesToReturn = new List < ConnectedDeviceInformation > ( ) ;
string FullOutput = ExecuteLibimobileProcess ( "idevice_id" ) ;
string LibimobileDeviceArguments = "" ;
var ConnectedDevicesUDIDs = FullOutput . Split ( new string [ ] { Environment . NewLine } , StringSplitOptions . None ) ;
foreach ( var ConnnectedUDID in ConnectedDevicesUDIDs )
{
string CurrentUDID = ConnnectedUDID ;
if ( ConnnectedUDID . Contains ( "(USB)" ) )
{
CurrentUDID = ConnnectedUDID . Split ( ' ' ) . First ( ) ;
LibimobileDeviceArguments = "-u " + CurrentUDID + args + "\"" + IPAPath + "\"" ;
string OutputInfo = ExecuteLibimobileProcess ( "ideviceinstaller" , LibimobileDeviceArguments ) ;
if ( OutputInfo . Contains ( "DONE" ) )
{
return true ;
}
else
{
Program . LogVerbose ( OutputInfo ) ;
return false ;
}
}
else if ( ConnnectedUDID . Contains ( "(Network)" ) )
{
// Network install doesn't seem to be working on Windows. USB Interface only for now.
// CurrentUDID = ConnnectedUDID.Split(' ').First();
// LibimobileDeviceArguments = "-n -u " + CurrentUDID + " -i " + IPAPath;
}
}
return true ;
}
public static bool UninstallIPAOnDevices ( string IPAPath )
{
return ActionOnIPAOnDevices ( " --uninstall " , IPAPath ) ;
}
public static bool InstallIPAOnDevices ( string IPAPath )
{
return ActionOnIPAOnDevices ( " -i " , IPAPath ) ;
}
public static ConnectedDeviceInformation [ ] GetAllConnectedDevices ( )
{
List < ConnectedDeviceInformation > DevicesToReturn = new List < ConnectedDeviceInformation > ( ) ;
string FullOutput = ExecuteLibimobileProcess ( "idevice_id" ) ;
string DeviceName = "" ;
string UDID = "" ;
string DeviceType = "" ;
string ConnectingInterface = "" ;
var ConnectedDevicesUDIDs = FullOutput . Split ( new string [ ] { Environment . NewLine } , StringSplitOptions . None ) ;
foreach ( var ConnnectedUDID in ConnectedDevicesUDIDs )
{
ConnectedDeviceInformation NewConnectedDevice ;
NewConnectedDevice . UDID = "" ;
String ParsedUDID = ConnnectedUDID . Split ( ' ' ) . First ( ) ;
String IdeviceInfoArgs = "-u " + ParsedUDID ;
if ( ConnnectedUDID . Contains ( "Network" ) )
{
ConnectingInterface = "Network" ;
IdeviceInfoArgs = "-n " + IdeviceInfoArgs ;
}
else
{
ConnectingInterface = "USB" ;
}
string OutputInfo = ExecuteLibimobileProcess ( "ideviceinfo" , IdeviceInfoArgs ) ;
if ( OutputInfo . Contains ( "not found!" ) )
{
Program . LogVerbose ( OutputInfo ) ;
}
else
{
foreach ( string Line in OutputInfo . Split ( Environment . NewLine . ToCharArray ( ) ) )
{
if ( Line . StartsWith ( "DeviceName: " ) )
{
2022-03-02 23:55:13 -05:00
DeviceName = Line . Substring ( 12 ) ;
2021-03-22 22:24:38 -04:00
}
else if ( Line . StartsWith ( "UniqueDeviceID: " ) )
{
UDID = Line . Split ( ':' ) . Last ( ) ;
UDID . Replace ( " " , "" ) ;
}
else if ( Line . StartsWith ( "ProductType: " ) )
{
DeviceType = Line . Split ( ':' ) . Last ( ) ;
DeviceType . Replace ( " " , "" ) ;
}
}
}
DevicesToReturn . Add ( new ConnectedDeviceInformation ( DeviceName , UDID , DeviceType , ConnectingInterface ) ) ;
DeviceName = "" ;
UDID = "" ;
DeviceType = "" ;
ConnectingInterface = "" ;
}
return DevicesToReturn . ToArray ( ) ;
2014-03-14 14:13:41 -04:00
}
}
}