Exemple #1
0
        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);
        }
        /**
         * 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.IsClient ? "Client" : ""), 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.IsClient ? "Client" : ""),
                                                           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)));
        }
		/**
		 * Handle spawning of the RPCUtility with parameters
		 */
		public static bool RunRPCUtilty( string RPCCommand, bool bIsSilent = false )
		{
			string CommandLine = "";
			string WorkingFolder = "";
			string DisplayCommandLine = "";
            string TempKeychain = "$HOME/Library/Keychains/UE4TempKeychain.keychain";
            string Certificate = "XcodeSupportFiles/" + MacSigningIdentityFilename;
			string LoginKeychain = "$HOME/Library/Keychains/login.keychain";
			ErrorCodes Error = ErrorCodes.Error_Unknown;

			switch (RPCCommand.ToLowerInvariant())
			{
			case "deletemacstagingfiles":
				Program.Log( " ... deleting staging files on the Mac" );
				DisplayCommandLine = "rm -rf Payload";
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "ensureprovisiondirexists":
				Program.Log(" ... creating provisioning profiles directory");

				DisplayCommandLine = String.Format("mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles");

				CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "\"";
				break;

			case "installprovision":
				// Note: The provision must have already been copied over to the Mac
				Program.Log(" ... installing .mobileprovision");

				DisplayCommandLine = String.Format("cp -f {0} ~/Library/MobileDevice/Provisioning\\ Profiles", MacMobileProvisionFilename);

				CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "\"";
				break;

			case "removeprovision":
				Program.Log(" ... removing .mobileprovision");
				DisplayCommandLine = String.Format("rm -f ~/Library/MobileDevice/Provisioning\\ Profiles/{0}", MacMobileProvisionFilename);
				CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "\"";
				break;

			case "setexec": 
				// Note: The executable must have already been copied over
				Program.Log(" ... setting executable bit");
				DisplayCommandLine = "chmod a+x \'" + RemoteExecutablePath + "\'";
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "prepackage":
				Program.Log(" ... running prepackage script remotely ");
				DisplayCommandLine = String.Format("sh prepackage.sh {0} " + Config.OSString + " {1} {2}", Program.GameName, Program.GameConfiguration, Program.Architecture);
				CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "\"";
				break;

			case "makeapp":
				Program.Log(" ... making application (codesign, etc...)");
				Program.Log("  Using signing identity '{0}'", Config.CodeSigningIdentity);
                DisplayCommandLine = "security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && " + CurrentBaseXCodeCommandLine;
                CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "/..\"";
				Error = ErrorCodes.Error_RemoteCertificatesNotFound;
				break;

			case "createkeychain":
				Program.Log(" ... creating temporary key chain with signing certificate");
				Program.Log("  Using signing identity '{0}'", Config.CodeSigningIdentity);
                DisplayCommandLine = "security create-keychain -p \"A\" \"" + TempKeychain + "\" && security list-keychains -s \"" + TempKeychain + "\" && security list-keychains && security set-keychain-settings -t 3600 -l  \"" + TempKeychain + "\" && security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && security import " + Certificate + " -k \"" + TempKeychain + "\" -P \"A\" -T /usr/bin/codesign -T /usr/bin/security -t agg && CERT_IDENTITY=$(security find-identity -v -p codesigning \"" + TempKeychain + "\" | head -1 | grep '\"' | sed -e 's/[^\"]*\"//' -e 's/\".*//') && security default-keychain -s \"" + TempKeychain + "\" && security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"A\" -D \"$CERT_IDENTITY\" -t private " + TempKeychain;
                CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "/..\"";
				break;

            case "deletekeychain":
				Program.Log(" ... remove temporary key chain");
				Program.Log("  Using signing identity '{0}'", Config.CodeSigningIdentity);
				DisplayCommandLine = "security list-keychains -s \"" + LoginKeychain + "\" && security delete-keychain \"" + TempKeychain + "\"";
				CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacXcodeStagingDir + "/..\"";
				break;

			case "validation":
				Program.Log( " ... validating distribution package" );
				DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/Validation " + RemoteAppDirectory;
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "deleteipa":
				Program.Log(" ... deleting IPA on Mac");
				DisplayCommandLine = "rm -f " + Config.IPAFilenameOnMac;
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "kill":
				Program.Log( " ... killing" );
				DisplayCommandLine = "killall " + Program.GameName;
				CommandLine = ". " + DisplayCommandLine;
				WorkingFolder = ".";
				break;

			case "strip":
				Program.Log( " ... stripping" );
				DisplayCommandLine = "/usr/bin/xcrun strip '" + RemoteExecutablePath + "'";
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "resign":
				Program.Log("... resigning");
				DisplayCommandLine = "bash -c '" + "chmod a+x ResignScript" + ";" + "./ResignScript" + "'";
				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

            case "zip":
				Program.Log( " ... zipping" );

				// NOTE: -y preserves symbolic links which is needed for iOS distro builds
				// -x excludes a file (excluding the dSYM keeps sizes smaller, and it shouldn't be in the IPA anyways)
				string dSYMName = "Payload/" + Program.GameName + Program.Architecture + ".app.dSYM";
				DisplayCommandLine = String.Format("zip -q -r -y -{0} -T {1} Payload iTunesArtwork -x {2}/ -x {2}/* " +
					"-x {2}/Contents/ -x {2}/Contents/* -x {2}/Contents/Resources/ -x {2}/Contents/Resources/* " +
					" -x {2}/Contents/Resources/DWARF/ -x {2}/Contents/Resources/DWARF/*",
					(int)Config.RecompressionSetting,
					Config.IPAFilenameOnMac,
					dSYMName);

				CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			case "gendsym":
				Program.Log( " ... generating DSYM" );

				string ExePath  = "Payload/" + Program.GameName + ".app/" + Program.GameName;
				string dSYMPath = Program.GameName + ".app.dSYM";
				DisplayCommandLine = String.Format("dsymutil -o {0} {1}", dSYMPath, ExePath);

				CommandLine = "\"" + MacStagingRootDir + "\"" + DisplayCommandLine;
				WorkingFolder = "\"" + MacStagingRootDir + "\"";
				break;

			default:
				Program.Error( "Unrecognized RPC command" );
				return ( false );
			}

			Program.Log( " ... working folder: " + WorkingFolder );
			Program.Log( " ... " + DisplayCommandLine );
			Program.Log(" ... full command: " +  MacName + " " + CommandLine);

			bool bSuccess = false;
			if( Config.bUseRPCUtil )
			{
				Program.Log( "Running RPC on " + MacName + " ... " );

				Process RPCUtil = new Process();
				RPCUtil.StartInfo.FileName = @"..\RPCUtility.exe";
				RPCUtil.StartInfo.UseShellExecute = false;
				RPCUtil.StartInfo.Arguments = MacName + " " + CommandLine;
				RPCUtil.StartInfo.RedirectStandardOutput = true;
				RPCUtil.StartInfo.RedirectStandardError = true;
				RPCUtil.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall);
				RPCUtil.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall);

				RPCUtil.Start();

				RPCUtil.BeginOutputReadLine();
				RPCUtil.BeginErrorReadLine();

				RPCUtil.WaitForExit();

				bSuccess = (RPCUtil.ExitCode == 0);
				if (bSuccess == false && !bIsSilent)
				{
					Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, RPCUtil.ExitCode);
					switch (RPCCommand.ToLowerInvariant())
					{
						case "installprovision":
							Program.Error("Ensure your access permissions for '~/Library/MobileDevice/Provisioning Profiles' are set correctly.");
							break;
						default:
							break;
					}
				}
			}
			else
			{
				Program.Log("Running SSH on " + MacName + " ... ");
				bSuccess = SSHCommandHelper.Command(MacName, DisplayCommandLine, WorkingFolder);
				if (bSuccess == false && !bIsSilent)
				{
					Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, Error);
					Program.ReturnCode = (int)Error;
				}
			}


			return bSuccess;
		}
Exemple #4
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");
            }

            // 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);
        }
        private void GenerateSigningRequestViaOpenSSL(string TargetCertRequestFileName, AsymmetricCipherKeyPair KeyPair)
        {
            // We expect openssl.exe to exist in the same directory as iPhonePackager
            string OpenSSLPath = Path.GetDirectoryName(Application.ExecutablePath) + @"\openssl.exe";

            if (!File.Exists(OpenSSLPath))
            {
                MessageBox.Show("A version of OpenSSL is required to generate certificate requests.  Please place OpenSSL.exe in Binaries\\DotNET\\IOS", Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            string EffectiveBuildPath = (Program.GameName.Length > 0) ? Config.BuildDirectory : Path.GetFullPath(".");

            // Create a temporary file to write the key pair out to (in a format that OpenSSL understands)
            string     KeyFileName = Path.GetTempFileName();
            TextWriter KeyWriter   = new StreamWriter(KeyFileName);

            PemWriter KeyWriterPEM = new PemWriter(KeyWriter);

            KeyWriterPEM.WriteObject(KeyPair);
            KeyWriter.Close();

            // Create a temporary file containing the configuration settings to drive OpenSSL
            string     ConfigFileName = Path.GetTempFileName();
            TextWriter ConfigFile     = new StreamWriter(ConfigFileName);

            ConfigFile.WriteLine("[ req ]");
            ConfigFile.WriteLine("distinguished_name     = req_distinguished_name");
            ConfigFile.WriteLine("prompt                 = no");

            ConfigFile.WriteLine("[ req_distinguished_name ]");
            ConfigFile.WriteLine("emailAddress           = {0}", EMailEditBox.Text);
            ConfigFile.WriteLine("commonName             = {0}", CommonNameEditBox.Text);
            ConfigFile.WriteLine("countryName            = {0}", System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName);

            ConfigFile.Close();

            // Invoke OpenSSL to generate the certificate request
            Program.Log("Running OpenSSL to generate certificate request...");

            string ResultsText;
            string Executable = OpenSSLPath;
            string Arguments  = String.Format("req -new -nodes -out \"{0}\" -key \"{1}\" -config \"{2}\"",
                                              TargetCertRequestFileName, KeyFileName, ConfigFileName);

            Utilities.RunExecutableAndWait(Executable, Arguments, out ResultsText);

            Program.Log(ResultsText);

            if (!File.Exists(TargetCertRequestFileName))
            {
                Program.Error("... Failed to generate certificate request");
            }
            else
            {
                Program.Log("... Successfully generated certificate request '{0}'", TargetCertRequestFileName);
            }

            // Clean up the temporary files we created
            File.Delete(KeyFileName);
            File.Delete(ConfigFileName);
        }
		/// <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}",
				};

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

			// 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"));
			}
			
			// Copy the mobile provision file over
			string CFBundleIdentifier = null;
			Info.GetString("CFBundleIdentifier", out CFBundleIdentifier);
			bool bNameMatch;
			string ProvisionWithPrefix = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch);
			if (!File.Exists(ProvisionWithPrefix))
			{
				ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".mobileprovision");
				if (!File.Exists(ProvisionWithPrefix))
				{
					ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory + "/NotForLicensees/", Program.GameName + ".mobileprovision");
					if (!File.Exists(ProvisionWithPrefix))
					{
						ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory, "UE4Game.mobileprovision");
						if (!File.Exists(ProvisionWithPrefix))
						{
							ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory + "/NotForLicensees/", "UE4Game.mobileprovision");
						}
					}
				}
			}
			string 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);
			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"));
			
			// 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
            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);

			// 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();
		}
Exemple #7
0
 public void Log(string Line)
 {
     Program.Log(Line);
 }
Exemple #8
0
        /**
         * Handle spawning of the RPCUtility with parameters
         */
        public static bool RunRPCUtilty(string RPCCommand)
        {
            string CommandLine        = "";
            string WorkingFolder      = "";
            string DisplayCommandLine = "";

            switch (RPCCommand.ToLowerInvariant())
            {
            case "deletemacstagingfiles":
                Program.Log(" ... deleting staging files on the Mac");
                DisplayCommandLine = "rm -rf Payload";
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "ensureprovisiondirexists":
                Program.Log(" ... creating provisioning profiles directory");

                DisplayCommandLine = String.Format("mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles");

                CommandLine   = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
                WorkingFolder = MacXcodeStagingDir;
                break;

            case "installprovision":
                // Note: The provision must have already been copied over to the Mac
                Program.Log(" ... installing .mobileprovision");

                DisplayCommandLine = String.Format("cp -f {0} ~/Library/MobileDevice/Provisioning\\ Profiles", MacMobileProvisionFilename);

                CommandLine   = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
                WorkingFolder = MacXcodeStagingDir;
                break;

            case "removeprovision":
                Program.Log(" ... removing .mobileprovision");
                DisplayCommandLine = String.Format("rm -f ~/Library/MobileDevice/Provisioning\\ Profiles/{0}", MacMobileProvisionFilename);
                CommandLine        = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacXcodeStagingDir;
                break;

            case "setexec":
                // Note: The executable must have already been copied over
                Program.Log(" ... setting executable bit");
                DisplayCommandLine = "chmod a+x \'" + RemoteExecutablePath + "\'";
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "prepackage":
                Program.Log(" ... running prepackage script remotely ");
                DisplayCommandLine = String.Format("sh prepackage.sh {0} IOS {1} {2}", Program.GameName, Program.GameConfiguration, Program.Architecture);
                CommandLine        = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacXcodeStagingDir;
                break;

            case "makeapp":
                Program.Log(" ... making application (codesign, etc...)");
                Program.Log("  Using signing identity '{0}'", Config.CodeSigningIdentity);
                DisplayCommandLine = CurrentBaseXCodeCommandLine;
                CommandLine        = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine;
                WorkingFolder      = "\"" + MacXcodeStagingDir + "/..\"";
                break;

            case "validation":
                Program.Log(" ... validating distribution package");
                DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/Validation " + RemoteAppDirectory;
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "deleteipa":
                Program.Log(" ... deleting IPA on Mac");
                DisplayCommandLine = "rm -f " + Config.IPAFilenameOnMac;
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "kill":
                Program.Log(" ... killing");
                DisplayCommandLine = "killall " + Program.GameName;
                CommandLine        = ". " + DisplayCommandLine;
                WorkingFolder      = ".";
                break;

            case "strip":
                Program.Log(" ... stripping");
                DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/strip '" + RemoteExecutablePath + "'";
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "resign":
                Program.Log("... resigning");
                DisplayCommandLine = "bash -c '" + "chmod a+x ResignScript" + ";" + "./ResignScript" + "'";
                CommandLine        = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder      = MacStagingRootDir;
                break;

            case "zip":
                Program.Log(" ... zipping");

                // NOTE: -y preserves symbolic links which is needed for iOS distro builds
                // -x excludes a file (excluding the dSYM keeps sizes smaller, and it shouldn't be in the IPA anyways)
                string dSYMName = "Payload/" + Program.GameName + Program.Architecture + ".app.dSYM";
                DisplayCommandLine = String.Format("zip -q -r -y -{0} -T {1} Payload iTunesArtwork -x {2}/ -x {2}/* " +
                                                   "-x {2}/Contents/ -x {2}/Contents/* -x {2}/Contents/Resources/ -x {2}/Contents/Resources/* " +
                                                   " -x {2}/Contents/Resources/DWARF/ -x {2}/Contents/Resources/DWARF/*",
                                                   (int)Config.RecompressionSetting,
                                                   Config.IPAFilenameOnMac,
                                                   dSYMName);

                CommandLine   = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine;
                WorkingFolder = MacStagingRootDir;
                break;

            case "gendsym":
                Program.Log(" ... generating DSYM");

                string ExePath  = "Payload/" + Program.GameName + ".app/" + Program.GameName;
                string dSYMPath = Program.GameName + ".app.dSYM";
                DisplayCommandLine = String.Format("dsymutil -o {0} {1}", dSYMPath, ExePath);

                CommandLine   = "\"" + MacStagingRootDir + "\"" + DisplayCommandLine;
                WorkingFolder = MacStagingRootDir;
                break;

            default:
                Program.Error("Unrecognized RPC command");
                return(false);
            }

            Program.Log(" ... working folder: " + WorkingFolder);
            Program.Log(" ... " + DisplayCommandLine);
            Program.Log(" ... full command: " + MacName + " " + CommandLine);

            bool bSuccess = false;

            if (Config.bUseRPCUtil)
            {
                Program.Log("Running RPC on " + MacName + " ... ");

                Process RPCUtil = new Process();
                RPCUtil.StartInfo.FileName               = @"..\RPCUtility.exe";
                RPCUtil.StartInfo.UseShellExecute        = false;
                RPCUtil.StartInfo.Arguments              = MacName + " " + CommandLine;
                RPCUtil.StartInfo.RedirectStandardOutput = true;
                RPCUtil.StartInfo.RedirectStandardError  = true;
                RPCUtil.OutputDataReceived              += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall);
                RPCUtil.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall);

                RPCUtil.Start();

                RPCUtil.BeginOutputReadLine();
                RPCUtil.BeginErrorReadLine();

                RPCUtil.WaitForExit();

                bSuccess = (RPCUtil.ExitCode == 0);
                if (bSuccess == false)
                {
                    Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, RPCUtil.ExitCode);
                    switch (RPCCommand.ToLowerInvariant())
                    {
                    case "installprovision":
                        Program.Error("Ensure your access permissions for '~/Library/MobileDevice/Provisioning Profiles' are set correctly.");
                        break;

                    default:
                        break;
                    }
                }
            }
            else
            {
                Program.Log("Running SSH on " + MacName + " ... ");
                bSuccess = SSHCommandHelper.Command(MacName, DisplayCommandLine, WorkingFolder);
            }


            return(bSuccess);
        }
		/**
		 * Copy a single file (deleting the existing file at the destination if it exists)
		 */
		static public void CopyRequiredFile(string SourcePath, string DestPath)
		{
			Program.Log(" ... '" + SourcePath + "' -> '" + DestPath + "'");
			CopyFile(new FileInfo(SourcePath), DestPath, true, DefaultRetryCount);
		}
		/**
		 * 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;
		}
Exemple #11
0
        /**
         * Using the stub IPA previously compiled on the Mac, create a new IPA with assets
         */
        void ResignIPA(FileOperations.FileSystemAdapter FileSystem)
        {
            try
            {
                DateTime StartTime = DateTime.Now;

                // Configure the custom code signer
                CustomCodeSigner SigningContext = new CustomCodeSigner();
                SigningContext.FileSystem = FileSystem;

                // Custom mobile provision?
                if (RBSpecifyMobileProvision.Checked)
                {
                    SigningContext.CustomMobileProvision = File.ReadAllBytes(MobileProvisionEdit.Text);
                }

                // Custom cert?
                if (RBUseExplicitCert.Checked)
                {
                    string CertificatePassword = "";
                    try
                    {
                        SigningContext.CustomSigningCert = new X509Certificate2(CertificateEdit.Text, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
                        //Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
                    }
                    catch (System.Security.Cryptography.CryptographicException ex)
                    {
                        // Try once with a password
                        if (PasswordDialog.RequestPassword(out CertificatePassword))
                        {
                            SigningContext.CustomSigningCert = new X509Certificate2(CertificateEdit.Text, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
                            //Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
                        }
                        else
                        {
                            // User cancelled dialog, rethrow
                            throw ex;
                        }
                    }
                }

                // Totally replace Info.plist? (just editing it is handled later)
                if (RBReplaceInfoPList.Checked)
                {
                    SigningContext.CustomInfoPList = ImportedPListData;
                }

                // Start the resign process
                SigningContext.PrepareForSigning();

                // Partially modify Info.plist?
                if (RBModifyInfoPList.Checked)
                {
                    SigningContext.Info.SetString("CFBundleDisplayName", DisplayNameEdit.Text);
                    SigningContext.Info.SetString("CFBundleIdentifier", BundleIDEdit.Text);
                }

                // Re-sign the executable
                SigningContext.PerformSigning();

                // Save the IPA
                Program.Log("Saving IPA ...");
                FileSystem.Close();

                TimeSpan ElapsedTime = DateTime.Now - StartTime;
                Program.Log(String.Format("Finished re-signing IPA in took {0:0.00} s", ElapsedTime.TotalSeconds));

                MessageBox.Show("Re-signing succeeded!", Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                ShowError("re-signing IPA", ex);
            }
        }
        public static void CacheMobileProvisions()
        {
            Program.Log("Caching provisions");
            string LocalProvisionFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library/MobileDevice/Provisioning Profiles");

            if (!Directory.Exists(LocalProvisionFolder))
            {
                Program.Log("Local Provision Folder {0} doesn't exist, creating..", LocalProvisionFolder);
                Directory.CreateDirectory(LocalProvisionFolder);
            }

            // copy all of the provisions from the game directory to the library
            if (!String.IsNullOrEmpty(Config.ProjectFile))
            {
                var ProjectFileBuildIOSPath = Path.GetDirectoryName(Config.ProjectFile) + "/Build/" + Config.OSString + "/";
                Program.Log("Finding provisions in {0}", ProjectFileBuildIOSPath);
                if (Directory.Exists(ProjectFileBuildIOSPath))
                {
                    foreach (string Provision in Directory.EnumerateFiles(ProjectFileBuildIOSPath, "*.mobileprovision", SearchOption.AllDirectories))
                    {
                        Log.TraceInformation(Provision);
                        string TargetFile = Config.ProvisionDirectory + Path.GetFileName(Provision);
                        if (!File.Exists(TargetFile) || File.GetLastWriteTime(TargetFile) < File.GetLastWriteTime(Provision))
                        {
                            FileInfo DestFileInfo;
                            if (File.Exists(TargetFile))
                            {
                                DestFileInfo            = new FileInfo(TargetFile);
                                DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
                            }
                            Program.Log("Copying {0} -> {1}", Provision, TargetFile);
                            File.Copy(Provision, TargetFile, true);
                            DestFileInfo            = new FileInfo(TargetFile);
                            DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
                            if (!File.Exists(TargetFile))
                            {
                                Program.Log("ERROR: Failed to copy {0} -> {1}", Provision, TargetFile);
                            }
                        }
                    }
                }
            }

            // copy all of the provisions from the engine directory to the library
            {
                string ProvisionDirectory = Environment.GetEnvironmentVariable("ProvisionDirectory") ?? Config.EngineBuildDirectory;
                Program.Log("Finding provisions in {0}", ProvisionDirectory);
                if (Directory.Exists(ProvisionDirectory))
                {
                    foreach (string Provision in Directory.EnumerateFiles(ProvisionDirectory, "*.mobileprovision", SearchOption.AllDirectories))
                    {
                        Log.TraceInformation(Provision);
                        string TargetFile = Config.ProvisionDirectory + Path.GetFileName(Provision);
                        if (!File.Exists(TargetFile) || File.GetLastWriteTime(TargetFile) < File.GetLastWriteTime(Provision))
                        {
                            FileInfo DestFileInfo;
                            if (File.Exists(TargetFile))
                            {
                                DestFileInfo            = new FileInfo(TargetFile);
                                DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
                            }
                            Program.Log("Copying {0} -> {1}", Provision, TargetFile);
                            File.Copy(Provision, Config.ProvisionDirectory + Path.GetFileName(Provision), true);
                            DestFileInfo            = new FileInfo(TargetFile);
                            DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
                            if (!File.Exists(TargetFile))
                            {
                                Program.Log("ERROR: Failed to copy {0} -> {1}", Provision, TargetFile);
                            }
                        }
                    }
                }
            }
        }
        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);

            // first sort all profiles so we look at newer ones first.
            IEnumerable <string> ProfileKeys = ProvisionLibrary.Select(KV => KV.Key)
                                               .OrderByDescending(K => ProvisionLibrary[K].CreationDate)
                                               .ToArray();

            // 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 (string Key in ProfileKeys)
                {
                    string          DebugName     = Path.GetFileName(Key);
                    MobileProvision TestProvision = ProvisionLibrary[Key];

                    // 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(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);
        }
 static public void ExecuteRemoteCommand(string RemoteCommand)
 {
     Program.Log("Running RPC on " + MacName + " ... ");
     RunRPCUtilty(RemoteCommand);
 }
        /// <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}",
            };

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

            // 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"));
            }


            // Copy the no sign resource rules file over
            if (!Config.bForDistribution)
            {
                FileOperations.CopyRequiredFile(@"..\..\..\Build\IOS\XcodeSupportFiles\CustomResourceRules.plist", Path.Combine(Config.PCStagingRootDir, "CustomResourceRules.plist"));
            }

            // Copy the mobile provision file over
            string ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".mobileprovision");

            if (!File.Exists(ProvisionWithPrefix))
            {
                ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.BuildDirectory + "/NotForLicensees/", Program.GameName + ".mobileprovision");
                if (!File.Exists(ProvisionWithPrefix))
                {
                    ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory, "UE4Game.mobileprovision");
                    if (!File.Exists(ProvisionWithPrefix))
                    {
                        ProvisionWithPrefix = FileOperations.FindPrefixedFile(Config.EngineBuildDirectory + "/NotForLicensees/", "UE4Game.mobileprovision");
                    }
                }
            }
            string 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);

            FileOperations.CopyRequiredFile(Config.RootRelativePath + @"Engine\Intermediate\IOS\UE4.xcodeproj\project.pbxproj", Path.Combine(Config.PCXcodeStagingDir, @"project.pbxproj.datecheck"));

            // needs Mac line endings so it can be executed
            string SrcPath  = @"..\..\..\Build\IOS\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();
        }