// CFBundleDisplayName
		
		// CFBundleName, fewer than 16 characters long

		// CFBundleIdentifier
		// http://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070
		// Alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.) characters.
		// The string should also be in reverse-DNS format.

		void ReloadPList()
		{
			string Filename = Config.GetPlistOverrideFilename();
			string SourcePList = ReadOrCreate(Filename);
			Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList);
			
			string BundleID;
			string BundleName;
			string BundleDisplayName;
			if (!Helper.GetString("CFBundleIdentifier", out BundleID))
			{
				BundleID = "com.YourCompany.GameNameNoSpaces";
				Helper.SetString("CFBundleIdentifier", BundleID);
			}

			if (!Helper.GetString("CFBundleName", out BundleName))
			{
				BundleName = "MyUDKGame";
				Helper.SetString("CFBundleName", BundleName);
			}

			if (!Helper.GetString("CFBundleDisplayName", out BundleDisplayName))
			{
				BundleDisplayName = "UDK Game";
				Helper.SetString("CFBundleDisplayName", BundleDisplayName);
			}

			BundleIdentifierEdit.Text = BundleID;
			BundleNameEdit.Text = BundleName;
			BundleDisplayNameEdit.Text = BundleDisplayName;
		}
        /// <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];
            }

            // 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");
        }
Exemple #3
0
        /**
         * Using the stub IPA previously compiled on the Mac, create a new IPA with assets
         */
        public static 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.{0}", Config.bVerbose ? "" : "  Only large files will be listed next, but other files are also being packaged.");
            FileSystem.Close();

            TimeSpan ZipLength = DateTime.Now - StartTime;

            FileInfo FinalZipInfo = new FileInfo(Config.GetIPAPath(".ipa"));

            Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s)",
                Zip.Name,
                ZipLength.TotalSeconds,
                FinalZipInfo.Length / (1024.0f * 1024.0f)));
        }
        /// <summary>
        /// Prepares this signer to sign an application
        ///   Modifies the following files:
        ///	 embedded.mobileprovision
        /// </summary>
        public void PrepareForSigning()
        {
            // Load Info.plist, which guides nearly everything else
            Info = LoadInfoPList();

            // Get the name of the bundle
            string CFBundleIdentifier;
            if (!Info.GetString("CFBundleIdentifier", out CFBundleIdentifier))
            {
                throw new InvalidDataException("Info.plist must contain the key CFBundleIdentifier");
            }

            // Load the mobile provision, which provides entitlements and a partial cert which can be used to find an installed certificate
            LoadMobileProvision(CFBundleIdentifier);
            if (Provision == null)
            {
                return;
            }

            // Install the Apple trust chain certs (required to do a CMS signature with full chain embedded)
            List<string> TrustChainCertFilenames = new List<string>();

            string CertPath = Path.GetFullPath(Config.EngineBuildDirectory);
            TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleWorldwideDeveloperRelationsCA.pem"));
            TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleRootCA.pem"));

            InstallCertificates(TrustChainCertFilenames);

            // Find and load the signing cert
            SigningCert = LoadSigningCertificate();
            if (SigningCert == null)
            {
                // Failed to find a cert already installed or to install, cannot proceed any futher
                Program.Error("... Failed to find a certificate that matches the mobile provision to be used for code signing");
                Program.ReturnCode = (int)ErrorCodes.Error_CertificateNotFound;
                throw new InvalidDataException("Certificate not found!");
            }
            else
            {
                Program.Log("... Found matching certificate '{0}' (valid from {1} to {2})", SigningCert.FriendlyName, SigningCert.GetEffectiveDateString(), SigningCert.GetExpirationDateString());
            }
        }
        /// <summary>
        /// Makes sure the required files for code signing exist and can be found
        /// </summary>
        public static bool FindRequiredFiles(out MobileProvision Provision, out X509Certificate2 Cert, out bool bHasOverridesFile, out bool bNameMatch, bool bNameCheck = true)
        {
            Provision = null;
            Cert = null;
            bHasOverridesFile = File.Exists(Config.GetPlistOverrideFilename());
            bNameMatch = false;

            string CFBundleIdentifier = Config.OverrideBundleName;

            if (string.IsNullOrEmpty(CFBundleIdentifier))
            {
                // Load Info.plist, which guides nearly everything else
                string plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist";
                if (!string.IsNullOrEmpty(Config.ProjectFile))
                {
                    plistFile = Path.GetDirectoryName(Config.ProjectFile) + "/Intermediate/IOS/" + Path.GetFileNameWithoutExtension(Config.ProjectFile) + "-Info.plist";

                    if (!File.Exists(plistFile))
                    {
                        plistFile = Config.IntermediateDirectory + "/UE4Game-Info.plist";
                        if (!File.Exists(plistFile))
                        {
                            plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist";
                        }
                    }
                }
                Utilities.PListHelper Info = null;
                try
                {
                    string RawInfoPList = File.ReadAllText(plistFile, Encoding.UTF8);
                    Info = new Utilities.PListHelper(RawInfoPList); ;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                if (Info == null)
                {
                    return false;
                }

                // Get the name of the bundle
                Info.GetString("CFBundleIdentifier", out CFBundleIdentifier);
                if (CFBundleIdentifier == null)
                {
                    return false;
                }
                else
                {
                    CFBundleIdentifier = CFBundleIdentifier.Replace("${BUNDLE_IDENTIFIER}", Path.GetFileNameWithoutExtension(Config.ProjectFile));
                }
            }

            // Check for a mobile provision
            try
            {
                string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch);
                Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
            }
            catch (Exception)
            {
            }

            // if we have a null provision see if we can find a compatible provision without checking for a certificate
            if (Provision == null)
            {
                try
                {
                    string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false);
                    Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
                }
                catch (Exception)
                {
                }

                // if we have a null provision see if we can find a valid provision without checking for name match
                if (Provision == null && !bNameCheck)
                {
                    try
                    {
                        string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false, false);
                        Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
                    }
                    catch (Exception)
                    {
                    }
                }
            }

            // Check for a suitable signature to match the mobile provision
            if (Provision != null)
            {
                Cert = CodeSignatureBuilder.FindCertificate(Provision);
            }

            return true;
        }
		/// <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");
		}
Exemple #7
0
        /// <summary>
        /// Makes sure the required files for code signing exist and can be found
        /// </summary>
        public static bool FindRequiredFiles(out MobileProvision Provision, out X509Certificate2 Cert, out bool bHasOverridesFile, out bool bNameMatch, bool bNameCheck = true)
        {
            Provision         = null;
            Cert              = null;
            bHasOverridesFile = File.Exists(Config.GetPlistOverrideFilename());
            bNameMatch        = false;

            string CFBundleIdentifier = Config.OverrideBundleName;

            if (string.IsNullOrEmpty(CFBundleIdentifier))
            {
                // Load Info.plist, which guides nearly everything else
                string plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist";
                if (!string.IsNullOrEmpty(Config.ProjectFile))
                {
                    plistFile = Path.GetDirectoryName(Config.ProjectFile) + "/Intermediate/" + Config.OSString + "/" + Path.GetFileNameWithoutExtension(Config.ProjectFile) + "-Info.plist";

                    if (!File.Exists(plistFile))
                    {
                        plistFile = Config.IntermediateDirectory + "/UE4Game-Info.plist";
                        if (!File.Exists(plistFile))
                        {
                            plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist";
                        }
                    }
                }
                Utilities.PListHelper Info = null;
                try
                {
                    string RawInfoPList = File.ReadAllText(plistFile, Encoding.UTF8);
                    Info = new Utilities.PListHelper(RawInfoPList);;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                if (Info == null)
                {
                    return(false);
                }

                // Get the name of the bundle
                Info.GetString("CFBundleIdentifier", out CFBundleIdentifier);
                if (CFBundleIdentifier == null)
                {
                    return(false);
                }
                else
                {
                    CFBundleIdentifier = CFBundleIdentifier.Replace("${BUNDLE_IDENTIFIER}", Path.GetFileNameWithoutExtension(Config.ProjectFile));
                }
            }

            // Check for a mobile provision
            try
            {
                string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch);
                Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
            }
            catch (Exception)
            {
            }

            // if we have a null provision see if we can find a compatible provision without checking for a certificate
            if (Provision == null)
            {
                try
                {
                    string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false);
                    Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
                }
                catch (Exception)
                {
                }

                // if we have a null provision see if we can find a valid provision without checking for name match
                if (Provision == null && !bNameCheck)
                {
                    try
                    {
                        string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false, false);
                        Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename);
                    }
                    catch (Exception)
                    {
                    }
                }
            }

            // Check for a suitable signature to match the mobile provision
            if (Provision != null)
            {
                Cert = CodeSignatureBuilder.FindCertificate(Provision);
            }

            return(true);
        }
        string PickIPAAndFetchBundleIdentifier()
        {
            string PickedFilename;
            if (ToolsHub.ShowOpenFileDialog(ToolsHub.IpaFilter, "Choose an IPA to provide the bundle identifier", "ipa", "", ref ToolsHub.ChoosingIpaDirectory, out PickedFilename))
            {
                FileOperations.FileSystemAdapter IPA = new FileOperations.ReadOnlyZipFileSystem(PickedFilename);

                if (IPA != null)
                {
                    byte[] InfoData = IPA.ReadAllBytes("Info.plist");
                    Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(InfoData));

                    string BundleID;
                    if (Info.GetString("CFBundleIdentifier", out BundleID))
                    {
                        return BundleID;
                    }
                }
            }

            return null;
        }
        /// <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 = "";
            }
        }
Exemple #10
0
        /**
         * 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, "*.*", Config.bIterate ? SearchOption.TopDirectoryOnly : 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/{2}",
                                                           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)));
        }
Exemple #11
0
        protected virtual byte[] CreateCodeResourcesDirectory(string CFBundleExecutable)
        {
            // Verify that there is a rules plist
            string CFBundleResourceSpecification;

            if (!Info.GetString("CFBundleResourceSpecification", out CFBundleResourceSpecification))
            {
                throw new InvalidDataException("Info.plist must contain the key CFBundleResourceSpecification");
            }

            // Create a rules dict that includes (by wildcard) everything but Info.plist and the rules file
            Dictionary <string, object> Rules = new Dictionary <string, object>();

            Rules.Add(".*", true);
            Rules.Add("^Info.plist", CreateOmittedResource(10));
            Rules.Add(CFBundleResourceSpecification, CreateOmittedResource(100));

            // Write the rules file out
            {
                Utilities.PListHelper RulesPList = new Utilities.PListHelper();
                RulesPList.AddKeyValuePair("rules", Rules);
                string PListString = RulesPList.SaveToString();
                FileSystem.WriteAllBytes(CFBundleResourceSpecification, Encoding.UTF8.GetBytes(PListString));
            }

            // Create the full list of files to exclude (some files get excluded by 'magic' even though they aren't listed as special by rules)
            Dictionary <string, object> TrueExclusionList = new Dictionary <string, object>();

            TrueExclusionList.Add("Info.plist", null);
            TrueExclusionList.Add(CFBundleResourceSpecification, null);
            TrueExclusionList.Add(CFBundleExecutable, null);
            TrueExclusionList.Add("CodeResources", null);
            TrueExclusionList.Add("_CodeSignature/CodeResources", null);

            // Hash each file
            IEnumerable <string>      FileList     = FileSystem.GetAllPayloadFiles();
            SHA1CryptoServiceProvider HashProvider = new SHA1CryptoServiceProvider();

            Utilities.PListHelper HashedFileEntries = new Utilities.PListHelper();
            foreach (string Filename in FileList)
            {
                if (!TrueExclusionList.ContainsKey(Filename))
                {
                    byte[] FileData = FileSystem.ReadAllBytes(Filename);
                    byte[] HashData = HashProvider.ComputeHash(FileData);

                    HashedFileEntries.AddKeyValuePair(Filename, HashData);
                }
            }

            // Create the CodeResources file that will contain the hashes
            Utilities.PListHelper CodeResources = new Utilities.PListHelper();
            CodeResources.AddKeyValuePair("files", HashedFileEntries);
            CodeResources.AddKeyValuePair("rules", Rules);

            // Write the CodeResources file out
            string CodeResourcesAsString = CodeResources.SaveToString();

            byte[] CodeResourcesAsBytes = Encoding.UTF8.GetBytes(CodeResourcesAsString);
            FileSystem.WriteAllBytes("_CodeSignature/CodeResources", CodeResourcesAsBytes);

            return(CodeResourcesAsBytes);
        }
Exemple #12
0
        /// <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");
            }

            // 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 entitlements blob
                string           TeamIdentifier        = Provision.ApplicationIdentifierPrefix;
                string           EntitlementsText      = BuildEntitlementString(CFBundleIdentifier, out TeamIdentifier);
                EntitlementsBlob FinalEntitlementsBlob = EntitlementsBlob.Create(EntitlementsText);

                // Create the code directory blob
                uint Version = CodeDirectoryBlob.cVersion2;
                if (CodeSigningBlobLC != null)
                {
                    CodeDirectoryBlob OldCodeDir = CodeSigningBlobLC.Payload.GetBlobByMagic(AbstractBlob.CSMAGIC_CODEDIRECTORY) as CodeDirectoryBlob;
                    Version = OldCodeDir.Version;
                }
                CodeDirectoryBlob FinalCodeDirectoryBlob = CodeDirectoryBlob.Create(CFBundleIdentifier, TeamIdentifier, SignedFileLength, Version);

                // 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)
                {
                    RequirementBlob.ExpressionOp OldExpression = null;
                    if (CodeSigningBlobLC != null)
                    {
                        RequirementsBlob OldRequirements = CodeSigningBlobLC.Payload.GetBlobByMagic(AbstractBlob.CSMAGIC_REQUIREMENTS_TABLE) as RequirementsBlob;
                        RequirementBlob  OldRequire      = OldRequirements.GetBlobByKey(0x00003) as RequirementBlob;
                        OldExpression = OldRequire.Expression;
                    }
                    FinalRequirementsBlob = RequirementsBlob.CreateEmpty();
                    FinalRequirementsBlob.Add(0x00003, RequirementBlob.CreateFromCertificate(SigningCert, CFBundleIdentifier, OldExpression));
                }

                // 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;

                // we no longer update the offsets in the load command as we keep the blob size the same
//				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");
                }

                if (FinalPayload.Length > CodeSigningBlobLC.BlobFileSize)
                {
                    throw new InvalidDataException("CMS signature blob size is too large for the allocated size, unable to create useful code signing data");
                }

                byte[] ZeroFill = new byte[CodeSigningBlobLC.BlobFileSize - FinalPayload.Length];

                OutputExeContext.PushPositionAndJump(BlobStartPosition);
                OutputExeContext.Write(FinalPayload);
                OutputExeContext.Write(ZeroFill);
                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);
        }
Exemple #13
0
        /// <summary>
        /// Copy the files always needed (even in a stub IPA)
        /// </summary>
        static public void CopyFilesNeededForMakeApp()
        {
            // Copy Info.plist over (modifiying it as needed)
            string SourcePListFilename = Utilities.GetPrecompileSourcePListFilename();

            Utilities.PListHelper Info = Utilities.PListHelper.CreateFromFile(SourcePListFilename);

            // Edit the plist
            CookTime.UpdateVersion(Info);

            // Write out the <GameName>-Info.plist file to the xcode staging directory
            string TargetPListFilename = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + "-Info.plist");

            Directory.CreateDirectory(Path.GetDirectoryName(TargetPListFilename));
            string OutString = Info.SaveToString();

            OutString = OutString.Replace("${EXECUTABLE_NAME}", Program.GameName);
            OutString = OutString.Replace("${BUNDLE_IDENTIFIER}", Program.GameName.Replace("_", ""));

            // this is a temp way to inject the iphone 6 images without needing to upgrade everyone's plist
            // eventually we want to generate this based on what the user has set in the project settings
            string[] IPhoneConfigs =
            {
                "Default-IPhone6",               "Landscape", "{375, 667}",
                "Default-IPhone6",               "Portrait",  "{375, 667}",
                "Default-IPhone6Plus-Landscape", "Landscape", "{414, 736}",
                "Default-IPhone6Plus-Portrait",  "Portrait",  "{414, 736}",
                "Default",                       "Landscape", "{320, 480}",
                "Default",                       "Portrait",  "{320, 480}",
                "Default-568h",                  "Landscape", "{320, 568}",
                "Default-568h",                  "Portrait",  "{320, 568}",
                "Default-IPhoneXS-Landscape",    "Landscape", "{375, 812}",
                "Default-IPhoneXS-Portrait",     "Portrait",  "{375, 812}",
            };

            StringBuilder NewLaunchImagesString = new StringBuilder("<key>UILaunchImages~iphone</key>\n\t\t<array>\n");

            for (int ConfigIndex = 0; ConfigIndex < IPhoneConfigs.Length; ConfigIndex += 3)
            {
                NewLaunchImagesString.Append("\t\t\t<dict>\n");
                NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageMinimumOSVersion</key>\n");
                NewLaunchImagesString.Append("\t\t\t\t<string>8.0</string>\n");
                NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageName</key>\n");
                NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 0]);
                NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageOrientation</key>\n");
                NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 1]);
                NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageSize</key>\n");
                NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 2]);
                NewLaunchImagesString.Append("\t\t\t</dict>\n");
            }

            // close it out
            NewLaunchImagesString.Append("\t\t\t</array>\n\t\t<key>UILaunchImages~ipad</key>");
            OutString = OutString.Replace("<key>UILaunchImages~ipad</key>", NewLaunchImagesString.ToString());

            byte[] RawInfoPList = Encoding.UTF8.GetBytes(OutString);
            File.WriteAllBytes(TargetPListFilename, RawInfoPList);

            Program.Log("Updating .plist: {0} --> {1}", SourcePListFilename, TargetPListFilename);

            string FinalMobileProvisionFilename = null;

            CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline();
            if (!Config.bAutomaticSigning)
            {
                // Copy the mobile provision file over
                string CFBundleIdentifier = null;
                Info.GetString("CFBundleIdentifier", out CFBundleIdentifier);

                string ProvisionWithPrefix;
                if (!FindMobileProvision(CFBundleIdentifier, out ProvisionWithPrefix))
                {
                    Program.Error("Unable to find mobileprovision");
                    return;
                }
                FinalMobileProvisionFilename = Path.Combine(Config.PCXcodeStagingDir, MacMobileProvisionFilename);
                FileOperations.CopyRequiredFile(ProvisionWithPrefix, FinalMobileProvisionFilename);

                // make sure this .mobileprovision file is newer than any other .mobileprovision file on the Mac (this file gets multiple games named the same file,
                // so the time stamp checking can fail when moving between games, a la the buildmachines!)
                File.SetLastWriteTime(FinalMobileProvisionFilename, DateTime.UtcNow);

                // copy the signing certificate over
                // export the signing certificate to a file
                MobileProvision Provision   = MobileProvisionParser.ParseFile(ProvisionWithPrefix);
                var             Certificate = CodeSignatureBuilder.FindCertificate(Provision);
                byte[]          Data        = Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "A");
                File.WriteAllBytes(Path.Combine(Config.PCXcodeStagingDir, MacSigningIdentityFilename), Data);
                Config.CodeSigningIdentity = Certificate.FriendlyName;                 // since the pipeline will use a temporary keychain that will contain only this certificate, this should be the only identity that will work

                if (Provision != null)
                {
                    Config.bForDistribution = !Provision.bDebug;
                }
                // regenerate command with new found identity
                CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline();

                // get the UUID
                string AllText = File.ReadAllText(FinalMobileProvisionFilename);
                string UUID    = "";
                int    idx     = AllText.IndexOf("<key>UUID</key>");
                if (idx > 0)
                {
                    idx = AllText.IndexOf("<string>", idx);
                    if (idx > 0)
                    {
                        idx += "<string>".Length;
                        UUID = AllText.Substring(idx, AllText.IndexOf("</string>", idx) - idx);
                    }
                }

                CurrentBaseXCodeCommandLine += String.Format(" PROVISIONING_PROFILE=" + UUID);
            }
            else
            {
                CurrentBaseXCodeCommandLine += " DEVELOPMENT_TEAM=" + Config.TeamID + " CODE_SIGN_STYLE=\"Automatic\" -allowProvisioningUpdates";
            }

            // look for an entitlements file (optional)
            string SourceEntitlements = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".entitlements");

            // set where to make the entitlements file (
            string TargetEntitlements = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + ".entitlements");

            if (File.Exists(SourceEntitlements))
            {
                FileOperations.CopyRequiredFile(SourceEntitlements, TargetEntitlements);
            }
            else
            {
                // we need to have something so Xcode will compile, so we just set the get-task-allow, since we know the value,
                // which is based on distribution or not (true means debuggable)
                File.WriteAllText(TargetEntitlements, string.Format("<plist><dict><key>get-task-allow</key><{0}/></dict></plist>",
                                                                    Config.bForDistribution ? "false" : "true"));
            }

            string ProjectFile = Config.RootRelativePath + @"Engine\Intermediate\ProjectFiles\UE4.xcodeproj\project.pbxproj";

            if (Program.GameName != "UE4Game")
            {
                ProjectFile = Path.GetDirectoryName(Config.IntermediateDirectory) + @"\ProjectFiles\" + Program.GameName + @".xcodeproj\project.pbxproj";
            }
            FileOperations.CopyRequiredFile(ProjectFile, Path.Combine(Config.PCXcodeStagingDir, @"project.pbxproj.datecheck"));

            // needs Mac line endings so it can be executed
            string SrcPath  = @"..\..\..\Build\" + Config.OSString + @"\XcodeSupportFiles\prepackage.sh";
            string DestPath = Path.Combine(Config.PCXcodeStagingDir, @"prepackage.sh");

            Program.Log(" ... '" + SrcPath + "' -> '" + DestPath + "'");
            string SHContents = File.ReadAllText(SrcPath);

            SHContents = SHContents.Replace("\r\n", "\n");
            File.WriteAllText(DestPath, SHContents);

            CookTime.CopySignedFiles();
        }