Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/HTML5/HTML5Platform.Automation.cs

669 lines
24 KiB
C#
Raw Normal View History

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using Ionic.Zip;
using System.IO;
using System.Web;
using System.Net;
using System.Linq;
using System.Text;
using AutomationTool;
using UnrealBuildTool;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
public class HTML5Platform : Platform
{
public HTML5Platform()
: base(UnrealTargetPlatform.HTML5)
{
}
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 = new UnrealBuildTool.ConfigCacheIni(UnrealTargetPlatform.HTML5, "Engine", Path.GetDirectoryName(Params.RawProjectPath.FullName), CommandUtils.CombinePaths(CommandUtils.CmdEnv.LocalRoot, "Engine"));
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";
// find Heap Size.
ulong HeapSize;
int ConfigHeapSize = 0;
// Valuer set by Editor UI
var bGotHeapSize = ConfigCache.GetInt32("/Script/HTML5PlatformEditor.HTML5TargetSettings", "HeapSize" + Params.ClientConfigsToBuild[0].ToString(), out ConfigHeapSize);
// Fallback if the previous method failed
if (!bGotHeapSize && !ConfigCache.GetInt32("/Script/BuildSettings.BuildSettings", "HeapSize" + Params.ClientConfigsToBuild[0].ToString(), out ConfigHeapSize)) // in Megs.
{
// we couldn't find a per config heap size, look for a common one.
if (!ConfigCache.GetInt32("/Script/BuildSettings.BuildSettings", "HeapSize", out ConfigHeapSize))
{
ConfigHeapSize = Params.IsCodeBasedProject ? 1024 : 512;
Log("Could not find Heap Size setting in .ini for Client config {0}", Params.ClientConfigsToBuild[0].ToString());
}
}
HeapSize = (ulong)ConfigHeapSize * 1024L * 1024L; // convert to bytes.
Log("Setting Heap size to {0} Mb ", ConfigHeapSize);
GenerateFileFromTemplate(TemplateFile, OutputFile, Params.ShortProjectName, Params.ClientConfigsToBuild[0].ToString(), Params.StageCommandline, !Params.IsCodeBasedProject, HeapSize);
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";
Copying //UE4/Dev-Platform to //UE4/Main ========================== MAJOR FEATURES + CHANGES ========================== Change 2719147 on 2015/10/07 by Mark.Satterthwaite Allow the shader cache to perform some precompilation synchronously on load before falling back to asynchronous compilation to balance load times against total time spent precompiling. Added a stat to the group that reports how long the precompile has been running until it completes so it is easier to track. Change 2719182 on 2015/10/07 by Mark.Satterthwaite Refactor the ShaderCache's internal data structures and change the way we handle recording whether a particular predraw state has been submitted to try and make it more efficient. Change 2719185 on 2015/10/07 by Mark.Satterthwaite Merging CL #2717701: Try and fix random crashes on Mac when manipulating bound-shader-states caused by ShaderCache potentially providing a bogus shader state pointer on exit from predraw. Change 2719434 on 2015/10/07 by Mark.Satterthwaite Make sure that Mac ensures reports have a source context and a sane callstack when sent to the crash-reports server. Change 2724764 on 2015/10/12 by Josh.Adams [Initial AppleTV support] Merging //depot/YakBranch/... to //UE4/Dev-Platform/... Change 2726266 on 2015/10/13 by Lee.Clark PS4 - Calc reserve size required for DMA copy when using unsafe command buffers Change 2726401 on 2015/10/13 by Mark.Satterthwaite Merging CL #2716418: Fix UE-15228 'Crash Report Client doesn't restart into project editor on Mac' by reporting the original command line supplied by LaunchMac, not the modified one that strips the project name. The CRC can then relaunch as expected. #jira UE-15228 Change 2726421 on 2015/10/13 by Lee.Clark PS4 - Don't try to clear invalid targets Change 2727040 on 2015/10/13 by Michael.Trepka Merging CL 2724777 - Fixed splash screen rendering for images with DPI different than 72 Change 2729783 on 2015/10/15 by Keith.Judge Fix huge memory leak in Test/Shipping configurations, caused because I am a numpty. Change 2729847 on 2015/10/15 by Mark.Satterthwaite Merging CL #2729846: On OS X unconstrain windows from the dimension of the parent display when in Windowed mode - it is OK for them to be larger in this case. They do need to be repositioned if on the Primary display so that they don't creep under the menu bar and become unmovable/unclosable and Fullscreen windows still need to be constrained to a single display. We can now take screenshots of windows that are larger than the display & not get grey bars beyond the cutoff. #jira UE-21992 Change 2729865 on 2015/10/15 by Keith.Judge Fast semantics - Finish up resource transitions, adding resource decompression where appropriate and using non-fast clears where we can't determine the resource transition. Change 2729897 on 2015/10/15 by Keith.Judge Fast Semantics - Make sure all GetData() calls are made safe with GPU fences. Change 2729972 on 2015/10/15 by Keith.Judge Removed the last vestiges of ID3D11DeviceContext/ID3D11DeviceContext1 from the Xbox RHI. Everything now uses ID3D11DeviceContextX directly. This should be marginally quicker as it stops a double call to ClearState(). Change 2731503 on 2015/10/16 by Keith.Judge Added _XDK_VERSION to the DDC key for textures, which should solve the issue of the tiling mode changing in August XDK (and future changes Microsoft may inflict). Change 2731596 on 2015/10/16 by Keith.Judge Fast Semantics - Add deferred resource deletion queue to make deleted resources be actually deleted a number of frames later so that the GPU is definitely finished with them. Hooked up the temporary SRVs for dynamic VBs as a first step. Change 2731928 on 2015/10/16 by Michael.Trepka PR #1659: Mac/Build.sh handles additional arguments (Contributed by judgeaxl) Change 2731934 on 2015/10/16 by Michael.Trepka PR #1618: added clang 3.7.0 -Wshift-negative-value ignore in JpegImageWrapper.cpp (Contributed by bsekura) Change 2732018 on 2015/10/16 by Mark.Satterthwaite Emit a shader code cache for each platforms requested shader formats, this is separate to the targeted formats as not all can or need to be cached. - The implementation extends the ShaderCache's hooks in FShaderResource's serialisation function to capture the required shaders. - Each target platform has its own list of cached shader formats, analogous to the list of targeted RHIs. Presently only the Mac implements this. - Code cached shaders are now compressed (for size) to reduce the overhead associated with keeping all the shader code around - this works esp. well for text-based formats like GLSL. Change 2732365 on 2015/10/16 by Josh.Adams - Packaging a TVOS .ipa now works (still haven't tried any of the Editor integration like Launch On) Change 2733170 on 2015/10/18 by Terence.Burns Fix for Android IAP query not returning entire inventory. Change 2733174 on 2015/10/18 by Terence.Burns Fix Movie player issue where wait for movie to finish isnt being respected. Seems a stray bUserCanceled event flag was causing this not to be observed. Added some verbose logging to apple movie player. Change 2733488 on 2015/10/19 by Mark.Satterthwaite Added the ability to merge the .ushadercache files used by the ShaderCache to store shader & draw state information. - Fixed a bug that would cause invalid shader membership and draw state information to be logged. - Added a separate command-line tool to merge shader cache files, currently Mac-only but in theory should work on other platforms too. Change 2735226 on 2015/10/20 by Mark.Satterthwaite Fix temporal AA rendering on GL/Mac OS X - you can't rely on EyeAdaptation values unless SM5 is available so only perform that code on SM5 & we must correctly clamp saturate(NaN) to 0 as the current hlslcc won't do that for us (& is required by the HLSL spec). The latter used to be clamped in the AA_ALPHA && AA_VELOCITY_WEIGHTING code block that was removed recently. #jira UE-21214 #jira UE-19913 Change 2736722 on 2015/10/21 by Daniel.Lamb Improved performance of cooking stats system. Change 2737172 on 2015/10/21 by Daniel.Lamb Improved cooking stats performance for ddc stats.
2015-12-10 16:56:55 -05:00
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();
}
void CompressFile(string Source, string Destination)
{
Log(" Compressing " + Source);
bool DeleteSource = false;
if( Source == Destination )
{
string CopyOrig = Source + ".Copy";
File.Copy(Source, CopyOrig);
Source = CopyOrig;
DeleteSource = true;
}
using (System.IO.Stream input = System.IO.File.OpenRead(Source))
{
using (var raw = System.IO.File.Create(Destination))
{
using (Stream compressor = new Ionic.Zlib.GZipStream(raw, Ionic.Zlib.CompressionMode.Compress,Ionic.Zlib.CompressionLevel.BestCompression))
{
byte[] buffer = new byte[2048];
int SizeRead = 0;
while ((SizeRead = input.Read(buffer, 0, buffer.Length)) != 0)
{
compressor.Write(buffer, 0, SizeRead);
}
}
}
}
if (DeleteSource && File.Exists(Source))
{
File.Delete(Source);
}
}
public override bool RequiresPackageToDeploy
{
get { return true; }
}
protected void GenerateFileFromTemplate(string InTemplateFile, string InOutputFile, string InGameName, string InGameConfiguration, string InArguments, bool IsContentOnly, ulong HeapSize)
{
StringBuilder outputContents = new StringBuilder();
using (StreamReader reader = new StreamReader(InTemplateFile))
{
string LineStr = null;
while (reader.Peek() != -1)
{
LineStr = reader.ReadLine();
if (LineStr.Contains("%GAME%"))
{
LineStr = LineStr.Replace("%GAME%", InGameName);
}
if (LineStr.Contains("%HEAPSIZE%"))
{
LineStr = LineStr.Replace("%HEAPSIZE%", HeapSize.ToString());
}
if (LineStr.Contains("%CONFIG%"))
{
string TempGameName = InGameName;
if (IsContentOnly)
TempGameName = "UE4Game";
LineStr = LineStr.Replace("%CONFIG%", (InGameConfiguration != "Development" ? (TempGameName + "-HTML5-" + InGameConfiguration) : TempGameName));
}
if (LineStr.Contains("%UE4CMDLINE%"))
{
InArguments = InArguments.Replace ("\"", "");
string[] Arguments = InArguments.Split(' ');
string ArgumentString = IsContentOnly ? "'../../../" + InGameName + "/" + InGameName + ".uproject '," : "";
for (int i = 0; i < Arguments.Length - 1; ++i)
{
ArgumentString += "'";
ArgumentString += Arguments[i];
ArgumentString += "'";
ArgumentString += ",' ',";
}
if (Arguments.Length > 0)
{
ArgumentString += "'";
ArgumentString += Arguments[Arguments.Length - 1];
ArgumentString += "'";
}
LineStr = LineStr.Replace("%UE4CMDLINE%", ArgumentString);
}
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.
}
}
}
protected void GenerateMacCommandFromTemplate(string InTemplateFile, string InOutputFile, string InMonoPath)
{
StringBuilder outputContents = new StringBuilder();
using (StreamReader reader = new StreamReader(InTemplateFile))
{
string InMonoPathParent = Path.GetDirectoryName(InMonoPath);
string LineStr = null;
while (reader.Peek() != -1)
{
LineStr = reader.ReadLine();
if (LineStr.Contains("${unreal_mono_pkg_path}"))
{
LineStr = LineStr.Replace("${unreal_mono_pkg_path}", InMonoPath);
}
if (LineStr.Contains("${unreal_mono_pkg_path_base}"))
{
LineStr = LineStr.Replace("${unreal_mono_pkg_path_base}", InMonoPathParent);
}
outputContents.Append(LineStr + '\n');
}
}
if (outputContents.Length > 0)
{
// Save the file. We Copy the template file to keep any permissions set to it.
try
{
Directory.CreateDirectory(Path.GetDirectoryName(InOutputFile));
if (File.Exists(InOutputFile))
{
File.SetAttributes(InOutputFile, File.GetAttributes(InOutputFile) & ~FileAttributes.ReadOnly);
File.Delete(InOutputFile);
}
File.Copy(InTemplateFile, InOutputFile);
File.SetAttributes(InOutputFile, File.GetAttributes(InOutputFile) & ~FileAttributes.ReadOnly);
using (var CmdFile = File.Open(InOutputFile, FileMode.OpenOrCreate | FileMode.Truncate))
{
Byte[] BytesToWrite = new UTF8Encoding(true).GetBytes(outputContents.ToString());
CmdFile.Write(BytesToWrite, 0, BytesToWrite.Length);
}
}
catch (Exception)
{
// Unable to write to the project file.
}
}
}
public override void GetFilesToDeployOrStage(ProjectParams Params, DeploymentContext SC)
{
}
public override void GetFilesToArchive(ProjectParams Params, DeploymentContext SC)
{
if (SC.StageTargetConfigurations.Count != 1)
{
throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
}
string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5");
string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data";
// copy the "Executable" to the archive directory
string GameExe = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename);
if (Params.ClientConfigsToBuild[0].ToString() != "Development")
{
GameExe += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString();
}
GameExe += ".js";
// put the HTML file to the package directory
string OutputFile = Path.Combine(PackagePath, (Params.ClientConfigsToBuild[0].ToString() != "Development" ? (Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) : Params.ShortProjectName)) + ".html";
// data file
SC.ArchiveFiles(PackagePath, Path.GetFileName(FinalDataLocation));
SC.ArchiveFiles(PackagePath, Path.GetFileName(FinalDataLocation + "gz"));
// data file js driver
SC.ArchiveFiles(PackagePath, Path.GetFileName(FinalDataLocation + ".js"));
SC.ArchiveFiles(PackagePath, Path.GetFileName(FinalDataLocation + ".jsgz"));
// main js file
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe));
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe + "gz"));
// memory init file
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe + ".mem"));
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe + ".memgz"));
// symbols file
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe + ".symbols"));
SC.ArchiveFiles(PackagePath, Path.GetFileName(GameExe + ".symbolsgz"));
// utilities
SC.ArchiveFiles(PackagePath, Path.GetFileName("Utility.js"));
SC.ArchiveFiles(PackagePath, Path.GetFileName("Utility.jsgz"));
// landing page.
SC.ArchiveFiles(PackagePath, Path.GetFileName(OutputFile));
// Archive HTML5 Server and a Readme.
var LaunchHelperPath = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/");
SC.ArchiveFiles(LaunchHelperPath, "HTML5LaunchHelper.exe");
SC.ArchiveFiles(Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5"), "Readme.txt");
SC.ArchiveFiles(PackagePath, Path.GetFileName(Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command")));
SC.ArchiveFiles(PackagePath, Path.GetFileName(Path.Combine(PackagePath, ".htaccess")));
if (HTMLPakAutomation.CanCreateMapPaks(Params))
{
// find all paks.
string[] Files = Directory.GetFiles(Path.Combine(PackagePath, Params.ShortProjectName), "*",SearchOption.AllDirectories);
foreach(string PakFile in Files)
{
var DestPak = PakFile.Replace(PackagePath,"");
SC.ArchivedFiles.Add(PakFile, DestPak);
}
}
UploadToS3(SC);
}
public override ProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params)
{
// look for browser
string BrowserPath = Params.Device.Replace("HTML5@", "");
// open the webpage
Int32 ServerPort = 8000;
var ConfigCache = new UnrealBuildTool.ConfigCacheIni(UnrealTargetPlatform.HTML5, "Engine", Path.GetDirectoryName(Params.RawProjectPath.FullName), CombinePaths(CmdEnv.LocalRoot, "Engine"));
ConfigCache.GetInt32("/Script/HTML5PlatformEditor.HTML5TargetSettings", "DeployServerPort", out ServerPort);
string WorkingDirectory = Path.GetDirectoryName(ClientApp);
string url = Path.GetFileName(ClientApp) +".html";
// Are we running via cook on the fly server?
// find our http url - This is awkward because RunClient doesn't have real information that NFS is running or not.
bool IsCookOnTheFly = false;
// 9/24/2014 @fixme - All this is convoluted, clean up.
// looks like cookonthefly commandline stopped adding protocol or the port :/ hard coding to DEFAULT_TCP_FILE_SERVING_PORT+1 (DEFAULT_HTTP_FILE_SERVING_PORT)
// This will fail if the NFS server is started with a different port - we need to modify driver .cs script to pass in IP/Port data correctly.
if (ClientCmdLine.Contains("filehostip"))
{
IsCookOnTheFly = true;
url = "http://127.0.0.1:41898/" + url;
}
if (IsCookOnTheFly)
{
url += "?cookonthefly=true";
}
else
{
url = String.Format("http://localhost:{0}/{1}", ServerPort, url);
}
// Check HTML5LaunchHelper source for command line args
var LowerBrowserPath = BrowserPath.ToLower();
var ProfileDirectory = Path.Combine(Utils.GetUserSettingDirectory().FullName, "UE4_HTML5", "user");
string BrowserCommandline = url;
if (LowerBrowserPath.Contains("chrome"))
{
BrowserCommandline += " " + String.Format("--user-data-dir=\\\"{0}\\\" --enable-logging --no-first-run", Path.Combine(ProfileDirectory, "chrome"));
}
else if (LowerBrowserPath.Contains("firefox"))
{
BrowserCommandline += " " + String.Format("-no-remote -profile \\\"{0}\\\"", Path.Combine(ProfileDirectory, "firefox"));
}
string LauncherArguments = string.Format(" -Browser=\"{0}\" + -BrowserCommandLine=\"{1}\" -ServerPort=\"{2}\" -ServerRoot=\"{3}\" ", new object[] { BrowserPath, BrowserCommandline, ServerPort, WorkingDirectory });
var LaunchHelperPath = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe");
ProcessResult BrowserProcess = Run(LaunchHelperPath, LauncherArguments, null, ClientRunFlags | ERunOptions.NoWaitForExit);
return BrowserProcess;
}
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly, string CookFlavor)
{
return "HTML5";
}
public override string GetCookExtraCommandLine(ProjectParams Params)
{
return HTMLPakAutomation.CanCreateMapPaks(Params) ? " -GenerateDependenciesForMaps " : "";
}
public override bool DeployPakInternalLowerCaseFilenames()
{
return false;
}
public override PakType RequiresPak(ProjectParams Params)
{
return HTMLPakAutomation.CanCreateMapPaks(Params) ? PakType.Never : PakType.Always;
}
public override string GetPlatformPakCommandLine()
{
return " -compress";
}
public override bool DeployLowerCaseFilenames(bool bUFSFile)
{
return false;
}
public override string LocalPathToTargetPath(string LocalPath, string LocalRoot)
{
return LocalPath;//.Replace("\\", "/").Replace(LocalRoot, "../../..");
}
public override bool IsSupported { get { return true; } }
public override List<string> GetDebugFileExtentions()
{
return new List<string> { ".pdb" };
}
#region Hooks
public override void PreBuildAgenda(UE4Build Build, UE4Build.BuildAgenda Agenda)
{
}
public override List<string> GetExecutableNames(DeploymentContext SC, bool bIsRun = false)
{
var ExecutableNames = new List<String>();
ExecutableNames.Add(Path.Combine(SC.ProjectRoot, "Binaries", "HTML5", SC.ShortProjectName));
return ExecutableNames;
}
#endregion
#region AMAZON S3
public void UploadToS3(DeploymentContext SC)
{
ConfigCacheIni Ini = ConfigCacheIni.CreateConfigCacheIni(SC.StageTargetPlatform.PlatformType, "Engine", DirectoryReference.FromFile(SC.RawProjectPath));
bool Upload = false;
string KeyId = "";
string AccessKey = "";
string BucketName = "";
string FolderName = "";
if (Ini.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "UploadToS3", out Upload))
{
if (!Upload)
return;
}
else
{
return;
}
bool AmazonIdentity = Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3KeyID", out KeyId) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3SecretAccessKey", out AccessKey) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3BucketName", out BucketName) &&
Ini.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "S3FolderName", out FolderName);
if ( !AmazonIdentity )
{
Log("Amazon S3 Incorrectly configured");
return;
}
if ( FolderName == "" )
{
FolderName = SC.ShortProjectName;
}
List<Task> UploadTasks = new List<Task>();
foreach(KeyValuePair<string, string> Entry in SC.ArchivedFiles)
{
FileInfo Info = new FileInfo(Entry.Key);
UploadTasks.Add (Task.Factory.StartNew(() => UploadToS3Worker(Info,KeyId,AccessKey,BucketName,FolderName)));
}
Task.WaitAll(UploadTasks.ToArray());
string URL = "http://" + BucketName + ".s3.amazonaws.com/" + FolderName + "/" + SC.ShortProjectName + ".html";
Log("Your project's shareable link is: " + URL);
Log("Upload Tasks finished.");
}
private static IDictionary<string, string> MimeTypeMapping = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
{ ".html", "text/html"},
{ ".jsgz", "application/x-javascript" }, // upload compressed javascript.
{ ".datagz", "appication/octet-stream"}
};
public void UploadToS3Worker(FileInfo Info, string KeyId, string AccessKey, string BucketName, string FolderName )
{
Log(" Uploading " + Info.Name);
// force upload files even if the timestamps haven't changed.
string TimeStamp = string.Format("{0:r}", DateTime.UtcNow);
string ContentType = "";
if (MimeTypeMapping.ContainsKey(Info.Extension))
{
ContentType = MimeTypeMapping[Info.Extension];
}
else
{
// default
ContentType = "application/octet-stream";
}
// URL to put things.
string URL = "http://" + BucketName + ".s3.amazonaws.com/" + FolderName + "/" + Info.Name;
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
// Upload.
Request.Method = "PUT";
Request.Headers["x-amz-date"] = TimeStamp;
Request.Headers["x-amz-acl"] = "public-read"; // we probably want to make public read by default.
// set correct content encoding for compressed javascript.
if ( Info.Extension.EndsWith("gz") )
{
Request.Headers["Content-Encoding"] = "gzip";
}
Request.ContentType = ContentType;
Request.ContentLength = Info.Length;
//http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
// Find Base64Encoded data.
UTF8Encoding EncodingMethod = new UTF8Encoding();
HMACSHA1 Signature = new HMACSHA1 { Key = EncodingMethod.GetBytes(AccessKey) };
// don't change this string.
string RequestString = "PUT\n\n" + ContentType + "\n\nx-amz-acl:public-read\nx-amz-date:" + TimeStamp + "\n/"+ BucketName + "/" + FolderName + "/" + Info.Name;
Byte[] ComputedHash = Signature.ComputeHash(EncodingMethod.GetBytes(RequestString));
var Base64Encoded = Convert.ToBase64String(ComputedHash);
// final amz auth header.
Request.Headers["Authorization"] = "AWS " + KeyId + ":" + Base64Encoded;
try
{
// may fail for really big stuff. YMMV. May need Multi part approach, we will see.
Byte[] FileData = File.ReadAllBytes(Info.FullName);
var requestStream = Request.GetRequestStream();
requestStream.Write(FileData, 0, FileData.Length);
requestStream.Close();
using (var response = Request.GetResponse() as HttpWebResponse)
{
var reader = new StreamReader(response.GetResponseStream());
reader.ReadToEnd();
}
}
catch (Exception ex)
{
Log("Could not connect to S3, incorrect S3 Keys? " + ex.ToString());
throw ex;
}
Log(Info.Name + " has been uploaded ");
}
#endregion
}