// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.IO; using System.Diagnostics; using System.Xml.Linq; using EpicGames.Core; using UnrealBuildBase; using Microsoft.Extensions.Logging; namespace UnrealBuildTool { class UEDeployTVOS : UEDeployIOS { public UEDeployTVOS(ILogger InLogger) : base(InLogger) { } protected override string GetTargetPlatformName() { return "TVOS"; } public static bool GenerateTVOSPList(string ProjectDirectory, bool bIsUnrealGame, string GameName, bool bIsClient, string ProjectName, string InEngineDir, string AppDirectory, UnrealPluginLanguage? UPL, string? BundleID, ILogger Logger) { // @todo tvos: THIS! // generate the Info.plist for future use string BuildDirectory = ProjectDirectory + "/Build/TVOS"; bool bSkipDefaultPNGs = false; string IntermediateDirectory = (bIsUnrealGame ? InEngineDir : ProjectDirectory) + "/Intermediate/TVOS"; 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 string BundleDisplayName; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleDisplayName", out BundleDisplayName); // bundle identifier string BundleIdentifier; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleIdentifier", out BundleIdentifier); if (!string.IsNullOrEmpty(BundleID)) { BundleIdentifier = BundleID; } // bundle name string BundleName; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleName", out BundleName); // short version string string BundleShortVersion; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "VersionInfo", out BundleShortVersion); // required capabilities string RequiredCaps = "\t\tarm64\n"; // minimum iOS version string MinVersionSetting = ""; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "MinimumiOSVersion", out MinVersionSetting); string MinVersion = GetMinimumOSVersion(MinVersionSetting, Logger); // extra plist data string ExtraData = ""; Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "AdditionalPlistData", out 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 StringBuilder(); 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, out bool bSupportsPortrait, out bool bSupportsLandscape) { bSupportsLandscape = bSupportsPortrait = true; return GenerateTVOSPList(ProjectDirectory, bIsUnrealGame, GameName, bIsClient, ProjectName, InEngineDir, AppDirectory, null, BundleID, Logger); } } }