// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml.Linq; namespace UnrealBuildTool { class UEDeployVisionOS : UEDeployIOS { public UEDeployVisionOS(ILogger InLogger) : base(InLogger) { } protected override string GetTargetPlatformName() { return "VisionOS"; } public static bool GenerateVisionOSPList(string ProjectDirectory, bool bIsUnrealGame, string GameName, bool bIsClient, string ProjectName, string InEngineDir, string AppDirectory, UnrealPluginLanguage? UPL, string? BundleID, ILogger Logger) { // @todo visionos: THIS! // generate the Info.plist for future use string BuildDirectory = ProjectDirectory + "/Build/VisionOS"; bool bSkipDefaultPNGs = false; string IntermediateDirectory = (bIsUnrealGame ? InEngineDir : ProjectDirectory) + "/Intermediate/VisionOS"; string PListFile = IntermediateDirectory + "/" + GameName + "-Info.plist"; // @todo tvos: This is really nasty - both IOS and TVOS are setting static vars VersionUtilities.BuildDirectory = BuildDirectory; VersionUtilities.GameName = GameName; // read the old file string OldPListData = File.Exists(PListFile) ? File.ReadAllText(PListFile) : ""; // get the settings from the ini file // plist replacements // @todo tvos: Are we going to make TVOS specific .ini files? DirectoryReference? DirRef = bIsUnrealGame ? (!string.IsNullOrEmpty(UnrealBuildTool.GetRemoteIniPath()) ? new DirectoryReference(UnrealBuildTool.GetRemoteIniPath()!) : null) : new DirectoryReference(ProjectDirectory); ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirRef, UnrealTargetPlatform.IOS); // bundle display name Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleDisplayName", out string BundleDisplayName); // bundle identifier Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleIdentifier", out string BundleIdentifier); if (!string.IsNullOrEmpty(BundleID)) { BundleIdentifier = BundleID; } // bundle name Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleName", out string BundleName); // short version string Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "VersionInfo", out string BundleShortVersion); // required capabilities string RequiredCaps = "\t\tarm64\n"; // minimum iOS version Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "MinimumiOSVersion", out string MinVersionSetting); string MinVersion = GetMinimumOSVersion(MinVersionSetting, Logger); // extra plist data Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "AdditionalPlistData", out string ExtraData); // create the final display name, including converting all entities for XML use string FinalDisplayName = BundleDisplayName.Replace("[PROJECT_NAME]", ProjectName).Replace("_", ""); FinalDisplayName = FinalDisplayName.Replace("&", "&"); FinalDisplayName = FinalDisplayName.Replace("\"", """); FinalDisplayName = FinalDisplayName.Replace("\'", "'"); FinalDisplayName = FinalDisplayName.Replace("<", "<"); FinalDisplayName = FinalDisplayName.Replace(">", ">"); // generate the plist file StringBuilder Text = new(); Text.AppendLine(""); Text.AppendLine(""); Text.AppendLine(""); Text.AppendLine(""); Text.AppendLine("\tCFBundleDevelopmentRegion"); Text.AppendLine("\ten"); Text.AppendLine("\tCFBundleDisplayName"); Text.AppendLine(string.Format("\t{0}", EncodeBundleName(BundleDisplayName, ProjectName))); Text.AppendLine("\tCFBundleExecutable"); string BundleExecutable = bIsUnrealGame ? (bIsClient ? "UnrealClient" : "UnrealGame") : (bIsClient ? GameName + "Client" : GameName); Text.AppendLine(string.Format("\t{0}", BundleExecutable)); Text.AppendLine("\tCFBundleIdentifier"); Text.AppendLine(string.Format("\t{0}", BundleIdentifier.Replace("[PROJECT_NAME]", ProjectName).Replace("_", ""))); Text.AppendLine("\tCFBundleInfoDictionaryVersion"); Text.AppendLine("\t6.0"); Text.AppendLine("\tCFBundleName"); Text.AppendLine(string.Format("\t{0}", EncodeBundleName(BundleName, ProjectName))); Text.AppendLine("\tCFBundlePackageType"); Text.AppendLine("\tAPPL"); Text.AppendLine("\tCFBundleSignature"); Text.AppendLine("\t????"); Text.AppendLine("\tCFBundleVersion"); Text.AppendLine(string.Format("\t{0}", VersionUtilities.UpdateBundleVersion(OldPListData, InEngineDir))); Text.AppendLine("\tCFBundleShortVersionString"); Text.AppendLine(string.Format("\t{0}", BundleShortVersion)); Text.AppendLine("\tLSRequiresIPhoneOS"); Text.AppendLine("\t"); Text.AppendLine("\tUIRequiredDeviceCapabilities"); Text.AppendLine("\t"); foreach (string Line in RequiredCaps.Split("\r\n".ToCharArray())) { if (!string.IsNullOrWhiteSpace(Line)) { Text.AppendLine(Line); } } Text.AppendLine("\t"); Text.AppendLine("\tTVTopShelfImage"); Text.AppendLine("\t"); Text.AppendLine("\t\tTVTopShelfPrimaryImageWide"); Text.AppendLine("\t\tTop Shelf Image Wide"); Text.AppendLine("\t"); Text.AppendLine("\tCFBundleIcons"); Text.AppendLine("\t"); Text.AppendLine("\t\tCFBundlePrimaryIcon"); Text.AppendLine("\t\tApp Icon"); Text.AppendLine("\t"); Text.AppendLine("\tUILaunchStoryboardName"); Text.AppendLine("\tLaunchScreen"); // write the iCloud container identifier, if present in the old file if (!string.IsNullOrEmpty(OldPListData)) { int index = OldPListData.IndexOf("ICloudContainerIdentifier"); if (index > 0) { index = OldPListData.IndexOf("", index) + 8; int length = OldPListData.IndexOf("", index) - index; string ICloudContainerIdentifier = OldPListData.Substring(index, length); Text.AppendLine("\tICloudContainerIdentifier"); Text.AppendLine(string.Format("\t{0}", ICloudContainerIdentifier)); } } Text.AppendLine(""); Text.AppendLine(""); // Create the intermediate directory if needed if (!Directory.Exists(IntermediateDirectory)) { Directory.CreateDirectory(IntermediateDirectory); } if (UPL != null) { // Allow UPL to modify the plist here XDocument XDoc; try { XDoc = XDocument.Parse(Text.ToString()); } catch (Exception e) { throw new BuildException("plist is invalid {0}\n{1}", e, Text.ToString()); } XDoc.DocumentType!.InternalSubset = ""; UPL.ProcessPluginNode("None", "iosPListUpdates", "", ref XDoc); string result = XDoc.Declaration?.ToString() + "\n" + XDoc.ToString().Replace("", ""); File.WriteAllText(PListFile, result); } else { File.WriteAllText(PListFile, Text.ToString()); } if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { if (!Directory.Exists(AppDirectory)) { Directory.CreateDirectory(AppDirectory); } File.WriteAllText(AppDirectory + "/Info.plist", Text.ToString()); } return bSkipDefaultPNGs; } public override bool GeneratePList(FileReference? ProjectFile, UnrealTargetConfiguration Config, string ProjectDirectory, bool bIsUnrealGame, string GameName, bool bIsClient, string ProjectName, string InEngineDir, string AppDirectory, List UPLScripts, string? BundleID, bool bBuildAsFramework) { return GenerateVisionOSPList(ProjectDirectory, bIsUnrealGame, GameName, bIsClient, ProjectName, InEngineDir, AppDirectory, null, BundleID, Logger); } } }