private InstalledPlatformInfo()
        {
            List <string>   InstalledPlatforms;
            ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, (DirectoryReference)null, UnrealTargetPlatform.Unknown);

            if (Ini.GetArray("InstalledPlatforms", "InstalledPlatformConfigurations", out InstalledPlatforms))
            {
                foreach (string InstalledPlatform in InstalledPlatforms)
                {
                    ParsePlatformConfiguration(InstalledPlatform);
                }
            }
        }
Example #2
0
        static InstalledPlatformInfo()
        {
            List <string>   InstalledPlatforms;
            ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, (DirectoryReference)null, UnrealTargetPlatform.Unknown);

            bool bHasInstalledPlatformInfo;

            if (Ini.TryGetValue("InstalledPlatforms", "HasInstalledPlatformInfo", out bHasInstalledPlatformInfo) && bHasInstalledPlatformInfo)
            {
                InstalledPlatformConfigurations = new List <InstalledPlatformConfiguration>();
                if (Ini.GetArray("InstalledPlatforms", "InstalledPlatformConfigurations", out InstalledPlatforms))
                {
                    foreach (string InstalledPlatform in InstalledPlatforms)
                    {
                        ParsePlatformConfiguration(InstalledPlatform);
                    }
                }
            }
        }
Example #3
0
        private void GetAppPrivileges(ConfigHierarchy EngineIni, StringBuilder Text)
        {
            List <string> AppPrivileges;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "AppPrivileges", out AppPrivileges);
            if (AppPrivileges != null)
            {
                foreach (string Privilege in AppPrivileges)
                {
                    string TrimmedPrivilege = Privilege.Trim(' ');
                    if (TrimmedPrivilege != "")
                    {
                        string PrivilegeString = string.Format("\t\t\t<uses-privilege ml:name=\"{0}\"/>", TrimmedPrivilege);
                        if (!Text.ToString().Contains(PrivilegeString))
                        {
                            Text.AppendLine(PrivilegeString);
                        }
                    }
                }
            }
        }
Example #4
0
        public string GenerateManifest(string ProjectName, bool bForDistribution, string Architecture)
        {
            ConfigHierarchy GameIni        = GetConfigCacheIni(ConfigHierarchyType.Game);
            string          ProjectVersion = string.Empty;

            GameIni.GetString("/Script/EngineSettings.GeneralProjectSettings", "ProjectVersion", out ProjectVersion);
            if (string.IsNullOrEmpty(ProjectVersion))
            {
                ProjectVersion = "1.0.0.0";
            }

            ConfigHierarchy EngineIni = GetConfigCacheIni(ConfigHierarchyType.Engine);
            Int32           VersionCode;

            EngineIni.GetInt32("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "VersionCode", out VersionCode);

            string SDKVersion             = GetMLSDKVersion(EngineIni);
            string PackageName            = GetPackageName(ProjectName);
            string ApplicationDisplayName = GetApplicationDisplayName(ProjectName);
            string MinimumAPILevel        = GetMinimumAPILevelRequired();
            string TargetExecutableName   = "bin/" + ProjectName;

            PackageManifest.version_name = ProjectVersion;
            PackageManifest.package      = PackageName;
            PackageManifest.version_code = Convert.ToUInt64(VersionCode);

            PackageManifest.application = new manifestApplication
            {
                sdk_version   = SDKVersion,
                min_api_level = MinimumAPILevel,
                visible_name  = ApplicationDisplayName
            };

            List <string> AppPrivileges;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "AppPrivileges", out AppPrivileges);

            List <string> ExtraComponentElements;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentElements", out ExtraComponentElements);

            // We start with 1 since there will always be a 'root' <component>
            int Size = 1;

            Size += (AppPrivileges != null) ? AppPrivileges.Count : 0;
            Size += (ExtraComponentElements != null) ? ExtraComponentElements.Count : 0;
            // Increment Size here if more elements are required in the <application> node.

            // Index used for sibling elements (app privileges, root component and any extra components)
            int CurrentIndex = 0;

            PackageManifest.application.Items = new object[Size];
            // Remove all invalid strings from the list of strings
            AppPrivileges.RemoveAll(item => item == "Invalid");
            // Privileges get added first
            for (int Index = 0; Index < AppPrivileges.Count(); ++Index)
            {
                string TrimmedPrivilege = AppPrivileges[Index].Trim(' ');
                if (TrimmedPrivilege != "")
                {
                    PackageManifest.application.Items[CurrentIndex] = new manifestApplicationUsesprivilege
                    {
                        name = TrimmedPrivilege,
                    };
                    CurrentIndex++;
                }
            }

            // Then our root component, this is important as `mldb launch` will use the first component in the manifest
            PackageManifest.application.Items[CurrentIndex] = new manifestApplicationComponent();
            manifestApplicationComponent RootComponent = (manifestApplicationComponent)PackageManifest.application.Items[CurrentIndex];

            RootComponent.name         = ".fullscreen";
            RootComponent.visible_name = ApplicationDisplayName;
            RootComponent.binary_name  = TargetExecutableName;
            RootComponent.type         = GetApplicationType();

            // Sub-elements under root <component>
            List <string> ExtraComponentSubElements;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentSubElements", out ExtraComponentSubElements);

            List <string> LocalizedAppNames;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "LocalizedAppNames", out LocalizedAppNames);

            // We start with 1 since there will always be an icon element
            int NumElementsInRootComponent = 1;

            NumElementsInRootComponent += (ExtraComponentSubElements != null) ? ExtraComponentSubElements.Count : 0;
            // If localized app names have been specified, add count of 1 for the <locale> tag.
            NumElementsInRootComponent += (LocalizedAppNames != null) ? 1 : 0;
            // Increment NumElementsInRootComponent here if more elements are required in the <component> node.

            RootComponent.Items = new object[NumElementsInRootComponent];

            // Root component icon
            Dictionary <string, manifestApplicationComponentIconTranslation> LocalizedIconsDict = new Dictionary <string, manifestApplicationComponentIconTranslation>();

            if (Directory.Exists(IconDirectory + "/Model"))
            {
                string[] IconModelSubDirectories = Directory.GetDirectories(IconDirectory + "/Model");
                foreach (var IconModelSubDirectory in IconModelSubDirectories)
                {
                    manifestApplicationComponentIconTranslation LocalizedIcon = new manifestApplicationComponentIconTranslation();
                    LocalizedIcon.language     = Path.GetFileName(IconModelSubDirectory);
                    LocalizedIcon.model_folder = GetIconModelStagingPath() + "/" + LocalizedIcon.language;
                    LocalizedIconsDict.Add(LocalizedIcon.language, LocalizedIcon);
                }
            }

            if (Directory.Exists(IconDirectory + "/Portal"))
            {
                string[] IconPortalSubDirectories = Directory.GetDirectories(IconDirectory + "/Portal");
                foreach (var IconPortalSubDirectory in IconPortalSubDirectories)
                {
                    manifestApplicationComponentIconTranslation LocalizedIcon;
                    string language = Path.GetFileName(IconPortalSubDirectory);
                    if (!LocalizedIconsDict.TryGetValue(language, out LocalizedIcon))
                    {
                        LocalizedIcon          = new manifestApplicationComponentIconTranslation();
                        LocalizedIcon.language = language;
                        LocalizedIconsDict.Add(LocalizedIcon.language, LocalizedIcon);
                    }

                    LocalizedIcon.portal_folder = GetIconPortalStagingPath() + "/" + LocalizedIcon.language;
                }
            }

            manifestApplicationComponentIconTranslation[] LocalizedIcons = new manifestApplicationComponentIconTranslation[LocalizedIconsDict.Count];
            int LocalizedIconIndex = 0;

            foreach (KeyValuePair <string, manifestApplicationComponentIconTranslation> LocalizedIconKVP in LocalizedIconsDict)
            {
                LocalizedIcons[LocalizedIconIndex++] = LocalizedIconKVP.Value;
            }

            RootComponent.Items[0] = new manifestApplicationComponentIcon();
            ((manifestApplicationComponentIcon)RootComponent.Items[0]).locale        = LocalizedIcons;
            ((manifestApplicationComponentIcon)RootComponent.Items[0]).model_folder  = GetIconModelStagingPath();
            ((manifestApplicationComponentIcon)RootComponent.Items[0]).portal_folder = GetIconPortalStagingPath();

            int RootComponentIndex = 1;

            if (ExtraComponentSubElements != null)
            {
                for (int Index = 0; Index < ExtraComponentSubElements.Count(); ++Index)
                {
                    Dictionary <string, string> NodeContent;
                    if (ConfigHierarchy.TryParse(ExtraComponentSubElements[Index], out NodeContent))
                    {
                        RootComponent.Items[RootComponentIndex] = GetComponentSubElement(NodeContent["ElementType"], NodeContent["Value"]);
                        RootComponentIndex++;
                    }
                }
            }

            // Localized app names
            if (LocalizedAppNames != null && LocalizedAppNames.Count > 0)
            {
                manifestApplicationComponentLocale LocaleTag = new manifestApplicationComponentLocale();
                LocaleTag.Items = new manifestApplicationComponentLocaleTranslation[LocalizedAppNames.Count];
                RootComponent.Items[RootComponentIndex] = LocaleTag;
                RootComponentIndex++;

                for (int i = 0; i < LocalizedAppNames.Count; ++i)
                {
                    Dictionary <string, string> NodeContent;
                    if (ConfigHierarchy.TryParse(LocalizedAppNames[i], out NodeContent))
                    {
                        LocaleTag.Items[i] = new manifestApplicationComponentLocaleTranslation
                        {
                            language     = NodeContent["LanguageCode"],
                            visible_name = NodeContent["AppName"]
                        };
                    }
                }
            }

            // Finally, add additional components
            CurrentIndex++;
            if (ExtraComponentElements != null)
            {
                for (int Index = 0; Index < ExtraComponentElements.Count(); ++Index)
                {
                    Dictionary <string, string> ComponentElement;
                    if (ConfigHierarchy.TryParse(ExtraComponentElements[Index], out ComponentElement))
                    {
                        PackageManifest.application.Items[CurrentIndex] = GetComponentElement(ComponentElement);
                        CurrentIndex++;
                    }
                }
            }

            // Wrap up serialization
            XmlSerializer           PackageManifestSerializer = new XmlSerializer(PackageManifest.GetType());
            XmlSerializerNamespaces MLNamespace = new XmlSerializerNamespaces();

            MLNamespace.Add("ml", "magicleap");
            StringWriter Writer = new StringWriter();

            PackageManifestSerializer.Serialize(Writer, PackageManifest, MLNamespace);

            // allow plugins to modify final manifest HERE
            XDocument XDoc;

            try
            {
                XDoc = XDocument.Parse(Writer.ToString());
            }
            catch (Exception e)
            {
                throw new BuildException("LuminManifest.xml is invalid {0}\n{1}", e, Writer.ToString());
            }

            UPL.ProcessPluginNode(Architecture, "luminManifestUpdates", "", ref XDoc);
            return(XDoc.ToString());
        }
        /// <summary>
        /// Parse crypto settings from INI file
        /// </summary>
        public static CryptoSettings ParseCryptoSettings(DirectoryReference InProjectDirectory, UnrealTargetPlatform InTargetPlatform)
        {
            CryptoSettings Settings = new CryptoSettings();

            ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, InProjectDirectory, InTargetPlatform);

            Ini.GetBool("PlatformCrypto", "PlatformRequiresDataCrypto", out Settings.bDataCryptoRequired);
            Ini.GetBool("PlatformCrypto", "PakSigningRequired", out Settings.PakSigningRequired);
            Ini.GetBool("PlatformCrypto", "PakEncryptionRequired", out Settings.PakEncryptionRequired);

            {
                // Start by parsing the legacy encryption.ini settings
                Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Encryption, InProjectDirectory, InTargetPlatform);
                Ini.GetBool("Core.Encryption", "SignPak", out Settings.bEnablePakSigning);

                string[] SigningKeyStrings = new string[3];
                Ini.GetString("Core.Encryption", "rsa.privateexp", out SigningKeyStrings[0]);
                Ini.GetString("Core.Encryption", "rsa.modulus", out SigningKeyStrings[1]);
                Ini.GetString("Core.Encryption", "rsa.publicexp", out SigningKeyStrings[2]);

                if (String.IsNullOrEmpty(SigningKeyStrings[0]) || String.IsNullOrEmpty(SigningKeyStrings[1]) || String.IsNullOrEmpty(SigningKeyStrings[2]))
                {
                    SigningKeyStrings = null;
                }
                else
                {
                    Settings.SigningKey = new SigningKeyPair();
                    Settings.SigningKey.PrivateKey.Exponent = ParseHexStringToByteArray(ProcessSigningKeyInputStrings(SigningKeyStrings[0]), 64);
                    Settings.SigningKey.PrivateKey.Modulus  = ParseHexStringToByteArray(ProcessSigningKeyInputStrings(SigningKeyStrings[1]), 64);
                    Settings.SigningKey.PublicKey.Exponent  = ParseHexStringToByteArray(ProcessSigningKeyInputStrings(SigningKeyStrings[2]), 64);
                    Settings.SigningKey.PublicKey.Modulus   = Settings.SigningKey.PrivateKey.Modulus;

                    if ((Settings.SigningKey.PrivateKey.Exponent.Length > 64) ||
                        (Settings.SigningKey.PrivateKey.Modulus.Length > 64) ||
                        (Settings.SigningKey.PublicKey.Exponent.Length > 64) ||
                        (Settings.SigningKey.PublicKey.Modulus.Length > 64))
                    {
                        throw new Exception(string.Format("[{0}] Signing keys parsed from encryption.ini are too long. They must be a maximum of 64 bytes long!", InProjectDirectory));
                    }
                }

                Ini.GetBool("Core.Encryption", "EncryptPak", out Settings.bEnablePakIndexEncryption);
                Settings.bEnablePakFullAssetEncryption = false;
                Settings.bEnablePakUAssetEncryption    = false;
                Settings.bEnablePakIniEncryption       = Settings.bEnablePakIndexEncryption;

                string EncryptionKeyString;
                Ini.GetString("Core.Encryption", "aes.key", out EncryptionKeyString);
                Settings.EncryptionKey = new EncryptionKey();

                if (EncryptionKeyString.Length > 0)
                {
                    if (EncryptionKeyString.Length < 32)
                    {
                        Log.WriteLine(LogEventType.Warning, "AES key parsed from encryption.ini is too short. It must be 32 bytes, so will be padded with 0s, giving sub-optimal security!");
                    }
                    else if (EncryptionKeyString.Length > 32)
                    {
                        Log.WriteLine(LogEventType.Warning, "AES key parsed from encryption.ini is too long. It must be 32 bytes, so will be truncated!");
                    }

                    Settings.EncryptionKey.Key = ParseAnsiStringToByteArray(EncryptionKeyString, 32);
                }
            }

            Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Crypto, InProjectDirectory, InTargetPlatform);
            string SectionName = "/Script/CryptoKeys.CryptoKeysSettings";
            ConfigHierarchySection CryptoSection = Ini.FindSection(SectionName);

            // If we have new format crypto keys, read them in over the top of the legacy settings
            if (CryptoSection != null && CryptoSection.KeyNames.Count() > 0)
            {
                Ini.GetBool(SectionName, "bEnablePakSigning", out Settings.bEnablePakSigning);
                Ini.GetBool(SectionName, "bEncryptPakIniFiles", out Settings.bEnablePakIniEncryption);
                Ini.GetBool(SectionName, "bEncryptPakIndex", out Settings.bEnablePakIndexEncryption);
                Ini.GetBool(SectionName, "bEncryptUAssetFiles", out Settings.bEnablePakUAssetEncryption);
                Ini.GetBool(SectionName, "bEncryptAllAssetFiles", out Settings.bEnablePakFullAssetEncryption);

                // Parse encryption key
                string EncryptionKeyString;
                Ini.GetString(SectionName, "EncryptionKey", out EncryptionKeyString);
                if (!string.IsNullOrEmpty(EncryptionKeyString))
                {
                    Settings.EncryptionKey      = new EncryptionKey();
                    Settings.EncryptionKey.Key  = System.Convert.FromBase64String(EncryptionKeyString);
                    Settings.EncryptionKey.Guid = Guid.Empty.ToString();
                    Settings.EncryptionKey.Name = "Embedded";
                }

                // Parse secondary encryption keys
                List <EncryptionKey> SecondaryEncryptionKeys = new List <EncryptionKey>();
                List <string>        SecondaryEncryptionKeyStrings;

                if (Ini.GetArray(SectionName, "SecondaryEncryptionKeys", out SecondaryEncryptionKeyStrings))
                {
                    foreach (string KeySource in SecondaryEncryptionKeyStrings)
                    {
                        EncryptionKey NewKey = new EncryptionKey();
                        SecondaryEncryptionKeys.Add(NewKey);

                        Regex Search = new Regex("\\(Guid=(?\'Guid\'.*),Name=\\\"(?\'Name\'.*)\\\",Key=\\\"(?\'Key\'.*)\\\"\\)");
                        Match Match  = Search.Match(KeySource);
                        if (Match.Success)
                        {
                            foreach (string GroupName in Search.GetGroupNames())
                            {
                                string Value = Match.Groups[GroupName].Value;
                                if (GroupName == "Guid")
                                {
                                    NewKey.Guid = Value;
                                }
                                else if (GroupName == "Name")
                                {
                                    NewKey.Name = Value;
                                }
                                else if (GroupName == "Key")
                                {
                                    NewKey.Key = System.Convert.FromBase64String(Value);
                                }
                            }
                        }
                    }
                }

                Settings.SecondaryEncryptionKeys = SecondaryEncryptionKeys.ToArray();

                // Parse signing key
                string PrivateExponent, PublicExponent, Modulus;
                Ini.GetString(SectionName, "SigningPrivateExponent", out PrivateExponent);
                Ini.GetString(SectionName, "SigningModulus", out Modulus);
                Ini.GetString(SectionName, "SigningPublicExponent", out PublicExponent);

                if (!String.IsNullOrEmpty(PrivateExponent) && !String.IsNullOrEmpty(PublicExponent) && !String.IsNullOrEmpty(Modulus))
                {
                    Settings.SigningKey = new SigningKeyPair();
                    Settings.SigningKey.PublicKey.Exponent  = System.Convert.FromBase64String(PublicExponent);
                    Settings.SigningKey.PublicKey.Modulus   = System.Convert.FromBase64String(Modulus);
                    Settings.SigningKey.PrivateKey.Exponent = System.Convert.FromBase64String(PrivateExponent);
                    Settings.SigningKey.PrivateKey.Modulus  = Settings.SigningKey.PublicKey.Modulus;
                }
            }

            // Parse project dynamic keychain keys
            if (InProjectDirectory != null)
            {
                ConfigHierarchy GameIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, InProjectDirectory, InTargetPlatform);
                if (GameIni != null)
                {
                    string Filename;
                    if (GameIni.GetString("ContentEncryption", "ProjectKeyChain", out Filename))
                    {
                        FileReference ProjectKeyChainFile = FileReference.Combine(InProjectDirectory, "Content", Filename);
                        if (FileReference.Exists(ProjectKeyChainFile))
                        {
                            List <EncryptionKey> EncryptionKeys = new List <EncryptionKey>();

                            if (Settings.SecondaryEncryptionKeys != null)
                            {
                                EncryptionKeys.AddRange(Settings.SecondaryEncryptionKeys);
                            }

                            string[] Lines = FileReference.ReadAllLines(ProjectKeyChainFile);
                            foreach (string Line in Lines)
                            {
                                string[] KeyParts = Line.Split(':');
                                if (KeyParts.Length == 4)
                                {
                                    EncryptionKey NewKey = new EncryptionKey();

                                    NewKey.Name = KeyParts[0];
                                    NewKey.Guid = KeyParts[2];
                                    NewKey.Key  = System.Convert.FromBase64String(KeyParts[3]);

                                    EncryptionKey ExistingKey = EncryptionKeys.Find((EncryptionKey OtherKey) => { return(OtherKey.Guid == NewKey.Guid); });
                                    if (ExistingKey != null && !CompareKey(ExistingKey.Key, NewKey.Key))
                                    {
                                        throw new Exception("Found multiple encryption keys with the same guid but different AES keys while merging secondary keys from the project key-chain!");
                                    }

                                    EncryptionKeys.Add(NewKey);
                                }
                            }

                            Settings.SecondaryEncryptionKeys = EncryptionKeys.ToArray();
                        }
                    }
                }
            }

            if (!Settings.bDataCryptoRequired)
            {
                CryptoSettings NewSettings = new CryptoSettings();
                NewSettings.SecondaryEncryptionKeys = Settings.SecondaryEncryptionKeys;
                Settings = NewSettings;
            }
            else
            {
                if (!Settings.PakSigningRequired)
                {
                    Settings.bEnablePakSigning = false;
                    Settings.SigningKey        = null;
                }

                if (!Settings.PakEncryptionRequired)
                {
                    Settings.bEnablePakFullAssetEncryption = false;
                    Settings.bEnablePakIndexEncryption     = false;
                    Settings.bEnablePakIniEncryption       = false;
                    Settings.EncryptionKey = null;
                    Settings.SigningKey    = null;
                }
            }

            // Check if we have a valid signing key that is of the old short form
            if (Settings.SigningKey != null && Settings.SigningKey.IsValid() && Settings.SigningKey.IsUnsecureLegacyKey())
            {
                Log.TraceWarningOnce("Project signing keys found in '{0}' are of the old insecure short format. Please regenerate them using the project crypto settings panel in the editor!", InProjectDirectory);
            }

            // Validate the settings we have read
            if (Settings.bDataCryptoRequired && Settings.bEnablePakSigning && (Settings.SigningKey == null || !Settings.SigningKey.IsValid()))
            {
                Log.TraceWarningOnce("Pak signing is enabled, but no valid signing keys were found. Please generate a key in the editor project crypto settings. Signing will be disabled");
                Settings.bEnablePakSigning = false;
            }

            if (Settings.bDataCryptoRequired && Settings.IsAnyEncryptionEnabled() && (Settings.EncryptionKey == null || !Settings.EncryptionKey.IsValid()))
            {
                Log.TraceWarningOnce("Pak encryption is enabled, but no valid encryption key was found. Please generate a key in the editor project crypto settings. Encryption will be disabled");
                Settings.bEnablePakUAssetEncryption    = false;
                Settings.bEnablePakFullAssetEncryption = false;
                Settings.bEnablePakIndexEncryption     = false;
                Settings.bEnablePakIniEncryption       = false;
            }

            return(Settings);
        }
        public string GenerateManifest(string ProjectName, bool bForDistribution, string Architecture)
        {
            ConfigHierarchy GameIni        = GetConfigCacheIni(ConfigHierarchyType.Game);
            string          ProjectVersion = string.Empty;

            GameIni.GetString("/Script/EngineSettings.GeneralProjectSettings", "ProjectVersion", out ProjectVersion);
            if (string.IsNullOrEmpty(ProjectVersion))
            {
                ProjectVersion = "1.0.0.0";
            }

            ConfigHierarchy EngineIni = GetConfigCacheIni(ConfigHierarchyType.Engine);
            int             VersionCode;

            EngineIni.GetInt32("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "VersionCode", out VersionCode);

            bool bInternetRequired;

            EngineIni.GetBool("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "bInternetRequired", out bInternetRequired);

            StringBuilder Text = new StringBuilder();

            string PackageName            = GetPackageName(ProjectName);
            string ApplicationDisplayName = GetApplicationDisplayName(ProjectName);
            string TargetExecutableName   = "bin/" + ProjectName;

            Text.AppendLine(string.Format("<manifest xmlns:ml=\"magicleap\" ml:package=\"{0}\" ml:version_name=\"{1}\" ml:version_code=\"{2}\">", PackageName, ProjectVersion, VersionCode));
            // @mltodo: query sdk_version
            Text.AppendLine(string.Format("\t<application ml:visible_name=\"{0}\" ml:is_debuggable=\"{1}\" ml:sdk_version=\"1.0\" ml:minimum_os=\"mlos_1.0\" ml:internet_required=\"{2}\">", ApplicationDisplayName, bForDistribution ? "false" : "true", bInternetRequired ? "true" : "false"));
            Text.AppendLine(string.Format("\t\t<component ml:name=\".fullscreen\" ml:visible_name=\"{0}\" ml:binary_name=\"{1}\" ml:type=\"{2}\">", ApplicationDisplayName, TargetExecutableName, GetApplicationType()));

            List <string> AppPrivileges;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "AppPrivileges", out AppPrivileges);
            if (AppPrivileges != null)
            {
                foreach (string Privilege in AppPrivileges)
                {
                    string TrimmedPrivilege = Privilege.Trim(' ');
                    if (TrimmedPrivilege != "")
                    {
                        string PrivilegeString = string.Format("\t\t\t<uses-privilege ml:name=\"{0}\"/>", TrimmedPrivilege);
                        if (!Text.ToString().Contains(PrivilegeString))
                        {
                            Text.AppendLine(PrivilegeString);
                        }
                    }
                }
            }

            string IconTag = string.Format("<icon ml:name=\"fullscreen\" ml:model_folder=\"{0}\" ml:portal_folder=\"{1}\"/>", GetIconModelStagingPath(), GetIconPortalStagingPath());

            Text.AppendLine(string.Format("\t\t\t{0}", IconTag));

            List <string> ExtraComponentNodes;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentNodes", out ExtraComponentNodes);
            if (ExtraComponentNodes != null)
            {
                foreach (string ComponentNode in ExtraComponentNodes)
                {
                    Text.AppendLine(string.Format("\t\t\t{0}", ComponentNode));
                }
            }

            Text.AppendLine("\t\t</component>");

            List <string> ExtraApplicationNodes;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraApplicationNodes", out ExtraApplicationNodes);
            if (ExtraApplicationNodes != null)
            {
                foreach (string ApplicationNode in ExtraApplicationNodes)
                {
                    Text.AppendLine(string.Format("\t\t{0}", ApplicationNode));
                }
            }

            Text.AppendLine(string.Format("\t\t{0}", IconTag));
            Text.AppendLine("\t</application>");
            Text.AppendLine("</manifest>");

            // allow plugins to modify final manifest HERE
            XDocument XDoc;

            try
            {
                XDoc = XDocument.Parse(Text.ToString());
            }
            catch (Exception e)
            {
                throw new BuildException("LuminManifest.xml is invalid {0}\n{1}", e, Text.ToString());
            }

            UPL.ProcessPluginNode(Architecture, "luminManifestUpdates", "", ref XDoc);
            return(XDoc.ToString());
        }
Example #7
0
        public string GenerateManifest(string ProjectName, bool bForDistribution, string Architecture)
        {
            ConfigHierarchy GameIni        = GetConfigCacheIni(ConfigHierarchyType.Game);
            string          ProjectVersion = string.Empty;

            GameIni.GetString("/Script/EngineSettings.GeneralProjectSettings", "ProjectVersion", out ProjectVersion);
            if (string.IsNullOrEmpty(ProjectVersion))
            {
                ProjectVersion = "1.0.0.0";
            }

            ConfigHierarchy EngineIni = GetConfigCacheIni(ConfigHierarchyType.Engine);
            Int32           VersionCode;

            EngineIni.GetInt32("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "VersionCode", out VersionCode);

            string SDKVersion             = GetMLSDKVersion(EngineIni);
            string PackageName            = GetPackageName(ProjectName);
            string ApplicationDisplayName = GetApplicationDisplayName(ProjectName);
            string MinimumAPILevel        = GetMinimumAPILevelRequired();
            string TargetExecutableName   = "bin/" + ProjectName;

            PackageManifest.version_name = ProjectVersion;
            PackageManifest.package      = PackageName;
            PackageManifest.version_code = Convert.ToUInt64(VersionCode);

            PackageManifest.application = new manifestApplication
            {
                sdk_version   = SDKVersion,
                min_api_level = MinimumAPILevel,
                visible_name  = ApplicationDisplayName
            };

            List <string> AppPrivileges;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "AppPrivileges", out AppPrivileges);

            List <string> ExtraComponentElements;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentElements", out ExtraComponentElements);

            // We always add an additional item as that will be our 'root' <component>
            int Size = (ExtraComponentElements == null ? AppPrivileges.Count() : AppPrivileges.Count() + ExtraComponentElements.Count()) + 1;
            // Index used for sibling elements (app privileges, root component and any extra components)
            int CurrentIndex = 0;

            PackageManifest.application.Items = new object[Size];
            // Remove all invalid strings from the list of strings
            AppPrivileges.RemoveAll(item => item == "Invalid");
            // Privileges get added first
            for (int Index = 0; Index < AppPrivileges.Count(); ++Index)
            {
                string TrimmedPrivilege = AppPrivileges[Index].Trim(' ');
                if (TrimmedPrivilege != "")
                {
                    PackageManifest.application.Items[CurrentIndex] = new manifestApplicationUsesprivilege
                    {
                        name = TrimmedPrivilege,
                    };
                    CurrentIndex++;
                }
            }

            // Then our root component, this is important as `mldb launch` will use the first component in the manifest
            PackageManifest.application.Items[CurrentIndex] = new manifestApplicationComponent();
            manifestApplicationComponent RootComponent = (manifestApplicationComponent)PackageManifest.application.Items[CurrentIndex];

            RootComponent.name         = ".fullscreen";
            RootComponent.visible_name = ApplicationDisplayName;
            RootComponent.binary_name  = TargetExecutableName;
            RootComponent.type         = GetApplicationType();

            // Sub-elements under root <component>
            List <string> ExtraComponentSubElements;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentSubElements", out ExtraComponentSubElements);
            RootComponent.Items = (ExtraComponentSubElements == null ? new object[1] : new object[ExtraComponentSubElements.Count() + 1]);

            // Root component icon
            RootComponent.Items[0] = new manifestApplicationComponentIcon();
            ((manifestApplicationComponentIcon)RootComponent.Items[0]).model_folder  = GetIconModelStagingPath();
            ((manifestApplicationComponentIcon)RootComponent.Items[0]).portal_folder = GetIconPortalStagingPath();

            if (ExtraComponentSubElements != null)
            {
                for (int Index = 0; Index < ExtraComponentSubElements.Count(); ++Index)
                {
                    Dictionary <string, string> NodeContent;
                    if (ConfigHierarchy.TryParse(ExtraComponentSubElements[Index], out NodeContent))
                    {
                        RootComponent.Items[Index + 1] = GetComponentSubElement(NodeContent["ElementType"], NodeContent["Value"]);
                    }
                }
            }

            // Finally, add additional components
            CurrentIndex++;
            if (ExtraComponentElements != null)
            {
                for (int Index = 0; Index < ExtraComponentElements.Count(); ++Index)
                {
                    Dictionary <string, string> ComponentElement;
                    if (ConfigHierarchy.TryParse(ExtraComponentElements[Index], out ComponentElement))
                    {
                        PackageManifest.application.Items[CurrentIndex] = GetComponentElement(ComponentElement);
                        CurrentIndex++;
                    }
                }
            }

            // Wrap up serialization
            XmlSerializer           PackageManifestSerializer = new XmlSerializer(PackageManifest.GetType());
            XmlSerializerNamespaces MLNamespace = new XmlSerializerNamespaces();

            MLNamespace.Add("ml", "magicleap");
            StringWriter Writer = new StringWriter();

            PackageManifestSerializer.Serialize(Writer, PackageManifest, MLNamespace);

            // allow plugins to modify final manifest HERE
            XDocument XDoc;

            try
            {
                XDoc = XDocument.Parse(Writer.ToString());
            }
            catch (Exception e)
            {
                throw new BuildException("LuminManifest.xml is invalid {0}\n{1}", e, Writer.ToString());
            }

            UPL.ProcessPluginNode(Architecture, "luminManifestUpdates", "", ref XDoc);
            return(XDoc.ToString());
        }
Example #8
0
        public List <string> CreateManifest(string InManifestName, string InOutputPath, string InIntermediatePath, FileReference InProjectFile, string InProjectDirectory, List <UnrealTargetConfiguration> InTargetConfigs, List <string> InExecutables)
        {
            // Verify we can find the SDK.
            string SDKDirectory = GetSDKDirectory();

            if (string.IsNullOrEmpty(SDKDirectory))
            {
                return(null);
            }

            // Check parameter values are valid.
            if (InTargetConfigs.Count != InExecutables.Count)
            {
                Log.TraceError("The number of target configurations ({0}) and executables ({1}) passed to manifest generation do not match.", InTargetConfigs.Count, InExecutables.Count);
                return(null);
            }
            if (InTargetConfigs.Count < 1)
            {
                Log.TraceError("The number of target configurations is zero, so we cannot generate a manifest.");
                return(null);
            }

            if (!CreateCheckDirectory(InOutputPath))
            {
                Log.TraceError("Failed to create output directory \"{0}\".", InOutputPath);
                return(null);
            }
            if (!CreateCheckDirectory(InIntermediatePath))
            {
                Log.TraceError("Failed to create intermediate directory \"{0}\".", InIntermediatePath);
                return(null);
            }

            OutputPath       = InOutputPath;
            IntermediatePath = InIntermediatePath;
            ProjectFile      = InProjectFile;
            ProjectPath      = InProjectDirectory;
            UpdatedFilePaths = new List <string>();

            // Load up INI settings. We'll use engine settings to retrieve the manifest configuration, but these may reference
            // values in either game or engine settings, so we'll keep both.
            GameIni   = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, DirectoryReference.FromFile(InProjectFile), ConfigPlatform);
            EngineIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(InProjectFile), ConfigPlatform);

            // Load and verify/clean culture list
            {
                List <string> CulturesToStageWithDuplicates;
                GameIni.GetArray("/Script/UnrealEd.ProjectPackagingSettings", "CulturesToStage", out CulturesToStageWithDuplicates);
                GameIni.GetString("/Script/UnrealEd.ProjectPackagingSettings", "DefaultCulture", out DefaultCulture);
                if (CulturesToStageWithDuplicates == null || CulturesToStageWithDuplicates.Count < 1)
                {
                    Log.TraceError("At least one culture must be selected to stage.");
                    return(null);
                }

                CulturesToStage = CulturesToStageWithDuplicates.Distinct().ToList();
            }
            if (DefaultCulture == null || DefaultCulture.Length < 1)
            {
                DefaultCulture = CulturesToStage[0];
                Log.TraceWarning("A default culture must be selected to stage. Using {0}.", DefaultCulture);
            }
            if (!CulturesToStage.Contains(DefaultCulture))
            {
                DefaultCulture = CulturesToStage[0];
                Log.TraceWarning("The default culture must be one of the staged cultures. Using {0}.", DefaultCulture);
            }

            List <string> PerCultureValues;

            if (EngineIni.GetArray(IniSection_PlatformTargetSettings, "PerCultureResources", out PerCultureValues))
            {
                foreach (string CultureCombinedValues in PerCultureValues)
                {
                    Dictionary <string, string> SeparatedCultureValues;
                    if (!ConfigHierarchy.TryParse(CultureCombinedValues, out SeparatedCultureValues))
                    {
                        Log.TraceWarning("Invalid per-culture resource value: {0}", CultureCombinedValues);
                        continue;
                    }

                    string StageId      = SeparatedCultureValues["StageId"];
                    int    CultureIndex = CulturesToStage.FindIndex(x => x == StageId);
                    if (CultureIndex >= 0)
                    {
                        CulturesToStage[CultureIndex] = SeparatedCultureValues["CultureId"];
                        if (DefaultCulture == StageId)
                        {
                            DefaultCulture = SeparatedCultureValues["CultureId"];
                        }
                    }
                }
            }
            // Only warn if shipping, we can run without translated cultures they're just needed for cert
            else if (InTargetConfigs.Contains(UnrealTargetConfiguration.Shipping))
            {
                Log.TraceInformation("Staged culture mappings not setup in the editor. See Per Culture Resources in the {0} Target Settings.", Platform.ToString());
            }

            // Clean out the resources intermediate path so that we know there are no stale binary files.
            string IntermediateResourceDirectory = Path.Combine(IntermediatePath, BuildResourceSubPath);

            RecursivelyForceDeleteDirectory(IntermediateResourceDirectory);
            if (!CreateCheckDirectory(IntermediateResourceDirectory))
            {
                Log.TraceError("Could not create directory {0}.", IntermediateResourceDirectory);
                return(null);
            }

            // Construct a single resource writer for the default (no-culture) values
            string DefaultResourceIntermediatePath = Path.Combine(IntermediateResourceDirectory, "resources.resw");

            DefaultResourceWriter = new UEResXWriter(DefaultResourceIntermediatePath);

            // Construct the ResXWriters for each culture
            PerCultureResourceWriters = new Dictionary <string, UEResXWriter>();
            foreach (string Culture in CulturesToStage)
            {
                string IntermediateStringResourcePath = Path.Combine(IntermediateResourceDirectory, Culture);
                string IntermediateStringResourceFile = Path.Combine(IntermediateStringResourcePath, "resources.resw");
                if (!CreateCheckDirectory(IntermediateStringResourcePath))
                {
                    Log.TraceWarning("Culture {0} resources not staged.", Culture);
                    CulturesToStage.Remove(Culture);
                    if (Culture == DefaultCulture)
                    {
                        DefaultCulture = CulturesToStage[0];
                        Log.TraceWarning("Default culture skipped. Using {0} as default culture.", DefaultCulture);
                    }
                    continue;
                }
                PerCultureResourceWriters.Add(Culture, new UEResXWriter(IntermediateStringResourceFile));
            }



            // Create the manifest document
            string IdentityName        = null;
            var    ManifestXmlDocument = new XDocument(GetManifest(InTargetConfigs, InExecutables, out IdentityName));

            // Export manifest to the intermediate directory then compare the contents to any existing target manifest
            // and replace if there are differences.
            string ManifestIntermediatePath = Path.Combine(IntermediatePath, InManifestName);
            string ManifestTargetPath       = Path.Combine(OutputPath, InManifestName);

            ManifestXmlDocument.Save(ManifestIntermediatePath);
            CompareAndReplaceModifiedTarget(ManifestIntermediatePath, ManifestTargetPath);
            ProcessManifest(InTargetConfigs, InExecutables, InManifestName, ManifestTargetPath, ManifestIntermediatePath);

            // Clean out any resource directories that we aren't staging
            string TargetResourcePath = Path.Combine(OutputPath, BuildResourceSubPath);

            if (Directory.Exists(TargetResourcePath))
            {
                List <string> TargetResourceDirectories = new List <string>(Directory.GetDirectories(TargetResourcePath, "*.*", SearchOption.AllDirectories));
                foreach (string ResourceDirectory in TargetResourceDirectories)
                {
                    if (!CulturesToStage.Contains(Path.GetFileName(ResourceDirectory)))
                    {
                        RecursivelyForceDeleteDirectory(ResourceDirectory);
                    }
                }
            }

            // Export the resource tables starting with the default culture
            string DefaultResourceTargetPath = Path.Combine(OutputPath, BuildResourceSubPath, "resources.resw");

            DefaultResourceWriter.Close();
            CompareAndReplaceModifiedTarget(DefaultResourceIntermediatePath, DefaultResourceTargetPath);

            foreach (var Writer in PerCultureResourceWriters)
            {
                Writer.Value.Close();

                string IntermediateStringResourceFile = Path.Combine(IntermediateResourceDirectory, Writer.Key, "resources.resw");
                string TargetStringResourceFile       = Path.Combine(OutputPath, BuildResourceSubPath, Writer.Key, "resources.resw");

                CompareAndReplaceModifiedTarget(IntermediateStringResourceFile, TargetStringResourceFile);
            }

            // Copy all the binary resources into the target directory.
            CopyResourcesToTargetDir();

            // The resource database is dependent on everything else calculated here (manifest, resource string tables, binary resources).
            // So if any file has been updated we'll need to run the config.
            if (UpdatedFilePaths.Count > 0)
            {
                // Create resource index configuration
                string PriExecutable            = GetMakePriBinaryPath();
                string ResourceConfigFile       = Path.Combine(IntermediatePath, "priconfig.xml");
                bool   bEnableAutoResourcePacks = false;
                EngineIni.GetBool(IniSection_PlatformTargetSettings, "bEnableAutoResourcePacks", out bEnableAutoResourcePacks);

                // If the game is not going to support language resource packs then merge the culture qualifiers.
                if (bEnableAutoResourcePacks || CulturesToStage.Count <= 1)
                {
                    RunExternalProgram(PriExecutable, "createconfig /cf \"" + ResourceConfigFile + "\" /dq " + DefaultCulture + " /o");
                }
                else
                {
                    RunExternalProgram(PriExecutable, "createconfig /cf \"" + ResourceConfigFile + "\" /dq " + String.Join("_", CulturesToStage) + " /o");
                }

                // Modify configuration to restrict indexing to the Resources directory (saves time and space)
                XmlDocument PriConfig = new XmlDocument();
                PriConfig.Load(ResourceConfigFile);

                // If the game is not going to support resource packs then remove the autoResourcePackages.
                if (!bEnableAutoResourcePacks)
                {
                    XmlNode PackagingNode = PriConfig.SelectSingleNode("/resources/packaging");
                    PackagingNode.ParentNode.RemoveChild(PackagingNode);
                }

                // The previous implementation using startIndexAt="Resources" did not produce the expected ResourceMapSubtree hierarchy, so this manually specifies all resources in a .resfiles instead.
                string ResourcesResFile = Path.Combine(IntermediatePath, "resources.resfiles");

                XmlNode      PriIndexNode  = PriConfig.SelectSingleNode("/resources/index");
                XmlAttribute PriStartIndex = PriIndexNode.Attributes["startIndexAt"];
                PriStartIndex.Value = ResourcesResFile;

                // Swap the default folder indexer-config to a RESFILES indexer-config.
                XmlElement FolderIndexerConfigNode = PriConfig.SelectSingleNode("/resources/index/indexer-config[@type='folder']") as XmlElement;
                FolderIndexerConfigNode.SetAttribute("type", "RESFILES");
                FolderIndexerConfigNode.RemoveAttribute("foldernameAsQualifier");
                FolderIndexerConfigNode.RemoveAttribute("filenameAsQualifier");

                PriConfig.Save(ResourceConfigFile);

                IEnumerable <string>      Resources     = Directory.EnumerateFiles(Path.Combine(OutputPath, BuildResourceSubPath), "*.*", SearchOption.AllDirectories);
                System.Text.StringBuilder ResourcesList = new System.Text.StringBuilder();
                foreach (string Resource in Resources)
                {
                    ResourcesList.AppendLine(Resource.Replace(OutputPath, "").TrimStart('\\'));
                }
                File.WriteAllText(ResourcesResFile, ResourcesList.ToString());

                // Remove previous pri files so we can enumerate which ones are new since the resource generator could produce a file for each staged language.
                IEnumerable <string> OldPriFiles = Directory.EnumerateFiles(IntermediatePath, "*.pri");
                foreach (string OldPri in OldPriFiles)
                {
                    try
                    {
                        File.Delete(OldPri);
                    }
                    catch (Exception)
                    {
                        Log.TraceError("Could not delete file {0}.", OldPri);
                    }
                }

                // Generate the resource index
                string ResourceLogFile   = Path.Combine(IntermediatePath, "ResIndexLog.xml");
                string ResourceIndexFile = Path.Combine(IntermediatePath, "resources.pri");

                string MakePriCommandLine = "new /pr \"" + OutputPath + "\" /cf \"" + ResourceConfigFile + "\" /mn \"" + ManifestTargetPath + "\" /il \"" + ResourceLogFile + "\" /of \"" + ResourceIndexFile + "\" /o";

                if (IdentityName != null)
                {
                    MakePriCommandLine += " /indexName \"" + IdentityName + "\"";
                }
                RunExternalProgram(PriExecutable, MakePriCommandLine);

                // Remove any existing pri target files that were not generated by this latest update
                IEnumerable <string> NewPriFiles    = Directory.EnumerateFiles(IntermediatePath, "*.pri");
                IEnumerable <string> TargetPriFiles = Directory.EnumerateFiles(OutputPath, "*.pri");
                foreach (string TargetPri in TargetPriFiles)
                {
                    if (!NewPriFiles.Contains(TargetPri))
                    {
                        try
                        {
                            File.Delete(TargetPri);
                        }
                        catch (Exception)
                        {
                            Log.TraceError("Could not remove stale file {0}.", TargetPri);
                        }
                    }
                }

                // Stage all the modified pri files to the output directory
                foreach (string NewPri in NewPriFiles)
                {
                    string NewResourceIndexFile   = Path.Combine(IntermediatePath, Path.GetFileName(NewPri));
                    string FinalResourceIndexFile = Path.Combine(OutputPath, Path.GetFileName(NewPri));
                    CompareAndReplaceModifiedTarget(NewResourceIndexFile, FinalResourceIndexFile);
                }
            }

            return(UpdatedFilePaths);
        }
Example #9
0
        protected void AddResourceEntry(string ResourceEntryName, string ConfigKey, string GenericINISection, string GenericINIKey, string DefaultValue, string ValueSuffix = "")
        {
            string ConfigScratchValue = null;

            // Get the default culture value
            string DefaultCultureScratchValue;

            if (EngineIni.GetString(IniSection_PlatformTargetSettings, "CultureStringResources", out DefaultCultureScratchValue))
            {
                Dictionary <string, string> Values;
                if (!ConfigHierarchy.TryParse(DefaultCultureScratchValue, out Values))
                {
                    Log.TraceError("Invalid default culture string resources: \"{0}\". Unable to add resource entry.", DefaultCultureScratchValue);
                    return;
                }

                ConfigScratchValue = Values[ConfigKey];
            }

            if (string.IsNullOrEmpty(ConfigScratchValue))
            {
                // No platform specific value is provided. Use the generic config or default value
                ConfigScratchValue = ReadIniString(GenericINIKey, GenericINISection, DefaultValue);
            }

            DefaultResourceWriter.AddResource(ResourceEntryName, ConfigScratchValue + ValueSuffix);

            // Find the default value
            List <string> PerCultureValues;

            if (EngineIni.GetArray(IniSection_PlatformTargetSettings, "PerCultureResources", out PerCultureValues))
            {
                foreach (string CultureCombinedValues in PerCultureValues)
                {
                    Dictionary <string, string> SeparatedCultureValues;
                    if (!ConfigHierarchy.TryParse(CultureCombinedValues, out SeparatedCultureValues) ||
                        !SeparatedCultureValues.ContainsKey("CultureStringResources") ||
                        !SeparatedCultureValues.ContainsKey("CultureId"))
                    {
                        Log.TraceError("Invalid per-culture resource: \"{0}\". Unable to add resource entry.", CultureCombinedValues);
                        continue;
                    }

                    var CultureId = SeparatedCultureValues["CultureId"];
                    if (CulturesToStage.Contains(CultureId))
                    {
                        Dictionary <string, string> CultureStringResources;
                        if (!ConfigHierarchy.TryParse(SeparatedCultureValues["CultureStringResources"], out CultureStringResources))
                        {
                            Log.TraceError("Invalid culture string resources: \"{0}\". Unable to add resource entry.", CultureCombinedValues);
                            continue;
                        }

                        var Value = CultureStringResources[ConfigKey];

                        if (CulturesToStage.Contains(CultureId) && !string.IsNullOrEmpty(Value))
                        {
                            var Writer = PerCultureResourceWriters[CultureId];
                            Writer.AddResource(ResourceEntryName, Value + ValueSuffix);
                        }
                    }
                }
            }
        }
Example #10
0
        public string GenerateManifest(string ProjectName, bool bForDistribution, string Architecture)
        {
            ConfigHierarchy GameIni        = GetConfigCacheIni(ConfigHierarchyType.Game);
            string          ProjectVersion = string.Empty;

            GameIni.GetString("/Script/EngineSettings.GeneralProjectSettings", "ProjectVersion", out ProjectVersion);
            if (string.IsNullOrEmpty(ProjectVersion))
            {
                ProjectVersion = "1.0.0.0";
            }

            ConfigHierarchy EngineIni = GetConfigCacheIni(ConfigHierarchyType.Engine);
            int             VersionCode;

            EngineIni.GetInt32("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "VersionCode", out VersionCode);

            string SDKVersion = GetMLSDKVersion(EngineIni);

            StringBuilder Text = new StringBuilder();

            string PackageName            = GetPackageName(ProjectName);
            string ApplicationDisplayName = GetApplicationDisplayName(ProjectName);
            string MinimumAPILevel        = GetMinimumAPILevelRequired();
            string TargetExecutableName   = "bin/" + ProjectName;

            Text.AppendLine(string.Format("<manifest xmlns:ml=\"magicleap\" ml:package=\"{0}\" ml:version_name=\"{1}\" ml:version_code=\"{2}\">", PackageName, ProjectVersion, VersionCode));
            Text.AppendLine(string.Format("\t<application ml:visible_name=\"{0}\" ml:sdk_version=\"{1}\" ml:min_api_level=\"{2}\">",
                                          ApplicationDisplayName,
                                          SDKVersion,
                                          MinimumAPILevel));
            GetAppPrivileges(EngineIni, Text);
            Text.AppendLine(string.Format("\t\t<component ml:name=\".fullscreen\" ml:visible_name=\"{0}\" ml:binary_name=\"{1}\" ml:type=\"{2}\">", ApplicationDisplayName, TargetExecutableName, GetApplicationType()));

            string IconTag = string.Format("<icon ml:model_folder=\"{0}\" ml:portal_folder=\"{1}\"/>", GetIconModelStagingPath(), GetIconPortalStagingPath());

            Text.AppendLine(string.Format("\t\t\t{0}", IconTag));

            List <string> ExtraComponentNodes;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraComponentNodes", out ExtraComponentNodes);
            if (ExtraComponentNodes != null)
            {
                foreach (string ComponentNode in ExtraComponentNodes)
                {
                    Text.AppendLine(string.Format("\t\t\t{0}", ComponentNode));
                }
            }

            Text.AppendLine("\t\t</component>");

            List <string> ExtraApplicationNodes;

            EngineIni.GetArray("/Script/LuminRuntimeSettings.LuminRuntimeSettings", "ExtraApplicationNodes", out ExtraApplicationNodes);
            if (ExtraApplicationNodes != null)
            {
                foreach (string ApplicationNode in ExtraApplicationNodes)
                {
                    Text.AppendLine(string.Format("\t\t{0}", ApplicationNode));
                }
            }

            Text.AppendLine("\t</application>");
            Text.AppendLine("</manifest>");

            // allow plugins to modify final manifest HERE
            XDocument XDoc;

            try
            {
                XDoc = XDocument.Parse(Text.ToString());
            }
            catch (Exception e)
            {
                throw new BuildException("LuminManifest.xml is invalid {0}\n{1}", e, Text.ToString());
            }

            UPL.ProcessPluginNode(Architecture, "luminManifestUpdates", "", ref XDoc);
            return(XDoc.ToString());
        }