using System; using System.Collections.Generic; using System.IO; using System.Xml; #if UNITY_EDITOR_OSX using UnityEditor.iOS.Xcode; #endif using UnityEngine; using Ubisoft.Hotel.Localisation; using Ubisoft.Hotel.Localisation.Editor; namespace Ubisoft.Hotel.Package.Editor { public class PlatformBuildProperty_iOS : PackageProperty { public const string ATTRIBUTE_ENTITLEMENTS_REQUIRED_NAME = "m_entitlementsRequired"; private const string PROPERTY_BUNDLE_LOCALISATIONS = "CFBundleLocalizations"; private const string PROPERTY_BUNDLE_DISPLAY_NAME = "CFBundleDisplayName"; private const string PROPERTY_BUNDLE_NAME = "CFBundleName"; private const string PROPERTY_HAS_LOCALISED_DISPLAY_NAME = "LSHasLocalizedDisplayName"; private const string PROPERTY_KEY = "key"; private const string INFOPLIST_MAIN_NODE_NAME = "plist/dict"; public enum EEntitlement { SignInWithApple, GameCenter } public enum EFramework { libz_tbd } public static PlatformBuildProperty_iOS CreateInstanceWithParams(string name) { PlatformBuildProperty_iOS returnValue = CreateInstance(); returnValue.name = name; return returnValue; } private static List s_entitlementKeys; public static List EntitlementKeys { get { if (s_entitlementKeys == null) { s_entitlementKeys = new List(Enum.GetNames(typeof(EEntitlement))); } return s_entitlementKeys; } } private static List s_entitlements; public static List Entitlements { get { if (s_entitlements == null) { s_entitlements = new List(); Array values = Enum.GetValues(typeof(EEntitlement)); foreach (EEntitlement entitlement in values) { s_entitlements.Add(entitlement); } } return s_entitlements; } } public static string GetKeyFromEntitlement(EEntitlement entitlement) { return EntitlementKeys[(int)entitlement]; } private static List s_frameworkKeys; public static List FrameworkKeys { get { if (s_frameworkKeys == null) { s_frameworkKeys = new List(Enum.GetNames(typeof(EFramework))) { [(int)EFramework.libz_tbd] = "libz.tbd" }; } return s_frameworkKeys; } } private static List s_frameworks; public static List Frameworks { get { if (s_frameworks == null) { s_frameworks = new List(); Array values = Enum.GetValues(typeof(EFramework)); foreach (EFramework framework in values) { s_frameworks.Add(framework); } } return s_frameworks; } } public static string GetKeyFromFramework(EFramework framework) { return FrameworkKeys[(int)framework]; } [SerializeField] private List m_entitlementsRequired; [SerializeField] private List m_frameworksRequired; [SerializeField] private EBoolean m_enableBitCode; [SerializeField] private List m_infoPlistProperties; // Raw info plist properties. Provide a string that contains the paragraph that you want to add to the info plist document verbatim. // Use this instead of m_infoPlistProperties when you need to address complicate cases that are not supported by m_infoPlistProperties // such as dictionaries that contain other dictionaries or arrays which are not supported by m_infoPlistProperties because Unity // doesn't allow recursive Serializable properties. [SerializeField] private List m_infoPlistRawProperties; public void Clear() { if (m_entitlementsRequired != null) { m_entitlementsRequired.Clear(); } if (m_frameworksRequired != null) { m_frameworksRequired.Clear(); } EnableBitCode = EBoolean.AsUnitySettings; if (m_infoPlistProperties != null) { m_infoPlistProperties.Clear(); } if (m_infoPlistRawProperties != null) { m_infoPlistRawProperties.Clear(); } } public override void Apply(BuildSuite buildSuite) { if (buildSuite != null) { buildSuite.OrderProperty(this); } } public void Join(PlatformBuildProperty_iOS property) { if (property != null) { // Entitlements if (property.EntitlementsRequired != null) { int count = property.EntitlementsRequired.Count; for (int i = 0; i < count; i++) { AddEntitlement(property.EntitlementsRequired[i]); } } // Frameworks if (property.FrameworksRequired != null) { int count = property.FrameworksRequired.Count; for (int i = 0; i < count; i++) { AddFramework(property.FrameworksRequired[i]); } } // EnableBitCode if (property.EnableBitCode != EBoolean.AsUnitySettings) { if (EnableBitCode == EBoolean.AsUnitySettings) { EnableBitCode = property.EnableBitCode; } else if (EnableBitCode != property.EnableBitCode) { Debug.EditorLogError("Conflict: EnableBitCode needs to be enabled or disabled for all properties: " + Debug.FormatTextInUserContext(property.name)); } } // infoPList properties if (property.m_infoPlistProperties != null) { AddToInfoPlistProperties(property.m_infoPlistProperties, property.Name); } // infoPList raw properties AddInfoPlistRawProperties(property.m_infoPlistRawProperties); } } public List EntitlementsRequired { get { return m_entitlementsRequired; } set { m_entitlementsRequired = value; } } public bool ContainsEntitlement(EEntitlement entitlement) { return EntitlementsRequired != null && EntitlementsRequired.Contains(entitlement); } public void AddEntitlement(EEntitlement entitlement) { if (EntitlementsRequired == null) { EntitlementsRequired = new List(); } if (!EntitlementsRequired.Contains(entitlement)) { EntitlementsRequired.Add(entitlement); } } public void RemoveEntitlement(EEntitlement entitlement) { if (EntitlementsRequired != null && EntitlementsRequired.Contains(entitlement)) { _ = EntitlementsRequired.Remove(entitlement); } } public List FrameworksRequired { get { return m_frameworksRequired; } set { m_frameworksRequired = value; } } public bool ContainsFramework(EFramework framework) { return FrameworksRequired != null && FrameworksRequired.Contains(framework); } public void AddFramework(EFramework framework) { if (FrameworksRequired == null) { FrameworksRequired = new List(); } if (!FrameworksRequired.Contains(framework)) { FrameworksRequired.Add(framework); } } public void RemoveEntitlement(EFramework framework) { if (FrameworksRequired != null && FrameworksRequired.Contains(framework)) { _ = FrameworksRequired.Remove(framework); } } public EBoolean EnableBitCode { get { return m_enableBitCode; } set => m_enableBitCode = value; } public void AddInfoPlistProperty(InfoPlistProperty value) { if (m_infoPlistProperties == null) { m_infoPlistProperties = new List(); } m_infoPlistProperties.Add(value); string languageIsoCode; HashSet languageIsoCodes = new HashSet(); foreach (InfoPlistProperty property in m_infoPlistProperties) { if (property.IsLocalisedStringType()) { foreach (LocalisedString localisedString in property.ValueAsLocalisedString) { languageIsoCode = LocalisationUtils.LanguageIdToIsoCode(localisedString.LanguageId); _ = languageIsoCodes.Add(languageIsoCode); } } } AddLocalisationsProperty(languageIsoCodes); } public void AddInfoPlistRawProperty(string value) { if (!string.IsNullOrEmpty(value)) { try { ValidateRawProperty(value); if (m_infoPlistRawProperties == null) { m_infoPlistRawProperties = new List(); } m_infoPlistRawProperties.Add(value); } catch (Exception e) { Debug.EditorLogError($"InfoPlist raw property malformed: name:{Debug.FormatTextInUserContext(Name)} value: {value} exception: {e.Message}"); } } } public void AddInfoPlistRawProperties(List value) { if (value != null) { if (m_infoPlistRawProperties == null) { m_infoPlistRawProperties = new List(); } foreach (string p in value) { AddInfoPlistRawProperty(p); } } } public void AddAppNameLocalisations(List appNameLocalisations) { // Update AppName properties based on appNameLocalisations // BundleDisplayName property string key = PROPERTY_BUNDLE_DISPLAY_NAME; InfoPlistProperty appNameProperty = GetInfoPlistPropertyByKey(key); if (appNameProperty == null) { appNameProperty = new InfoPlistProperty(key, new List()); AddInfoPlistProperty(appNameProperty); } appNameProperty.ValueAsLocalisedString.Clear(); // BundleName property key = PROPERTY_BUNDLE_NAME; InfoPlistProperty appShortNameProperty = GetInfoPlistPropertyByKey(key); if (appShortNameProperty == null) { appShortNameProperty = new InfoPlistProperty(key, new List()); AddInfoPlistProperty(appShortNameProperty); } appShortNameProperty.ValueAsLocalisedString.Clear(); LocalisedString localisedString; int count = appNameLocalisations.Count; for (int i = 0; i < count; i++) { // App name localisedString = new LocalisedString(appNameLocalisations[i].LanguageId, appNameLocalisations[i].Name); appNameProperty.ValueAsLocalisedString.Add(localisedString); // App short name localisedString = new LocalisedString(appNameLocalisations[i].LanguageId, appNameLocalisations[i].ShortName); appShortNameProperty.ValueAsLocalisedString.Add(localisedString); } // HasLocalizedDisplayName property key = PROPERTY_HAS_LOCALISED_DISPLAY_NAME; InfoPlistProperty property = GetInfoPlistPropertyByKey(key); if (property == null) { property = new InfoPlistProperty(key, true); AddInfoPlistProperty(property); } property.Value = "1"; } private void AddLocalisationsProperty(HashSet languageIsoCodes) { if (languageIsoCodes.Count > 0) { // Update localisation property based on languageIsoCodes InfoPlistProperty localisationProperty = GetInfoPlistPropertyByKey(PROPERTY_BUNDLE_LOCALISATIONS); if (localisationProperty == null) { localisationProperty = new InfoPlistProperty(PROPERTY_BUNDLE_LOCALISATIONS, new List(), true); AddInfoPlistProperty(localisationProperty); } localisationProperty.ValueAsList.Clear(); InfoPlistItem item; int i = 0; foreach (string key in languageIsoCodes) { item = new InfoPlistItem(i.ToString(), key); localisationProperty.ValueAsList.Add(item); ++i; } } } private void AddToInfoPlistProperties(List value, string propertyName) { if (value != null) { InfoPlistProperty property; int count = value.Count; for (int i = 0; i < count; i++) { if (value[i] == null) { Debug.EditorLogError("Property " + Debug.FormatTextInCodeContext(propertyName) + " is attempting to define a null " + Debug.FormatTextInCodeContext("InfoPlistProperty")); } else if (!value[i].IsValid()) { Debug.EditorLogError("InfoPlistProperty" + " " + Debug.FormatTextInUserContext(value[i].Key) + " defined by property " + Debug.FormatTextInCodeContext(propertyName) + " is invalid. Error: " + value[i].GetDebugIsValidDescription()); } else { property = GetInfoPlistPropertyByKey(value[i].Key); if (property == null) { AddInfoPlistProperty(value[i].Clone()); } else { if (property.IsMultipleType() && value[i].IsMultipleType()) { if (value[i].ValueAsList != null) { int jcount = value[i].ValueAsList.Count; for (int j = 0; j < jcount; j++) { if (value[i].ValueAsList[j] != null) { property.ValueAsList.Add(value[i].ValueAsList[j].Clone()); } } } } else { Debug.EditorLogError("Duplicate InfoPlistProperty " + Debug.FormatTextInUserContext(property.Key) + " could not be merged."); } } } } } } private void AddPropertyToLocalisedStrings(InfoPlistProperty property, Dictionary> localisedStrings) { if (property.IsLocalisedStringType()) { ELanguageId languageId; int jcount = property.ValueAsLocalisedString.Count; for (int j = 0; j < jcount; j++) { languageId = property.ValueAsLocalisedString[j].LanguageId; if (!localisedStrings.ContainsKey(languageId)) { localisedStrings.Add(languageId, new Dictionary()); } if (localisedStrings[languageId].ContainsKey(property.Key)) { Debug.EditorLogError("InfoPlist property " + Debug.FormatTextInUserContext(property.Key) + " defined more than once"); } else { localisedStrings[languageId].Add(property.Key, property.ValueAsLocalisedString[j].Value); } } } } private InfoPlistProperty GetInfoPlistPropertyByKey(string key) { InfoPlistProperty returnValue = null; if (m_infoPlistProperties != null) { int count = m_infoPlistProperties.Count; for (int i = 0; i < count && returnValue == null; i++) { if (m_infoPlistProperties[i].Key == key) { returnValue = m_infoPlistProperties[i]; } } } return returnValue; } public void PreBuild() { } public void PostBuild(string pathToBuiltProject) { #if UNITY_EDITOR_OSX Debug.EditorLog("PlatformProperty_iOS.OnPostprocessBuild " + pathToBuiltProject); string pbxProjectPath = PBXProject.GetPBXProjectPath(pathToBuiltProject); ProcessInfoPlist(pathToBuiltProject); PBXProject pbxProject = new PBXProject(); pbxProject.ReadFromString(File.ReadAllText(pbxProjectPath)); ProcessEntitlements(pbxProjectPath, pbxProject); ProcessFrameworks(pbxProject); // Patches FRAMEWORK_SEARCH_PATHS and LD_RUNPATH_SEARCH_PATHS to make sure Pods frameworks can be linked, // which we found out that wouldn't happen when Orion MonetisationCore package is included in the app's // manifest.json string target = GetMainTargetGuid(pbxProject); PatchBuildProperty(pbxProject, target, "FRAMEWORK_SEARCH_PATHS"); PatchBuildProperty(pbxProject, target, "LD_RUNPATH_SEARCH_PATHS"); string pbxProjectAsString = pbxProject.WriteToString(); // pbxProject.SetBuildProperty(target, "ENABLE_BITCODE", value) doesn't change the scheme values so // we need to use brute force (https://forum.unity.com/threads/bitcode-bundle-could-not-be-generated-issue.897590/#post-5909051) if (EnableBitCode != EBoolean.AsUnitySettings) { string currentValue = EnableBitCode == EBoolean.True ? "NO" : "YES"; string newValue = EnableBitCode == EBoolean.True ? "YES" : "NO"; pbxProjectAsString = pbxProjectAsString.Replace($"ENABLE_BITCODE = {currentValue};", $"ENABLE_BITCODE = {newValue};"); } File.WriteAllText(pbxProjectPath, pbxProjectAsString); #endif } #if UNITY_EDITOR_OSX private void PatchBuildProperty(PBXProject pbxProject, string target, string name) { string value = pbxProject.GetBuildPropertyForAnyConfig(target, name); if (!string.IsNullOrEmpty(value)) { value = value.Trim(); // If the build property is not empty and it doesn't contain the 'inherited' keyword then // we need to add it to make sure pods will work out if (!value.Contains("$(inherited)")) { string newValue = pbxProject.GetBuildPropertyForAnyConfig(target, name); pbxProject.SetBuildProperty(target, name, "$(inherited)"); pbxProject.AddBuildProperty(target, name, value); Debug.EditorLog($"Patching {name} in { target } build property: {value} -> {newValue}"); } } } private void ProcessEntitlements(string pbxProjectPath, PBXProject pbxProject) { if (EntitlementsRequired != null) { string targetGuid = GetMainTargetGuid(pbxProject); int count = EntitlementsRequired.Count; if (count > 0) { ProjectCapabilityManager projCapability = GetProjectCapabilityManager(pbxProjectPath, "Entitlements.entitlements", targetGuid); EEntitlement entitlement; for (int i = 0; i < count; i++) { entitlement = EntitlementsRequired[i]; switch (entitlement) { case EEntitlement.SignInWithApple: projCapability.AddSignInWithApple(); break; case EEntitlement.GameCenter: projCapability.AddGameCenter(); break; } } projCapability.WriteToFile(); } } } private void ProcessFrameworks(PBXProject pbxProject) { if (FrameworksRequired != null) { string targetGuid = GetFrameworkTargetGuid(pbxProject); int count = FrameworksRequired.Count; if (count > 0) { string key; for (int i = 0; i < count; i++) { key = GetKeyFromFramework(FrameworksRequired[i]); if (string.IsNullOrEmpty(key)) { Debug.EditorLogError($"Error when processing frameworks: No valid key found for framework {Debug.FormatTextInCodeContext(FrameworksRequired[i].ToString())}"); } else { pbxProject.AddFrameworkToProject(targetGuid, key, false); } } } } } private void ProcessInfoPlist(string pathToBuiltProject) { try { string file = Path.Combine(pathToBuiltProject, "Info.plist"); if (!File.Exists(file)) { Debug.EditorLogError($"Unable to process Info.plist because no file found at {file}"); return; } XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(file); XmlNode dict = xmlDocument.SelectSingleNode(INFOPLIST_MAIN_NODE_NAME); if (dict != null) { Dictionary> localisedStrings = new Dictionary>(); PlistItem pListItem; InfoPlistProperty property; int count = m_infoPlistProperties.Count; for (int i = 0; i < count; i++) { property = m_infoPlistProperties[i]; if (property != null && property.IsValid()) { pListItem = GetPlistItem(dict, property.Key); if (pListItem != null) { _ = dict.RemoveChild(pListItem.ItemKeyNode); _ = dict.RemoveChild(pListItem.ItemValueNode); } XmlElement valueNode = property.GetValueNode(xmlDocument); if (valueNode == null) { Debug.EditorLogWarning(property.ToString() + " not supported"); } else { XmlElement keyNode = xmlDocument.CreateElement(PROPERTY_KEY); keyNode.InnerText = property.Key; _ = dict.AppendChild(keyNode); _ = dict.AppendChild(valueNode); } AddPropertyToLocalisedStrings(property, localisedStrings); } else { if (property == null) { Debug.EditorLogError("Null InfoPlist property is not valid"); } else { Debug.EditorLogError("InfoPlist property " + Debug.FormatTextInUserContext(property.Key) + " is not valid: " + property.GetDebugIsValidDescription()); } } } // Add raw properties if (m_infoPlistRawProperties != null) { foreach (string p in m_infoPlistRawProperties) { if (!string.IsNullOrEmpty(p)) { AddRawPropertiesToXmlDocument(xmlDocument, p); } } } xmlDocument.Save(file); // Remove extra garbage added by the XmlDocument save UpdateStringInFile(file, "dtd\"[]>", "dtd\">"); ProcessInfoPlistStrings(pathToBuiltProject, localisedStrings); } else { Debug.EditorLogError("Info.plist is not valid"); } } catch (Exception e) { Debug.EditorLogError("Unable to update Info.plist: " + e); } } #endif private static void ValidateRawProperty(string value) { var xmlData = @""; xmlData += @""; xmlData += @""; xmlData += ""; xmlData += value; xmlData += ""; xmlData += ""; XmlDocument doc = new XmlDocument(); doc.LoadXml(xmlData); } #if UNITY_EDITOR_OSX private static void AddRawPropertiesToXmlDocument(XmlDocument xmlDoc, string strXml) { XmlDocumentFragment xmlDocFragment = xmlDoc.CreateDocumentFragment(); xmlDocFragment.InnerXml = strXml; _ = xmlDoc.SelectSingleNode(INFOPLIST_MAIN_NODE_NAME).AppendChild(xmlDocFragment); } private void ProcessInfoPlistStrings(string pathToBuiltProject, Dictionary> localisedStrings) { if (localisedStrings.Count > 0) { Dictionary availableGUIDs = new Dictionary(); HashSet languageKeys = new HashSet(); PBXProject proj = new PBXProject(); string projPath = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj"; proj.ReadFromString(File.ReadAllText(projPath)); string strMainGUID = ""; string mainVariantGUID = ""; string defaultLanguageIsoCode = LocalisationUtils.LanguageIdToIsoCode(LocalisationUtils.DEFAULT_LANGUAGE_ID); // Save foreach (KeyValuePair> keyValuePair in localisedStrings) { string languageIsoCode = LocalisationUtils.LanguageIdToIsoCode(keyValuePair.Key); _ = languageKeys.Add(languageIsoCode); string fileContent = ""; string languageFolder = pathToBuiltProject + "/" + languageIsoCode + ".lproj"; string languageFile = languageFolder + "/InfoPlist.strings"; if (Directory.Exists(languageFolder)) { if (File.Exists(languageFile)) { fileContent = File.ReadAllText(languageFile); } } else { _ = Directory.CreateDirectory(languageFolder); } foreach (KeyValuePair translations in keyValuePair.Value) { fileContent += "\"" + translations.Key + "\" = \"" + translations.Value + "\";\n"; } File.WriteAllText(languageFile, fileContent); string pbxFilePath = languageIsoCode + ".lproj/InfoPlist.strings"; string projectGUID = proj.AddFile(pbxFilePath, languageFile); availableGUIDs.Add(languageIsoCode, projectGUID); if (languageIsoCode == defaultLanguageIsoCode) { string languageMainFolder = pathToBuiltProject + "/strings.lproj"; if (!Directory.Exists(languageMainFolder)) { _ = Directory.CreateDirectory(languageMainFolder); } string languageMainFile = languageMainFolder + "/InfoPlist.strings"; File.WriteAllText(languageMainFile, fileContent); // PBXFileReference string pbxMainFilePath = "strings.lproj/InfoPlist.strings"; strMainGUID = proj.AddFile(pbxMainFilePath, languageMainFile); proj.RemoveFile(strMainGUID); // PBXGroup mainVariantGUID = proj.AddFile(pbxMainFilePath + "_tmp", languageMainFile + "_tmp"); proj.RemoveFile(mainVariantGUID); File.Delete(languageMainFile); Directory.Delete(languageMainFolder); } } foreach (KeyValuePair p in availableGUIDs) { proj.RemoveFile(p.Value); } string writeContent = proj.WriteToString(); string writeContentFinal = ""; byte[] byteArray = HtString.GetBytes(writeContent, true); MemoryStream streamMem = new MemoryStream(byteArray); StreamReader streamMemReader = new StreamReader(streamMem); while (!streamMemReader.EndOfStream) { string line = streamMemReader.ReadLine(); if (line.Contains("/* CustomTemplate */ = {")) { writeContentFinal += line + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += "\t\t\t\t" + mainVariantGUID + " /* InfoPlist.strings */," + "\n"; } else if (line.Contains("Begin PBXResourcesBuildPhase section")) { writeContentFinal += line + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += streamMemReader.ReadLine() + "\n"; writeContentFinal += "\t\t\t\t" + strMainGUID + " /* InfoPlist.strings in Resources */," + "\n"; } else if (line.Contains("Begin PBXFileReference section")) { writeContentFinal += line + "\n"; writeContentFinal += "\t\t" + strMainGUID + " /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = " + mainVariantGUID + " /* InfoPlist.strings */; };" + "\n"; foreach (KeyValuePair item in availableGUIDs) { writeContentFinal += "\t\t" + item.Value + " /* " + item.Key + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + item.Key + "; path = " + item.Key + ".lproj/InfoPlist.strings; sourceTree = \"\"; };" + "\n"; } } else if (line.Contains("End PBXSourcesBuildPhase section")) { writeContentFinal += line + "\n"; writeContentFinal += "\n"; writeContentFinal += "/* Begin PBXVariantGroup section */\n"; writeContentFinal += "\t\t" + mainVariantGUID + " /* InfoPlist.strings */ = {\n"; writeContentFinal += "\t\t\tisa = PBXVariantGroup;\n"; writeContentFinal += "\t\t\tchildren = (\n"; foreach (KeyValuePair item in availableGUIDs) { writeContentFinal += "\t\t\t\t" + item.Value + " /* " + item.Key + " */," + "\n"; } writeContentFinal += "\t\t\t);\n"; writeContentFinal += "\t\t\tname = InfoPlist.strings;\n"; writeContentFinal += "\t\t\tsourceTree = \"\";\n"; writeContentFinal += "\t\t};\n"; writeContentFinal += "/* End PBXVariantGroup section */\n"; writeContentFinal += "\n"; } else if (!line.Contains($"{defaultLanguageIsoCode}.lproj/InfoPlist.strings;")) { writeContentFinal += line + "\n"; } } streamMemReader.Close(); streamMem.Close(); File.WriteAllText(projPath, writeContentFinal); } } private string GetMainTargetGuid(PBXProject pbxProject) { #if UNITY_2019_1_OR_NEWER return pbxProject.GetUnityMainTargetGuid(); #else return PBXProject.GetUnityTargetName(); #endif } private string GetFrameworkTargetGuid(PBXProject pbxProject) { #if UNITY_2019_3_OR_NEWER return pbxProject.GetUnityFrameworkTargetGuid(); #else return pbxProject.TargetGuidByName(PBXProject.GetUnityTargetName()); #endif } private ProjectCapabilityManager GetProjectCapabilityManager(string pbxProjectPath, string entitlementsPath, string targetGuid) { #if UNITY_2019_1_OR_NEWER return new ProjectCapabilityManager(pbxProjectPath, entitlementsPath, null, targetGuid); #else return new ProjectCapabilityManager(pbxProjectPath, entitlementsPath, targetGuid); #endif } #region utils internal class PlistItem { internal XmlNode ItemKeyNode { get; set; } internal XmlNode ItemValueNode { get; set; } internal PlistItem(XmlNode keyNode, XmlNode valueNode) { ItemKeyNode = keyNode; ItemValueNode = valueNode; } } internal PlistItem GetPlistItem(XmlNode dict, string name) { for (int i = 0; i < dict.ChildNodes.Count - 1; i++) { XmlNode node = dict.ChildNodes.Item(i); if (node.Name.ToLower().Equals(PROPERTY_KEY) && node.InnerText.ToLower().Equals(name.Trim().ToLower())) { XmlNode valueNode = dict.ChildNodes.Item(i + 1); if (!valueNode.Name.ToLower().Equals(PROPERTY_KEY)) { return new PlistItem(node, valueNode); } else { Debug.EditorLogError($"Missing value for '{Debug.FormatTextInCodeContext(PROPERTY_KEY)}' for Info.plist element {Debug.FormatTextInUserContext(name)}"); } } } return null; } internal void UpdateStringInFile(string file, string subject, string replacement) { try { if (!File.Exists(file)) { return; } string processedContents = ""; using (StreamReader sr = new StreamReader(file)) { while (sr.Peek() >= 0) { string line = sr.ReadLine(); processedContents += line.Replace(subject, replacement) + "\n"; } } File.Delete(file); using (StreamWriter streamWriter = File.CreateText(file)) { streamWriter.Write(processedContents); } } catch (Exception e) { Debug.EditorLogError("Unable to update string in file: " + e); } } #endregion #endif } }