Пример #1
0
        /// <summary>
        /// Creates an entitlements blob string from the entitlements structure in the mobile provision, merging in an on disk file if it is present.
        /// </summary>
        private string BuildEntitlementString(string CFBundleIdentifier)
        {
            // Load the base entitlements string from the mobile provision
            string ProvisionEntitlements = Provision.GetEntitlementsString(CFBundleIdentifier);

            // See if there is an override entitlements file on disk
            string UserOverridesEntitlementsFilename = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".entitlements");

            if (File.Exists(UserOverridesEntitlementsFilename))
            {
                // Merge in the entitlements from the on disk file as overrides
                Program.Log("Merging override entitlements from {0} into provision specified entitlements", Path.GetFileName(UserOverridesEntitlementsFilename));

                Utilities.PListHelper Merger = new Utilities.PListHelper(ProvisionEntitlements);
                string Overrides             = File.ReadAllText(UserOverridesEntitlementsFilename, Encoding.UTF8);
                Merger.MergePlistIn(Overrides);

                return(Merger.SaveToString());
            }
            else
            {
                // The ones from the mobile provision need no overrides
                return(ProvisionEntitlements);
            }
        }
Пример #2
0
        protected virtual byte[] CreateCodeResourcesDirectory(string CFBundleExecutable)
        {
            // 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));

            // 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(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);
        }
Пример #3
0
        /// <summary>
        /// Extracts the dict values for the Entitlements key and creates a new full .plist file
        /// from them (with outer plist and dict keys as well as doctype, etc...)
        /// </summary>
        public string GetEntitlementsString(string CFBundleIdentifier, out string TeamIdentifier)
        {
            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
            string CurrentApplicationIdentifier;

            XCentPList.GetString("application-identifier", out CurrentApplicationIdentifier);
            XCentPList.GetString("com.apple.developer.team-identifier", out TeamIdentifier);

//			if (CurrentApplicationIdentifier.Contains("*"))
            {
                // Replace the application identifier
                string NewApplicationIdentifier = String.Format("{0}.{1}", ApplicationIdentifierPrefix, CFBundleIdentifier);
                XCentPList.SetString("application-identifier", NewApplicationIdentifier);


                // Replace the keychain access groups
                // Note: This isn't robust, it ignores the existing value in the wildcard and uses the same value for
                // each entry.  If there is a legitimate need for more than one entry in the access group list, then
                // don't use a wildcard!
                List <string> KeyGroups = XCentPList.GetArray("keychain-access-groups", "string");

                for (int i = 0; i < KeyGroups.Count; ++i)
                {
                    string Entry = KeyGroups[i];
                    if (Entry.Contains("*"))
                    {
                        Entry = NewApplicationIdentifier;
                    }
                    KeyGroups[i] = Entry;
                }

                XCentPList.SetValueForKey("keychain-access-groups", KeyGroups);
            }

            return(XCentPList.SaveToString());
        }
Пример #4
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-IPhoneX-Landscape",     "Landscape", "{375, 812}",
                "Default-IPhoneX-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);

            // 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();
        }
Пример #5
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, "*.*", SearchOption.AllDirectories);
                foreach (string Filename in PayloadFiles)
                {
                    // Get the relative path to the file (this implementation only works because we know the files are all
                    // deeper than the base dir, since they were generated from a search)
                    string AbsoluteFilename = Path.GetFullPath(Filename);
                    string RelativeFilename = AbsoluteFilename.Substring(SourceDir.Length + 1).Replace('\\', '/');

                    string ZipAbsolutePath = String.Format("Payload/{0}{1}.app/{1}",
                                                           Program.GameName,
                                                           Program.Architecture,
                                                           RelativeFilename);

                    byte[] FileContents = File.ReadAllBytes(AbsoluteFilename);
                    if (FileContents.Length == 0)
                    {
                        // Zero-length files added by Ionic cause installation/upgrade to fail on device with error 0xE8000050
                        // We store a single byte in the files as a workaround for now
                        FileContents    = new byte[1];
                        FileContents[0] = 0;
                    }

                    FileSystem.WriteAllBytes(RelativeFilename, FileContents);

                    if ((FileContents.Length >= 1024 * 1024) || (Config.bVerbose))
                    {
                        FilesBeingModifiedToPrintOut.Add(ZipAbsolutePath);
                    }
                }
            }

            // Re-sign the executable if there is a signing context
            if (CodeSigner != null)
            {
                if (Config.OverrideBundleName != null)
                {
                    CodeSigner.Info.SetString("CFBundleDisplayName", Config.OverrideBundleName);
                    string CFBundleIdentifier;
                    if (CodeSigner.Info.GetString("CFBundleIdentifier", out CFBundleIdentifier))
                    {
                        CodeSigner.Info.SetString("CFBundleIdentifier", CFBundleIdentifier + "_" + Config.OverrideBundleName);
                    }
                }
                CodeSigner.PerformSigning();
            }

            // Stick in the iTunesArtwork PNG if available
            string iTunesArtworkPath = Path.Combine(Config.BuildDirectory, "iTunesArtwork");

            if (File.Exists(iTunesArtworkPath))
            {
                Zip.UpdateFile(iTunesArtworkPath, "");
            }

            // Save the Zip

            Program.Log("Compressing files into IPA (-compress={1}).{0}", Config.bVerbose ? "" : "  Only large files will be listed next, but other files are also being packaged.", Config.RecompressionSetting);
            FileSystem.Close();

            TimeSpan ZipLength = DateTime.Now - StartTime;

            FileInfo FinalZipInfo = new FileInfo(Zip.Name);


            Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s for all steps)",
                                      Zip.Name,
                                      ZipLength.TotalSeconds,
                                      FinalZipInfo.Length / (1024.0f * 1024.0f)));
        }
Пример #6
0
        /// <summary>
        /// Extracts the dict values for the Entitlements key and creates a new full .plist file
        /// from them (with outer plist and dict keys as well as doctype, etc...)
        /// </summary>
        public string GetEntitlementsString(string CFBundleIdentifier, out string TeamIdentifier)
        {
            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
            string CurrentApplicationIdentifier;

            XCentPList.GetString("application-identifier", out CurrentApplicationIdentifier);
            XCentPList.GetString("com.apple.developer.team-identifier", out TeamIdentifier);

            //			if (CurrentApplicationIdentifier.Contains("*"))
            {
                // Replace the application identifier
                string NewApplicationIdentifier = String.Format("{0}.{1}", ApplicationIdentifierPrefix, CFBundleIdentifier);
                XCentPList.SetString("application-identifier", NewApplicationIdentifier);


                // Replace the keychain access groups
                // Note: This isn't robust, it ignores the existing value in the wildcard and uses the same value for
                // each entry.  If there is a legitimate need for more than one entry in the access group list, then
                // don't use a wildcard!
                List <string> KeyGroups = XCentPList.GetArray("keychain-access-groups", "string");

                for (int i = 0; i < KeyGroups.Count; ++i)
                {
                    string Entry = KeyGroups[i];
                    if (Entry.Contains("*"))
                    {
                        Entry = NewApplicationIdentifier;
                    }
                    KeyGroups[i] = Entry;
                }

                XCentPList.SetValueForKey("keychain-access-groups", KeyGroups);
            }

            // must have CloudKit and CloudDocuments for com.apple.developer.icloud-services
            // otherwise the game will not be listed in the Settings->iCloud apps menu on the device
            {
                // iOS only
                if (Platform == "IOS" && XCentPList.HasKey("com.apple.developer.icloud-services"))
                {
                    List <string> ServicesGroups = XCentPList.GetArray("com.apple.developer.icloud-services", "string");
                    ServicesGroups.Clear();

                    ServicesGroups.Add("CloudKit");
                    ServicesGroups.Add("CloudDocuments");
                    XCentPList.SetValueForKey("com.apple.developer.icloud-services", ServicesGroups);
                }

                // For distribution builds, the entitlements from mobileprovisioning have a modified syntax
                if (Config.bForDistribution)
                {
                    // remove the wildcards from the ubiquity-kvstore-identifier string
                    if (XCentPList.HasKey("com.apple.developer.ubiquity-kvstore-identifier"))
                    {
                        string UbiquityKvstoreString;
                        XCentPList.GetString("com.apple.developer.ubiquity-kvstore-identifier", out UbiquityKvstoreString);

                        int DotPosition = UbiquityKvstoreString.LastIndexOf("*");
                        if (DotPosition >= 0)
                        {
                            string TeamPrefix = DotPosition > 1 ? UbiquityKvstoreString.Substring(0, DotPosition - 1) : TeamIdentifier;
                            string NewUbiquityKvstoreIdentifier = String.Format("{0}.{1}", TeamPrefix, CFBundleIdentifier);
                            XCentPList.SetValueForKey("com.apple.developer.ubiquity-kvstore-identifier", NewUbiquityKvstoreIdentifier);
                        }
                    }

                    // remove the wildcards from the ubiquity-container-identifiers array
                    if (XCentPList.HasKey("com.apple.developer.ubiquity-container-identifiers"))
                    {
                        List <string> UbiquityContainerIdentifiersGroups = XCentPList.GetArray("com.apple.developer.ubiquity-container-identifiers", "string");

                        for (int i = 0; i < UbiquityContainerIdentifiersGroups.Count; i++)
                        {
                            int DotPosition = UbiquityContainerIdentifiersGroups[i].LastIndexOf("*");
                            if (DotPosition >= 0)
                            {
                                string TeamPrefix = DotPosition > 1 ? UbiquityContainerIdentifiersGroups[i].Substring(0, DotPosition - 1) : TeamIdentifier;
                                string NewUbiquityContainerIdentifier = String.Format("{0}.{1}", TeamPrefix, CFBundleIdentifier);
                                UbiquityContainerIdentifiersGroups[i] = NewUbiquityContainerIdentifier;
                            }
                        }

                        if (UbiquityContainerIdentifiersGroups.Count == 0)
                        {
                            string NewUbiquityKvstoreIdentifier = String.Format("{0}.{1}", TeamIdentifier, CFBundleIdentifier);
                            UbiquityContainerIdentifiersGroups.Add(NewUbiquityKvstoreIdentifier);
                        }

                        XCentPList.SetValueForKey("com.apple.developer.ubiquity-container-identifiers", UbiquityContainerIdentifiersGroups);
                    }

                    // remove the wildcards from the developer.associated-domains array or string
                    if (XCentPList.HasKey("com.apple.developer.associated-domains"))
                    {
                        string AssociatedDomainsString;
                        XCentPList.GetString("com.apple.developer.associated-domains", out AssociatedDomainsString);

                        //check if the value is string
                        if (AssociatedDomainsString != null && AssociatedDomainsString.Contains("*"))
                        {
                            XCentPList.RemoveKeyValue("com.apple.developer.associated-domains");
                        }
                        else
                        {
                            //check if the value is an array
                            List <string> AssociatedDomainsGroup = XCentPList.GetArray("com.apple.developer.associated-domains", "string");

                            if (AssociatedDomainsGroup.Count == 1 && AssociatedDomainsGroup[0].Contains("*"))
                            {
                                XCentPList.RemoveKeyValue("com.apple.developer.associated-domains");
                            }
                        }
                    }

                    // remove development keys - generated when the cloudkit container is in development mode
                    XCentPList.RemoveKeyValue("com.apple.developer.icloud-container-development-container-identifiers");
                }

                // set the icloud-container-environment according to the project settings
                if (XCentPList.HasKey("com.apple.developer.icloud-container-environment"))
                {
                    List <string> ContainerEnvironmentGroup = XCentPList.GetArray("com.apple.developer.icloud-container-environment", "string");

                    if (ContainerEnvironmentGroup.Count != 0)
                    {
                        ContainerEnvironmentGroup.Clear();

                        // The new value is a string, not an array
                        string NewContainerEnvironment = Config.bForDistribution ? "Production" : "Development";
                        XCentPList.SetValueForKey("com.apple.developer.icloud-container-environment", NewContainerEnvironment);
                    }
                }
            }

            return(XCentPList.SaveToString());
        }
Пример #7
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);
        }
Пример #8
0
        protected virtual byte[] CreateCodeResourcesDirectory(string CFBundleExecutable)
        {
            // Create a rules dict that includes (by wildcard) everything but Info.plist and the rules file
            Dictionary <string, object> Rules1 = new Dictionary <string, object>();

            Rules1.Add("^", true);
            Rules1.Add("^.*\\.lproj/", CreateOptionalResource(1000));
            Rules1.Add("^.*\\.lproj/locversion.plist$", CreateOmittedResource(1100));
            Rules1.Add("^version.plist$", true);

            Dictionary <string, object> Rules2 = new Dictionary <string, object>();

            Rules2.Add(".*\\.dSYM($|/)", CreateResource(11));
            Rules2.Add("^", CreateResource(20));
            Rules2.Add("^(.*/)?\\.DS_Store$", CreateOmittedResource(2000));
            Rules2.Add("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", CreateNestedResource(10));
            Rules2.Add("^.*", true);
            Rules2.Add("^.*\\.lproj/", CreateOptionalResource(1000));
            Rules2.Add("^.*\\.lproj/locversion.plist$", CreateOmittedResource(1100));
            Rules2.Add("^Info\\.plist$", CreateOmittedResource(20));
            Rules2.Add("^PkgInfo$", CreateOmittedResource(20));
            Rules2.Add("^[^/]+$", CreateNestedResource(10));
            Rules2.Add("^embedded\\.provisionprofile$", CreateResource(20));
            Rules2.Add("^version\\.plist$", CreateResource(20));

            // 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> TrueExclusionList1 = new Dictionary <string, object>();

            TrueExclusionList1.Add(CFBundleExecutable, null);
            TrueExclusionList1.Add("CodeResources", null);
            TrueExclusionList1.Add("_CodeSignature/CodeResources", null);

            Dictionary <string, object> TrueExclusionList2 = new Dictionary <string, object>();

            TrueExclusionList2.Add("Info.plist", null);
            TrueExclusionList2.Add("PkgInfo", null);
            TrueExclusionList2.Add(CFBundleExecutable, null);
            TrueExclusionList2.Add("CodeResources", null);
            TrueExclusionList2.Add("_CodeSignature/CodeResources", null);

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

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

                    HashedFileEntries1.AddKeyValuePair(Filename, HashData);
                }
                if (!TrueExclusionList2.ContainsKey(Filename))
                {
                    byte[] FileData = FileSystem.ReadAllBytes(Filename);
                    byte[] HashData = HashProvider.ComputeHash(FileData);

                    HashedFileEntries2.AddKeyValuePair(Filename, HashData);
                }
            }

            // Create the CodeResources file that will contain the hashes
            Utilities.PListHelper CodeResources = new Utilities.PListHelper();
            CodeResources.AddKeyValuePair("files", HashedFileEntries1);
            CodeResources.AddKeyValuePair("files2", HashedFileEntries2);
            CodeResources.AddKeyValuePair("rules", Rules1);
            CodeResources.AddKeyValuePair("rules2", Rules2);

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

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

            return(CodeResourcesAsBytes);
        }
Пример #9
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)));
        }
Пример #10
0
        /// <summary>
        /// Creates an entitlements blob string from the entitlements structure in the mobile provision, merging in an on disk file if it is present.
        /// </summary>
        private string BuildEntitlementString(string CFBundleIdentifier)
        {
            // Load the base entitlements string from the mobile provision
            string ProvisionEntitlements = Provision.GetEntitlementsString(CFBundleIdentifier);

            // See if there is an override entitlements file on disk
            string UserOverridesEntitlementsFilename = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".entitlements");
            if (File.Exists(UserOverridesEntitlementsFilename))
            {
                // Merge in the entitlements from the on disk file as overrides
                Program.Log("Merging override entitlements from {0} into provision specified entitlements", Path.GetFileName(UserOverridesEntitlementsFilename));

                Utilities.PListHelper Merger = new Utilities.PListHelper(ProvisionEntitlements);
                string Overrides = File.ReadAllText(UserOverridesEntitlementsFilename, Encoding.UTF8);
                Merger.MergePlistIn(Overrides);

                return Merger.SaveToString();
            }
            else
            {
                // The ones from the mobile provision need no overrides
                return ProvisionEntitlements;
            }
        }
Пример #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;
        }
Пример #12
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}",
            };

            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();
        }
Пример #13
0
		protected virtual byte[] CreateCodeResourcesDirectory(string CFBundleExecutable)
		{
			// Create a rules dict that includes (by wildcard) everything but Info.plist and the rules file
			Dictionary<string, object> Rules1 = new Dictionary<string, object>();
			Rules1.Add("^", true);
			Rules1.Add("^.*\\.lproj/", CreateOptionalResource(1000));
			Rules1.Add("^.*\\.lproj/locversion.plist$", CreateOmittedResource(1100));
			Rules1.Add("^version.plist$", true);

			Dictionary<string, object> Rules2 = new Dictionary<string, object>();
			Rules2.Add(".*\\.dSYM($|/)", CreateResource(11));
			Rules2.Add("^", CreateResource(20));
			Rules2.Add("^(.*/)?\\.DS_Store$", CreateOmittedResource(2000));
			Rules2.Add("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", CreateNestedResource(10));
			Rules2.Add("^.*", true);
			Rules2.Add("^.*\\.lproj/", CreateOptionalResource(1000));
			Rules2.Add("^.*\\.lproj/locversion.plist$", CreateOmittedResource(1100));
			Rules2.Add("^Info\\.plist$", CreateOmittedResource(20));
			Rules2.Add("^PkgInfo$", CreateOmittedResource(20));
			Rules2.Add("^[^/]+$", CreateNestedResource(10));
			Rules2.Add("^embedded\\.provisionprofile$", CreateResource(20));
			Rules2.Add("^version\\.plist$", CreateResource(20));

			// 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> TrueExclusionList1 = new Dictionary<string, object>();
			TrueExclusionList1.Add(CFBundleExecutable, null);
			TrueExclusionList1.Add("CodeResources", null);
			TrueExclusionList1.Add("_CodeSignature/CodeResources", null);

			Dictionary<string, object> TrueExclusionList2 = new Dictionary<string, object>();
			TrueExclusionList2.Add("Info.plist", null);
			TrueExclusionList2.Add("PkgInfo", null);
			TrueExclusionList2.Add(CFBundleExecutable, null);
			TrueExclusionList2.Add("CodeResources", null);
			TrueExclusionList2.Add("_CodeSignature/CodeResources", null);

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

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

					HashedFileEntries1.AddKeyValuePair(Filename, HashData);
				}
				if (!TrueExclusionList2.ContainsKey(Filename))
				{
					byte[] FileData = FileSystem.ReadAllBytes(Filename);
					byte[] HashData = HashProvider.ComputeHash(FileData);

					HashedFileEntries2.AddKeyValuePair(Filename, HashData);
				}
			}

			// Create the CodeResources file that will contain the hashes
			Utilities.PListHelper CodeResources = new Utilities.PListHelper();
			CodeResources.AddKeyValuePair("files", HashedFileEntries1);
			CodeResources.AddKeyValuePair("files2", HashedFileEntries2);
			CodeResources.AddKeyValuePair("rules", Rules1);
			CodeResources.AddKeyValuePair("rules2", Rules2);

			// Write the CodeResources file out
			string CodeResourcesAsString = CodeResources.SaveToString();
			byte[] CodeResourcesAsBytes = Encoding.UTF8.GetBytes(CodeResourcesAsString);
			FileSystem.WriteAllBytes("_CodeSignature/CodeResources", CodeResourcesAsBytes);

			return CodeResourcesAsBytes;
		}