// CFBundleDisplayName // CFBundleName, fewer than 16 characters long // CFBundleIdentifier // http://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070 // Alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.) characters. // The string should also be in reverse-DNS format. void ReloadPList() { string Filename = Config.GetPlistOverrideFilename(); string SourcePList = ReadOrCreate(Filename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); string BundleID; string BundleName; string BundleDisplayName; if (!Helper.GetString("CFBundleIdentifier", out BundleID)) { BundleID = "com.YourCompany.GameNameNoSpaces"; Helper.SetString("CFBundleIdentifier", BundleID); } if (!Helper.GetString("CFBundleName", out BundleName)) { BundleName = "MyUDKGame"; Helper.SetString("CFBundleName", BundleName); } if (!Helper.GetString("CFBundleDisplayName", out BundleDisplayName)) { BundleDisplayName = "UDK Game"; Helper.SetString("CFBundleDisplayName", BundleDisplayName); } BundleIdentifierEdit.Text = BundleID; BundleNameEdit.Text = BundleName; BundleDisplayNameEdit.Text = BundleDisplayName; }
/// <summary> /// 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); } }
// CFBundleDisplayName // CFBundleName, fewer than 16 characters long // CFBundleIdentifier // http://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070 // Alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.) characters. // The string should also be in reverse-DNS format. void ReloadPList() { string Filename = Config.GetPlistOverrideFilename(); string SourcePList = ReadOrCreate(Filename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); string BundleID; string BundleName; string BundleDisplayName; if (!Helper.GetString("CFBundleIdentifier", out BundleID)) { BundleID = "com.YourCompany.GameNameNoSpaces"; Helper.SetString("CFBundleIdentifier", BundleID); } if (!Helper.GetString("CFBundleName", out BundleName)) { BundleName = "MyUDKGame"; Helper.SetString("CFBundleName", BundleName); } if (!Helper.GetString("CFBundleDisplayName", out BundleDisplayName)) { BundleDisplayName = "UDK Game"; Helper.SetString("CFBundleDisplayName", BundleDisplayName); } BundleIdentifierEdit.Text = BundleID; BundleNameEdit.Text = BundleName; BundleDisplayNameEdit.Text = BundleDisplayName; }
/// <summary> /// Updates the version string and then applies the settings in the user overrides plist /// </summary> /// <param name="Info"></param> public static void UpdateVersion(Utilities.PListHelper Info) { // Update the minor version number if the current one is older than the version tracker file // Assuming that the version will be set explicitly in the overrides file for distribution VersionUtilities.UpdateMinorVersion(Info); // Mark the type of build (development or distribution) Info.SetString("EpicPackagingMode", Config.bForDistribution ? "Distribution" : "Development"); }
private void SavePList(Utilities.PListHelper Helper, string Filename) { FileInfo DestInfo = new FileInfo(Filename); if (DestInfo.Exists && DestInfo.IsReadOnly) { DestInfo.IsReadOnly = false; } Helper.SaveToFile(Filename); }
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); }
/// <summary> /// Prepares this signer to sign an application /// Modifies the following files: /// embedded.mobileprovision /// </summary> public void PrepareForSigning() { // Load Info.plist, which guides nearly everything else Info = LoadInfoPList(); // Get the name of the bundle string CFBundleIdentifier; if (!Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { throw new InvalidDataException("Info.plist must contain the key CFBundleIdentifier"); } // Load the mobile provision, which provides entitlements and a partial cert which can be used to find an installed certificate LoadMobileProvision(CFBundleIdentifier); if (Provision == null) { return; } // Install the Apple trust chain certs (required to do a CMS signature with full chain embedded) List <string> TrustChainCertFilenames = new List <string>(); string CertPath = Path.GetFullPath(Config.EngineBuildDirectory); TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleWorldwideDeveloperRelationsCA.pem")); TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleRootCA.pem")); InstallCertificates(TrustChainCertFilenames); // Find and load the signing cert SigningCert = LoadSigningCertificate(); if (SigningCert == null) { // Failed to find a cert already installed or to install, cannot proceed any futher Program.Error("... Failed to find a certificate that matches the mobile provision to be used for code signing"); Program.ReturnCode = (int)ErrorCodes.Error_CertificateNotFound; throw new InvalidDataException("Certificate not found!"); } else { Program.Log("... Found matching certificate '{0}' (valid from {1} to {2})", SigningCert.FriendlyName, SigningCert.GetEffectiveDateString(), SigningCert.GetExpirationDateString()); } }
/// <summary> /// 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()); }
/// <summary> /// Updates the minor version in the CFBundleVersion key of the specified PList if this is a new package. /// Also updates the key EpicAppVersion with the bundle version and the current date/time (no year) /// </summary> public static void UpdateMinorVersion(Utilities.PListHelper PList) { string CFBundleVersion; if (PList.GetString("CFBundleVersion", out CFBundleVersion)) { string UpdatedValue = CalculateUpdatedMinorVersionString(CFBundleVersion); Program.Log("Found CFBundleVersion string '{0}' and updated it to '{1}'", CFBundleVersion, UpdatedValue); PList.SetString("CFBundleVersion", UpdatedValue); } else { CFBundleVersion = "0.0"; } // Write a second key with the packaging date/time in it string PackagingTime = DateTime.Now.ToString(@"MM-dd HH:mm"); PList.SetString("EpicAppVersion", CFBundleVersion + " " + PackagingTime); }
private void SaveChanges() { // Development settings { // Open the existing plist override file string DevFilename = Config.GetPlistOverrideFilename(false); string SourcePList = ReadOrCreate(DevFilename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); // Jam the edited values in if (!Helper.HasKey("UIFileSharingEnabled")) { Helper.SetValueForKey("UIFileSharingEnabled", true); } Helper.SetString("CFBundleIdentifier", BundleIdentifierEdit.Text); Helper.SetString("CFBundleName", BundleNameEdit.Text); Helper.SetString("CFBundleDisplayName", BundleDisplayNameEdit.Text); // Save the modified plist SavePList(Helper, DevFilename); } // Distribution settings { // Open the existing plist override file string DistFilename = Config.GetPlistOverrideFilename(true); string SourcePList = ReadOrCreate(DistFilename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); // Jam the edited values in if (!Helper.HasKey("UIFileSharingEnabled")) { Helper.SetValueForKey("UIFileSharingEnabled", false); } // Save the modified plist SavePList(Helper, DistFilename); } }
private void SaveChanges() { // Development settings { // Open the existing plist override file string DevFilename = Config.GetPlistOverrideFilename(false); string SourcePList = ReadOrCreate(DevFilename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); // Jam the edited values in if (!Helper.HasKey("UIFileSharingEnabled")) { Helper.SetValueForKey("UIFileSharingEnabled", true); } Helper.SetString("CFBundleIdentifier", BundleIdentifierEdit.Text); Helper.SetString("CFBundleName", BundleNameEdit.Text); Helper.SetString("CFBundleDisplayName", BundleDisplayNameEdit.Text); // Save the modified plist SavePList(Helper, DevFilename); } // Distribution settings { // Open the existing plist override file string DistFilename = Config.GetPlistOverrideFilename(true); string SourcePList = ReadOrCreate(DistFilename); Utilities.PListHelper Helper = new Utilities.PListHelper(SourcePList); // Jam the edited values in if (!Helper.HasKey("UIFileSharingEnabled")) { Helper.SetValueForKey("UIFileSharingEnabled", false); } // Save the modified plist SavePList(Helper, DistFilename); } }
string PickIPAAndFetchBundleIdentifier() { string PickedFilename; if (ToolsHub.ShowOpenFileDialog(ToolsHub.IpaFilter, "Choose an IPA to provide the bundle identifier", "ipa", "", ref ToolsHub.ChoosingIpaDirectory, out PickedFilename)) { FileOperations.FileSystemAdapter IPA = new FileOperations.ReadOnlyZipFileSystem(PickedFilename); if (IPA != null) { byte[] InfoData = IPA.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(InfoData)); string BundleID; if (Info.GetString("CFBundleIdentifier", out BundleID)) { return(BundleID); } } } return(null); }
/// <summary> /// Constructs a MobileProvision from an xml blob extracted from the real ASN.1 file /// </summary> public MobileProvision(string EmbeddedPListText) { Data = new Utilities.PListHelper(EmbeddedPListText); // Now extract things // Key: ApplicationIdentifierPrefix, Array<String> List<string> PrefixList = Data.GetArray("ApplicationIdentifierPrefix", "string"); if (PrefixList.Count > 1) { Program.Warning("Found more than one entry for ApplicationIdentifierPrefix in the .mobileprovision, using the first one found"); } if (PrefixList.Count > 0) { ApplicationIdentifierPrefix = PrefixList[0]; } // Key: DeveloperCertificates, Array<Data> (uuencoded) string CertificatePassword = ""; List<string> CertificateList = Data.GetArray("DeveloperCertificates", "data"); foreach (string EncodedCert in CertificateList) { byte[] RawCert = Convert.FromBase64String(EncodedCert); DeveloperCertificates.Add(new X509Certificate2(RawCert, CertificatePassword)); } // Key: Name, String if (!Data.GetString("Name", out ProvisionName)) { ProvisionName = "(unknown)"; } // Key: ProvisionedDevices, Array<String> ProvisionedDeviceIDs = Data.GetArray("ProvisionedDevices", "string"); }
/// <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(); }
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); }
/// <summary> /// Constructs a MobileProvision from an xml blob extracted from the real ASN.1 file /// </summary> public MobileProvision(string EmbeddedPListText) { Data = new Utilities.PListHelper(EmbeddedPListText); // Now extract things // Key: ApplicationIdentifierPrefix, Array<String> List <string> PrefixList = Data.GetArray("ApplicationIdentifierPrefix", "string"); if (PrefixList.Count > 1) { Program.Warning("Found more than one entry for ApplicationIdentifierPrefix in the .mobileprovision, using the first one found"); } if (PrefixList.Count > 0) { ApplicationIdentifierPrefix = PrefixList[0]; } // Example date string from the XML: "2014-06-30T20:45:55Z"; DateTimeStyles AppleDateStyle = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; string CreationDateString; if (Data.GetDate("CreationDate", out CreationDateString)) { CreationDate = DateTime.Parse(CreationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } string ExpirationDateString; if (Data.GetDate("ExpirationDate", out ExpirationDateString)) { ExpirationDate = DateTime.Parse(ExpirationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } // Key: DeveloperCertificates, Array<Data> (uuencoded) string CertificatePassword = ""; List <string> CertificateList = Data.GetArray("DeveloperCertificates", "data"); foreach (string EncodedCert in CertificateList) { byte[] RawCert = Convert.FromBase64String(EncodedCert); DeveloperCertificates.Add(new X509Certificate2(RawCert, CertificatePassword)); } // Key: Name, String if (!Data.GetString("Name", out ProvisionName)) { ProvisionName = "(unknown)"; } // Key: ProvisionedDevices, Array<String> ProvisionedDeviceIDs = Data.GetArray("ProvisionedDevices", "string"); // Key: application-identifier, Array<String> Utilities.PListHelper XCentPList = null; Data.ProcessValueForKey("Entitlements", "dict", delegate(XmlNode ValueNode) { XCentPList = Utilities.PListHelper.CloneDictionaryRootedAt(ValueNode); }); // Modify the application-identifier to be fully qualified if needed if (!XCentPList.GetString("application-identifier", out ApplicationIdentifier)) { ApplicationIdentifier = "(unknown)"; } // check for get-task-allow bDebug = XCentPList.GetBool("get-task-allow"); if (!Data.GetString("UUID", out UUID)) { UUID = "(unkown)"; } List <string> Platforms = Data.GetArray("Platform", "string"); if (Platforms.Contains("iOS")) { Platform = "IOS"; } else if (Platforms.Contains("tvOS")) { Platform = "TVOS"; } else { Platform = ""; } }
/// <summary> /// Prepares this signer to sign an application /// Modifies the following files: /// embedded.mobileprovision /// </summary> public void PrepareForSigning() { // Load Info.plist, which guides nearly everything else Info = LoadInfoPList(); // Get the name of the bundle string CFBundleIdentifier; if (!Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { throw new InvalidDataException("Info.plist must contain the key CFBundleIdentifier"); } // Load the mobile provision, which provides entitlements and a partial cert which can be used to find an installed certificate LoadMobileProvision(CFBundleIdentifier); if (Provision == null) { return; } // Install the Apple trust chain certs (required to do a CMS signature with full chain embedded) List<string> TrustChainCertFilenames = new List<string>(); string CertPath = Path.GetFullPath(Config.EngineBuildDirectory); TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleWorldwideDeveloperRelationsCA.pem")); TrustChainCertFilenames.Add(Path.Combine(CertPath, "AppleRootCA.pem")); InstallCertificates(TrustChainCertFilenames); // Find and load the signing cert SigningCert = LoadSigningCertificate(); if (SigningCert == null) { // Failed to find a cert already installed or to install, cannot proceed any futher Program.Error("... Failed to find a certificate that matches the mobile provision to be used for code signing"); Program.ReturnCode = (int)ErrorCodes.Error_CertificateNotFound; throw new InvalidDataException("Certificate not found!"); } else { Program.Log("... Found matching certificate '{0}' (valid from {1} to {2})", SigningCert.FriendlyName, SigningCert.GetEffectiveDateString(), SigningCert.GetExpirationDateString()); } }
/// <summary> /// Makes sure the required files for code signing exist and can be found /// </summary> public static bool FindRequiredFiles(out MobileProvision Provision, out X509Certificate2 Cert, out bool bHasOverridesFile, out bool bNameMatch, bool bNameCheck = true) { Provision = null; Cert = null; bHasOverridesFile = File.Exists(Config.GetPlistOverrideFilename()); bNameMatch = false; string CFBundleIdentifier = Config.OverrideBundleName; if (string.IsNullOrEmpty(CFBundleIdentifier)) { // Load Info.plist, which guides nearly everything else string plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist"; if (!string.IsNullOrEmpty(Config.ProjectFile)) { plistFile = Path.GetDirectoryName(Config.ProjectFile) + "/Intermediate/IOS/" + Path.GetFileNameWithoutExtension(Config.ProjectFile) + "-Info.plist"; if (!File.Exists(plistFile)) { plistFile = Config.IntermediateDirectory + "/UE4Game-Info.plist"; if (!File.Exists(plistFile)) { plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist"; } } } Utilities.PListHelper Info = null; try { string RawInfoPList = File.ReadAllText(plistFile, Encoding.UTF8); Info = new Utilities.PListHelper(RawInfoPList); ; } catch (Exception ex) { Console.WriteLine(ex.Message); } if (Info == null) { return false; } // Get the name of the bundle Info.GetString("CFBundleIdentifier", out CFBundleIdentifier); if (CFBundleIdentifier == null) { return false; } else { CFBundleIdentifier = CFBundleIdentifier.Replace("${BUNDLE_IDENTIFIER}", Path.GetFileNameWithoutExtension(Config.ProjectFile)); } } // Check for a mobile provision try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } // if we have a null provision see if we can find a compatible provision without checking for a certificate if (Provision == null) { try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } // if we have a null provision see if we can find a valid provision without checking for name match if (Provision == null && !bNameCheck) { try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false, false); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } } } // Check for a suitable signature to match the mobile provision if (Provision != null) { Cert = CodeSignatureBuilder.FindCertificate(Provision); } return true; }
/// <summary> /// Constructs a MobileProvision from an xml blob extracted from the real ASN.1 file /// </summary> public MobileProvision(string EmbeddedPListText) { Data = new Utilities.PListHelper(EmbeddedPListText); // Now extract things // Key: ApplicationIdentifierPrefix, Array<String> List<string> PrefixList = Data.GetArray("ApplicationIdentifierPrefix", "string"); if (PrefixList.Count > 1) { Program.Warning("Found more than one entry for ApplicationIdentifierPrefix in the .mobileprovision, using the first one found"); } if (PrefixList.Count > 0) { ApplicationIdentifierPrefix = PrefixList[0]; } // Example date string from the XML: "2014-06-30T20:45:55Z"; DateTimeStyles AppleDateStyle = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; string CreationDateString; if (Data.GetDate("CreationDate", out CreationDateString)) { CreationDate = DateTime.Parse(CreationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } string ExpirationDateString; if (Data.GetDate("ExpirationDate", out ExpirationDateString)) { ExpirationDate = DateTime.Parse(ExpirationDateString, CultureInfo.InvariantCulture, AppleDateStyle); } // Key: DeveloperCertificates, Array<Data> (uuencoded) string CertificatePassword = ""; List<string> CertificateList = Data.GetArray("DeveloperCertificates", "data"); foreach (string EncodedCert in CertificateList) { byte[] RawCert = Convert.FromBase64String(EncodedCert); DeveloperCertificates.Add(new X509Certificate2(RawCert, CertificatePassword)); } // Key: Name, String if (!Data.GetString("Name", out ProvisionName)) { ProvisionName = "(unknown)"; } // Key: ProvisionedDevices, Array<String> ProvisionedDeviceIDs = Data.GetArray("ProvisionedDevices", "string"); // Key: application-identifier, Array<String> Utilities.PListHelper XCentPList = null; Data.ProcessValueForKey("Entitlements", "dict", delegate(XmlNode ValueNode) { XCentPList = Utilities.PListHelper.CloneDictionaryRootedAt(ValueNode); }); // Modify the application-identifier to be fully qualified if needed if (!XCentPList.GetString("application-identifier", out ApplicationIdentifier)) { ApplicationIdentifier = "(unknown)"; } // check for get-task-allow bDebug = XCentPList.GetBool("get-task-allow"); }
/// <summary> /// Makes sure the required files for code signing exist and can be found /// </summary> public static bool FindRequiredFiles(out MobileProvision Provision, out X509Certificate2 Cert, out bool bHasOverridesFile, out bool bNameMatch, bool bNameCheck = true) { Provision = null; Cert = null; bHasOverridesFile = File.Exists(Config.GetPlistOverrideFilename()); bNameMatch = false; string CFBundleIdentifier = Config.OverrideBundleName; if (string.IsNullOrEmpty(CFBundleIdentifier)) { // Load Info.plist, which guides nearly everything else string plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist"; if (!string.IsNullOrEmpty(Config.ProjectFile)) { plistFile = Path.GetDirectoryName(Config.ProjectFile) + "/Intermediate/IOS/" + Path.GetFileNameWithoutExtension(Config.ProjectFile) + "-Info.plist"; if (!File.Exists(plistFile)) { plistFile = Config.IntermediateDirectory + "/UE4Game-Info.plist"; if (!File.Exists(plistFile)) { plistFile = Config.EngineBuildDirectory + "/UE4Game-Info.plist"; } } } Utilities.PListHelper Info = null; try { string RawInfoPList = File.ReadAllText(plistFile, Encoding.UTF8); Info = new Utilities.PListHelper(RawInfoPList);; } catch (Exception ex) { Console.WriteLine(ex.Message); } if (Info == null) { return(false); } // Get the name of the bundle Info.GetString("CFBundleIdentifier", out CFBundleIdentifier); if (CFBundleIdentifier == null) { return(false); } else { CFBundleIdentifier = CFBundleIdentifier.Replace("${BUNDLE_IDENTIFIER}", Path.GetFileNameWithoutExtension(Config.ProjectFile)); } } // Check for a mobile provision try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } // if we have a null provision see if we can find a compatible provision without checking for a certificate if (Provision == null) { try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } // if we have a null provision see if we can find a valid provision without checking for name match if (Provision == null && !bNameCheck) { try { string MobileProvisionFilename = MobileProvision.FindCompatibleProvision(CFBundleIdentifier, out bNameMatch, false, false); Provision = MobileProvisionParser.ParseFile(MobileProvisionFilename); } catch (Exception) { } } } // Check for a suitable signature to match the mobile provision if (Provision != null) { Cert = CodeSignatureBuilder.FindCertificate(Provision); } return(true); }
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; }
/** * Using the stub IPA previously compiled on the Mac, create a new IPA with assets */ static public void RepackageIPAFromStub() { if (string.IsNullOrEmpty(Config.RepackageStagingDirectory) || !Directory.Exists(Config.RepackageStagingDirectory)) { Program.Error("Directory specified with -stagedir could not be found!"); return; } DateTime StartTime = DateTime.Now; CodeSignatureBuilder CodeSigner = null; // Clean the staging directory Program.ExecuteCommand("Clean", null); // Create a copy of the IPA so as to not trash the original ZipFile Zip = SetupWorkIPA(); if (Zip == null) { return; } string ZipWorkingDir = String.Format("Payload/{0}{1}.app/", Program.GameName, Program.Architecture); FileOperations.ZipFileSystem FileSystem = new FileOperations.ZipFileSystem(Zip, ZipWorkingDir); // Check for a staged plist that needs to be merged into the main one { // Determine if there is a staged one we should try to use instead string PossiblePList = Path.Combine(Config.RepackageStagingDirectory, "Info.plist"); if (File.Exists(PossiblePList)) { if (Config.bPerformResignWhenRepackaging) { Program.Log("Found Info.plist ({0}) in stage, which will be merged in with stub plist contents", PossiblePList); // Merge the two plists, using the staged one as the authority when they conflict byte[] StagePListBytes = File.ReadAllBytes(PossiblePList); string StageInfoString = Encoding.UTF8.GetString(StagePListBytes); byte[] StubPListBytes = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper StubInfo = new Utilities.PListHelper(Encoding.UTF8.GetString(StubPListBytes)); StubInfo.MergePlistIn(StageInfoString); // Write it back to the cloned stub, where it will be used for all subsequent actions byte[] MergedPListBytes = Encoding.UTF8.GetBytes(StubInfo.SaveToString()); FileSystem.WriteAllBytes("Info.plist", MergedPListBytes); } else { Program.Warning("Found Info.plist ({0}) in stage that will be ignored; IPP cannot combine it with the stub plist since -sign was not specified", PossiblePList); } } } // Get the name of the executable file string CFBundleExecutable; { // Load the .plist from the stub byte[] RawInfoPList = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(RawInfoPList)); // Get the name of the executable file if (!Info.GetString("CFBundleExecutable", out CFBundleExecutable)) { throw new InvalidDataException("Info.plist must contain the key CFBundleExecutable"); } } // Tell the file system about the executable file name so that we can set correct attributes on // the file when zipping it up FileSystem.ExecutableFileName = CFBundleExecutable; // Prepare for signing if requested if (Config.bPerformResignWhenRepackaging) { // Start the resign process (load the mobileprovision and info.plist, find the cert, etc...) CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = FileSystem; CodeSigner.PrepareForSigning(); // Merge in any user overrides that exist UpdateVersion(CodeSigner.Info); } // Empty the current staging directory FileOperations.DeleteDirectory(new DirectoryInfo(Config.PCStagingRootDir)); // we will zip files in the pre-staged payload dir string ZipSourceDir = Config.RepackageStagingDirectory; // Save the zip Program.Log("Saving IPA ..."); FilesBeingModifiedToPrintOut.Clear(); Zip.SaveProgress += UpdateSaveProgress; Zip.CompressionLevel = (Ionic.Zlib.CompressionLevel)Config.RecompressionSetting; // Add all of the payload files, replacing existing files in the stub IPA if necessary (should only occur for icons) { string SourceDir = Path.GetFullPath(ZipSourceDir); string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories); foreach (string Filename in PayloadFiles) { // Get the relative path to the file (this implementation only works because we know the files are all // deeper than the base dir, since they were generated from a search) string AbsoluteFilename = Path.GetFullPath(Filename); string RelativeFilename = AbsoluteFilename.Substring(SourceDir.Length + 1).Replace('\\', '/'); string ZipAbsolutePath = String.Format("Payload/{0}{1}.app/{1}", Program.GameName, Program.Architecture, RelativeFilename); byte[] FileContents = File.ReadAllBytes(AbsoluteFilename); if (FileContents.Length == 0) { // Zero-length files added by Ionic cause installation/upgrade to fail on device with error 0xE8000050 // We store a single byte in the files as a workaround for now FileContents = new byte[1]; FileContents[0] = 0; } FileSystem.WriteAllBytes(RelativeFilename, FileContents); if ((FileContents.Length >= 1024 * 1024) || (Config.bVerbose)) { FilesBeingModifiedToPrintOut.Add(ZipAbsolutePath); } } } // Re-sign the executable if there is a signing context if (CodeSigner != null) { if (Config.OverrideBundleName != null) { CodeSigner.Info.SetString("CFBundleDisplayName", Config.OverrideBundleName); string CFBundleIdentifier; if (CodeSigner.Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { CodeSigner.Info.SetString("CFBundleIdentifier", CFBundleIdentifier + "_" + Config.OverrideBundleName); } } CodeSigner.PerformSigning(); } // Stick in the iTunesArtwork PNG if available string iTunesArtworkPath = Path.Combine(Config.BuildDirectory, "iTunesArtwork"); if (File.Exists(iTunesArtworkPath)) { Zip.UpdateFile(iTunesArtworkPath, ""); } // Save the Zip Program.Log("Compressing files into IPA (-compress={1}).{0}", Config.bVerbose ? "" : " Only large files will be listed next, but other files are also being packaged.", Config.RecompressionSetting); FileSystem.Close(); TimeSpan ZipLength = DateTime.Now - StartTime; FileInfo FinalZipInfo = new FileInfo(Zip.Name); Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s for all steps)", Zip.Name, ZipLength.TotalSeconds, FinalZipInfo.Length / (1024.0f * 1024.0f))); }
public XmlElement ConvertValueToPListFormat(object Value) { XmlElement ValueElement = null; if (Value is string) { ValueElement = Doc.CreateElement("string"); ValueElement.InnerText = Value as string; } else if (Value is Dictionary <string, object> ) { ValueElement = Doc.CreateElement("dict"); foreach (var KVP in Value as Dictionary <string, object> ) { AddKeyValuePair(ValueElement, KVP.Key, KVP.Value); } } else if (Value is Utilities.PListHelper) { Utilities.PListHelper PList = Value as Utilities.PListHelper; ValueElement = Doc.CreateElement("dict"); XmlNode SourceDictionaryNode = PList.Doc.DocumentElement.SelectSingleNode("/plist/dict"); foreach (XmlNode TheirChild in SourceDictionaryNode) { ValueElement.AppendChild(Doc.ImportNode(TheirChild, true)); } } else if (Value is Array) { if (Value is byte[]) { ValueElement = Doc.CreateElement("data"); ValueElement.InnerText = Convert.ToBase64String(Value as byte[]); } else { ValueElement = Doc.CreateElement("array"); foreach (var A in Value as Array) { ValueElement.AppendChild(ConvertValueToPListFormat(A)); } } } else if (Value is IList) { ValueElement = Doc.CreateElement("array"); foreach (var A in Value as IList) { ValueElement.AppendChild(ConvertValueToPListFormat(A)); } } else if (Value is bool) { ValueElement = Doc.CreateElement(((bool)Value) ? "true" : "false"); } else if (Value is double) { ValueElement = Doc.CreateElement("real"); ValueElement.InnerText = ((double)Value).ToString(); } else if (Value is int) { ValueElement = Doc.CreateElement("integer"); ValueElement.InnerText = ((int)Value).ToString(); } else { throw new InvalidDataException(String.Format("Object '{0}' is in an unknown type that cannot be converted to PList format", Value)); } return(ValueElement); }
/// <summary> /// Prepares this signer to sign an application /// Modifies the following files: /// embedded.mobileprovision /// </summary> public void PrepareForSigning() { // Load Info.plist, which guides nearly everything else Info = LoadInfoPList(); // Load the mobile provision, which provides entitlements and a partial cert which can be used to find an installed certificate LoadMobileProvision(); if (Provision == null) { return; } // Install the Apple trust chain certs (required to do a CMS signature with full chain embedded) List<string> TrustChainCertFilenames = new List<string>(); TrustChainCertFilenames.Add("AppleWorldwideDeveloperRelationsCA.pem"); TrustChainCertFilenames.Add("AppleRootCA.pem"); InstallCertificates(TrustChainCertFilenames); // Find and load the signing cert SigningCert = LoadSigningCertificate(); if (SigningCert == null) { // Failed to find a cert already installed or to install, cannot proceed any futher Program.Error("... Failed to find a certificate that matches the mobile provision to be used for code signing"); } else { Program.Log("... Found matching certificate '{0}'", SigningCert.FriendlyName); } }
/// <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()); }
/** * Using the stub IPA previously compiled on the Mac, create a new IPA with assets */ public static void RepackageIPAFromStub() { if (string.IsNullOrEmpty(Config.RepackageStagingDirectory) || !Directory.Exists(Config.RepackageStagingDirectory)) { Program.Error("Directory specified with -stagedir could not be found!"); return; } DateTime StartTime = DateTime.Now; CodeSignatureBuilder CodeSigner = null; // Clean the staging directory Program.ExecuteCommand("Clean", null); // Create a copy of the IPA so as to not trash the original ZipFile Zip = SetupWorkIPA(); if (Zip == null) { return; } string ZipWorkingDir = String.Format("Payload/{0}{1}.app/", Program.GameName, Program.Architecture); FileOperations.ZipFileSystem FileSystem = new FileOperations.ZipFileSystem(Zip, ZipWorkingDir); // Check for a staged plist that needs to be merged into the main one { // Determine if there is a staged one we should try to use instead string PossiblePList = Path.Combine(Config.RepackageStagingDirectory, "Info.plist"); if (File.Exists(PossiblePList)) { if (Config.bPerformResignWhenRepackaging) { Program.Log("Found Info.plist ({0}) in stage, which will be merged in with stub plist contents", PossiblePList); // Merge the two plists, using the staged one as the authority when they conflict byte[] StagePListBytes = File.ReadAllBytes(PossiblePList); string StageInfoString = Encoding.UTF8.GetString(StagePListBytes); byte[] StubPListBytes = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper StubInfo = new Utilities.PListHelper(Encoding.UTF8.GetString(StubPListBytes)); StubInfo.MergePlistIn(StageInfoString); // Write it back to the cloned stub, where it will be used for all subsequent actions byte[] MergedPListBytes = Encoding.UTF8.GetBytes(StubInfo.SaveToString()); FileSystem.WriteAllBytes("Info.plist", MergedPListBytes); } else { Program.Warning("Found Info.plist ({0}) in stage that will be ignored; IPP cannot combine it with the stub plist since -sign was not specified", PossiblePList); } } } // Get the name of the executable file string CFBundleExecutable; { // Load the .plist from the stub byte[] RawInfoPList = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(RawInfoPList)); // Get the name of the executable file if (!Info.GetString("CFBundleExecutable", out CFBundleExecutable)) { throw new InvalidDataException("Info.plist must contain the key CFBundleExecutable"); } } // Tell the file system about the executable file name so that we can set correct attributes on // the file when zipping it up FileSystem.ExecutableFileName = CFBundleExecutable; // Prepare for signing if requested if (Config.bPerformResignWhenRepackaging) { // Start the resign process (load the mobileprovision and info.plist, find the cert, etc...) CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = FileSystem; CodeSigner.PrepareForSigning(); // Merge in any user overrides that exist UpdateVersion(CodeSigner.Info); } // Empty the current staging directory FileOperations.DeleteDirectory(new DirectoryInfo(Config.PCStagingRootDir)); // we will zip files in the pre-staged payload dir string ZipSourceDir = Config.RepackageStagingDirectory; // Save the zip Program.Log("Saving IPA ..."); FilesBeingModifiedToPrintOut.Clear(); Zip.SaveProgress += UpdateSaveProgress; Zip.CompressionLevel = (Ionic.Zlib.CompressionLevel)Config.RecompressionSetting; // Add all of the payload files, replacing existing files in the stub IPA if necessary (should only occur for icons) { string SourceDir = Path.GetFullPath(ZipSourceDir); string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories); foreach (string Filename in PayloadFiles) { // Get the relative path to the file (this implementation only works because we know the files are all // deeper than the base dir, since they were generated from a search) string AbsoluteFilename = Path.GetFullPath(Filename); string RelativeFilename = AbsoluteFilename.Substring(SourceDir.Length + 1).Replace('\\', '/'); string ZipAbsolutePath = String.Format("Payload/{0}{1}.app/{1}", Program.GameName, Program.Architecture, RelativeFilename); byte[] FileContents = File.ReadAllBytes(AbsoluteFilename); if (FileContents.Length == 0) { // Zero-length files added by Ionic cause installation/upgrade to fail on device with error 0xE8000050 // We store a single byte in the files as a workaround for now FileContents = new byte[1]; FileContents[0] = 0; } FileSystem.WriteAllBytes(RelativeFilename, FileContents); if ((FileContents.Length >= 1024 * 1024) || (Config.bVerbose)) { FilesBeingModifiedToPrintOut.Add(ZipAbsolutePath); } } } // Re-sign the executable if there is a signing context if (CodeSigner != null) { if (Config.OverrideBundleName != null) { CodeSigner.Info.SetString("CFBundleDisplayName", Config.OverrideBundleName); string CFBundleIdentifier; if (CodeSigner.Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { CodeSigner.Info.SetString("CFBundleIdentifier", CFBundleIdentifier + "_" + Config.OverrideBundleName); } } CodeSigner.PerformSigning(); } // Stick in the iTunesArtwork PNG if available string iTunesArtworkPath = Path.Combine(Config.BuildDirectory, "iTunesArtwork"); if (File.Exists(iTunesArtworkPath)) { Zip.UpdateFile(iTunesArtworkPath, ""); } // Save the Zip Program.Log("Compressing files into IPA.{0}", Config.bVerbose ? "" : " Only large files will be listed next, but other files are also being packaged."); FileSystem.Close(); TimeSpan ZipLength = DateTime.Now - StartTime; FileInfo FinalZipInfo = new FileInfo(Config.GetIPAPath(".ipa")); Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s)", Zip.Name, ZipLength.TotalSeconds, FinalZipInfo.Length / (1024.0f * 1024.0f))); }
/// <summary> /// 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; } }
string PickIPAAndFetchBundleIdentifier() { string PickedFilename; if (ToolsHub.ShowOpenFileDialog(ToolsHub.IpaFilter, "Choose an IPA to provide the bundle identifier", "ipa", "", ref ToolsHub.ChoosingIpaDirectory, out PickedFilename)) { FileOperations.FileSystemAdapter IPA = new FileOperations.ReadOnlyZipFileSystem(PickedFilename); if (IPA != null) { byte[] InfoData = IPA.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(InfoData)); string BundleID; if (Info.GetString("CFBundleIdentifier", out BundleID)) { return BundleID; } } } return null; }
/// <summary> /// 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(); }
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; }