Example #1
0
    protected void GenerateFileFromTemplate(string InTemplateFile, string InOutputFile, ProjectParams Params, ConfigHierarchy ConfigCache)
    {
        bool   IsContentOnly        = !Params.IsCodeBasedProject;
        string ProjectConfiguration = Params.ClientConfigsToBuild[0].ToString();

        string UE4GameName = IsContentOnly ? "UE4Game" : Params.ShortProjectName;
        string ProjectName = Params.ShortProjectName;

        if (ProjectConfiguration != "Development")
        {
            UE4GameName += "-HTML5-" + ProjectConfiguration;
            ProjectName += "-HTML5-" + ProjectConfiguration;
        }

        string CanvasScaleMode;

        ConfigCache.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "CanvasScalingMode", out CanvasScaleMode);

        string HeapSize = HTML5SDKInfo.HeapSize(ConfigCache, ProjectConfiguration).ToString();

        StringBuilder outputContents = new StringBuilder();

        using (StreamReader reader = new StreamReader(InTemplateFile))
        {
            string LineStr = null;
            while (reader.Peek() != -1)
            {
                LineStr = reader.ReadLine();
                if (LineStr.Contains("%TIMESTAMP%"))
                {
                    string TimeStamp = DateTime.UtcNow.ToString("yyyyMMddHHmm");
                    LineStr = LineStr.Replace("%TIMESTAMP%", TimeStamp);
                }

                if (LineStr.Contains("%SHORTNAME%"))
                {
                    LineStr = LineStr.Replace("%SHORTNAME%", Params.ShortProjectName);
                }

                if (LineStr.Contains("%UE4GAMENAME%"))
                {
                    LineStr = LineStr.Replace("%UE4GAMENAME%", UE4GameName);
                }

                if (LineStr.Contains("%PROJECTNAME%"))
                {
                    LineStr = LineStr.Replace("%PROJECTNAME%", ProjectName);
                }

                if (LineStr.Contains("%SERVE_COMPRESSED%"))
                {
                    LineStr = LineStr.Replace("%SERVE_COMPRESSED%", Compressed ? "true" : "false");
                }

                if (LineStr.Contains("%DISABLE_INDEXEDDB%"))
                {
                    LineStr = LineStr.Replace("%DISABLE_INDEXEDDB%",
                                              enableIndexedDB ? "" : "enableReadFromIndexedDB = false;\nenableWriteToIndexedDB = false;");
                }

                if (LineStr.Contains("%HEAPSIZE%"))
                {
                    LineStr = LineStr.Replace("%HEAPSIZE%", HeapSize + " * 1024 * 1024");
                }

                if (LineStr.Contains("%UE4CMDLINE%"))
                {
                    string ArgumentString = "'../../../" + Params.ShortProjectName + "/" + Params.ShortProjectName + ".uproject',";
                    ArgumentString += "'-stdout',";                     // suppress double printing to console.log
                    LineStr         = LineStr.Replace("%UE4CMDLINE%", ArgumentString);
                }

                if (LineStr.Contains("%CANVASSCALEMODE%"))
                {
                    string mode = "2 /*ASPECT*/";                     // default
                    if (CanvasScaleMode.Equals("stretch", StringComparison.InvariantCultureIgnoreCase))
                    {
                        mode = "1 /*STRETCH*/";
                    }
                    else if (CanvasScaleMode.Equals("fixed", StringComparison.InvariantCultureIgnoreCase))
                    {
                        mode = "3 /*FIXED*/";
                    }
                    LineStr = LineStr.Replace("%CANVASSCALEMODE%", mode);
                }

                outputContents.AppendLine(LineStr);
            }
        }

        if (outputContents.Length > 0)
        {
            // Save the file
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(InOutputFile));
                File.WriteAllText(InOutputFile, outputContents.ToString(), Encoding.UTF8);
            }
            catch (Exception)
            {
                // Unable to write to the project file.
            }
        }
    }
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        Log("Package {0}", Params.RawProjectPath);

        Log("Setting Emscripten SDK for packaging..");
        HTML5SDKInfo.SetupEmscriptenTemp();
        HTML5SDKInfo.SetUpEmscriptenConfigFile();

        string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5");

        if (!Directory.Exists(PackagePath))
        {
            Directory.CreateDirectory(PackagePath);
        }

        // ini configurations
        var  ConfigCache    = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5);
        bool targetingAsmjs = false;       // inverted checked - this will be going away soon...
        bool targetWebGL1   = false;       // inverted checked - this will be going away soon...

        if (ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "TargetAsmjs", out targetingAsmjs))
        {
            targetingWasm = !targetingAsmjs;
        }
        if (ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "TargetWebGL1", out targetWebGL1))
        {
            targetWebGL2 = !targetWebGL1;
        }

        // Debug and Development builds are not uncompressed to:
        // - speed up iteration times
        // - ensure (IndexedDB) data are not cached/used
        // Shipping builds "can be":
        // - compressed
        // - (IndexedDB) cached
        if (Params.ClientConfigsToBuild[0].ToString() == "Shipping")
        {
            ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "Compressed", out Compressed);
            ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "EnableIndexedDB", out enableIndexedDB);
        }
        Log("HTML5Platform.Automation: TargetWasm = " + targetingWasm);
        Log("HTML5Platform.Automation: TargetWebGL2 = " + targetWebGL2);
        Log("HTML5Platform.Automation: Compressed = " + Compressed);
        Log("HTML5Platform.Automation: EnableIndexedDB = " + enableIndexedDB);

        string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data";

        if (HTMLPakAutomation.CanCreateMapPaks(Params))
        {
            HTMLPakAutomation PakAutomation = new HTMLPakAutomation(Params, SC);

            // Create Necessary Paks.
            PakAutomation.CreateEnginePak();
            PakAutomation.CreateGamePak();
            PakAutomation.CreateContentDirectoryPak();

            // Create Emscripten Package from Necessary Paks. - This will be the VFS.
            PakAutomation.CreateEmscriptenDataPackage(PackagePath, FinalDataLocation);

            // Create All Map Paks which  will be downloaded on the fly.
            PakAutomation.CreateMapPak();

            // Create Delta Paks if setup.
            List <string> Paks = new List <string>();
            ConfigCache.GetArray("/Script/HTML5PlatformEditor.HTML5TargetSettings", "LevelTransitions", out Paks);

            if (Paks != null)
            {
                foreach (var Pak in Paks)
                {
                    var    Matched = Regex.Matches(Pak, "\"[^\"]+\"", RegexOptions.IgnoreCase);
                    string MapFrom = Path.GetFileNameWithoutExtension(Matched[0].ToString().Replace("\"", ""));
                    string MapTo   = Path.GetFileNameWithoutExtension(Matched[1].ToString().Replace("\"", ""));
                    PakAutomation.CreateDeltaMapPaks(MapFrom, MapTo);
                }
            }
        }
        else
        {
            // we need to operate in the root
            string PythonPath   = HTML5SDKInfo.Python();
            string PackagerPath = HTML5SDKInfo.EmscriptenPackager();

            using (new ScopedEnvVar("EM_CONFIG", HTML5SDKInfo.DOT_EMSCRIPTEN))
            {
                using (new PushedDirectory(Path.Combine(Params.BaseStageDirectory, "HTML5")))
                {
                    string CmdLine = string.Format("\"{0}\" \"{1}\" --preload . --js-output=\"{1}.js\" --no-heap-copy", PackagerPath, FinalDataLocation);
                    RunAndLog(CmdEnv, PythonPath, CmdLine);
                }
            }
        }

        // copy the "Executable" to the package directory
        string GameBasename = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename);

        if (Params.ClientConfigsToBuild[0].ToString() != "Development")
        {
            GameBasename += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString();
        }
        // no extension
        string GameBasepath            = Path.GetDirectoryName(Params.ProjectGameExeFilename);
        string FullGameBasePath        = Path.Combine(GameBasepath, GameBasename);
        string FullPackageGameBasePath = Path.Combine(PackagePath, GameBasename);

        // with extension
        string GameExe                = GameBasename + ".js";
        string FullGameExePath        = Path.Combine(GameBasepath, GameExe);
        string FullPackageGameExePath = Path.Combine(PackagePath, GameExe);
        // special case -- this will be removed when asm.js has been deprecated
        string ASMJS_FullPackageGameExePath = Path.Combine(PackagePath, GameBasename + "_asm.js");


        // ensure the ue4game binary exists, if applicable
        if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullGameExePath))
        {
            Log("Failed to find game application " + FullGameExePath);
            throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find application {0}. You may need to build the UE4 project with your target configuration and platform.", FullGameExePath);
        }

        if (FullGameExePath != FullPackageGameExePath)         // TODO: remove this check
        {
            File.Copy(FullGameExePath + ".symbols", FullPackageGameExePath + ".symbols", true);
            if (targetingWasm)
            {
                File.Copy(FullGameBasePath + ".wasm", FullPackageGameBasePath + ".wasm", true);
                File.Copy(FullGameExePath, FullPackageGameExePath, true);
            }
            else
            {
                File.Copy(FullGameExePath + ".mem", FullPackageGameExePath + ".mem", true);
                File.Copy(FullGameBasePath + ".asm.js", FullPackageGameBasePath + ".asm.js", true);
            }
        }

        File.SetAttributes(FullPackageGameExePath + ".symbols", FileAttributes.Normal);
        if (targetingWasm)
        {
            File.SetAttributes(FullPackageGameBasePath + ".wasm", FileAttributes.Normal);
            File.SetAttributes(FullPackageGameExePath, FileAttributes.Normal);
        }
        else
        {
            File.SetAttributes(FullPackageGameExePath + ".mem", FileAttributes.Normal);
            File.SetAttributes(FullPackageGameBasePath + ".asm.js", FileAttributes.Normal);
            File.Copy(FullGameExePath, ASMJS_FullPackageGameExePath, true);             // --separate-asm // UE-45058
            File.SetAttributes(ASMJS_FullPackageGameExePath, FileAttributes.Normal);
        }


        // put the HTML file to the package directory
        string TemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/GameX.html.template");
        string OutputFile   = Path.Combine(PackagePath,
                                           (Params.ClientConfigsToBuild[0].ToString() != "Development" ?
                                            (Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) :
                                            Params.ShortProjectName)) + ".html";

        GenerateFileFromTemplate(TemplateFile,
                                 OutputFile,
                                 Params.ShortProjectName,
                                 Params.ClientConfigsToBuild[0].ToString(),
                                 Params.StageCommandline,
                                 !Params.IsCodeBasedProject,
                                 HTML5SDKInfo.HeapSize(ConfigCache, Params.ClientConfigsToBuild[0].ToString()));

        string MacBashTemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/RunMacHTML5LaunchHelper.command.template");
        string MacBashOutputFile   = Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command");
        string MonoPath            = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/BatchFiles/Mac/SetupMono.sh");

        GenerateMacCommandFromTemplate(MacBashTemplateFile, MacBashOutputFile, MonoPath);

        string htaccessTemplate = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/htaccess.template");
        string htaccesspath     = Path.Combine(PackagePath, ".htaccess");

        if (File.Exists(htaccesspath))
        {
            FileAttributes attributes = File.GetAttributes(htaccesspath);
            if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                attributes &= ~FileAttributes.ReadOnly;
                File.SetAttributes(htaccesspath, attributes);
            }
        }
        File.Copy(htaccessTemplate, htaccesspath, true);

        string JSDir  = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5");
        string OutDir = PackagePath;

        // Gather utlity .js files and combine into one file
        string[] UtilityJavaScriptFiles = Directory.GetFiles(JSDir, "*.js");

        string DestinationFile = OutDir + "/Utility.js";

        File.Delete(DestinationFile);
        foreach (var UtilityFile in UtilityJavaScriptFiles)
        {
            string Data = File.ReadAllText(UtilityFile);
            File.AppendAllText(DestinationFile, Data);
        }

        if (Compressed)
        {
            Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so (gzip) compressing files for web servers.");

            // Compress all files. These are independent tasks which can be threaded.
            List <Task> CompressionTasks = new List <Task>();

            // Note that the main .data file is never gzip compressed, because we rely on UnrealPak having compressed it already (above), and gzipping the pak file on
            // top is showing negligible benefit. Check the console output from UnrealPak run to verify that it is indeed compressed.
//			CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation, FinalDataLocation + "gz")));

            // data file .js driver.
            CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation + ".js", FinalDataLocation + ".jsgz")));

            if (targetingWasm)
            {
                // main game code
                CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameBasePath + ".wasm", FullPackageGameBasePath + ".wasmgz")));
                // main js.
                CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath, FullPackageGameExePath + "gz")));
            }
            else
            {
                // mem init file.
                CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath + ".mem", FullPackageGameExePath + ".memgz")));
                // main js.
                CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameBasePath + ".asm.js", FullPackageGameBasePath + ".asm.jsgz")));
                // main game code
                CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(ASMJS_FullPackageGameExePath, ASMJS_FullPackageGameExePath + "gz")));
            }

            // symbols file.
            CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath + ".symbols", FullPackageGameExePath + ".symbolsgz")));

            // Utility
            CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(OutDir + "/Utility.js", OutDir + "/Utility.jsgz")));

            Task.WaitAll(CompressionTasks.ToArray());
        }
        else
        {
            Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so not compressing. Build Shipping configuration to compress files to save space.");

            // nuke old compressed files to prevent using stale files
            File.Delete(FinalDataLocation + "gz");
            File.Delete(FinalDataLocation + ".jsgz");
            File.Delete(FullPackageGameExePath + "gz");
            File.Delete(FullPackageGameBasePath + ".wasmgz");
            File.Delete(FullPackageGameExePath + ".memgz");
            File.Delete(FullPackageGameExePath + ".symbolsgz");
            File.Delete(OutDir + "/Utility.jsgz");
        }

        File.Copy(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe"), CombinePaths(OutDir, "HTML5LaunchHelper.exe"), true);
//		Task.WaitAll(CompressionTasks);
        PrintRunTime();
    }
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        Log("Package {0}", Params.RawProjectPath);

        Log("Setting Emscripten SDK for packaging..");
        HTML5SDKInfo.SetupEmscriptenTemp();
        HTML5SDKInfo.SetUpEmscriptenConfigFile();

        string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5");

        if (!Directory.Exists(PackagePath))
        {
            Directory.CreateDirectory(PackagePath);
        }
        string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data";

        var ConfigCache = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5);

        if (HTMLPakAutomation.CanCreateMapPaks(Params))
        {
            HTMLPakAutomation PakAutomation = new HTMLPakAutomation(Params, SC);

            // Create Necessary Paks.
            PakAutomation.CreateEnginePak();
            PakAutomation.CreateGamePak();
            PakAutomation.CreateContentDirectoryPak();

            // Create Emscripten Package from Necessary Paks. - This will be the VFS.
            PakAutomation.CreateEmscriptenDataPackage(PackagePath, FinalDataLocation);

            // Create All Map Paks which  will be downloaded on the fly.
            PakAutomation.CreateMapPak();

            // Create Delta Paks if setup.
            List <string> Paks = new List <string>();
            ConfigCache.GetArray("/Script/HTML5PlatformEditor.HTML5TargetSettings", "LevelTransitions", out Paks);

            if (Paks != null)
            {
                foreach (var Pak in Paks)
                {
                    var    Matched = Regex.Matches(Pak, "\"[^\"]+\"", RegexOptions.IgnoreCase);
                    string MapFrom = Path.GetFileNameWithoutExtension(Matched[0].ToString().Replace("\"", ""));
                    string MapTo   = Path.GetFileNameWithoutExtension(Matched[1].ToString().Replace("\"", ""));
                    PakAutomation.CreateDeltaMapPaks(MapFrom, MapTo);
                }
            }
        }
        else
        {
            // we need to operate in the root
            string PythonPath   = HTML5SDKInfo.Python();
            string PackagerPath = HTML5SDKInfo.EmscriptenPackager();

            using (new ScopedEnvVar("EM_CONFIG", HTML5SDKInfo.DOT_EMSCRIPTEN))
            {
                using (new PushedDirectory(Path.Combine(Params.BaseStageDirectory, "HTML5")))
                {
                    string CmdLine = string.Format("\"{0}\" \"{1}\" --preload . --js-output=\"{1}.js\"", PackagerPath, FinalDataLocation);
                    RunAndLog(CmdEnv, PythonPath, CmdLine);
                }
            }
        }

        // copy the "Executable" to the package directory
        string GameExe = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename);

        if (Params.ClientConfigsToBuild[0].ToString() != "Development")
        {
            GameExe += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString();
        }
        GameExe += ".js";

        // ensure the ue4game binary exists, if applicable
        string FullGameExePath = Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe);

        if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullGameExePath))
        {
            Log("Failed to find game application " + FullGameExePath);
            throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find application {0}. You may need to build the UE4 project with your target configuration and platform.", FullGameExePath);
        }

        if (Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) != Path.Combine(PackagePath, GameExe))
        {
            File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe), Path.Combine(PackagePath, GameExe), true);
            File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) + ".mem", Path.Combine(PackagePath, GameExe) + ".mem", true);
            File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) + ".symbols", Path.Combine(PackagePath, GameExe) + ".symbols", true);
        }

        File.SetAttributes(Path.Combine(PackagePath, GameExe), FileAttributes.Normal);
        File.SetAttributes(Path.Combine(PackagePath, GameExe) + ".mem", FileAttributes.Normal);
        File.SetAttributes(Path.Combine(PackagePath, GameExe) + ".symbols", FileAttributes.Normal);


        // put the HTML file to the package directory
        string TemplateFileName = "GameX.html.template";
        string TemplateFile     = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", TemplateFileName);
        string OutputFile       = Path.Combine(PackagePath, (Params.ClientConfigsToBuild[0].ToString() != "Development" ? (Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) : Params.ShortProjectName)) + ".html";

        GenerateFileFromTemplate(TemplateFile, OutputFile, Params.ShortProjectName, Params.ClientConfigsToBuild[0].ToString(), Params.StageCommandline, !Params.IsCodeBasedProject, HTML5SDKInfo.HeapSize(ConfigCache, Params.ClientConfigsToBuild[0].ToString()));

        string MacBashTemplateFile = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", "RunMacHTML5LaunchHelper.command.template");
        string MacBashOutputFile   = Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command");
        string MonoPath            = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "BatchFiles", "Mac", "SetupMono.sh");

        GenerateMacCommandFromTemplate(MacBashTemplateFile, MacBashOutputFile, MonoPath);

        string htaccessTemplate = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", "htaccess.template");
        string htaccesspath     = Path.Combine(PackagePath, ".htaccess");

        if (File.Exists(htaccesspath))
        {
            FileAttributes attributes = File.GetAttributes(htaccesspath);
            if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                attributes &= ~FileAttributes.ReadOnly;
                File.SetAttributes(htaccesspath, attributes);
            }
        }
        File.Copy(htaccessTemplate, htaccesspath, true);

        string JSDir  = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5");
        string OutDir = PackagePath;

        // Gather utlity .js files and combine into one file
        string[] UtilityJavaScriptFiles = Directory.GetFiles(JSDir, "*.js");

        string DestinationFile = OutDir + "/Utility.js";

        File.Delete(DestinationFile);
        foreach (var UtilityFile in UtilityJavaScriptFiles)
        {
            string Data = File.ReadAllText(UtilityFile);
            File.AppendAllText(DestinationFile, Data);
        }

        // Compress all files. These are independent tasks which can be threaded.
        Task[] CompressionTasks = new Task[6];
        //data file.
        CompressionTasks[0] = Task.Factory.StartNew(() => CompressFile(FinalDataLocation, FinalDataLocation + "gz"));
        // data file .js driver.
        CompressionTasks[1] = Task.Factory.StartNew(() => CompressFile(FinalDataLocation + ".js", FinalDataLocation + ".jsgz"));
        // main js.
        CompressionTasks[2] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe), Path.Combine(PackagePath, GameExe) + "gz"));
        // mem init file.
        CompressionTasks[3] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe) + ".mem", Path.Combine(PackagePath, GameExe) + ".memgz"));
        // symbols file.
        CompressionTasks[4] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe) + ".symbols", Path.Combine(PackagePath, GameExe) + ".symbolsgz"));
        // Utility
        CompressionTasks[5] = Task.Factory.StartNew(() => CompressFile(OutDir + "/Utility.js", OutDir + "/Utility.jsgz"));

        File.Copy(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe"), CombinePaths(OutDir, "HTML5LaunchHelper.exe"), true);
        Task.WaitAll(CompressionTasks);
        PrintRunTime();
    }