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; } Zip.Close(); if (Provision != null) { var DeviceList = DeploymentHelper.Get().EnumerateConnectedDevices(); foreach (var DeviceInfo in DeviceList) { 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 // We'll still try installing anyways, but this message is more friendly than the failure we get back from MobileDeviceInterface 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.")); } } } DeploymentHelper.Get().InstallIPAOnDevice(IPAPath); }
/** * Copy a set of files */ static public bool CopyFiles(string SourceFolder, string DestFolder, string DisplayDestFolder, string FileSpec, string ExcludeExtension) { if (DisplayDestFolder == null) { DisplayDestFolder = DestFolder; } Program.Log(" ... '" + SourceFolder + "\\" + FileSpec + "' -> '" + DisplayDestFolder + "'"); // Ensure the source folder exists - fail otherwise DirectoryInfo SourceFolderInfo = new DirectoryInfo(SourceFolder); if (!SourceFolderInfo.Exists) { Program.Warning("Source folder does not exist: " + SourceFolderInfo); return(false); } // Ensure the destination folder exists - create otherwise DirectoryInfo DestFolderInfo = new DirectoryInfo(DestFolder); if (!DestFolderInfo.Exists) { Directory.CreateDirectory(DestFolder); } foreach (FileInfo SourceInfo in SourceFolderInfo.GetFiles(FileSpec)) { // Don't copy any file with the excluded extension if (ExcludeExtension != null && SourceInfo.Extension == ExcludeExtension) { continue; } string DestPathName = Path.Combine(DestFolderInfo.FullName, SourceInfo.Name); CopyFile(SourceInfo, DestPathName, false, DefaultRetryCount); } return(true); }
/** * Using the stub IPA previously compiled on the Mac, create a new IPA with assets */ static public void RepackageIPAFromStub() { if (string.IsNullOrEmpty(Config.RepackageStagingDirectory) || !Directory.Exists(Config.RepackageStagingDirectory)) { Program.Error("Directory specified with -stagedir could not be found!"); return; } DateTime StartTime = DateTime.Now; CodeSignatureBuilder CodeSigner = null; // Clean the staging directory Program.ExecuteCommand("Clean", null); // Create a copy of the IPA so as to not trash the original ZipFile Zip = SetupWorkIPA(); if (Zip == null) { return; } string ZipWorkingDir = String.Format("Payload/{0}{1}.app/", Program.GameName, Program.Architecture); FileOperations.ZipFileSystem FileSystem = new FileOperations.ZipFileSystem(Zip, ZipWorkingDir); // Check for a staged plist that needs to be merged into the main one { // Determine if there is a staged one we should try to use instead string PossiblePList = Path.Combine(Config.RepackageStagingDirectory, "Info.plist"); if (File.Exists(PossiblePList)) { if (Config.bPerformResignWhenRepackaging) { Program.Log("Found Info.plist ({0}) in stage, which will be merged in with stub plist contents", PossiblePList); // Merge the two plists, using the staged one as the authority when they conflict byte[] StagePListBytes = File.ReadAllBytes(PossiblePList); string StageInfoString = Encoding.UTF8.GetString(StagePListBytes); byte[] StubPListBytes = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper StubInfo = new Utilities.PListHelper(Encoding.UTF8.GetString(StubPListBytes)); StubInfo.MergePlistIn(StageInfoString); // Write it back to the cloned stub, where it will be used for all subsequent actions byte[] MergedPListBytes = Encoding.UTF8.GetBytes(StubInfo.SaveToString()); FileSystem.WriteAllBytes("Info.plist", MergedPListBytes); } else { Program.Warning("Found Info.plist ({0}) in stage that will be ignored; IPP cannot combine it with the stub plist since -sign was not specified", PossiblePList); } } } // Get the name of the executable file string CFBundleExecutable; { // Load the .plist from the stub byte[] RawInfoPList = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(RawInfoPList)); // Get the name of the executable file if (!Info.GetString("CFBundleExecutable", out CFBundleExecutable)) { throw new InvalidDataException("Info.plist must contain the key CFBundleExecutable"); } } // Tell the file system about the executable file name so that we can set correct attributes on // the file when zipping it up FileSystem.ExecutableFileName = CFBundleExecutable; // Prepare for signing if requested if (Config.bPerformResignWhenRepackaging) { // Start the resign process (load the mobileprovision and info.plist, find the cert, etc...) CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = FileSystem; CodeSigner.PrepareForSigning(); // Merge in any user overrides that exist UpdateVersion(CodeSigner.Info); } // Empty the current staging directory FileOperations.DeleteDirectory(new DirectoryInfo(Config.PCStagingRootDir)); // we will zip files in the pre-staged payload dir string ZipSourceDir = Config.RepackageStagingDirectory; // Save the zip Program.Log("Saving IPA ..."); FilesBeingModifiedToPrintOut.Clear(); Zip.SaveProgress += UpdateSaveProgress; Zip.CompressionLevel = (Ionic.Zlib.CompressionLevel)Config.RecompressionSetting; // Add all of the payload files, replacing existing files in the stub IPA if necessary (should only occur for icons) { string SourceDir = Path.GetFullPath(ZipSourceDir); string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories); foreach (string Filename in PayloadFiles) { // Get the relative path to the file (this implementation only works because we know the files are all // deeper than the base dir, since they were generated from a search) string AbsoluteFilename = Path.GetFullPath(Filename); string RelativeFilename = AbsoluteFilename.Substring(SourceDir.Length + 1).Replace('\\', '/'); string ZipAbsolutePath = String.Format("Payload/{0}{1}.app/{1}", Program.GameName, Program.Architecture, RelativeFilename); byte[] FileContents = File.ReadAllBytes(AbsoluteFilename); if (FileContents.Length == 0) { // Zero-length files added by Ionic cause installation/upgrade to fail on device with error 0xE8000050 // We store a single byte in the files as a workaround for now FileContents = new byte[1]; FileContents[0] = 0; } FileSystem.WriteAllBytes(RelativeFilename, FileContents); if ((FileContents.Length >= 1024 * 1024) || (Config.bVerbose)) { FilesBeingModifiedToPrintOut.Add(ZipAbsolutePath); } } } // Re-sign the executable if there is a signing context if (CodeSigner != null) { if (Config.OverrideBundleName != null) { CodeSigner.Info.SetString("CFBundleDisplayName", Config.OverrideBundleName); string CFBundleIdentifier; if (CodeSigner.Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { CodeSigner.Info.SetString("CFBundleIdentifier", CFBundleIdentifier + "_" + Config.OverrideBundleName); } } CodeSigner.PerformSigning(); } // Stick in the iTunesArtwork PNG if available string iTunesArtworkPath = Path.Combine(Config.BuildDirectory, "iTunesArtwork"); if (File.Exists(iTunesArtworkPath)) { Zip.UpdateFile(iTunesArtworkPath, ""); } // Save the Zip Program.Log("Compressing files into IPA (-compress={1}).{0}", Config.bVerbose ? "" : " Only large files will be listed next, but other files are also being packaged.", Config.RecompressionSetting); FileSystem.Close(); TimeSpan ZipLength = DateTime.Now - StartTime; FileInfo FinalZipInfo = new FileInfo(Zip.Name); Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s for all steps)", Zip.Name, ZipLength.TotalSeconds, FinalZipInfo.Length / (1024.0f * 1024.0f))); }
public static string FindCompatibleProvision(string CFBundleIdentifier, out bool bNameMatch, bool bCheckCert = true, bool bCheckIdentifier = true, bool bCheckDistro = true) { bNameMatch = false; // remap the gamename if necessary string GameName = Program.GameName; if (GameName == "UE4Game") { if (Config.ProjectFile.Length > 0) { GameName = Path.GetFileNameWithoutExtension(Config.ProjectFile); } } // ensure the provision directory exists if (!Directory.Exists(Config.ProvisionDirectory)) { Directory.CreateDirectory(Config.ProvisionDirectory); } if (Config.bProvision) { if (File.Exists(Config.ProvisionDirectory + "/" + Config.Provision)) { return(Config.ProvisionDirectory + "/" + Config.Provision); } } #region remove after we provide an install mechanism CacheMobileProvisions(); #endregion // cache the provision library Dictionary <string, MobileProvision> ProvisionLibrary = new Dictionary <string, MobileProvision>(); foreach (string Provision in Directory.EnumerateFiles(Config.ProvisionDirectory, "*.mobileprovision")) { MobileProvision p = MobileProvisionParser.ParseFile(Provision); ProvisionLibrary.Add(Provision, p); if (p.FileName.Contains(p.UUID) && !File.Exists(Path.Combine(Config.ProvisionDirectory, "UE4_" + p.UUID + ".mobileprovision"))) { File.Copy(Provision, Path.Combine(Config.ProvisionDirectory, "UE4_" + p.UUID + ".mobileprovision")); p = MobileProvisionParser.ParseFile(Path.Combine(Config.ProvisionDirectory, "UE4_" + p.UUID + ".mobileprovision")); ProvisionLibrary.Add(Path.Combine(Config.ProvisionDirectory, "UE4_" + p.UUID + ".mobileprovision"), p); } } Program.Log("Searching for mobile provisions that match the game '{0}' (distribution: {3}) with CFBundleIdentifier='{1}' in '{2}'", GameName, CFBundleIdentifier, Config.ProvisionDirectory, Config.bForDistribution); // check the cache for a provision matching the app id (com.company.Game) // First checking for a contains match and then for a wildcard match for (int Phase = -1; Phase < 3; ++Phase) { if (Phase == -1 && string.IsNullOrEmpty(Config.ProvisionUUID)) { continue; } foreach (KeyValuePair <string, MobileProvision> Pair in ProvisionLibrary) { string DebugName = Path.GetFileName(Pair.Key); MobileProvision TestProvision = Pair.Value; // make sure the file is not managed by Xcode if (Path.GetFileName(TestProvision.FileName).ToLower().Equals(TestProvision.UUID.ToLower() + ".mobileprovision")) { continue; } Program.LogVerbose(" Phase {0} considering provision '{1}' named '{2}'", Phase, DebugName, TestProvision.ProvisionName); if (TestProvision.ProvisionName == "iOS Team Provisioning Profile: " + CFBundleIdentifier) { Program.LogVerbose(" Failing as provisioning is automatic"); continue; } // check to see if the platform is the same as what we are looking for if (!string.IsNullOrEmpty(TestProvision.Platform) && TestProvision.Platform != Config.OSString && !string.IsNullOrEmpty(Config.OSString)) { //Program.LogVerbose(" Failing platform {0} Config: {1}", TestProvision.Platform, Config.OSString); continue; } // Validate the name bool bPassesNameCheck = false; if (Phase == -1) { bPassesNameCheck = TestProvision.UUID == Config.ProvisionUUID; bNameMatch = bPassesNameCheck; } else if (Phase == 0) { bPassesNameCheck = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1) == CFBundleIdentifier; bNameMatch = bPassesNameCheck; } else if (Phase == 1) { if (TestProvision.ApplicationIdentifier.Contains("*")) { string CompanyName = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1); if (CompanyName != "*") { CompanyName = CompanyName.Substring(0, CompanyName.LastIndexOf(".")); bPassesNameCheck = CFBundleIdentifier.StartsWith(CompanyName); } } } else { if (TestProvision.ApplicationIdentifier.Contains("*")) { string CompanyName = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1); bPassesNameCheck = CompanyName == "*"; } } if (!bPassesNameCheck && bCheckIdentifier) { Program.LogVerbose(" .. Failed phase {0} name check (provision app ID was {1})", Phase, TestProvision.ApplicationIdentifier); continue; } if (Config.bForDistribution) { // Check to see if this is a distribution provision. get-task-allow must be false for distro profiles. // TestProvision.ProvisionedDeviceIDs.Count==0 is not a valid check as ad-hoc distro profiles do list devices. bool bDistroProv = !TestProvision.bDebug; if (!bDistroProv) { Program.LogVerbose(" .. Failed distribution check (mode={0}, get-task-allow={1}, #devices={2})", Config.bForDistribution, TestProvision.bDebug, TestProvision.ProvisionedDeviceIDs.Count); continue; } } else { if (bCheckDistro) { bool bPassesDebugCheck = TestProvision.bDebug; if (!bPassesDebugCheck) { Program.LogVerbose(" .. Failed debugging check (mode={0}, get-task-allow={1}, #devices={2})", Config.bForDistribution, TestProvision.bDebug, TestProvision.ProvisionedDeviceIDs.Count); continue; } } else { if (!TestProvision.bDebug) { Config.bForceStripSymbols = true; } } } // Check to see if the provision is in date DateTime CurrentUTCTime = DateTime.UtcNow; bool bPassesDateCheck = (CurrentUTCTime >= TestProvision.CreationDate) && (CurrentUTCTime < TestProvision.ExpirationDate); if (!bPassesDateCheck) { Program.LogVerbose(" .. Failed time period check (valid from {0} to {1}, but UTC time is now {2})", TestProvision.CreationDate, TestProvision.ExpirationDate, CurrentUTCTime); continue; } // check to see if we have a certificate for this provision bool bPassesHasMatchingCertCheck = false; if (bCheckCert) { X509Certificate2 Cert = CodeSignatureBuilder.FindCertificate(TestProvision); bPassesHasMatchingCertCheck = (Cert != null); if (bPassesHasMatchingCertCheck && Config.bCert) { bPassesHasMatchingCertCheck &= (CryptoAdapter.GetFriendlyNameFromCert(Cert) == Config.Certificate); } } else { bPassesHasMatchingCertCheck = true; } if (!bPassesHasMatchingCertCheck) { Program.LogVerbose(" .. Failed to find a matching certificate that was in date"); continue; } // Made it past all the tests Program.LogVerbose(" Picked '{0}' with AppID '{1}' and Name '{2}' as a matching provision for the game '{3}'", DebugName, TestProvision.ApplicationIdentifier, TestProvision.ProvisionName, GameName); return(Pair.Key); } } // check to see if there is already an embedded provision string EmbeddedMobileProvisionFilename = Path.Combine(Config.RepackageStagingDirectory, "embedded.mobileprovision"); Program.Warning("Failed to find a valid matching mobile provision, will attempt to use the embedded mobile provision instead if present"); return(EmbeddedMobileProvisionFilename); }
/// <summary> /// Constructs a MobileProvision from an xml blob extracted from the real ASN.1 file /// </summary> public MobileProvision(string EmbeddedPListText) { Data = new Utilities.PListHelper(EmbeddedPListText); // Now extract things // Key: ApplicationIdentifierPrefix, Array<String> List <string> PrefixList = Data.GetArray("ApplicationIdentifierPrefix", "string"); if (PrefixList.Count > 1) { Program.Warning("Found more than one entry for ApplicationIdentifierPrefix in the .mobileprovision, using the first one found"); } if (PrefixList.Count > 0) { ApplicationIdentifierPrefix = PrefixList[0]; } // Example date string from the XML: "2014-06-30T20:45:55Z"; DateTimeStyles AppleDateStyle = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; string CreationDateString; if (Data.GetDate("CreationDate", out CreationDateString)) { CreationDate = DateTime.Parse(CreationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } string ExpirationDateString; if (Data.GetDate("ExpirationDate", out ExpirationDateString)) { ExpirationDate = DateTime.Parse(ExpirationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } // Key: DeveloperCertificates, Array<Data> (uuencoded) string CertificatePassword = ""; List <string> CertificateList = Data.GetArray("DeveloperCertificates", "data"); foreach (string EncodedCert in CertificateList) { byte[] RawCert = Convert.FromBase64String(EncodedCert); DeveloperCertificates.Add(new X509Certificate2(RawCert, CertificatePassword)); } // Key: Name, String if (!Data.GetString("Name", out ProvisionName)) { ProvisionName = "(unknown)"; } // Key: ProvisionedDevices, Array<String> ProvisionedDeviceIDs = Data.GetArray("ProvisionedDevices", "string"); // Key: application-identifier, Array<String> Utilities.PListHelper XCentPList = null; Data.ProcessValueForKey("Entitlements", "dict", delegate(XmlNode ValueNode) { XCentPList = Utilities.PListHelper.CloneDictionaryRootedAt(ValueNode); }); // Modify the application-identifier to be fully qualified if needed if (!XCentPList.GetString("application-identifier", out ApplicationIdentifier)) { ApplicationIdentifier = "(unknown)"; } // check for get-task-allow bDebug = XCentPList.GetBool("get-task-allow"); if (!Data.GetString("UUID", out UUID)) { UUID = "(unkown)"; } List <string> Platforms = Data.GetArray("Platform", "string"); if (Platforms.Contains("iOS")) { Platform = "IOS"; } else if (Platforms.Contains("tvOS")) { Platform = "TVOS"; } else { Platform = ""; } }
static int Main(string[] args) { // remember the working directory at start, as the game path could be relative to this path string InitialCurrentDirectory = Environment.CurrentDirectory; // set the working directory to the location of the application (so relative paths always work) Environment.CurrentDirectory = Path.GetDirectoryName(Application.ExecutablePath); AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); // A simple, top-level try-catch block try { if (!ParseCommandLine(ref args)) { Log("Usage: iPhonePackager <Command> <GameName> [RPCCommand &| Switch]"); Log(""); Log("Common commands:"); Log(" ... RepackageIPA GameName"); Log(" ... PackageIPA GameName"); Log(" ... PackageApp GameName"); Log(" ... Deploy PathToIPA"); Log(" ... RepackageFromStage GameName"); Log(" ... Devices"); Log(" ... Validate"); Log(" ... Install"); Log(""); Log("Configuration switches:"); Log(" -stagedir <path> sets the directory to copy staged files from (defaults to none)"); Log(" -project <path> path to the project being packaged"); Log(" -compress=fast|best|none packaging compression level (defaults to none)"); Log(" -strip strip symbols during packaging"); Log(" -config game configuration (e.g., Shipping, Development, etc...)"); Log(" -distribution packaging for final distribution"); Log(" -createstub packaging stub IPA for later repackaging"); Log(" -mac <MacName> overrides the machine to use for any Mac operations"); Log(" -arch <Architecture> sets the architecture to use (blank for default, -simulator for simulator builds)"); Log(" -device <DeviceID> sets the device to install the IPA on"); Log(""); Log("Commands: RPC, Clean"); Log(" StageMacFiles, GetIPA, Deploy, Install, Uninstall"); Log(""); Log("RPC Commands: SetExec, InstallProvision, MakeApp, DeleteIPA, Copy, Kill, Strip, Zip, GenDSYM"); Log(""); Log("Sample commandlines:"); Log(" ... iPhonePackager Deploy UDKGame Release"); Log(" ... iPhonePackager RPC SwordGame Shipping MakeApp"); return((int)ErrorCodes.Error_Arguments); } Log("Executing iPhonePackager " + String.Join(" ", args)); Log("CWD: " + Directory.GetCurrentDirectory()); Log("Initial Dir: " + InitialCurrentDirectory); Log("Env CWD: " + Environment.CurrentDirectory); // Ensure shipping configuration for final distributions if (Config.bForDistribution && (GameConfiguration != "Shipping")) { Program.Warning("Distribution builds should be made in the Shipping configuration!"); } // process the GamePath (if could be ..\Samples\MyDemo\ or ..\Samples\MyDemo\MyDemo.uproject GameName = Path.GetFileNameWithoutExtension(GamePath); if (GameName.Equals("UE4", StringComparison.InvariantCultureIgnoreCase) || GameName.Equals("Engine", StringComparison.InvariantCultureIgnoreCase)) { GameName = "UE4Game"; } // setup configuration if (!Config.Initialize(InitialCurrentDirectory, GamePath)) { return((int)ErrorCodes.Error_Arguments); } switch (MainCommand.ToLowerInvariant()) { case "validate": // check to see if iTunes is installed string dllPath = ""; if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) { dllPath = "/Applications/Xcode.app/Contents/MacOS/Xcode"; } else { dllPath = Microsoft.Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Apple Inc.\\Apple Mobile Device Support\\Shared", "iTunesMobileDeviceDLL", null) as string; if (String.IsNullOrEmpty(dllPath) || !File.Exists(dllPath)) { dllPath = Microsoft.Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Apple Inc.\\Apple Mobile Device Support\\Shared", "MobileDeviceDLL", null) as string; } } if (String.IsNullOrEmpty(dllPath) || !File.Exists(dllPath)) { Error("iTunes Not Found!!", (int)ErrorCodes.Error_SDKNotFound); } else { // validate there is a useable provision and cert MobileProvision Provision; X509Certificate2 Cert; bool bHasOverrides; bool bNameMatch; bool foundPlist = CodeSignatureBuilder.FindRequiredFiles(out Provision, out Cert, out bHasOverrides, out bNameMatch); if (!foundPlist) { Error("Could not find a valid plist file!!", (int)ErrorCodes.Error_InfoPListNotFound); } else if (Provision == null && Cert == null) { Error("No Provision or cert found!!", (int)ErrorCodes.Error_ProvisionAndCertificateNotFound); } else if (Provision == null) { Error("No Provision found!!", (int)ErrorCodes.Error_ProvisionNotFound); } else if (Cert == null) { Error("No Signing Certificate found!!", (int)ErrorCodes.Error_CertificateNotFound); } } break; case "packageapp": if (CheckArguments()) { if (Config.bCreateStubSet) { Error("packageapp cannot be used with the -createstub switch"); Program.ReturnCode = (int)ErrorCodes.Error_Arguments; } else { // Create the .app on the Mac CompileTime.CreateApplicationDirOnMac(); } } break; case "repackagefromstage": if (CheckArguments()) { if (Config.bCreateStubSet) { Error("repackagefromstage cannot be used with the -createstub switches"); Program.ReturnCode = (int)ErrorCodes.Error_Arguments; } else { bool bProbablyCreatedStub = Utilities.GetEnvironmentVariable("ue.IOSCreateStubIPA", true); if (!bProbablyCreatedStub) { Warning("ue.IOSCreateStubIPA is currently FALSE, which means you may be repackaging with an out of date stub IPA!"); } CookTime.RepackageIPAFromStub(); } } break; // this is the "super fast just move executable" mode for quick programmer iteration case "dangerouslyfast": if (CheckArguments()) { CompileTime.DangerouslyFastMode(); } break; case "packageipa": if (CheckArguments()) { CompileTime.PackageIPAOnMac(); } break; case "install": GameName = ""; if (Config.bProvision) { ToolsHub.TryInstallingMobileProvision(Config.Provision, false); } if (Config.bCert) { ToolsHub.TryInstallingCertificate_PromptForKey(Config.Certificate, false); } CodeSignatureBuilder.FindCertificates(); CodeSignatureBuilder.FindProvisions(Config.OverrideBundleName); break; case "certificates": { CodeSignatureBuilder.FindCertificates(); CodeSignatureBuilder.FindProvisions(Config.OverrideBundleName); } break; case "resigntool": RunInVisualMode(delegate { return(new GraphicalResignTool()); }); break; case "certrequest": RunInVisualMode(delegate { return(new GenerateSigningRequestDialog()); }); break; case "gui": RunInVisualMode(delegate { return(ToolsHub.CreateShowingTools()); }); break; case "devices": ListDevices(); break; default: // Commands by themself default to packaging for the device if (CheckArguments()) { ExecuteCommand(MainCommand, MainRPCCommand); } break; } } catch (Exception Ex) { Error("Application exception: " + Ex.ToString()); if (ReturnCode == 0) { Program.ReturnCode = (int)ErrorCodes.Error_Unknown; } } finally { if (DeploymentHelper.DeploymentServerProcess != null) { DeploymentHelper.DeploymentServerProcess.Close(); } } Environment.ExitCode = ReturnCode; return(ReturnCode); }
public static string FindCompatibleProvision(string CFBundleIdentifier, out bool bNameMatch, bool bCheckCert = true, bool bCheckIdentifier = true) { bNameMatch = false; // remap the gamename if necessary string GameName = Program.GameName; if (GameName == "UE4Game") { if (Config.ProjectFile.Length > 0) { GameName = Path.GetFileNameWithoutExtension(Config.ProjectFile); } } // ensure the provision directory exists if (!Directory.Exists(Config.ProvisionDirectory)) { Directory.CreateDirectory(Config.ProvisionDirectory); } if (Config.bProvision) { if (File.Exists(Config.ProvisionDirectory + "/" + Config.Provision)) { return(Config.ProvisionDirectory + "/" + Config.Provision); } } #region remove after we provide an install mechanism // copy all of the provisions from the game directory to the library if (!String.IsNullOrEmpty(Config.ProjectFile)) { var ProjectFileBuildIOSPath = Path.GetDirectoryName(Config.ProjectFile) + "/Build/IOS/"; if (Directory.Exists(ProjectFileBuildIOSPath)) { foreach (string Provision in Directory.EnumerateFiles(ProjectFileBuildIOSPath, "*.mobileprovision", SearchOption.AllDirectories)) { if (!File.Exists(Config.ProvisionDirectory + Path.GetFileName(Provision)) || File.GetLastWriteTime(Config.ProvisionDirectory + Path.GetFileName(Provision)) < File.GetLastWriteTime(Provision)) { FileInfo DestFileInfo; if (File.Exists(Config.ProvisionDirectory + Path.GetFileName(Provision))) { DestFileInfo = new FileInfo(Config.ProvisionDirectory + Path.GetFileName(Provision)); DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; } File.Copy(Provision, Config.ProvisionDirectory + Path.GetFileName(Provision), true); DestFileInfo = new FileInfo(Config.ProvisionDirectory + Path.GetFileName(Provision)); DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; } } } } // copy all of the provisions from the engine directory to the library { if (Directory.Exists(Config.EngineBuildDirectory)) { foreach (string Provision in Directory.EnumerateFiles(Config.EngineBuildDirectory, "*.mobileprovision", SearchOption.AllDirectories)) { if (!File.Exists(Config.ProvisionDirectory + Path.GetFileName(Provision)) || File.GetLastWriteTime(Config.ProvisionDirectory + Path.GetFileName(Provision)) < File.GetLastWriteTime(Provision)) { FileInfo DestFileInfo; if (File.Exists(Config.ProvisionDirectory + Path.GetFileName(Provision))) { DestFileInfo = new FileInfo(Config.ProvisionDirectory + Path.GetFileName(Provision)); DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; } File.Copy(Provision, Config.ProvisionDirectory + Path.GetFileName(Provision), true); DestFileInfo = new FileInfo(Config.ProvisionDirectory + Path.GetFileName(Provision)); DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; } } } } #endregion // cache the provision library Dictionary <string, MobileProvision> ProvisionLibrary = new Dictionary <string, MobileProvision>(); foreach (string Provision in Directory.EnumerateFiles(Config.ProvisionDirectory, "*.mobileprovision")) { MobileProvision p = MobileProvisionParser.ParseFile(Provision); ProvisionLibrary.Add(Provision, p); } Program.Log("Searching for mobile provisions that match the game '{0}' with CFBundleIdentifier='{1}' in '{2}'", GameName, CFBundleIdentifier, Config.ProvisionDirectory); // check the cache for a provision matching the app id (com.company.Game) // First checking for a contains match and then for a wildcard match for (int Phase = 0; Phase < 3; ++Phase) { foreach (KeyValuePair <string, MobileProvision> Pair in ProvisionLibrary) { string DebugName = Path.GetFileName(Pair.Key); MobileProvision TestProvision = Pair.Value; Program.LogVerbose(" Phase {0} considering provision '{1}' named '{2}'", Phase, DebugName, TestProvision.ProvisionName); // Validate the name bool bPassesNameCheck = false; if (Phase == 0) { bPassesNameCheck = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1) == CFBundleIdentifier; bNameMatch = bPassesNameCheck; } else if (Phase == 1) { if (TestProvision.ApplicationIdentifier.Contains("*")) { string CompanyName = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1); if (CompanyName != "*") { CompanyName = CompanyName.Substring(0, CompanyName.LastIndexOf(".")); bPassesNameCheck = CFBundleIdentifier.StartsWith(CompanyName); } } } else { if (TestProvision.ApplicationIdentifier.Contains("*")) { string CompanyName = TestProvision.ApplicationIdentifier.Substring(TestProvision.ApplicationIdentifierPrefix.Length + 1); bPassesNameCheck = CompanyName == "*"; } } if (!bPassesNameCheck && bCheckIdentifier) { Program.LogVerbose(" .. Failed phase {0} name check (provision app ID was {1})", Phase, TestProvision.ApplicationIdentifier); continue; } if (Config.bForDistribution) { // check to see if this is a distribution provision bool bDistroProv = (TestProvision.ProvisionedDeviceIDs.Count == 0) && !TestProvision.bDebug; if (!bDistroProv) { Program.LogVerbose(" .. Failed distribution check (mode={0}, get-task-allow={1}, #devices={2})", Config.bForDistribution, TestProvision.bDebug, TestProvision.ProvisionedDeviceIDs.Count); continue; } } else { // check to see if we pass the debug check for non-distribution bool bPassesDebugCheck = TestProvision.bDebug; if (!bPassesDebugCheck) { Program.LogVerbose(" .. Failed debugging check (mode={0}, get-task-allow={1}, #devices={2})", Config.bForDistribution, TestProvision.bDebug, TestProvision.ProvisionedDeviceIDs.Count); continue; } } // Check to see if the provision is in date DateTime CurrentUTCTime = DateTime.UtcNow; bool bPassesDateCheck = (CurrentUTCTime >= TestProvision.CreationDate) && (CurrentUTCTime < TestProvision.ExpirationDate); if (!bPassesDateCheck) { Program.LogVerbose(" .. Failed time period check (valid from {0} to {1}, but UTC time is now {2})", TestProvision.CreationDate, TestProvision.ExpirationDate, CurrentUTCTime); continue; } // check to see if we have a certificate for this provision bool bPassesHasMatchingCertCheck = false; if (bCheckCert) { X509Certificate2 Cert = CodeSignatureBuilder.FindCertificate(TestProvision); bPassesHasMatchingCertCheck = (Cert != null); if (bPassesHasMatchingCertCheck && Config.bCert) { bPassesHasMatchingCertCheck &= (Cert.FriendlyName == Config.Certificate); } } else { bPassesHasMatchingCertCheck = true; } if (!bPassesHasMatchingCertCheck) { Program.LogVerbose(" .. Failed to find a matching certificate that was in date"); continue; } // Made it past all the tests Program.LogVerbose(" Picked '{0}' with AppID '{1}' and Name '{2}' as a matching provision for the game '{3}'", DebugName, TestProvision.ApplicationIdentifier, TestProvision.ProvisionName, GameName); return(Pair.Key); } } // check to see if there is already an embedded provision string EmbeddedMobileProvisionFilename = Path.Combine(Config.RepackageStagingDirectory, "embedded.mobileprovision"); Program.Warning("Failed to find a valid matching mobile provision, will attempt to use the embedded mobile provision instead if present"); return(EmbeddedMobileProvisionFilename); }
/// <summary> /// Does the actual work of signing the application /// Modifies the following files: /// Info.plist /// [Executable] (file name derived from CFBundleExecutable in the Info.plist, e.g., UDKGame) /// _CodeSignature/CodeResources /// [ResourceRules] (file name derived from CFBundleResourceSpecification, e.g., CustomResourceRules.plist) /// </summary> public void PerformSigning() { DateTime SigningTime = DateTime.Now; // Get the name of the executable file string CFBundleExecutable; if (!Info.GetString("CFBundleExecutable", out CFBundleExecutable)) { throw new InvalidDataException("Info.plist must contain the key CFBundleExecutable"); } // Get the name of the bundle string CFBundleIdentifier; if (!Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { throw new InvalidDataException("Info.plist must contain the key CFBundleIdentifier"); } // Verify there is a resource rules file and make a dummy one if needed. // If it's missing, CreateCodeResourceDirectory can't proceed (the Info.plist is already written to disk at that point) if (!Info.HasKey("CFBundleResourceSpecification")) { // Couldn't find the key, create a dummy one string CFBundleResourceSpecification = "CustomResourceRules.plist"; Info.SetString("CFBundleResourceSpecification", CFBundleResourceSpecification); Program.Warning("Info.plist was missing the key CFBundleResourceSpecification, creating a new resource rules file '{0}'.", CFBundleResourceSpecification); } // Save the Info.plist out byte[] RawInfoPList = Encoding.UTF8.GetBytes(Info.SaveToString()); Info.SetReadOnly(true); FileSystem.WriteAllBytes("Info.plist", RawInfoPList); Program.Log(" ... Writing updated Info.plist"); // Create the code resources file and load it byte[] ResourceDirBytes = CreateCodeResourcesDirectory(CFBundleExecutable); // Open the executable Program.Log("Opening source executable..."); byte[] SourceExeData = FileSystem.ReadAllBytes(CFBundleExecutable); FatBinaryFile FatBinary = new FatBinaryFile(); FatBinary.LoadFromBytes(SourceExeData); //@TODO: Verify it's an executable (not an object file, etc...) ulong CurrentStreamOffset = 0; byte[] FinalExeData = new byte[SourceExeData.Length + 1024 * 1024]; int ArchIndex = 0; foreach (MachObjectFile Exe in FatBinary.MachObjectFiles) { Program.Log("... Processing one mach object (binary is {0})", FatBinary.bIsFatBinary ? "fat" : "thin"); // Pad the memory stream with extra room to handle any possible growth in the code signing data int OverSize = 1024 * 1024; int ExeSize = (FatBinary.bIsFatBinary ? (int)FatBinary.Archs[ArchIndex].Size : SourceExeData.Length); MemoryStream OutputExeStream = new MemoryStream(ExeSize + OverSize); // Copy the data up to the executable into the final stream if (FatBinary.bIsFatBinary) { if (ArchIndex == 0) { OutputExeStream.Seek(0, SeekOrigin.Begin); OutputExeStream.Write(SourceExeData, (int)CurrentStreamOffset, (int)FatBinary.Archs[ArchIndex].Offset - (int)CurrentStreamOffset); OutputExeStream.Seek(0, SeekOrigin.Begin); byte[] HeaderData = OutputExeStream.ToArray(); HeaderData.CopyTo(FinalExeData, (long)CurrentStreamOffset); CurrentStreamOffset += (ulong)HeaderData.Length; } else { byte[] ZeroData = new byte[(int)FatBinary.Archs[ArchIndex].Offset - (int)CurrentStreamOffset]; ZeroData.CopyTo(FinalExeData, (long)CurrentStreamOffset); CurrentStreamOffset += (ulong)ZeroData.Length; } } // Copy the executable into the stream int ExeOffset = (FatBinary.bIsFatBinary ? (int)FatBinary.Archs[ArchIndex].Offset : 0); OutputExeStream.Seek(0, SeekOrigin.Begin); OutputExeStream.Write(SourceExeData, ExeOffset, ExeSize); OutputExeStream.Seek(0, SeekOrigin.Begin); long Length = OutputExeStream.Length; // Find out if there was an existing code sign blob and find the linkedit segment command MachLoadCommandCodeSignature CodeSigningBlobLC = null; MachLoadCommandSegment LinkEditSegmentLC = null; foreach (MachLoadCommand Command in Exe.Commands) { if (CodeSigningBlobLC == null) { CodeSigningBlobLC = Command as MachLoadCommandCodeSignature; } if (LinkEditSegmentLC == null) { LinkEditSegmentLC = Command as MachLoadCommandSegment; if (LinkEditSegmentLC.SegmentName != "__LINKEDIT") { LinkEditSegmentLC = null; } } } if (LinkEditSegmentLC == null) { throw new InvalidDataException("Did not find a Mach segment load command for the __LINKEDIT segment"); } // If the existing code signing blob command is missing, make sure there is enough space to add it // Insert the code signing blob if it isn't present //@TODO: Insert the code signing blob if it isn't present if (CodeSigningBlobLC == null) { throw new InvalidDataException("Did not find a Code Signing LC. Injecting one into a fresh executable is not currently supported."); } // Verify that the code signing blob is at the end of the linkedit segment (and thus can be expanded if needed) if ((CodeSigningBlobLC.BlobFileOffset + CodeSigningBlobLC.BlobFileSize) != (LinkEditSegmentLC.FileOffset + LinkEditSegmentLC.FileSize)) { throw new InvalidDataException("Code Signing LC was present but not at the end of the __LINKEDIT segment, unable to replace it"); } int SignedFileLength = (int)CodeSigningBlobLC.BlobFileOffset; // Create the code directory blob CodeDirectoryBlob FinalCodeDirectoryBlob = CodeDirectoryBlob.Create(CFBundleIdentifier, SignedFileLength); // Create the entitlements blob string EntitlementsText = BuildEntitlementString(CFBundleIdentifier); EntitlementsBlob FinalEntitlementsBlob = EntitlementsBlob.Create(EntitlementsText); // Create or preserve the requirements blob RequirementsBlob FinalRequirementsBlob = null; if ((CodeSigningBlobLC != null) && Config.bMaintainExistingRequirementsWhenCodeSigning) { RequirementsBlob OldRequirements = CodeSigningBlobLC.Payload.GetBlobByMagic(AbstractBlob.CSMAGIC_REQUIREMENTS_TABLE) as RequirementsBlob; FinalRequirementsBlob = OldRequirements; } if (FinalRequirementsBlob == null) { FinalRequirementsBlob = RequirementsBlob.CreateEmpty(); } // Create the code signature blob (which actually signs the code directory) CodeDirectorySignatureBlob CodeSignatureBlob = CodeDirectorySignatureBlob.Create(); // Create the code signature superblob (which contains all of the other signature-related blobs) CodeSigningTableBlob CodeSignPayload = CodeSigningTableBlob.Create(); CodeSignPayload.Add(0x00000, FinalCodeDirectoryBlob); CodeSignPayload.Add(0x00002, FinalRequirementsBlob); CodeSignPayload.Add(0x00005, FinalEntitlementsBlob); CodeSignPayload.Add(0x10000, CodeSignatureBlob); // The ordering of the following steps (and doing the signature twice below) must be preserved. // The reason is there are some chicken-and-egg issues here: // The code directory stores a hash of the header, but // The header stores the size of the __LINKEDIT section, which is where the signature blobs go, but // The CMS signature blob signs the code directory // // So, we need to know the size of a signature blob in order to write a header that is itself hashed // and signed by the signature blob // Do an initial signature just to get the size Program.Log("... Initial signature step ({0:0.00} s elapsed so far)", (DateTime.Now - SigningTime).TotalSeconds); CodeSignatureBlob.SignCodeDirectory(SigningCert, SigningTime, FinalCodeDirectoryBlob); // Compute the size of everything, and push it into the EXE header byte[] DummyPayload = CodeSignPayload.GetBlobBytes(); // Adjust the header and load command to have the correct size for the code sign blob WritingContext OutputExeContext = new WritingContext(new BinaryWriter(OutputExeStream)); long BlobLength = DummyPayload.Length; long NonCodeSigSize = (long)LinkEditSegmentLC.FileSize - CodeSigningBlobLC.BlobFileSize; long BlobStartPosition = NonCodeSigSize + (long)LinkEditSegmentLC.FileOffset; LinkEditSegmentLC.PatchFileLength(OutputExeContext, (uint)(NonCodeSigSize + BlobLength)); CodeSigningBlobLC.PatchPositionAndSize(OutputExeContext, (uint)BlobStartPosition, (uint)BlobLength); // Now that the executable loader command has been inserted and the appropriate section modified, compute all the hashes Program.Log("... Computing hashes ({0:0.00} s elapsed so far)", (DateTime.Now - SigningTime).TotalSeconds); OutputExeContext.Flush(); // Fill out the special hashes FinalCodeDirectoryBlob.GenerateSpecialSlotHash(CodeDirectoryBlob.cdInfoSlot, RawInfoPList); FinalCodeDirectoryBlob.GenerateSpecialSlotHash(CodeDirectoryBlob.cdRequirementsSlot, FinalRequirementsBlob.GetBlobBytes()); FinalCodeDirectoryBlob.GenerateSpecialSlotHash(CodeDirectoryBlob.cdResourceDirSlot, ResourceDirBytes); FinalCodeDirectoryBlob.GenerateSpecialSlotHash(CodeDirectoryBlob.cdApplicationSlot); FinalCodeDirectoryBlob.GenerateSpecialSlotHash(CodeDirectoryBlob.cdEntitlementSlot, FinalEntitlementsBlob.GetBlobBytes()); // Fill out the regular hashes FinalCodeDirectoryBlob.ComputeImageHashes(OutputExeStream.ToArray()); // And compute the final signature Program.Log("... Final signature step ({0:0.00} s elapsed so far)", (DateTime.Now - SigningTime).TotalSeconds); CodeSignatureBlob.SignCodeDirectory(SigningCert, SigningTime, FinalCodeDirectoryBlob); // Generate the signing blob and place it in the output (verifying it didn't change in size) byte[] FinalPayload = CodeSignPayload.GetBlobBytes(); if (DummyPayload.Length != FinalPayload.Length) { throw new InvalidDataException("CMS signature blob changed size between practice run and final run, unable to create useful code signing data"); } OutputExeContext.PushPositionAndJump(BlobStartPosition); OutputExeContext.Write(FinalPayload); OutputExeContext.PopPosition(); // Truncate the data so the __LINKEDIT section extends right to the end Program.Log("... Committing all edits ({0:0.00} s elapsed so far)", (DateTime.Now - SigningTime).TotalSeconds); OutputExeContext.CompleteWritingAndClose(); Program.Log("... Truncating/copying final binary", DateTime.Now - SigningTime); ulong DesiredExecutableLength = LinkEditSegmentLC.FileSize + LinkEditSegmentLC.FileOffset; if ((ulong)Length < DesiredExecutableLength) { throw new InvalidDataException("Data written is smaller than expected, unable to finish signing process"); } byte[] Data = OutputExeStream.ToArray(); Data.CopyTo(FinalExeData, (long)CurrentStreamOffset); CurrentStreamOffset += DesiredExecutableLength; // update the header if it is a fat binary if (FatBinary.bIsFatBinary) { FatBinary.Archs[ArchIndex].Size = (uint)DesiredExecutableLength; } // increment the architecture index ArchIndex++; } // re-write the header FatBinary.WriteHeader(ref FinalExeData, 0); // resize to the finale size Array.Resize(ref FinalExeData, (int)CurrentStreamOffset); //@todo: Extend the file system interface so we don't have to copy 20 MB just to truncate a few hundred bytes // Save the patched and signed executable Program.Log("Saving signed executable... ({0:0.00} s elapsed so far)", (DateTime.Now - SigningTime).TotalSeconds); FileSystem.WriteAllBytes(CFBundleExecutable, FinalExeData); Program.Log("Finished code signing, which took {0:0.00} s", (DateTime.Now - SigningTime).TotalSeconds); }
public void Warning(string Line) { Program.Warning(Line); }
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"); } if (Config.FilesForBackup.Count > 0) { if (!DeploymentHelper.Get().BackupFiles(ApplicationIdentifier, Config.FilesForBackup.ToArray())) { Program.Error("Failed to transfer manifest file from device to PC"); Program.ReturnCode = (int)ErrorCodes.Error_DeviceBackupFailed; } } else if (!DeploymentHelper.Get().BackupDocumentsDirectory(ApplicationIdentifier, Config.GetRootBackedUpDocumentsDirectory())) { Program.Error("Failed to transfer documents directory from device to PC"); Program.ReturnCode = (int)ErrorCodes.Error_DeviceBackupFailed; } } break; case "uninstall": { string ApplicationIdentifier = RPCCommand; if (ApplicationIdentifier == null) { ApplicationIdentifier = Utilities.GetStringFromPList("CFBundleIdentifier"); } if (!DeploymentHelper.Get().UninstallIPAOnDevice(ApplicationIdentifier)) { Program.Error("Failed to uninstall IPA on device"); Program.ReturnCode = (int)ErrorCodes.Error_AppUninstallFailed; } } break; case "deploy": case "install": { string IPAPath = GamePath; string AdditionalCommandline = Program.AdditionalCommandline; if (!String.IsNullOrEmpty(AdditionalCommandline) && !Config.bIterate) { // 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. byte[] CommandlineBytes = Zip.ReadAllBytes("ue4commandline.txt"); string ExistingCommandline = Encoding.UTF8.GetString(CommandlineBytes, 0, CommandlineBytes.Length); if (ExistingCommandline != AdditionalCommandline) { // Ensure we have a temp dir to stage our temporary ipa if (!Directory.Exists(Config.PCStagingRootDir)) { Directory.CreateDirectory(Config.PCStagingRootDir); } string TmpFilePath = Path.Combine(Path.GetDirectoryName(Config.PCStagingRootDir), Path.GetFileNameWithoutExtension(IPAPath) + ".tmp.ipa"); if (File.Exists(TmpFilePath)) { File.Delete(TmpFilePath); } File.Copy(IPAPath, TmpFilePath); // Get the project name: string ProjectFile = ExistingCommandline.Split(' ').FirstOrDefault(); // Write out the new commandline. FileOperations.ZipFileSystem WritableZip = new FileOperations.ZipFileSystem(TmpFilePath); byte[] NewCommandline = Encoding.UTF8.GetBytes(ProjectFile + " " + AdditionalCommandline); WritableZip.WriteAllBytes("ue4commandline.txt", NewCommandline); // We need to residn the application after the commandline file has changed. CodeSignatureBuilder CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = WritableZip; CodeSigner.PrepareForSigning(); CodeSigner.PerformSigning(); WritableZip.Close(); // 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(); } if (Config.bIterate) { string ApplicationIdentifier = RPCCommand; if (String.IsNullOrEmpty(ApplicationIdentifier)) { ApplicationIdentifier = Utilities.GetStringFromPList("CFBundleIdentifier"); } DeploymentHelper.Get().DeviceId = Config.DeviceId; if (!DeploymentHelper.Get().InstallFilesOnDevice(ApplicationIdentifier, Config.DeltaManifest)) { Program.Error("Failed to install Files on device"); Program.ReturnCode = (int)ErrorCodes.Error_FilesInstallFailed; } } else if (File.Exists(IPAPath)) { DeploymentHelper.Get().DeviceId = Config.DeviceId; if (!DeploymentHelper.Get().InstallIPAOnDevice(IPAPath)) { Program.Error("Failed to install IPA on device"); Program.ReturnCode = (int)ErrorCodes.Error_AppInstallFailed; } } else { Program.Error(String.Format("Failed to find IPA file: '{0}'", IPAPath)); Program.ReturnCode = (int)ErrorCodes.Error_AppNotFound; } } break; default: return(false); } return(true); }
/** * Copy a single file (deleting the existing file at the destination if it exists) */ static public bool CopyFile(FileInfo SourceFileInfo, string DestFileName, bool bCopyMustSucceed, int MaxRetryCount) { int CopyTryCount = 0; bool bFileCopiedSuccessfully = false; // if the file doesn't exist, but isn't needed, then just early out if (!bCopyMustSucceed && !SourceFileInfo.Exists) { return false; } if (Config.bVerbose) { Program.Log(String.Format(" Copy: {0} -> {1}, last modified at {2}", SourceFileInfo.FullName, DestFileName, SourceFileInfo.LastWriteTime)); } do { // If this isn't the first time through, sleep a little before trying again if (CopyTryCount > 0) { Thread.Sleep(1000); } CopyTryCount++; try { // Delete the destination file if it exists FileInfo DestInfo = new FileInfo(DestFileName); if (DestInfo.Exists) { DestInfo.IsReadOnly = false; DestInfo.Delete(); } Directory.CreateDirectory(Path.GetDirectoryName(DestFileName)); // Copy over the new file SourceFileInfo.CopyTo(DestFileName); // Make sure the destination file is writable DestInfo = new FileInfo(DestFileName); DestInfo.IsReadOnly = false; DestInfo.LastWriteTime = SourceFileInfo.LastWriteTime; // Sanity check if (DestInfo.Length != SourceFileInfo.Length) { throw new Exception("Error: File copy failed (source and destination are different sizes)"); } // Success! bFileCopiedSuccessfully = true; } catch( Exception Ex ) { Program.Warning("Failed to copy file to '" + DestFileName + "'"); Program.Warning(" Exception: " + Ex.Message + " (going to retry copy)"); } } while (!bFileCopiedSuccessfully && (CopyTryCount < MaxRetryCount)); if (!bFileCopiedSuccessfully) { string Msg = "Failed to copy file to " + DestFileName + " and there are no more retries!"; if (bCopyMustSucceed) { Program.Error(Msg); Program.ReturnCode = (int)ErrorCodes.Error_CopyFile; } else { Program.Warning(Msg); } } return bFileCopiedSuccessfully; }