Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Android/UEDeployAndroid.cs
Matthew Griffin 1fa8a231ea Copying //UE4/Release-Staging-4.13 to //UE4/Dev-Main (Source: //UE4/Release-4.13 @ 3072953)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3072953 on 2016/08/01 by Uriel.Doyon

	Texture GUIDs are now included in cooked builds, as they are required by the texture streamer to link build data to in game textures.
	#jira UE-34045

Change 3072915 on 2016/08/01 by Nick.Whiting

	Fixing Google VR Preview being distorted by fixing merge error specifying number of verts per distortion mesh

	#jira UE-34044

Change 3072891 on 2016/08/01 by Nick.Whiting

	Integrating fix from DevVR to force GameGetsMouseControl to on when using VR PIE, since you always need focus for motion controllers

	#jira UE-33579

Change 3072885 on 2016/08/01 by Nick.Darnell

	UMG/Slate - Moving the SlateTextureAtlasInterface to engine, and redoing the parameters so that we can properly calculate the UV start and size information with full knowledge inside the texture so that we can deal with problems like squaring that happens on PVRTC textures.  Also moving the interface to engine so that dependent plugins/other consumers don't need to load their modules extremely early to deal with the RHIRenderers need to load early for the shaders, also wanted to avoid loading it when Paper2D is needed on servers.

	Continued - adding missing file.

	#jira UE-32876

Change 3072869 on 2016/08/01 by Ori.Cohen

	Fix CIS

	#jira UE-3402

Change 3072862 on 2016/08/01 by Josh.Adams

	- Fixed case issue for Linux
	#jira UE-34020

Change 3072818 on 2016/08/01 by Nick.Darnell

	UMG/Slate - Moving the SlateTextureAtlasInterface to engine, and redoing the parameters so that we can properly calculate the UV start and size information with full knowledge inside the texture so that we can deal with problems like squaring that happens on PVRTC textures.  Also moving the interface to engine so that dependent plugins/other consumers don't need to load their modules extremely early to deal with the RHIRenderers need to load early for the shaders, also wanted to avoid loading it when Paper2D is needed on servers.

	#jira UE-32876

Change 3072756 on 2016/08/01 by John.Billon

	Fixed crash when setting a composite texture on a render target cube.
	#Jira UE-33885

Change 3072755 on 2016/08/01 by John.Billon

	Exposed GPUMorphTargets (r.MorphTarget.Mode) as a project setting.
	#Jira UE-33574

Change 3072753 on 2016/08/01 by John.Billon

	Fixed a possible null dereference in distrubutions that was causing crashes when changing particle parameters.
	#Jira UE-32565
	#Jira UE-29528

Change 3072665 on 2016/08/01 by Ben.Marsh

	Fix parse errors in BuildGraph example script.

Change 3072664 on 2016/08/01 by Mike.Beach

	Mirrors CL 3072620 from Dev-Blueprints.

	Reverting a presumptive (guessed-at) fix from CL 2830752 (UE-22075). This was preventing REINST classes from retaining certain UObject references (specifically data/objects stored in Actor's "CurrentTransactionAnnotation" member). Those objects would be GC'd during the reinstancing process, and when we copied that data over later, towards the end of reinstancing, we'd be copying bad object pointers to the new actors.

	#jira UE-29631

Change 3072656 on 2016/08/01 by Mike.Beach

	Mirrors CL 3072614 from Dev-Blueprints.

	Fixing an issue where hot-reloading a Blueprint parent class was not reinstancing skeleton CDOs. This caused problems later where the skel class layout didn't reflect the CDO object.

	#jira UE-29613

Change 3072649 on 2016/08/01 by Mike.Beach

	Mirrors CL 3071292 from Dev-Blueprints.

	Preventing the Blueprint reinstancer's Function/PropertyMap from being GC'd during compile. This was causing issues where new functions/properties were being allocated in the same pointer location, and UpdateBytecodeReferences() was replacing those references as well (specifically in unrelated class's Children->Next chain, linking in functions/properties that did not belong to that class). This was causing a multitude of problems (mainly bad property offset read/writes and endless field iterator loops).

	#jira UE-29631

Change 3072568 on 2016/08/01 by Phillip.Kavan

	Blueprints: Prevent a crash on load in RemoveNodeAndPromoteChildren when removing a corrupted SCS node if it has no parent link (the children are moved to the root node instead)

	Mirrored from //Orion/Dev-General (CLs# 3065749/3065868).

	#jira UE-32780

Change 3072565 on 2016/08/01 by Rolando.Caloca

	UE4.13 - More info to track down crash with missing Primitive uniform buffer
	#jira UE-33418

Change 3072526 on 2016/08/01 by Matt.Kuhlenschmidt

	Fix hovering broken in the details panel
	#jira UE-20903

Change 3072509 on 2016/08/01 by Matt.Kuhlenschmidt

	Removed nested list views in a details panel customization which  caused the scrollbar in the details panel to become unusable
	#jira UE-20903

Change 3072479 on 2016/08/01 by Ori.Cohen

	Fix potential crash when calling SetSkeletalMesh on a skeletal mesh component that's using per poly collision

	#JIRA UE-34023

Change 3072438 on 2016/08/01 by Chris.Wood

	Fixed ICU dll loading logic so that monolithic tools like CRC don't try to load them.
	[UE-33943] - Crash Report Client window not opening in a packaged build

	#jira UE-33943
	#test Editor, run Packaged QAGame, crash Packaged QAGame, runs CrashReportClient, run SlateViewer, run EpicGamesLauncher

Change 3072360 on 2016/08/01 by Chris.Babcock

	Enable Google Play Games for ARM64 on Android
	#jira UE-34031
	#ue4
	#android

Change 3072337 on 2016/08/01 by Mitchell.Wilson

	Saving multiple files from VR template to resolve empty engine version warnings.
	#jira UE-33937

Change 3072302 on 2016/08/01 by Lina.Halper

	Fix issue where weight doesn't update correctly while updating list

	#jira: UE-33023

Change 3072250 on 2016/08/01 by Lina.Halper

	Add error message when rename failed

	#jira: UE-33661

Change 3072103 on 2016/08/01 by Lina.Halper

	- Undid previous propagating change of morphtarget - Refresh function
	- Made sure whatever happening, the buffer size remains sane and render thread will always get the same size

	#code review: Rolando.Caloca
	#jira: UE-33923

Change 3072062 on 2016/08/01 by Jurre.deBaare

	Static Mesh Editor and Persona viewport are very dark
	#fix Added same default config value for the directional light rotation as in FPreviewScene (otherwise would result in nulled rotator)
	#jira UE-33945

Change 3072061 on 2016/08/01 by Jurre.deBaare

	Incorrect importing of morph target weights when setting the percentage bases option on import
	#fix use original number of singular values to index into the weights array (otherwise we would be reading incorrect data if NumUsedSingularValues != the original number
	#jira UE-34003

Change 3072052 on 2016/08/01 by Chris.Babcock

	Vulkan extension fixes for Android
	#jira UE-32943
	#ue4
	#android

Change 3072039 on 2016/08/01 by Mitchell.Wilson

	Adding blueprint child of Paper2D character to the 2DSideScrollerExampleMap.
	#jira UE-33843

Change 3072003 on 2016/08/01 by Rob.Cannaday

	Change category of OnlineSubsystem, OnlineFramework from "TODO" to "Online Platform" to match other online subsystems.
	#jira UE-34008

Change 3071942 on 2016/08/01 by Matthew.Griffin

	Adding feature pack for TP_VirtualRealityBP

Change 3071937 on 2016/08/01 by Max.Chen

	Sequence Recorder: Fix a bug where transforms wouldn't be captured if an anim recorder exists but the skeletal mesh that the anim recorder is supposed to capture doesn't exist. This fixes the first person template character not getting recorded.

	#jira UE-32918

Change 3071932 on 2016/08/01 by Dmitry.Rekman

	Linux: fix launch on (UE-33934)

	#tests Tested launching on a native host.
	#jira UE-33934

	(Edigrating CL 3071928 //UE4/Dev-Platform/... to //UE4/Release-4.13/...)

Change 3071926 on 2016/08/01 by Andrew.Rodham

	Sequencer: Fixed exponential slowdown when restoring selection states

	#jira UE-33918

Change 3071917 on 2016/08/01 by Mitchell.Wilson

	Disabled shadow casting on RTS_Env_Ice_Fort_Trim pieces that are placed along the path in TowerDefenseMap and rebuilt lighting.
	#jira UE-15196

Change 3071914 on 2016/08/01 by Allan.Bentham

	Fix for incorrect feature level when using networked PIE.
	#jira UE-25807

Change 3071894 on 2016/08/01 by Andrew.Rodham

	Sequence Recorder: CIS fix
	#jira UE-31277

Change 3071884 on 2016/08/01 by phillip.patterson

	Updated UMG_Invalidation.uasset to Include Combo Box Test

	#jira UE-29618

Change 3071869 on 2016/08/01 by Mitchell.Wilson

	Changed LPF Freq Max on example 1.5 to bettery demonstrate Loww Pass Filter feature.
	#jira UE-33714

Change 3071868 on 2016/08/01 by phillip.patterson

	Added UMG_Invalidation.uasset for a test case

	#jira UE-29618

Change 3071855 on 2016/08/01 by Jurre.deBaare

	Engine fails to compile in Alembic with DebugBuildsActuallyUseDebugCRT enabled
	#fix Recompiled zlib to be correct debug version
	#jira UE-27576

Change 3071853 on 2016/08/01 by Jurre.deBaare

	Fix issue with debug asserts not compiling correctly
	#fix Debug build will use a different macro path in DetourAssert/RecastAssert in which there is a , instead of a ; which the compiler complains about
	#jira UE-33989

Change 3071851 on 2016/08/01 by Matt.Kuhlenschmidt

	Added guards against force deleting objects garbage collecting  objects while they are being deleted.  This will still ensure in an attempt to isolate the actual issue.
	#jira UE-33013

Change 3071849 on 2016/08/01 by Tom.Looman

	Resaved content files with engine version for VR Template

	#jira ue-33325

Change 3071822 on 2016/08/01 by Mitchell.Wilson

	Adding crosshair to WeapLauncher when not sighted in.
	#jira UE-30617

Change 3071798 on 2016/08/01 by Andrew.Rodham

	Sequencer: Fixed various issues to do with recording attached components

	There were several edge cases where attached components would be recorded with incorrect animation, transforms, or not recorded at all.

	#jira UE-30574
	#jira UE-31277

Change 3071789 on 2016/08/01 by Tom.Looman

	Fixed warning of missing gamemode in VR Template.

	#jira ue-33325

Change 3071787 on 2016/08/01 by Mitchell.Wilson

	Cleared material interface on Neutral.uasset to resolve a warning.
	#jira UE-33957

Change 3071784 on 2016/08/01 by Robert.Manuszewski

	Making sure UMediaPlayer objects are not added to any GC clusters because they can load additional assets after they had PostLoad called on them and that results in Disregard For GC assumptions being violated.

	#jira UE-33692
	#jira UE-33814

Change 3071746 on 2016/08/01 by Tom.Looman

	Added config.ini for Feature Pack creation to VR Template

	#jira ue-33325

Change 3071694 on 2016/08/01 by Robert.Manuszewski

	Fixing crash after opening edit config data for remote build then packaging

	#jira UE-33719

Change 3071660 on 2016/08/01 by Dmitriy.Dyomin

	Fixed: Nexus 5, Android 4.4.4 has inverted R/B color channels with r.TonemapperFilm enabled (replaced usage of LinearToSrgbBranching with LinearToSrgbBranchless for mobile)
	Also removed "OutputDevice" branches that are not used on mobile, otherwise generated LUT pixel shader has more than 2k lines and device refuses to compile it
	#jira UE-30104

Change 3071657 on 2016/08/01 by Matthew.Griffin

	Excluded TP_VirtualRealityBP Template from Mac Binary builds.

Change 3071651 on 2016/08/01 by Tom.Looman

	Removed config.ini ref from content.txt

	#jira UE-33325

Change 3071645 on 2016/08/01 by Jurre.deBaare

	Merge Actor Tool missing option to deselect Export Specific LOD
	#fix Added the ability to export a specific LOD or all LODs for the selected objects
	#jira UE-33100

	Non wrapped UVs in static mesh cause incorrect UVs on (HLOD) merged static mesh
	#fix force to generate unique UVs for baking out the material to ensure we get the correct texture data
	#jira UE-29976

Change 3071608 on 2016/08/01 by Thomas.Sarkanen

	Bringing hitch tracking fix over from Orion.

	Engine: Properly resetting the hitch buckets at the start of each FPS chart, so hitch time isn't accumulated across multiple runs

	#jira UE-33911 - Time spent in hitch tracking buckets is not reset between fps chart captures (so it grows each capture in a session)

Change 3071606 on 2016/08/01 by Matthew.Griffin

	Added TP_VirtualRealityBP to list of templates to make feature packs from, build DDC for and include in binary build.
	#jira UE-33959

Change 3071584 on 2016/08/01 by Matthew.Griffin

	Added support for per file Intellisense PCH settings, to improve its startup speed - disabled by default due to crashes.
	Split GetDirectIncludeDependencies function so that part of it could be used without having a build target.
	#jira UE-23720

Change 3071479 on 2016/07/31 by Dmitriy.Dyomin

	Fixed FAssetPtr remapping issues for duplicated packages (level Save As, level Duplicate). This fixes issue with broken foliage base cache.
	Hardened code in foliage base cache and removed asserts, so maps with broken cache can still load
	Merged from Dev-Mobile CL# 3057039
	#jira UE-32774

Change 3071478 on 2016/07/31 by Uriel.Doyon

	Fixed UnbuiltInstanceBoundsList not being reset correctly, creating broken rendered primitives.
	#jira UE-32585

Change 3071282 on 2016/07/30 by Max.Chen

	Cine Camera Actor: CIS fix

	#jira UE-33805

Change 3071272 on 2016/07/30 by Max.Chen

	Cine Camera Actor: Fix debug focus plane not getting updated when animated. Refactor UpdateDebugFocusPlane so that it's called from the actor tick instead of just in GetCameraView.

	#jira UE-33805

Change 3071229 on 2016/07/30 by Ben.Marsh

	Fix static analysis warning.

Change 3071077 on 2016/07/29 by Max.Chen

	Sequencer: Set relative location, rotation, scale explicitly to identity instead of calling ResetRelativeTransform because we don't want overlaps to fire until after the update pass.

	#jira UE-33432

Change 3071076 on 2016/07/29 by Max.Chen

	Sequencer: Set event track eval order to fire first. This fixes some ambiguity and also a bug where transform tracks don't evaluate pre/post if the eval position is changed in the middle of evaluation.

	#jira UE-33078

Change 3071070 on 2016/07/29 by Max.Chen

	Sequence Recorder: Record actors as possessables

	Added GetWorld() check when resolving bindings in case the world is being torn down.

	Copy from Dev-Sequencer

	#jira UE-33969

Change 3071069 on 2016/07/29 by Max.Chen

	Sequencer: Add Convert to Possessable

	Copy from Dev-Sequencer

	#jira UE-32139

Change 3071058 on 2016/07/29 by Max.Chen

	Sequencer: Tweak track colors

	Audio track brighter
	Transform, bool, event tracks less saturated
	Recording subsection more saturated
	Fade track gradient

	Copy from Dev-Sequencer

	#jira UE-33968

Change 3071057 on 2016/07/29 by Max.Chen

	Sequencer: Remove curve editor visibility as a toggleable config. It's now just a toggleable state that defaults to false. This fixes unexpected behavior of staying in the curve editor when restarting the editor or switching to a different level sequence asset.

	Copy from Dev-Sequencer

	#jira UE-33967

Change 3071004 on 2016/07/29 by Lauren.Ridge

	Fix for crash on color picker summon due to null SWindow #rb chris.gagnon

	#jira UE-33966

Change 3070956 on 2016/07/29 by Chris.Babcock

	Disable Oculus stress tests on Android(for now) to remove shader dependency unhandled on Mac editor
	#jira UE-33607
	#ue4
	#android

Change 3070807 on 2016/07/29 by Nick.Darnell

	Slate - Disabling thickness calculation in slate lines, the underlying code doesn't properly handle the edge cases that causes a breakdown and the lines become flipped/twisted, or have zero width.

	#jira UE-30481

Change 3070779 on 2016/07/29 by Rob.Cannaday

	Re-add bCompileSteamOSS as deprecated with a notice on how to use OnlineSubsystemSteam
	Remove reference to bCompileSteamOSS from GameModule.Build.cs.template, replacing it with a comment of how to include OnlineSubsystemSteam
	#jira UE-33922

Change 3070766 on 2016/07/29 by Matt.Kuhlenschmidt

	Make sure richtooltips are not generated for hidden enum items so that there is not a mismatch between rich tooltips and enum items (causing a crash)

	#jira UE-33914

Change 3070764 on 2016/07/29 by Phillip.Kavan

	[UE-20581] Optimize BP auto-recompile on PIE startup for BPs with multiple dependencies.

	Mirrored from CL# 3065278. (resubmitted as edit)

	#jira UE-20581

Change 3070757 on 2016/07/29 by Nick.Darnell

	Slate - Anything that requests a CreateUpdatableTexture from the SlateRHIRenderer and later releaseses it, the renderer now keeps those releases around for an extra frame on the game thread to avoid deleting a pointer that may have already been queued up on the CPU side of the renderer to be used in an element batch.  Which is what happens if you remove a widget in it's own tick, that happens to also contain a web browser widget.

	#jira UE-33450

Change 3070741 on 2016/07/29 by Phillip.Kavan

	Back out previous submit (forgot to convert to edit).

	#jira UE-20581

Change 3070737 on 2016/07/29 by Phillip.Kavan

	[UE-20581] Optimize BP auto-recompile on PIE startup for BPs with multiple dependencies.

	Mirrored from CL# 3065278.

	#jira UE-20581

Change 3070695 on 2016/07/29 by Ryan.Vance

	#jira UE-32145
	We were using the wrong texture format for the rift ogl bridge.
	Removed derived ogl bridge destructor to fix assert.
	Based on CL 3069701 from Oculus

Change 3070632 on 2016/07/29 by Mitchell.Wilson

	Rebuilt lighting for SubwaySequencer
	#jira UE-33564

Change 3070620 on 2016/07/29 by Chris.Babcock

	Fast ASTC texture compression, using ISPC.

	#jira UE-32308

Change 3070586 on 2016/07/29 by phillip.patterson

	Updating Sequencer_Focus for test case

	#jira UE-29618

Change 3070539 on 2016/07/29 by Jon.Nabozny

	Fix PhysX error where CCD is enabled on a Kinematic body. (This is copied from 3061370)

	#jira UE-33463

Change 3070538 on 2016/07/29 by Mitchell.Wilson

	Resaving TowerDefenseMap_Effects, TowerDefenseMap_Lights, and TowerDefenseMap_M to resolve MikkTSpace warnings.
	#jira UE-29730

Change 3070467 on 2016/07/29 by Lauren.Ridge

	Making the Color Picker accessible in VR Editing mode, hiding the eyedropper in VR mode. #rb mike.fricker

	#jira UE-33920
	#jira UE-33769

Change 3070460 on 2016/07/29 by Lauren.Ridge

	Changing VR Screenshot mode to use direct capture of the mirrored view on the monitor #rb mike.fricker

	#jira UE-32413

Change 3070455 on 2016/07/29 by Lauren.Ridge

	Fixes for auto-entry to VR mode. Adding HMD validity checks, Steam VR only switches to not worn after being in the worn state, adding default setting to ini file. #rb mike.fricker

	#jira UE-33635

Change 3070404 on 2016/07/29 by John.Pollard

	Fix: Console command "Open" crashes with dedicated server settings

	#jira UE-32511

Change 3070380 on 2016/07/29 by Matt.Kuhlenschmidt

	Fix incorrect tooltip for the lerp instruction in the material editor

	#jira UE-33896

Change 3070376 on 2016/07/29 by Ryan.Vance

	#jira UEVR-32
	Support base and neo multi-view vertex shaders on ps4.
	Compile both base and neo versions of the multi-view enabled vertex shaders.
	Pack them together in the resulting shader code.
	Unpack them and load the correct version when creating the vertex shader instance.

Change 3070345 on 2016/07/29 by James.Cobbett

	#jira UE-29618 Submitting test assets for Alembic Importer

Change 3070315 on 2016/07/29 by Ben.Woodhouse

	(cherry picked from dev-rendering)
	Fix for cooker crash with BC6H textures (XB1, but may affect other platforms). Also fixes corruption issue with texture slices not being a multiple of 4 pixels (expanding as necessary), courtesy of Stu McKenna at the Coalition
	Tested fix on xbox, PC and PS4, using QAGame
	#jira UE-28592

Change 3070314 on 2016/07/29 by Ben.Woodhouse

	(cherry picked from dev-rendering)

	Fix div 0 in motion blur. This caused artifacts in some fairly common cases
	#jira UE-32331

Change 3070272 on 2016/07/29 by Jon.Nabozny

	Fix CIS by removing unused (and deprecated) call to GetMovementInputVector() in ShooterCharacter.

	#jira UE-33944

Change 3070235 on 2016/07/29 by Mitchell.Wilson

	Hid Camera_Movement effects when the user is interacting with the sand in BP_RakeStuff.
	#jira UE-32742

Change 3070221 on 2016/07/29 by Jurre.deBaare

	HLOD: The forced viewing level slider does not stay in sync after building a cluster
	#fix The minimum drawing distance was being set to the original instead of the current value which would make it behaviour as normal (not being forced)
	#jira UE-32187

Change 3070218 on 2016/07/29 by Jurre.deBaare

	HLOD: Shadow logic in ALODActor is messed up
	#fix Moved shadow determination logic
	#jira UE-31753

Change 3070212 on 2016/07/29 by Jurre.deBaare

	HLOD Outliner scrolls back to the top when generating proxy meshes
	#fix Not refreshing the HLOD Outliner (not needed), and force scroll into view the first selected cluster
	#jira UE-30384

Change 3070176 on 2016/07/29 by Jurre.deBaare

	Some post processing features in Preview Scene Settings do not update immediately
	#fix Vector values as properties were not getting picked up due to their outer not being the struct but an FVector :D
	#jira UE-33895

Change 3070175 on 2016/07/29 by Jurre.deBaare

	Static Mesh Editor does not display Vertex Colors in Lit mode
	#fix Caused by not disabling advanced features which used to happen for preview scenes by default :/
	#jira UE-32977

Change 3070163 on 2016/07/29 by Chris.Wood

	Changed log warnings to info when WinSAT assessment unavailable
	[UE-30198] - WinSAT assessment unavailable when running Hardware Survey
	#jira UE-30198

	trivial change

Change 3070154 on 2016/07/29 by Matthew.Griffin

	Removed exceptions for IOS .a files now we are building code projects
	Additional inclusions for Android/IOS that cannot be determined via Build Products/Runtime Dependencies
	#jira UE-33868

Change 3070124 on 2016/07/29 by Alex.Delesky

	#jira UE-32911 - Fixing an issue where thumbnail preview scenes would spawn an additional instance of its preview actor even if one was already in the scene. Also fixes a crash on shutdown due to cached thumbnail scenes not being released when thumbnail renderers begin destruction.

Change 3070060 on 2016/07/29 by Chris.Wood

	Fixed command line argument saved to crash reports and used to restart crash processes
	[UE-30665] - CrashReporterClient send and restart does not reopen the project
	#jira UE-30665

	trivial re-add of a line lost in a confusing merge.

Change 3070035 on 2016/07/29 by Allan.Bentham

	Add cvars 'r.Android.DisableVulkanSupport' and 'r.Android.DisableOpenGLES31Support'
	Allows device profiles to disable vulkan and/or ES3.1.
	#jira UE-33379

Change 3070027 on 2016/07/29 by Tom.Looman

	Added fresh VR Template BP (origin: //depot/usr/Tom.Looman/VRTemplate/)

	#jira UE-33325

Change 3070009 on 2016/07/29 by James.Golding

	Disable 'convert proc mesh to static mesh' when template is selected. Also don't create static mesh if procmesh generate no geom.
	#jira UE-32395

Change 3070007 on 2016/07/29 by James.Golding

	Fix highlight when searching Anim Curves
	#jira UE-33073

Change 3070002 on 2016/07/29 by James.Golding

	Fix complex collision drawing in StaticMesh Editor
	#jira UE-33062

Change 3069998 on 2016/07/29 by Jon.Nabozny

	Fix AShooterCharacter heavy breathing even when running but not moving.

	#jira UE-32398

Change 3069980 on 2016/07/29 by James.Golding

	Add UV support to ProcMeshComp collision
	Fix bUseComplexAsSimpleCollision not being applied because ProcMeshBodySetup was transient, so setting was lost
	Move ProceduralMeshComponent out of 'experimental'
	#jira UE-29850, UE-33003

Change 3069970 on 2016/07/29 by James.Golding

	Add #if WITH_PHYSX around ISimEventCallbackFactory at Ori#s suggestion (forgot to do this in initial checkin CL 3053969)
	#jira UE-32819

Change 3069969 on 2016/07/29 by Andrew.Porter

	Adding movie test content to NotForLicensee.

	#jira UE-29618

Change 3069962 on 2016/07/29 by Chris.Wood

	Writing CrashReportClient config section from Engine config in crashing app to crash report folder. Crash Report Client reads new file and sets project-specific settings.
	[UE-31820] - CrashReportClient config is getting merged between streams and projects containing project-specific settings
	#jira UE-31820

	Affects Core and CrashReportClient.
	Removes existing values from CRC's own engine config file because they are project-specific and the file is agnostic.
	Added project-specific values to engine config with defaults set in BaseEngine.ini.
	Added overrides to Orion config.

Change 3069908 on 2016/07/29 by Jurre.deBaare

	Saving assets with UGS build, fixes build warnings
	#jira UE-123

Change 3069889 on 2016/07/29 by Jurre.deBaare

	Build fix for -game builds (missing WITH_EDITOR ifdef)
	#jira UE-123

Change 3069877 on 2016/07/29 by Allan.Bentham

	Add Android ES3.1, vulkan and iOS Metal material quality settings to project settings.
	Fixed issue that prevented settings editor saving out array property changes.

	#jira UE-33379

Change 3069872 on 2016/07/29 by Jurre.deBaare

	Added option to disable post processing option in preview scene
	Inverted normals on the sky sphere (asset change)

	Bad performance when changing (slider) values for the advanced preview scene
	#fix Small optimizations and now only save the data on closing of the preview scene tab
	#jira UE-33496

	Persona floor offset not being correct
	#fix Re-added floor offsetting mechanism (even though I find it very ugly), which now sets the advanced preview scene's floor offset
	#jira UE-32278

	Add a shortcut for hiding/showing the sky (and maybe the floor) in asset viewers
	#fix I will now hide the environment and O the floor
	#jira UE-33498

	Directional light rotation not saved with advanced preview scene profiles
	#fix Now does :)
	#jira UE-33619

Change 3069838 on 2016/07/29 by Luke.Thatcher

	Fix crash in ShooterGame when running the server. Paper2D plugin now has a dependency on the SlateRHIRenderer module, which needs to be loaded in PostConfigInit phase, otherwise shader types in the slate renderer module are not initialized in time.
	#jira UE-33671

Change 3069440 on 2016/07/28 by patrickr.donovan

	#jira UE-29618
	Test content for AA and materials with tessellation enabled and absolute world position material function.

Change 3069148 on 2016/07/28 by Lina.Halper

	Morphtarget deletion crash

	#jira: UE-33851
	#code review: Roalndo.caloca

Change 3069144 on 2016/07/28 by Michael.Trepka

	Check if UnrealBuildTool.csproj exists before trying to compile it in Mac GenerateProjectFiles.sh. Fixes a problem in binary distribution where the script would show an error (but still succeed) due to missing UnrealBuildTool.csproj

	#jira UE-31863

Change 3069021 on 2016/07/28 by Dmitry.Rekman

	Linux: a number of small fixes from pull requests.

	- Includes PR #1905 (UE-24848) by madsystem (arch installation: changed from clang to clang35)
	- Includes PR #2120 (UE-27742) by ABeekhub (mono-mvc for opensuse)
	- Includes PR #2131 (UE-27894) by vityafx (QMake build problem (no c++11 standard))
	- Includes PR #2305 (UE-29781) by salamanderrake (MakefileGenerator.cs small changes)
	- Includes PR #2361 (UE-30452) by salamanderrake (QMakefileProjectGenerator.cs fix for missing Includes paths and removal of duplicates)

	#tests Generated cmake, qmake, make projects and tested them (using ueprojectdirs and not standalone projects).  No tests done for arch/suse changes.

	#jira UE-24848
	#jira UE-27742
	#jira UE-27894
	#jira UE-29781
	#jira UE-30452

	(Edigrating CL 3069016 from //UE4/Dev-Platform/... to //UE4/Release-4.13/...)

Change 3068867 on 2016/07/28 by Mike.Fricker

	Mesh Paint: Fixed various bugs
	- Fixed brush preview not rendered for lasers/mouse when not actively painting (UE-33554)
	- Fixed 'full press' over actors preventing UI from being clickable (UE-33550)
	- Fixed brush cursor displayed when hovering over UI (including selection bar/close button) (UE-33551)
	- Fixed VR transform gizmo getting in the way of everything while painting (it is now hidden while in mesh paint mode)
	- Fixed not being able to interact with UIs after messing around with mesh paint (UE-33621)

	#jira UE-33554
	#jira UE-33550
	#jira UE-33551
	#jira UE-33621

Change 3068758 on 2016/07/28 by Mitchell.Wilson

	Minor update to BP_RakeStuff to solve issue with sand turning black when raking the same spot.
	#jira UE-33684

Change 3068733 on 2016/07/28 by Ori.Cohen

	Temp fix to make sure that deferred bodies that add angular impulse do not crash (From Benn.G)

	#JIRA UE-32630

Change 3068713 on 2016/07/28 by Lina.Halper

	#Checking in Benn G's fix

	 Fixed crash when adding a section to a zero length montage. Fixed nullptr deref in montage handling code and disabled menu option to add a section when zero length (makes no sense to do that).

	#jira UE-33633
	#code review: Benn.Gallagher

Change 3068580 on 2016/07/28 by John.Pollard

	Disable hot reloading when using single process MP PIE

	Fixes UE-30516 - Crash in FObjectReplicator::StartReplicating when removing replicated uproperty and hot reloading with two players

	#jira UE-30516

Change 3068550 on 2016/07/28 by Jurre.deBaare

	Merge Actors: "Bake Vertex Data" is incorrectly listed underm materials
	#fix Added another flag and renamed the old one + added tooltips :) This also required some changes to the merge path just to make sure we end up with the correct data
	#jira UE-31886

Change 3068549 on 2016/07/28 by Jurre.deBaare

	Merged static meshes stop reaction to the Trace Complex on Move flag.
	#fix merge physics now defaults to true in the actor merging settings (people assumed the system didn't work and hadn't seen the option)
	#jira UE-30403

Change 3068548 on 2016/07/28 by Jurre.deBaare

	Merge Actor tool can no longer merge just materials for an actor
	#fix removed requirement of more than one static mesh component (left code in to renable later on once we add a bake materials button for actors in the world)
	#jira UE-32797

Change 3068547 on 2016/07/28 by Jurre.deBaare

	Make sure the advanced preview scene tab is shown by default
	#fix Made the tab spawn by default in all possible situations (as part of the existing UI layout)
	#jira UE-33499

Change 3068546 on 2016/07/28 by Jurre.deBaare

	Textures created from generating proxy meshes have incorrect compression format on tooltip

	#fix Required a PostEditChange call for the UTextures to correctly propogate the compression type
	#jira UE-30365

Change 3068543 on 2016/07/28 by Danny.Bouimad

	#jira UE-29618
	Made useability changes to the Phsyical Animation Profile Map

Change 3068407 on 2016/07/28 by Mitchell.Wilson

	Set delete index variable to 0 on reset in BP_RakeStuff to fix an issue with sand turning black when raking repeatedly in one place
	#jira UE-33684

Change 3068403 on 2016/07/28 by Ben.Marsh

	Add warnings and ignore entries in .uprojectdirs files which reference directories outside the root directory.

	#jira UE-33459

Change 3068358 on 2016/07/28 by Martin.Wilson

	Set default compression to NoClear as None is not a valid compression

	#jira UE-31958

Change 3068346 on 2016/07/28 by Benjamin.Hyder

	Updating TM-ContactShadows to include static meshes

	#jira UE-29618

Change 3068336 on 2016/07/28 by Martin.Wilson

	Added a new mode to Montage_Play so that we can choose what value we return (either length of the montage or the play time duration).

	#jira UE-32101

Change 3068321 on 2016/07/28 by Martin.Wilson

	Export bone selection widgets so that other modules can use them

	#Jira UE-30361

Change 3068316 on 2016/07/28 by Martin.Wilson

	Expose Root Motion Mode

	#jira UE-14431

Change 3068307 on 2016/07/28 by Benjamin.Hyder

	Rebuilding lighting in QA-Materials

	#jira UE-29618

Change 3068299 on 2016/07/28 by Benjamin.Hyder

	Renaming TM_Noise to TM-Noise

	#jira UE-29618

Change 3068285 on 2016/07/28 by Martin.Wilson

	Remove option to clear compression on animation sequences

	#jira UE-31957

Change 3068282 on 2016/07/28 by Benjamin.Hyder

	Re-Saving QA-Materials to remove log spam

	#jira UE-29618

Change 3068271 on 2016/07/28 by Martin.Wilson

	Add check to highlight recursion issue caused by game code

	#jira UE-31417

Change 3068259 on 2016/07/28 by Jamie.Dale

	Fixed UObject churn caused by re-use of a single thumbnail scene for BP and class types

	#jira UE-31709

Change 3068257 on 2016/07/28 by Jamie.Dale

	Removed some code that was no longer needed and could cause a crash

	#jira UE-33342

Change 3068204 on 2016/07/28 by Nick.Darnell

	Slate - Reverting the SMenuAnchor to a previous version, there was no reason afterall to need to use the last painted window as the host for menus, ended up solving it a lower level by properly supporting the deferral groups on the SVirtualWindow under different conditions.

	Slate - The hit test grid now properly records the hit test path for the invalidation box, so that when input is recieved, a widget path containing only one instance of the invalidation box is created, premitting things like mouse capture to properly work.

	UMG - Further refinements and improvements to the Widget Interaction Component.  This completes the documentation and a fixes several bugs with it that were found after the integration to main occured.

	#jira UE-33845

Change 3068197 on 2016/07/28 by Martin.Wilson

	Fix abstract notify state classes showing up in create menu

	#jira UE-33864

	Fix copy paste notifies introducing cross animation references

	#jira UE-32801

Change 3068183 on 2016/07/28 by Matthew.Griffin

	Remove hard coded staging for Crash Reporter and use its receipt instead
	#jira UE-33800

Change 3068097 on 2016/07/28 by Dmitriy.Dyomin

	Fixed: Decals don't render on Zenfone 2 (Added proper detection of FP16 render target support)
	#jira UE-22679

Change 3068074 on 2016/07/28 by Matthew.Griffin

	Added DDC nodes to list of content/shader modifiers for notifications

Change 3068053 on 2016/07/28 by Jack.Porter

	After resampling or changing landscape component size, delete any new components that are entirely in regions that correspond to previously deleted components

	#jira UE-5335

Change 3068043 on 2016/07/28 by Jack.Porter

	Fix crash in mobile preview when selecting objects during shader compilation

	#jira UE-33862

Change 3068031 on 2016/07/28 by Gareth.Martin

	Fix hang when changing material which is used on landscape and "LogMaterial: 0.03 seconds spent updating 1 materials, 1 interfaces, 0 instances, 1 with static permutations." log spam
	#jira UE-33708

Change 3068030 on 2016/07/28 by Gareth.Martin

	Fix "Max Pitch Angle" and "Random Yaw" foliage options being ignored in procedural foliage.
	#jira UE-20476

Change 3068029 on 2016/07/28 by Gareth.Martin

	Fixed landscape "continuous" sculpting not working in multiple viewports
	- the editor would tick with another viewport which didn't have the mouse down, ending the stroke. Now only the "active" viewport can end the stroke.
	#jira UE-32347

Change 3068013 on 2016/07/28 by Thomas.Sarkanen

	Added a tick dependency for slave components

	Ensures that slave components always get ticked after master components.
	Prevents potential main thread stall updating morph targets in slave components.

	#jira UE-23045 - Slave components could benefit from a tick dependency on master components

Change 3068011 on 2016/07/28 by Thomas.Sarkanen

	Added space bar as a shortcut to play/pause animation playback in Persona

	#jira UE-26788 - Framework - Animation - Add Hotkeys to the Viewport for Play/Pause

Change 3068009 on 2016/07/28 by Thomas.Sarkanen

	Multi-arg console commands now accept string commands with or without quotes

	Pre-parsed out each token prior to calling ImportText() rather than relying on ImportText's internal logic. This allows us to properly parse out quoted and non-quoted values as well as being robust to escape sequences etc.
	Removed old legacy code designed to fix trailing string params not being parsed correctly.
	Updated some NULLs to nullptr.

	#jira UE-23661 - Multi-arg console commands that take string params don't accept string params without quotation marks

Change 3067854 on 2016/07/28 by Dmitriy.Dyomin

	Fixed: World composition tiles that have child actor inside will become mdified if any other tile is unloaded
	#jira UE-33440

Change 3067831 on 2016/07/28 by Dmitriy.Dyomin

	Fixed: Landscape GrassType does not have the option to exclude Decals
	#jira UE-26669

Change 3067826 on 2016/07/28 by Dmitriy.Dyomin

	Fixed: Deleting foliage actor from foliage menu does not remove actors from PIE until editor is restarted
	Also fixed Replace foliage type case
	#jira UE-32010

Change 3067824 on 2016/07/28 by Dmitriy.Dyomin

	Fixed: The Empty Level is named "NewWorld" in the World Outliner as opposed to "Untitled"
	#jira UE-24767

Change 3067794 on 2016/07/27 by Jack.Porter

	Expose Lighting Channels to Foliage and Landscape Grass

	#jira UE-32794

Change 3067782 on 2016/07/27 by Jack.Porter

	Fixed crash on device when playing sounds when packaged using Android_Multi

	#jira UE-31981

Change 3067760 on 2016/07/27 by Jack.Porter

	Fixed issue where  landscape flatten target grid preview is displayed on wrong landscape when switching landscape target

	#jira UE-11756

Change 3067748 on 2016/07/27 by Dmitry.Rekman

	Linux: fix packaged projects not being runnable (UE-33608).

	- Added a shell script to run the binary.

	#jira UE-33608

	(Edigrating 3067587 from //UE4/Dev-Platform/... to //UE4/Release-4.13/...)

Change 3067512 on 2016/07/27 by Jeff.Fisher

	UEVR-13 PSVR: TCR Requirements (first two items)
	https://udn.unrealengine.com/questions/301886/trying-to-use-vrheadsetlost-and-vrheadsetreconnect.html
	https://udn.unrealengine.com/questions/302238/how-to-handle-morpheus-disconnection-event.html#answer-303803
	https://udn.unrealengine.com/questions/300748/psvr-trc-compliance.html
	Unshelved from pending changelist '3065760 (UE4/Dev-VR)
	-Implements HMD connect/disconnect/reconnect handling along the lines of sony sample tutorial_vr/basic_setup.
	-Known issue: some tracker location popping during reconnect.  I will try to fix that next.
	#jira UEVR-13
	#review-3066558 @chad.taylor

Change 3067511 on 2016/07/27 by Jeff.Fisher

	Duplicating 3058093 (UE4/Dev-VR)
	Linking SceHmdSetupDialog_stub_weak so one can easily use the sceHmdSetup library to pop up the system hmd setup dialog, if one wishes (someone did, we probably will soon).
	#jira UEVR-13

Change 3067488 on 2016/07/27 by Ori.Cohen

	Make the UI more clear for which physical animation is currently being editted.

	#JIRA UE-33332

Change 3067481 on 2016/07/27 by Chris.Babcock

	AAR support and updating libraries:
	- Google Play Games native C++ SDK 2.1
	- Google Play Services 9.2.0
	- android-support-v4.jar 23.0.0
	#jira UEPLAT-1251
	#jira UE-19190
	#ue4
	#android

Change 3067478 on 2016/07/27 by Ori.Cohen

	Fix it so renaming of physical animation profiles (and constraint profiles) do not lose previous settings

	#JIRA UE-33276, UE-33331

Change 3067474 on 2016/07/27 by Ori.Cohen

	Make it so property index comes in on reset of array value and duplication (From Matt.K)

	#JIRA UE-33276

Change 3067457 on 2016/07/27 by Ori.Cohen

	Fix currently highlighted text in physics profiles being copied over when chaing current profile

	#JIRA UE-33282

Change 3067451 on 2016/07/27 by Ori.Cohen

	Fix the case where objects welded together (even though they're simulating) do not re-weld when being detached in a long chain

	#JIRA UE-32531

Change 3067443 on 2016/07/27 by Ori.Cohen

	Make skeletalMeshComponent a property of physical animation component so things can be setup in the construction script.
	Mark the component as experimental and only expose valid functions into construction script

	#JIRA UE-33656

Change 3067439 on 2016/07/27 by Ori.Cohen

	Added more logging info for potential fixed framerate negative delta time crash

	#JIRA UE-32219

Change 3067348 on 2016/07/27 by mason.seay

	Updating map to have hit events test.

	#jira UE-29618

Change 3067342 on 2016/07/27 by Mitchell.Wilson

	Updating collision on TwinStickUFO to resolve issue with the ship getting stuck when rotating due to collision being offset slightly
	#jira UE-15698

Change 3067332 on 2016/07/27 by Dmitry.Rekman

	Fix for libstdc++ problems (UE-33584).

	#tests Built UE4Editor/UE4Game on Ubuntu 16.04 and 15.10.

	#JIRA UE-33584

	(Redoing CL 3065551 from Dev-Platform).

Change 3067262 on 2016/07/27 by Lina.Halper

	DUPEFIX: Fix compile issue of non-editor build due to - Reduce functions is not editoronly

	#tests: PIE/compile editor build/noneditor
	#jira: UE-33477

Change 3067228 on 2016/07/27 by Lina.Halper

	This fixes crash where mesh has changed hierarchy but hasn't been remapped yet.

	#jira: UE-29880

Change 3067168 on 2016/07/27 by Lina.Halper

	DUPEFIX: Smartname guid will be discarded during cooking, and once it's cooked, it's trusted to have correct name.

	#code review:Martin.Wilson, Benn.Gallagher
	#tests: cooked test map, run test map, PIE, saving content, loading standalone game
	#jira: UE-33454

Change 3067162 on 2016/07/27 by Lina.Halper

	pose asset source animation/animation asset preview pose now have proper skeleton filter

	#jira: UE-32607

Change 3067160 on 2016/07/27 by Lina.Halper

	Fix issue with preview curve not working when no asset

	#jira: UE-33402

Change 3067138 on 2016/07/27 by Lina.Halper

	DUPEFIX: Fix the guid keep generated by adding to the database.

	- This caused worse problem with non-deterministic cooking -   This doesn't fix UE-33454 for 100%, but this was the main reason why this was so visible

	#jira: UE-33772, UE-33454
	#tests: cooked AI_Test map, editor rename curves

Change 3067129 on 2016/07/27 by Lina.Halper

	DUPEFIX- Fix additive issue with remove linear key and built the new animation DDC
	#tests: Jump_Recovery_Additive, PIE
	#jira: UE-33477

Change 3067128 on 2016/07/27 by Michael.Trepka

	Copy of CL 3062046

	PRAGMA_DISABLE_OPTIMIZATION_ACTUAL and PRAGMA_ENABLE_OPTIMIZATION_ACTUAL defines for iOS

	#jira UE-33683

Change 3067104 on 2016/07/27 by Lina.Halper

	DUPEFIX: Support different samplerate for reimport with set range

	#jira: UE-16027

Change 3067093 on 2016/07/27 by Lina.Halper

	DUPE FIX: Fix baking is applied twice in the new created animation

	#jira: UE-31120

Change 3067088 on 2016/07/27 by Lina.Halper

	Fix issues with rename/delete of the curves

	#jira: UE-33663, UE-33730, UE-33661, UE-33662

Change 3066795 on 2016/07/27 by Mark.Satterthwaite

	Fix a race-condition in FMetalBlendState's constructor that could lead to crashes or use of the incorrect blend-state. This is a partial fix for UE-33778 which appears to have several causes.
	#jira UE-33778

Change 3066788 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3066338:
	Handle releasing an SRV/UAV & the source object within a single Metal command-buffer.
	#jira UE-33779

Change 3066786 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3064743:
	Proper fix for FORT-27685 - on Metal it is invalid to fail to set uniform parameters on a shader - if you don't set the parameter the buffer binding may be nil or too small for the data accessed in the shader and the GPU will then crash.
	#jira UE-33827
	#jira FORT-27685

Change 3066768 on 2016/07/27 by samuel.proctor

	Updated child blueprint used for profiler testing

	#jira UE-29618

Change 3066733 on 2016/07/27 by samuel.proctor

	Refreshed broken node in profiler test asset

	#jira UE-29618

Change 3066670 on 2016/07/27 by Sam.Deiter

	#Jira UEDOC-3139 Adding the blending tool tip images.

Change 3066669 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3063329:
	CL #3046743 was breaking other samples in unexpected ways after a recent Main merge, so make a Metal-specific change to the shader instead and amend the MetalBackend to better match HLSL's handling of NaN/inf with common single-precision float intrinsics. This is sufficient to fix the AtmosphericFog and the recent regressions.
	#jira UE-33600
	#jira UE-33028
	#jira UE-27879
	#jira UE-25802

Change 3066668 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3063327:
	Added FSpeedTreeWindNullUniformBuffer as a global resource to bind to shaders that require a SpeedTreeData uniform but don't yet have data available as a nil binding is invalid on Metal.
	#jira UE-32068

Change 3066625 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3062160:
	Fix the fix for handling RHISetStreamSource overriding stride on Metal - not all MTLVertexDescriptors are equally hashable so do this ourselves.
	#jira UE-33355

Change 3066624 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3063328:
	Mac Metal DXT/BC textures can have mip-levels smaller than the block size (they switch to uncompressed data).
	#jira UE-33820

Change 3066589 on 2016/07/27 by Mark.Satterthwaite

	Duplicate CL #3060590 to fix UE-33819:
	Fix FORT-27340: Mac Metal cannot natively support PF_G8 + sRGB as not all Mac GPUs have single-channel sRGB formats (according to Apple) so we must manually pack & unpack to RGBA8_sRGB - the code to do this was missing from UpdateTexture2D.
	#jira UE-33819

Change 3066588 on 2016/07/27 by Matt.Kuhlenschmidt

	Fixed Reset to default not updating when selecting new assets

	#jira UE-33817

Change 3066509 on 2016/07/27 by mason.seay

	Phys material needed for TM-SliceProcMesh

	#jira UE-29618

Change 3066500 on 2016/07/27 by mason.seay

	Rebuilt lighting

	#jira UE-29618

Change 3066499 on 2016/07/27 by Jurre.deBaare

	Map build should not generate empty HLOD folder in Editor
	#fix Asset outer (hlod folder/asset) was created regardless of whether or not it was needed, now checks first :)
	#jira UE-29564

Change 3066498 on 2016/07/27 by Jurre.deBaare

	HLOD outliner drag and drop operation can cause log spam
	#fix Found the log spam was coming from the scene outliner itself, caused by Formatting call receiving incorrect argument names which is now fixed
	#jira UE-32106

Change 3066485 on 2016/07/27 by Alan.Noon

	Resubmitting fixes for Puzzle Templates. Rebuilt in 4.13 via UGS
	#jira UE-30564

Change 3066470 on 2016/07/27 by mason.seay

	Test map and updating blueprint for slicing proc mesh

	#jira UE-29618

Change 3066367 on 2016/07/27 by Matthew.Griffin

	Switch UE4 Binary Release to be the job that runs nightly instead of the Full Build we use in main

Change 3066337 on 2016/07/27 by Matthew.Griffin

	Remaking CL 3066327 by Matthew.Griffin@Matthew.Griffin_G5772_MainStream on 2016-07-27 15:39

		Adding ArchiveDir parameter to Fortnite build command as it ignores StagingDir and has been filling up network drive

Change 3066158 on 2016/07/27 by Ben.Marsh

	Reverting assets causing warning, via integration from //UE4/Main.

Change 3065651 on 2016/07/26 by Ben.Marsh

	Remaking CL 3065267 by Alan.Noon@Alan.Noon_Z3739_Main_9938 on 2016/07/26 16:35:14

		Updated Puzzle Template (BP and C++) to mimic each other in terms of content, labelling and setup.

Change 3065650 on 2016/07/26 by Ben.Marsh

	Remaking CL 3065358 by James.Brinkerhoff@James.Brinkerhoff_Z2862_Ocean-Staging on 2016/07/26 17:31:04

		Hotfix for Ocean from CL 3065311: Fixes the load/apply order when applying customizations to characters in the editor

Change 3065649 on 2016/07/26 by Ben.Marsh

	Remaking CL 3065268 by Max.Chen@Max.Chen_T4664_UE4_Main on 2016/07/26 16:35:18

		Sequencer: Revert 3057233 because it breaks sequence recording.

		Copy from Dev-Sequencer

		#jira UE-33569

Change 3065308 on 2016/07/26 by Ben.Marsh

	Fix failure to parse EC settings for 4.13 branch.

Change 3065235 on 2016/07/26 by Ben.Marsh

	Set the IsReleaseBranch flag to true for builds in the Release-4.13 branch.

[CL 3079611 by Matthew Griffin in Main branch]
2016-08-05 17:47:48 -04:00

2372 lines
90 KiB
C#

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
using System.Xml.Linq;
namespace UnrealBuildTool
{
public class UEDeployAndroid : UEBuildDeploy
{
/// <summary>
/// Internal usage for GetApiLevel
/// </summary>
private List<string> PossibleApiLevels = null;
private FileReference ProjectFile;
public UEDeployAndroid(FileReference InProjectFile)
{
ProjectFile = InProjectFile;
}
private UnrealPluginLanguage UPL = null;
public void SetAndroidPluginData(List<string> Architectures, List<string> inPluginExtraData)
{
List<string> NDKArches = new List<string>();
foreach (var Arch in Architectures)
{
NDKArches.Add(GetNDKArch(Arch));
}
UPL = new UnrealPluginLanguage(ProjectFile, inPluginExtraData, NDKArches, "xmlns:android", "xmlns:android=\"http://schemas.android.com/apk/res/android\"", UnrealTargetPlatform.Android);
// APL.SetTrace();
}
/// <summary>
/// Simple function to pipe output asynchronously
/// </summary>
private void ParseApiLevel(object Sender, DataReceivedEventArgs Event)
{
// DataReceivedEventHandler is fired with a null string when the output stream is closed. We don't want to
// print anything for that event.
if (!String.IsNullOrEmpty(Event.Data))
{
string Line = Event.Data;
if (Line.StartsWith("id:"))
{
// the line should look like: id: 1 or "android-19"
string[] Tokens = Line.Split("\"".ToCharArray());
if (Tokens.Length >= 2)
{
PossibleApiLevels.Add(Tokens[1]);
}
}
}
}
private Dictionary<string, ConfigCacheIni> ConfigCache = null;
private ConfigCacheIni GetConfigCacheIni(string baseIniName)
{
if (ConfigCache == null)
{
ConfigCache = new Dictionary<string, ConfigCacheIni>();
}
ConfigCacheIni config = null;
if (!ConfigCache.TryGetValue(baseIniName, out config))
{
config = ConfigCacheIni.CreateConfigCacheIni(UnrealTargetPlatform.Android, "Engine", DirectoryReference.FromFile(ProjectFile));
ConfigCache.Add(baseIniName, config);
}
return config;
}
private string CachedSDKLevel = null;
private string GetSdkApiLevel(AndroidToolChain ToolChain)
{
if (CachedSDKLevel == null)
{
// ask the .ini system for what version to use
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string SDKLevel;
Ini.GetString("/Script/AndroidPlatformEditor.AndroidSDKSettings", "SDKAPILevel", out SDKLevel);
// if we want to use whatever version the ndk uses, then use that
if (SDKLevel == "matchndk")
{
SDKLevel = ToolChain.GetNdkApiLevel();
}
// run a command and capture output
if (SDKLevel == "latest")
{
// we expect there to be one, so use the first one
string AndroidCommandPath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/tools/android" + (Utils.IsRunningOnMono ? "" : ".bat"));
var ExeInfo = new ProcessStartInfo(AndroidCommandPath, "list targets");
ExeInfo.UseShellExecute = false;
ExeInfo.RedirectStandardOutput = true;
using (var GameProcess = Process.Start(ExeInfo))
{
PossibleApiLevels = new List<string>();
GameProcess.BeginOutputReadLine();
GameProcess.OutputDataReceived += ParseApiLevel;
GameProcess.WaitForExit();
}
if (PossibleApiLevels != null && PossibleApiLevels.Count > 0)
{
SDKLevel = ToolChain.GetLargestApiLevel(PossibleApiLevels.ToArray());
}
else
{
throw new BuildException("Can't make an APK without an API installed (see \"android.bat list targets\")");
}
}
Console.WriteLine("Building Java with SDK API level '{0}'", SDKLevel);
CachedSDKLevel = SDKLevel;
}
return CachedSDKLevel;
}
private bool IsVulkanSDKAvailable()
{
bool bHaveVulkan = false;
// First look for VulkanSDK (two possible env variables)
string VulkanSDKPath = Environment.GetEnvironmentVariable("VULKAN_SDK");
if (String.IsNullOrEmpty(VulkanSDKPath))
{
VulkanSDKPath = Environment.GetEnvironmentVariable("VK_SDK_PATH");
}
// Note: header is the same for all architectures so just use arch-arm
string NDKPath = Environment.GetEnvironmentVariable("NDKROOT");
string NDKVulkanIncludePath = NDKPath + "/android-24/arch-arm/usr/include/vulkan";
// Use NDK Vulkan header if discovered, or VulkanSDK if available
if (File.Exists(NDKVulkanIncludePath + "/vulkan.h"))
{
bHaveVulkan = true;
}
else
if (!String.IsNullOrEmpty(VulkanSDKPath))
{
bHaveVulkan = true;
}
return bHaveVulkan;
}
public static string GetOBBVersionNumber(int PackageVersion)
{
string VersionString = PackageVersion.ToString("0");
return VersionString;
}
public bool PackageDataInsideApk(bool bDisallowPackagingDataInApk, ConfigCacheIni Ini = null)
{
if (bDisallowPackagingDataInApk)
{
return false;
}
// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni("Engine");
}
// we check this a lot, so make it easy
bool bPackageDataInsideApk;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageDataInsideApk", out bPackageDataInsideApk);
return bPackageDataInsideApk;
}
public bool DisableVerifyOBBOnStartUp(ConfigCacheIni Ini = null)
{
// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni("Engine");
}
// we check this a lot, so make it easy
bool bDisableVerifyOBBOnStartUp;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bDisableVerifyOBBOnStartUp", out bDisableVerifyOBBOnStartUp);
return bDisableVerifyOBBOnStartUp;
}
private static string GetAntPath()
{
// look up an ANT_HOME env var
string AntHome = Environment.GetEnvironmentVariable("ANT_HOME");
if (!string.IsNullOrEmpty(AntHome) && Directory.Exists(AntHome))
{
string AntPath = AntHome + "/bin/ant" + (Utils.IsRunningOnMono ? "" : ".bat");
// use it if found
if (File.Exists(AntPath))
{
return AntPath;
}
}
// otherwise, look in the eclipse install for the ant plugin (matches the unzipped Android ADT from Google)
string PluginDir = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/../eclipse/plugins");
if (Directory.Exists(PluginDir))
{
string[] Plugins = Directory.GetDirectories(PluginDir, "org.apache.ant*");
// use the first one with ant.bat
if (Plugins.Length > 0)
{
foreach (string PluginName in Plugins)
{
string AntPath = PluginName + "/bin/ant" + (Utils.IsRunningOnMono ? "" : ".bat");
// use it if found
if (File.Exists(AntPath))
{
return AntPath;
}
}
}
}
throw new BuildException("Unable to find ant.bat (via %ANT_HOME% or %ANDROID_HOME%/../eclipse/plugins/org.apache.ant*");
}
private static void CopyFileDirectory(string SourceDir, string DestDir, Dictionary<string, string> Replacements)
{
if (!Directory.Exists(SourceDir))
{
return;
}
string[] Files = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories);
foreach (string Filename in Files)
{
// skip template files
if (Path.GetExtension(Filename) == ".template")
{
continue;
}
// make the dst filename with the same structure as it was in SourceDir
string DestFilename = Path.Combine(DestDir, Utils.MakePathRelativeTo(Filename, SourceDir));
if (File.Exists(DestFilename))
{
File.Delete(DestFilename);
}
// make the subdirectory if needed
string DestSubdir = Path.GetDirectoryName(DestFilename);
if (!Directory.Exists(DestSubdir))
{
Directory.CreateDirectory(DestSubdir);
}
// some files are handled specially
string Ext = Path.GetExtension(Filename);
if (Ext == ".xml")
{
string Contents = File.ReadAllText(Filename);
// replace some varaibles
foreach (var Pair in Replacements)
{
Contents = Contents.Replace(Pair.Key, Pair.Value);
}
// write out file
File.WriteAllText(DestFilename, Contents);
}
else
{
File.Copy(Filename, DestFilename);
// remove any read only flags
FileInfo DestFileInfo = new FileInfo(DestFilename);
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
}
}
}
private static void DeleteDirectory(string InPath, string SubDirectoryToKeep = "")
{
// skip the dir we want to
if (String.Compare(Path.GetFileName(InPath), SubDirectoryToKeep, true) == 0)
{
return;
}
// delete all files in here
string[] Files;
try
{
Files = Directory.GetFiles(InPath);
}
catch (Exception)
{
// directory doesn't exist so all is good
return;
}
foreach (string Filename in Files)
{
try
{
// remove any read only flags
FileInfo FileInfo = new FileInfo(Filename);
FileInfo.Attributes = FileInfo.Attributes & ~FileAttributes.ReadOnly;
FileInfo.Delete();
}
catch (Exception)
{
Log.TraceInformation("Failed to delete all files in directory {0}. Continuing on...", InPath);
}
}
string[] Dirs = Directory.GetDirectories(InPath, "*.*", SearchOption.TopDirectoryOnly);
foreach (string Dir in Dirs)
{
DeleteDirectory(Dir, SubDirectoryToKeep);
// try to delete the directory, but allow it to fail (due to SubDirectoryToKeep still existing)
try
{
Directory.Delete(Dir);
}
catch (Exception)
{
// do nothing
}
}
}
public string GetUE4BuildFilePath(String EngineDirectory)
{
return Path.GetFullPath(Path.Combine(EngineDirectory, "Build/Android/Java"));
}
public string GetUE4JavaSrcPath()
{
return Path.Combine("src", "com", "epicgames", "ue4");
}
public string GetUE4JavaFilePath(String EngineDirectory)
{
return Path.GetFullPath(Path.Combine(GetUE4BuildFilePath(EngineDirectory), GetUE4JavaSrcPath()));
}
public string GetUE4JavaBuildSettingsFileName(String EngineDirectory)
{
return Path.Combine(GetUE4JavaFilePath(EngineDirectory), "JavaBuildSettings.java");
}
public string GetUE4JavaDownloadShimFileName(string Directory)
{
return Path.Combine(Directory, "DownloadShim.java");
}
public string GetUE4TemplateJavaSourceDir(string Directory)
{
return Path.Combine(GetUE4BuildFilePath(Directory), "JavaTemplates");
}
public string GetUE4TemplateJavaDestination(string Directory, string FileName)
{
return Path.Combine(Directory, FileName);
}
public string GetUE4JavaOBBDataFileName(string Directory)
{
return Path.Combine(Directory, "OBBData.java");
}
public class TemplateFile
{
public string SourceFile;
public string DestinationFile;
}
private void MakeDirectoryIfRequired(string DestFilename)
{
string DestSubdir = Path.GetDirectoryName(DestFilename);
if (!Directory.Exists(DestSubdir))
{
Directory.CreateDirectory(DestSubdir);
}
}
public void WriteJavaOBBDataFile(string FileName, string PackageName, List<string> ObbSources)
{
Log.TraceInformation("\n==== Writing to OBB data file {0} ====", FileName);
var Ini = GetConfigCacheIni("Engine");
int StoreVersion;
Ini.GetInt32("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "StoreVersion", out StoreVersion);
string[] obbDataFile = File.Exists(FileName) ? File.ReadAllLines(FileName) : null;
StringBuilder obbData = new StringBuilder("package " + PackageName + ";\n\n");
obbData.Append("public class OBBData\n{\n");
obbData.Append("public static class XAPKFile {\npublic final boolean mIsMain;\npublic final String mFileVersion;\n");
obbData.Append("public final long mFileSize;\nXAPKFile(boolean isMain, String fileVersion, long fileSize) {\nmIsMain = isMain;\nmFileVersion = fileVersion;\nmFileSize = fileSize;\n");
obbData.Append("}\n}\n\n");
// write the data here
obbData.Append("public static final XAPKFile[] xAPKS = {\n");
// For each obb file... but we only have one... for now anyway.
bool first = ObbSources.Count > 1;
foreach (string ObbSource in ObbSources)
{
obbData.Append("new XAPKFile(\ntrue, // true signifies a main file\n");
obbData.AppendFormat("\"{0}\", // the version of the APK that the file was uploaded against\n", GetOBBVersionNumber(StoreVersion));
obbData.AppendFormat("{0}L // the length of the file in bytes\n", File.Exists(ObbSource) ? new FileInfo(ObbSource).Length : 0);
obbData.AppendFormat("){0}\n", first ? "," : "");
first = false;
}
obbData.Append("};\n"); // close off data
//
obbData.Append("};\n"); // close class definition off
if (obbDataFile == null || !obbDataFile.SequenceEqual((obbData.ToString()).Split('\n')))
{
MakeDirectoryIfRequired(FileName);
using (StreamWriter outputFile = new StreamWriter(FileName, false))
{
var obbSrc = obbData.ToString().Split('\n');
foreach (var line in obbSrc)
{
outputFile.WriteLine(line);
}
}
}
else
{
Log.TraceInformation("\n==== OBB data file up to date so not writing. ====");
}
}
public void WriteJavaDownloadSupportFiles(string ShimFileName, IEnumerable<TemplateFile> TemplateFiles, Dictionary<string, string> replacements)
{
// Deal with the Shim first as that is a known target and is easy to deal with
// If it exists then read it
string[] DestFileContent = File.Exists(ShimFileName) ? File.ReadAllLines(ShimFileName) : null;
StringBuilder ShimFileContent = new StringBuilder("package com.epicgames.ue4;\n\n");
ShimFileContent.AppendFormat("import {0}.OBBDownloaderService;\n", replacements["$$PackageName$$"]);
ShimFileContent.AppendFormat("import {0}.DownloaderActivity;\n", replacements["$$PackageName$$"]);
ShimFileContent.Append("\n\npublic class DownloadShim\n{\n");
ShimFileContent.Append("\tpublic static OBBDownloaderService DownloaderService;\n");
ShimFileContent.Append("\tpublic static DownloaderActivity DownloadActivity;\n");
ShimFileContent.Append("\tpublic static Class<DownloaderActivity> GetDownloaderType() { return DownloaderActivity.class; }\n");
ShimFileContent.Append("}\n");
Log.TraceInformation("\n==== Writing to shim file {0} ====", ShimFileName);
// If they aren't the same then dump out the settings
if (DestFileContent == null || !DestFileContent.SequenceEqual((ShimFileContent.ToString()).Split('\n')))
{
MakeDirectoryIfRequired(ShimFileName);
using (StreamWriter outputFile = new StreamWriter(ShimFileName, false))
{
var shimSrc = ShimFileContent.ToString().Split('\n');
foreach (var line in shimSrc)
{
outputFile.WriteLine(line);
}
}
}
else
{
Log.TraceInformation("\n==== Shim data file up to date so not writing. ====");
}
// Now we move on to the template files
foreach (var template in TemplateFiles)
{
string[] templateSrc = File.ReadAllLines(template.SourceFile);
string[] templateDest = File.Exists(template.DestinationFile) ? File.ReadAllLines(template.DestinationFile) : null;
for (int i = 0; i < templateSrc.Length; ++i)
{
string srcLine = templateSrc[i];
bool changed = false;
foreach (var kvp in replacements)
{
if (srcLine.Contains(kvp.Key))
{
srcLine = srcLine.Replace(kvp.Key, kvp.Value);
changed = true;
}
}
if (changed)
{
templateSrc[i] = srcLine;
}
}
Log.TraceInformation("\n==== Writing to template target file {0} ====", template.DestinationFile);
if (templateDest == null || templateSrc.Length != templateDest.Length || !templateSrc.SequenceEqual(templateDest))
{
MakeDirectoryIfRequired(template.DestinationFile);
using (StreamWriter outputFile = new StreamWriter(template.DestinationFile, false))
{
foreach (var line in templateSrc)
{
outputFile.WriteLine(line);
}
}
}
else
{
Log.TraceInformation("\n==== Template target file up to date so not writing. ====");
}
}
}
private static string GetNDKArch(string UE4Arch)
{
switch (UE4Arch)
{
case "-armv7": return "armeabi-v7a";
case "-arm64": return "arm64-v8a";
case "-x64": return "x86_64";
case "-x86": return "x86";
default: throw new BuildException("Unknown UE4 architecture {0}", UE4Arch);
}
}
public static string GetUE4Arch(string NDKArch)
{
switch (NDKArch)
{
case "armeabi-v7a": return "-armv7";
case "arm64-v8a": return "-arm64";
case "x86": return "-x86";
case "arm64": return "-arm64";
case "x86_64":
case "x64": return "-x64";
// default: throw new BuildException("Unknown NDK architecture '{0}'", NDKArch);
// future-proof by returning armv7 for unknown
default: return "-armv7";
}
}
private static void StripDebugSymbols(string SourceFileName, string TargetFileName, string UE4Arch)
{
// Copy the file and remove read-only if necessary
File.Copy(SourceFileName, TargetFileName, true);
FileAttributes Attribs = File.GetAttributes(TargetFileName);
if (Attribs.HasFlag(FileAttributes.ReadOnly))
{
File.SetAttributes(TargetFileName, Attribs & ~FileAttributes.ReadOnly);
}
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.FileName = AndroidToolChain.GetStripExecutablePath(UE4Arch);
StartInfo.Arguments = "--strip-debug \"" + TargetFileName + "\"";
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
Utils.RunLocalProcessAndLogOutput(StartInfo);
}
private static void CopySTL(AndroidToolChain ToolChain, string UE4BuildPath, string UE4Arch, string NDKArch, bool bForDistribution)
{
string GccVersion = "4.6";
if (Directory.Exists(Environment.ExpandEnvironmentVariables("%NDKROOT%/sources/cxx-stl/gnu-libstdc++/4.9")))
{
GccVersion = "4.9";
}
else if (Directory.Exists(Environment.ExpandEnvironmentVariables("%NDKROOT%/sources/cxx-stl/gnu-libstdc++/4.8")))
{
GccVersion = "4.8";
}
// copy it in!
string SourceSTLSOName = Environment.ExpandEnvironmentVariables("%NDKROOT%/sources/cxx-stl/gnu-libstdc++/") + GccVersion + "/libs/" + NDKArch + "/libgnustl_shared.so";
string FinalSTLSOName = UE4BuildPath + "/libs/" + NDKArch + "/libgnustl_shared.so";
// check to see if libgnustl_shared.so is newer than last time we copied (or needs stripping for distribution)
bool bFileExists = File.Exists(FinalSTLSOName);
TimeSpan Diff = File.GetLastWriteTimeUtc(FinalSTLSOName) - File.GetLastWriteTimeUtc(SourceSTLSOName);
if (bForDistribution || !bFileExists || Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1)
{
if (bFileExists)
{
File.Delete(FinalSTLSOName);
}
Directory.CreateDirectory(Path.GetDirectoryName(FinalSTLSOName));
if (bForDistribution)
{
// Strip debug symbols for distribution builds
StripDebugSymbols(SourceSTLSOName, FinalSTLSOName, UE4Arch);
}
else
{
File.Copy(SourceSTLSOName, FinalSTLSOName, true);
}
}
}
//@TODO: To enable the NVIDIA Gfx Debugger - these paths need to be standardized
private static void CopyGfxDebugger(string UE4BuildPath, string UE4Arch, string NDKArch)
{
/*
Directory.CreateDirectory(UE4BuildPath + "/libs/" + NDKArch);
File.Copy("F:/NVPACK/android-kk-egl-t124-a32/Stripped_libNvPmApi.Core.so", UE4BuildPath + "/libs/" + NDKArch + "/libNvPmApi.Core.so", true);
File.Copy("F:/NVPACK/android-kk-egl-t124-a32/Stripped_libNvidia_gfx_debugger.so", UE4BuildPath + "/libs/" + NDKArch + "/libNvidia_gfx_debugger.so", true);
*/
}
private static int RunCommandLineProgramAndReturnResult(string WorkingDirectory, string Command, string Params, string OverrideDesc = null, bool bUseShellExecute = false)
{
if (OverrideDesc == null)
{
Log.TraceInformation("\nRunning: " + Command + " " + Params);
}
else if (OverrideDesc != "")
{
Log.TraceInformation(OverrideDesc);
Log.TraceVerbose("\nRunning: " + Command + " " + Params);
}
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.WorkingDirectory = WorkingDirectory;
StartInfo.FileName = Command;
StartInfo.Arguments = Params;
StartInfo.UseShellExecute = bUseShellExecute;
StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process Proc = new Process();
Proc.StartInfo = StartInfo;
Proc.Start();
Proc.WaitForExit();
return Proc.ExitCode;
}
private static void RunCommandLineProgramWithException(string WorkingDirectory, string Command, string Params, string OverrideDesc = null, bool bUseShellExecute = false)
{
if (OverrideDesc == null)
{
Log.TraceInformation("\nRunning: " + Command + " " + Params);
}
else if (OverrideDesc != "")
{
Log.TraceInformation(OverrideDesc);
Log.TraceVerbose("\nRunning: " + Command + " " + Params);
}
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.WorkingDirectory = WorkingDirectory;
StartInfo.FileName = Command;
StartInfo.Arguments = Params;
StartInfo.UseShellExecute = bUseShellExecute;
StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process Proc = new Process();
Proc.StartInfo = StartInfo;
Proc.Start();
Proc.WaitForExit();
// android bat failure
if (Proc.ExitCode != 0)
{
throw new BuildException("{0} failed with args {1}", Command, Params);
}
}
private bool CheckApplicationName(string UE4BuildPath, string ProjectName, out string ApplicationDisplayName)
{
string StringsXMLPath = Path.Combine(UE4BuildPath, "res/values/strings.xml");
ApplicationDisplayName = null;
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ApplicationDisplayName", out ApplicationDisplayName);
// use project name if display name is left blank
if (String.IsNullOrWhiteSpace(ApplicationDisplayName))
{
ApplicationDisplayName = ProjectName;
}
// replace escaped characters (note: changes &# pattern before &, then patches back to allow escaped character codes in the string)
ApplicationDisplayName = ApplicationDisplayName.Replace("&#", "$@#$").Replace("&", "&amp;").Replace("'", "\\'").Replace("\"", "\\\"").Replace("<", "&lt;").Replace(">", "&gt;").Replace("$@#$", "&#");
// if it doesn't exist, need to repackage
if (!File.Exists(StringsXMLPath))
{
return true;
}
// read it and see if needs to be updated
string Contents = File.ReadAllText(StringsXMLPath);
// find the key
string AppNameTag = "<string name=\"app_name\">";
int KeyIndex = Contents.IndexOf(AppNameTag);
// if doesn't exist, need to repackage
if (KeyIndex < 0)
{
return true;
}
// get the current value
KeyIndex += AppNameTag.Length;
int TagEnd = Contents.IndexOf("</string>", KeyIndex);
if (TagEnd < 0)
{
return true;
}
string CurrentApplicationName = Contents.Substring(KeyIndex, TagEnd - KeyIndex);
// no need to do anything if matches
if (CurrentApplicationName == ApplicationDisplayName)
{
// name matches, no need to force a repackage
return false;
}
// need to repackage
return true;
}
private void UpdateProjectProperties(AndroidToolChain ToolChain, string UE4BuildPath, string ProjectName)
{
Log.TraceInformation("\n===={0}====UPDATING BUILD CONFIGURATION FILES====================================================", DateTime.Now.ToString());
// get all of the libs (from engine + project)
string JavaLibsDir = Path.Combine(UE4BuildPath, "JavaLibs");
string[] LibDirs = Directory.GetDirectories(JavaLibsDir);
// get existing project.properties lines (if any)
string ProjectPropertiesFile = Path.Combine(UE4BuildPath, "project.properties");
string[] PropertiesLines = new string[] { };
if (File.Exists(ProjectPropertiesFile))
{
PropertiesLines = File.ReadAllLines(ProjectPropertiesFile);
}
// figure out how many libraries were already listed (if there were more than this listed, then we need to start the file over, because we need to unreference a library)
int NumOutstandingAlreadyReferencedLibs = 0;
foreach (string Line in PropertiesLines)
{
if (Line.StartsWith("android.library.reference."))
{
NumOutstandingAlreadyReferencedLibs++;
}
}
// now go through each one and verify they are listed in project properties, and if not, add them
List<string> LibsToBeAdded = new List<string>();
foreach (string LibDir in LibDirs)
{
// put it in terms of the subdirectory that would be in the project.properties
string RelativePath = "JavaLibs/" + Path.GetFileName(LibDir);
// now look for this in the existing file
bool bWasReferencedAlready = false;
foreach (string Line in PropertiesLines)
{
if (Line.StartsWith("android.library.reference.") && Line.EndsWith(RelativePath))
{
// this lib was already referenced, don't need to readd
bWasReferencedAlready = true;
break;
}
}
if (bWasReferencedAlready)
{
// if it was, no further action needed, and count it off
NumOutstandingAlreadyReferencedLibs--;
}
else
{
// otherwise, we need to add it to the project properties
LibsToBeAdded.Add(RelativePath);
}
}
// now at this point, if there are any outstanding already referenced libs, we have too many, so we have to start over
if (NumOutstandingAlreadyReferencedLibs > 0)
{
// @todo android: If a user had a project.properties in the game, NEVER do this
Log.TraceInformation("There were too many libs already referenced in project.properties, tossing it");
File.Delete(ProjectPropertiesFile);
LibsToBeAdded.Clear();
foreach (string LibDir in LibDirs)
{
// put it in terms of the subdirectory that would be in the project.properties
LibsToBeAdded.Add("JavaLibs/" + Path.GetFileName(LibDir));
}
}
// now update the project for each library
string AndroidCommandPath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/tools/android" + (Utils.IsRunningOnMono ? "" : ".bat"));
string UpdateCommandLine = "--silent update project --subprojects --name " + ProjectName + " --path . --target " + GetSdkApiLevel(ToolChain);
foreach (string Lib in LibsToBeAdded)
{
string LocalUpdateCommandLine = UpdateCommandLine + " --library " + Lib;
// make sure each library has a build.xml - --subprojects doesn't create build.xml files, but it will create project.properties
// and later code needs each lib to have a build.xml
RunCommandLineProgramWithException(UE4BuildPath, AndroidCommandPath, "--silent update lib-project --path " + Lib + " --target " + GetSdkApiLevel(ToolChain), "");
RunCommandLineProgramWithException(UE4BuildPath, AndroidCommandPath, LocalUpdateCommandLine, "Updating project.properties, local.properties, and build.xml for " + Path.GetFileName(Lib) + "...");
}
}
private string GetAllBuildSettings(AndroidToolChain ToolChain, string BuildPath, bool bForDistribution, bool bMakeSeparateApks, bool bPackageDataInsideApk, bool bDisableVerifyOBBOnStartUp)
{
// make the settings string - this will be char by char compared against last time
StringBuilder CurrentSettings = new StringBuilder();
CurrentSettings.AppendLine(string.Format("NDKROOT={0}", Environment.GetEnvironmentVariable("NDKROOT")));
CurrentSettings.AppendLine(string.Format("ANDROID_HOME={0}", Environment.GetEnvironmentVariable("ANDROID_HOME")));
CurrentSettings.AppendLine(string.Format("ANT_HOME={0}", Environment.GetEnvironmentVariable("ANT_HOME")));
CurrentSettings.AppendLine(string.Format("JAVA_HOME={0}", Environment.GetEnvironmentVariable("JAVA_HOME")));
CurrentSettings.AppendLine(string.Format("SDKVersion={0}", GetSdkApiLevel(ToolChain)));
CurrentSettings.AppendLine(string.Format("bForDistribution={0}", bForDistribution));
CurrentSettings.AppendLine(string.Format("bMakeSeparateApks={0}", bMakeSeparateApks));
CurrentSettings.AppendLine(string.Format("bPackageDataInsideApk={0}", bPackageDataInsideApk));
CurrentSettings.AppendLine(string.Format("bDisableVerifyOBBOnStartUp={0}", bDisableVerifyOBBOnStartUp));
// all AndroidRuntimeSettings ini settings in here
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
ConfigCacheIni.IniSection Section = Ini.FindSection("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings");
if (Section != null)
{
foreach (string Key in Section.Keys)
{
List<string> Values = Section[Key];
foreach (string Value in Values)
{
CurrentSettings.AppendLine(string.Format("{0}={1}", Key, Value));
}
}
}
Section = Ini.FindSection("/Script/AndroidPlatformEditor.AndroidSDKSettings");
if (Section != null)
{
foreach (string Key in Section.Keys)
{
List<string> Values = Section[Key];
foreach (string Value in Values)
{
CurrentSettings.AppendLine(string.Format("{0}={1}", Key, Value));
}
}
}
var Arches = ToolChain.GetAllArchitectures();
foreach (string Arch in Arches)
{
CurrentSettings.AppendFormat("Arch={0}{1}", Arch, Environment.NewLine);
}
var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
foreach (string GPUArch in GPUArchitectures)
{
CurrentSettings.AppendFormat("GPUArch={0}{1}", GPUArch, Environment.NewLine);
}
return CurrentSettings.ToString();
}
private bool CheckDependencies(AndroidToolChain ToolChain, string ProjectName, string ProjectDirectory, string UE4BuildFilesPath, string GameBuildFilesPath, string EngineDirectory, List<string> SettingsFiles,
string CookFlavor, string OutputPath, string UE4BuildPath, bool bMakeSeparateApks, bool bPackageDataInsideApk)
{
var Arches = ToolChain.GetAllArchitectures();
var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
// check all input files (.so, java files, .ini files, etc)
bool bAllInputsCurrent = true;
foreach (string Arch in Arches)
{
foreach (string GPUArch in GPUArchitectures)
{
string SourceSOName = AndroidToolChain.InlineArchName(OutputPath, Arch, GPUArch);
// if the source binary was UE4Game, replace it with the new project name, when re-packaging a binary only build
string ApkFilename = Path.GetFileNameWithoutExtension(OutputPath).Replace("UE4Game", ProjectName);
string DestApkName = Path.Combine(ProjectDirectory, "Binaries/Android/") + ApkFilename + ".apk";
// if we making multiple Apks, we need to put the architecture into the name
if (bMakeSeparateApks)
{
DestApkName = AndroidToolChain.InlineArchName(DestApkName, Arch, GPUArch);
}
// check to see if it's out of date before trying the slow make apk process (look at .so and all Engine and Project build files to be safe)
List<String> InputFiles = new List<string>();
InputFiles.Add(SourceSOName);
InputFiles.AddRange(Directory.EnumerateFiles(UE4BuildFilesPath, "*.*", SearchOption.AllDirectories));
if (Directory.Exists(GameBuildFilesPath))
{
InputFiles.AddRange(Directory.EnumerateFiles(GameBuildFilesPath, "*.*", SearchOption.AllDirectories));
}
// make sure changed java files will rebuild apk
InputFiles.AddRange(SettingsFiles);
// rebuild if .pak files exist for OBB in APK case
if (bPackageDataInsideApk)
{
string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks";
if (Directory.Exists(PAKFileLocation))
{
var PakFiles = Directory.EnumerateFiles(PAKFileLocation, "*.pak", SearchOption.TopDirectoryOnly);
foreach (var Name in PakFiles)
{
InputFiles.Add(Name);
}
}
}
// look for any newer input file
DateTime ApkTime = File.GetLastWriteTimeUtc(DestApkName);
foreach (var InputFileName in InputFiles)
{
if (File.Exists(InputFileName))
{
// skip .log files
if (Path.GetExtension(InputFileName) == ".log")
{
continue;
}
DateTime InputFileTime = File.GetLastWriteTimeUtc(InputFileName);
if (InputFileTime.CompareTo(ApkTime) > 0)
{
bAllInputsCurrent = false;
Log.TraceInformation("{0} is out of date due to newer input file {1}", DestApkName, InputFileName);
break;
}
}
}
}
}
return bAllInputsCurrent;
}
private int ConvertDepthBufferIniValue(string IniValue)
{
switch (IniValue.ToLower())
{
case "bits16":
return 16;
case "bits24":
return 24;
case "bits32":
return 32;
default:
return 0;
}
}
private string ConvertOrientationIniValue(string IniValue)
{
switch (IniValue.ToLower())
{
case "portrait":
return "portrait";
case "reverseportrait":
return "reversePortrait";
case "sensorportrait":
return "sensorPortrait";
case "landscape":
return "landscape";
case "reverselandscape":
return "reverseLandscape";
case "sensorlandscape":
return "sensorLandscape";
case "sensor":
return "sensor";
case "fullsensor":
return "fullSensor";
default:
return "landscape";
}
}
private void DetermineScreenOrientationRequirements(out bool bNeedPortrait, out bool bNeedLandscape)
{
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string Orientation;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "Orientation", out Orientation);
bNeedLandscape = false;
bNeedPortrait = false;
switch (Orientation.ToLower())
{
case "portrait":
bNeedPortrait = true;
break;
case "reverseportrait":
bNeedPortrait = true;
break;
case "sensorportrait":
bNeedPortrait = true;
break;
case "landscape":
bNeedLandscape = true;
break;
case "reverselandscape":
bNeedLandscape = true;
break;
case "sensorlandscape":
bNeedLandscape = true;
break;
case "sensor":
bNeedPortrait = true;
bNeedLandscape = true;
break;
case "fullsensor":
bNeedPortrait = true;
bNeedLandscape = true;
break;
default:
bNeedPortrait = true;
bNeedLandscape = true;
break;
}
}
private void PickDownloaderScreenOrientation(string UE4BuildPath, bool bNeedPortrait, bool bNeedLandscape)
{
// Remove unused downloader_progress.xml to prevent missing resource
if (!bNeedPortrait)
{
string LayoutPath = UE4BuildPath + "/res/layout-port/downloader_progress.xml";
if (File.Exists(LayoutPath))
{
File.Delete(LayoutPath);
}
}
if (!bNeedLandscape)
{
string LayoutPath = UE4BuildPath + "/res/layout-land/downloader_progress.xml";
if (File.Exists(LayoutPath))
{
File.Delete(LayoutPath);
}
}
// Loop through each of the resolutions (only /res/drawable/ is required, others are optional)
string[] Resolutions = new string[] { "/res/drawable/", "/res/drawable-ldpi/", "/res/drawable-mdpi/", "/res/drawable-hdpi/", "/res/drawable-xhdpi/" };
foreach (string ResolutionPath in Resolutions)
{
string PortraitFilename = UE4BuildPath + ResolutionPath + "downloadimagev.png";
if (bNeedPortrait)
{
if (!File.Exists(PortraitFilename) && (ResolutionPath == "/res/drawable/"))
{
Log.TraceWarning("Warning: Downloader screen source image {0} not available, downloader screen will not function properly!", PortraitFilename);
}
}
else
{
// Remove unused image
if (File.Exists(PortraitFilename))
{
File.Delete(PortraitFilename);
}
}
string LandscapeFilename = UE4BuildPath + ResolutionPath + "downloadimageh.png";
if (bNeedLandscape)
{
if (!File.Exists(LandscapeFilename) && (ResolutionPath == "/res/drawable/"))
{
Log.TraceWarning("Warning: Downloader screen source image {0} not available, downloader screen will not function properly!", LandscapeFilename);
}
}
else
{
// Remove unused image
if (File.Exists(LandscapeFilename))
{
File.Delete(LandscapeFilename);
}
}
}
}
private void PickSplashScreenOrientation(string UE4BuildPath, bool bNeedPortrait, bool bNeedLandscape)
{
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
bool bShowLaunchImage = false;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bShowLaunchImage", out bShowLaunchImage);
bool bPackageForGearVR;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForGearVR", out bPackageForGearVR);
//override the parameters if we are not showing a launch image or are packaging for GearVR
if (bPackageForGearVR || !bShowLaunchImage)
{
bNeedPortrait = bNeedLandscape = false;
}
// Remove unused styles.xml to prevent missing resource
if (!bNeedPortrait)
{
string StylesPath = UE4BuildPath + "/res/values-port/styles.xml";
if (File.Exists(StylesPath))
{
File.Delete(StylesPath);
}
}
if (!bNeedLandscape)
{
string StylesPath = UE4BuildPath + "/res/values-land/styles.xml";
if (File.Exists(StylesPath))
{
File.Delete(StylesPath);
}
}
// Loop through each of the resolutions (only /res/drawable/ is required, others are optional)
string[] Resolutions = new string[] { "/res/drawable/", "/res/drawable-ldpi/", "/res/drawable-mdpi/", "/res/drawable-hdpi/", "/res/drawable-xhdpi/" };
foreach (string ResolutionPath in Resolutions)
{
string PortraitFilename = UE4BuildPath + ResolutionPath + "splashscreen_portrait.png";
if (bNeedPortrait)
{
if (!File.Exists(PortraitFilename) && (ResolutionPath == "/res/drawable/"))
{
Log.TraceWarning("Warning: Splash screen source image {0} not available, splash screen will not function properly!", PortraitFilename);
}
}
else
{
// Remove unused image
if (File.Exists(PortraitFilename))
{
File.Delete(PortraitFilename);
}
}
string LandscapeFilename = UE4BuildPath + ResolutionPath + "splashscreen_landscape.png";
if (bNeedLandscape)
{
if (!File.Exists(LandscapeFilename) && (ResolutionPath == "/res/drawable/"))
{
Log.TraceWarning("Warning: Splash screen source image {0} not available, splash screen will not function properly!", LandscapeFilename);
}
}
else
{
// Remove unused image
if (File.Exists(LandscapeFilename))
{
File.Delete(LandscapeFilename);
}
}
}
}
private string GetPackageName(string ProjectName)
{
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string PackageName;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "PackageName", out PackageName);
// replace some variables
PackageName = PackageName.Replace("[PROJECT]", ProjectName);
PackageName = PackageName.Replace("-", "_");
return PackageName;
}
private string GetPublicKey()
{
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string PlayLicenseKey = "";
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "GooglePlayLicenseKey", out PlayLicenseKey);
return PlayLicenseKey;
}
private string GenerateManifest(AndroidToolChain ToolChain, string ProjectName, bool bIsForDistribution, bool bPackageDataInsideApk, string GameBuildFilesPath, bool bHasOBBFiles, bool bDisableVerifyOBBOnStartUp, string UE4Arch, string GPUArch)
{
string Arch = GetNDKArch(UE4Arch);
int NDKLevelInt = ToolChain.GetNdkApiLevelInt();
// 64-bit targets must be android-21 or higher
if (NDKLevelInt < 21)
{
if (UE4Arch == "-arm64" || UE4Arch == "-x64")
{
NDKLevelInt = 21;
}
}
// ini file to get settings from
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string PackageName = GetPackageName(ProjectName);
bool bEnableGooglePlaySupport;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bEnableGooglePlaySupport", out bEnableGooglePlaySupport);
string DepthBufferPreference;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "DepthBufferPreference", out DepthBufferPreference);
int MinSDKVersion;
Ini.GetInt32("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "MinSDKVersion", out MinSDKVersion);
int TargetSDKVersion = MinSDKVersion;
Ini.GetInt32("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "TargetSDKVersion", out TargetSDKVersion);
int StoreVersion;
Ini.GetInt32("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "StoreVersion", out StoreVersion);
string VersionDisplayName;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "VersionDisplayName", out VersionDisplayName);
string Orientation;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "Orientation", out Orientation);
bool EnableFullScreen;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bFullScreen", out EnableFullScreen);
List<string> ExtraManifestNodeTags;
Ini.GetArray("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraManifestNodeTags", out ExtraManifestNodeTags);
List<string> ExtraApplicationNodeTags;
Ini.GetArray("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraApplicationNodeTags", out ExtraApplicationNodeTags);
List<string> ExtraActivityNodeTags;
Ini.GetArray("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraActivityNodeTags", out ExtraActivityNodeTags);
string ExtraActivitySettings;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraActivitySettings", out ExtraActivitySettings);
string ExtraApplicationSettings;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraApplicationSettings", out ExtraApplicationSettings);
List<string> ExtraPermissions;
Ini.GetArray("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraPermissions", out ExtraPermissions);
bool bPackageForGearVR;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForGearVR", out bPackageForGearVR);
bool bSupportsVulkan;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bSupportsVulkan", out bSupportsVulkan);
if (bSupportsVulkan)
{
bSupportsVulkan = IsVulkanSDKAvailable();
}
bool bEnableIAP = false;
Ini.GetBool("OnlineSubsystemGooglePlay.Store", "bSupportsInAppPurchasing", out bEnableIAP);
bool bShowLaunchImage = false;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bShowLaunchImage", out bShowLaunchImage);
string InstallLocation;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "InstallLocation", out InstallLocation);
switch(InstallLocation.ToLower())
{
case "preferexternal":
InstallLocation = "preferExternal";
break;
case "auto":
InstallLocation = "auto";
break;
default:
InstallLocation = "internalOnly";
break;
}
// fix up the MinSdkVersion
if (NDKLevelInt > 19)
{
if (MinSDKVersion < 21)
{
MinSDKVersion = 21;
Log.TraceInformation("Fixing minSdkVersion; NDK level above 19 requires minSdkVersion of 21 (arch={0})", UE4Arch.Substring(1));
}
}
// disable GearVR if not supported platform (in this case only armv7 for now)
if (UE4Arch != "-armv7")
{
if (bPackageForGearVR)
{
Log.TraceInformation("Disabling Package For GearVR for unsupported architecture {0}", UE4Arch);
bPackageForGearVR = false;
}
}
// disable splash screen for GearVR (for now)
if (bPackageForGearVR)
{
if (bShowLaunchImage)
{
Log.TraceInformation("Disabling Show Launch Image for GearVR enabled application");
bShowLaunchImage = false;
}
}
StringBuilder Text = new StringBuilder();
Text.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
Text.AppendLine("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"");
Text.AppendLine(string.Format(" package=\"{0}\"", PackageName));
if (ExtraManifestNodeTags != null)
{
foreach (string Line in ExtraManifestNodeTags)
{
Text.AppendLine(" " + Line);
}
}
Text.AppendLine(string.Format(" android:installLocation=\"{0}\"", InstallLocation));
Text.AppendLine(string.Format(" android:versionCode=\"{0}\"", StoreVersion));
Text.AppendLine(string.Format(" android:versionName=\"{0}\">", VersionDisplayName));
Text.AppendLine("");
Text.AppendLine("\t<!-- Application Definition -->");
Text.AppendLine("\t<application android:label=\"@string/app_name\"");
Text.AppendLine("\t android:icon=\"@drawable/icon\"");
if (ExtraApplicationNodeTags != null)
{
foreach (string Line in ExtraApplicationNodeTags)
{
Text.AppendLine("\t " + Line);
}
}
Text.AppendLine("\t android:hasCode=\"true\">");
if (bShowLaunchImage)
{
// normal application settings
Text.AppendLine("\t\t<activity android:name=\"com.epicgames.ue4.SplashActivity\"");
Text.AppendLine("\t\t android:label=\"@string/app_name\"");
Text.AppendLine("\t\t android:theme=\"@style/UE4SplashTheme\"");
Text.AppendLine("\t\t android:launchMode=\"singleTask\"");
Text.AppendLine(string.Format("\t\t android:screenOrientation=\"{0}\"", ConvertOrientationIniValue(Orientation)));
Text.AppendLine(string.Format("\t\t android:debuggable=\"{0}\">", bIsForDistribution ? "false" : "true"));
Text.AppendLine("\t\t\t<intent-filter>");
Text.AppendLine("\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />");
Text.AppendLine(string.Format("\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />"));
Text.AppendLine("\t\t\t</intent-filter>");
Text.AppendLine("\t\t</activity>");
Text.AppendLine("\t\t<activity android:name=\"com.epicgames.ue4.GameActivity\"");
Text.AppendLine("\t\t android:label=\"@string/app_name\"");
Text.AppendLine("\t\t android:theme=\"@style/UE4SplashTheme\"");
Text.AppendLine("\t\t android:configChanges=\"screenSize|orientation|keyboardHidden|keyboard\"");
}
else
{
Text.AppendLine("\t\t<activity android:name=\"com.epicgames.ue4.GameActivity\"");
Text.AppendLine("\t\t android:label=\"@string/app_name\"");
Text.AppendLine("\t\t android:theme=\"@android:style/Theme.Black.NoTitleBar.Fullscreen\"");
Text.AppendLine("\t\t android:configChanges=\"screenSize|orientation|keyboardHidden|keyboard\"");
}
Text.AppendLine("\t\t android:launchMode=\"singleTask\"");
Text.AppendLine(string.Format("\t\t android:screenOrientation=\"{0}\"", ConvertOrientationIniValue(Orientation)));
if (ExtraActivityNodeTags != null)
{
foreach (string Line in ExtraActivityNodeTags)
{
Text.AppendLine("\t\t " + Line);
}
}
Text.AppendLine(string.Format("\t\t android:debuggable=\"{0}\">", bIsForDistribution ? "false" : "true"));
Text.AppendLine("\t\t\t<meta-data android:name=\"android.app.lib_name\" android:value=\"UE4\"/>");
if (!bShowLaunchImage)
{
Text.AppendLine("\t\t\t<intent-filter>");
Text.AppendLine("\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />");
Text.AppendLine(string.Format("\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />"));
Text.AppendLine("\t\t\t</intent-filter>");
}
if (!string.IsNullOrEmpty(ExtraActivitySettings))
{
ExtraActivitySettings = ExtraActivitySettings.Replace("\\n", "\n");
foreach (string Line in ExtraActivitySettings.Split("\r\n".ToCharArray()))
{
Text.AppendLine("\t\t\t" + Line);
}
}
string ActivityAdditionsFile = Path.Combine(GameBuildFilesPath, "ManifestActivityAdditions.txt");
if (File.Exists(ActivityAdditionsFile))
{
foreach (string Line in File.ReadAllLines(ActivityAdditionsFile))
{
Text.AppendLine("\t\t\t" + Line);
}
}
Text.AppendLine("\t\t</activity>");
// For OBB download support
if (bShowLaunchImage)
{
Text.AppendLine("\t\t<activity android:name=\".DownloaderActivity\"");
Text.AppendLine(string.Format("\t\t android:screenOrientation=\"{0}\"", ConvertOrientationIniValue(Orientation)));
Text.AppendLine("\t\t android:configChanges=\"screenSize|orientation|keyboardHidden|keyboard\"");
Text.AppendLine("\t\t android:theme=\"@style/UE4SplashTheme\" />");
}
else
{
Text.AppendLine("\t\t<activity android:name=\".DownloaderActivity\" />");
}
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.DepthBufferPreference\" android:value=\"{0}\"/>", ConvertDepthBufferIniValue(DepthBufferPreference)));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.bPackageDataInsideApk\" android:value=\"{0}\"/>", bPackageDataInsideApk ? "true" : "false"));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.bVerifyOBBOnStartUp\" android:value=\"{0}\"/>", (bIsForDistribution && !bDisableVerifyOBBOnStartUp) ? "true" : "false"));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.bShouldHideUI\" android:value=\"{0}\"/>", EnableFullScreen ? "true" : "false"));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.ProjectName\" android:value=\"{0}\"/>", ProjectName));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.bHasOBBFiles\" android:value=\"{0}\"/>", bHasOBBFiles ? "true" : "false"));
Text.AppendLine(string.Format("\t\t<meta-data android:name=\"com.epicgames.ue4.GameActivity.bSupportsVulkan\" android:value=\"{0}\"/>", bSupportsVulkan ? "true" : "false"));
Text.AppendLine("\t\t<meta-data android:name=\"com.google.android.gms.games.APP_ID\"");
Text.AppendLine("\t\t android:value=\"@string/app_id\" />");
Text.AppendLine("\t\t<meta-data android:name=\"com.google.android.gms.version\"");
Text.AppendLine("\t\t android:value=\"@integer/google_play_services_version\" />");
Text.AppendLine("\t\t<activity android:name=\"com.google.android.gms.ads.AdActivity\"");
Text.AppendLine("\t\t android:configChanges=\"keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize\"/>");
if (!string.IsNullOrEmpty(ExtraApplicationSettings))
{
ExtraApplicationSettings = ExtraApplicationSettings.Replace("\\n", "\n");
foreach (string Line in ExtraApplicationSettings.Split("\r\n".ToCharArray()))
{
Text.AppendLine("\t\t" + Line);
}
}
string ApplicationAdditionsFile = Path.Combine(GameBuildFilesPath, "ManifestApplicationAdditions.txt");
if (File.Exists(ApplicationAdditionsFile))
{
foreach (string Line in File.ReadAllLines(ApplicationAdditionsFile))
{
Text.AppendLine("\t\t" + Line);
}
}
// Required for OBB download support
Text.AppendLine("\t\t<service android:name=\"OBBDownloaderService\" />");
Text.AppendLine("\t\t<receiver android:name=\"AlarmReceiver\" />");
Text.AppendLine("\t</application>");
Text.AppendLine("");
Text.AppendLine("\t<!-- Requirements -->");
// check for an override for the requirements section of the manifest
string RequirementsOverrideFile = Path.Combine(GameBuildFilesPath, "ManifestRequirementsOverride.txt");
if (File.Exists(RequirementsOverrideFile))
{
foreach (string Line in File.ReadAllLines(RequirementsOverrideFile))
{
Text.AppendLine("\t" + Line);
}
}
else
{
// need just the number part of the sdk
Text.AppendLine(string.Format("\t<uses-sdk android:minSdkVersion=\"{0}\" android:targetSdkVersion=\"{1}\"/>", MinSDKVersion, TargetSDKVersion));
Text.AppendLine("\t<uses-feature android:glEsVersion=\"" + AndroidToolChain.GetGLESVersionFromGPUArch(GPUArch) + "\" android:required=\"true\" />");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.INTERNET\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>");
Text.AppendLine("\t<uses-permission android:name=\"com.android.vending.CHECK_LICENSE\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>");
Text.AppendLine("\t<uses-permission android:name=\"android.permission.VIBRATE\"/>");
// Text.AppendLine("\t<uses-permission android:name=\"android.permission.DISABLE_KEYGUARD\"/>");
if (bEnableIAP)
{
Text.AppendLine("\t<uses-permission android:name=\"com.android.vending.BILLING\"/>");
}
if (ExtraPermissions != null)
{
foreach (string Permission in ExtraPermissions)
{
Text.AppendLine(string.Format("\t<uses-permission android:name=\"{0}\"/>", Permission));
}
}
string RequirementsAdditionsFile = Path.Combine(GameBuildFilesPath, "ManifestRequirementsAdditions.txt");
if (File.Exists(RequirementsAdditionsFile))
{
foreach (string Line in File.ReadAllLines(RequirementsAdditionsFile))
{
Text.AppendLine("\t" + Line);
}
}
}
Text.AppendLine("</manifest>");
// allow plugins to modify final manifest HERE
XDocument XDoc;
try
{
XDoc = XDocument.Parse(Text.ToString());
}
catch (Exception e)
{
throw new BuildException("AndroidManifest.xml is invalid {0}\n{1}", e, Text.ToString());
}
UPL.ProcessPluginNode(Arch, "androidManifestUpdates", "", ref XDoc);
return XDoc.ToString();
}
private string GenerateProguard(string Arch, string EngineSourcePath, string GameBuildFilesPath)
{
StringBuilder Text = new StringBuilder();
string ProguardFile = Path.Combine(EngineSourcePath, "proguard-project.txt");
if (File.Exists(ProguardFile))
{
foreach (string Line in File.ReadAllLines(ProguardFile))
{
Text.AppendLine(Line);
}
}
string ProguardAdditionsFile = Path.Combine(GameBuildFilesPath, "ProguardAdditions.txt");
if (File.Exists(ProguardAdditionsFile))
{
foreach (string Line in File.ReadAllLines(ProguardAdditionsFile))
{
Text.AppendLine(Line);
}
}
// add plugin additions
return UPL.ProcessPluginNode(Arch, "proguardAdditions", Text.ToString());
}
private void ValidateGooglePlay(string UE4BuildPath)
{
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
bool bEnableGooglePlaySupport;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bEnableGooglePlaySupport", out bEnableGooglePlaySupport);
if (!bEnableGooglePlaySupport)
{
// do not need to do anything; it is fine
return;
}
string IniAppId;
bool bInvalidIniAppId = false;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "GamesAppID", out IniAppId);
//validate the value found in the AndroidRuntimeSettings
Int64 Value;
if (IniAppId.Length == 0 || !Int64.TryParse(IniAppId, out Value))
{
bInvalidIniAppId = true;
}
bool bInvalid = false;
string ReplacementId = "";
String Filename = Path.Combine(UE4BuildPath, "res", "values", "GooglePlayAppID.xml");
if (File.Exists(Filename))
{
string[] FileContent = File.ReadAllLines(Filename);
int LineIndex = -1;
foreach (string Line in FileContent)
{
++LineIndex;
int StartIndex = Line.IndexOf("\"app_id\">");
if (StartIndex < 0)
continue;
StartIndex += 9;
int EndIndex = Line.IndexOf("</string>");
if (EndIndex < 0)
continue;
string XmlAppId = Line.Substring(StartIndex, EndIndex - StartIndex);
//validate that the AppId matches the .ini value for the GooglePlay AppId, assuming it's valid
if (!bInvalidIniAppId && IniAppId.CompareTo(XmlAppId) != 0)
{
Log.TraceInformation("Replacing Google Play AppID in GooglePlayAppID.xml with AndroidRuntimeSettings .ini value");
bInvalid = true;
ReplacementId = IniAppId;
}
else if(XmlAppId.Length == 0 || !Int64.TryParse(XmlAppId, out Value))
{
Log.TraceWarning("\nWARNING: GooglePlay Games App ID is invalid! Replacing it with \"1\"");
//write file with something which will fail but not cause an exception if executed
bInvalid = true;
ReplacementId = "1";
}
if(bInvalid)
{
// remove any read only flags if invalid so it can be replaced
FileInfo DestFileInfo = new FileInfo(Filename);
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
//preserve the rest of the file, just fix up this line
string NewLine = Line.Replace("\"app_id\">" + XmlAppId + "</string>", "\"app_id\">" + ReplacementId + "</string>");
FileContent[LineIndex] = NewLine;
File.WriteAllLines(Filename, FileContent);
}
break;
}
}
else
{
string NewAppId;
// if we don't have an appID to use from the config, write file with something which will fail but not cause an exception if executed
if (bInvalidIniAppId)
{
Log.TraceWarning("\nWARNING: Creating GooglePlayAppID.xml using a Google Play AppID of \"1\" because there was no valid AppID in AndroidRuntimeSettings!");
NewAppId = "1";
}
else
{
Log.TraceInformation("Creating GooglePlayAppID.xml with AndroidRuntimeSettings .ini value");
NewAppId = IniAppId;
}
File.WriteAllText(Filename, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"app_id\">" + NewAppId + "</string>\n</resources>\n");
}
}
private bool FilesAreDifferent(string SourceFilename, string DestFilename)
{
// source must exist
FileInfo SourceInfo = new FileInfo(SourceFilename);
if (!SourceInfo.Exists)
{
throw new BuildException("Can't make an APK without file [{0}]", SourceFilename);
}
// different if destination doesn't exist
FileInfo DestInfo = new FileInfo(DestFilename);
if (!DestInfo.Exists)
{
return true;
}
// file lengths differ?
if (SourceInfo.Length != DestInfo.Length)
{
return true;
}
// validate timestamps
TimeSpan Diff = DestInfo.LastWriteTimeUtc - SourceInfo.LastWriteTimeUtc;
if (Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1)
{
return true;
}
// could check actual bytes just to be sure, but good enough
return false;
}
private bool RequiresOBB(bool bDisallowPackageInAPK, string OBBLocation)
{
if (bDisallowPackageInAPK)
{
Log.TraceInformation("APK contains data.");
return false;
}
else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("uebp_LOCAL_ROOT")))
{
Log.TraceInformation("On build machine.");
return true;
}
else
{
Log.TraceInformation("Looking for OBB.");
return File.Exists(OBBLocation);
}
}
private void MakeApk(AndroidToolChain ToolChain, string ProjectName, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks, bool bIncrementalPackage, bool bDisallowPackagingDataInApk)
{
Log.TraceInformation("\n===={0}====PREPARING TO MAKE APK=================================================================", DateTime.Now.ToString());
// cache some tools paths
string NDKBuildPath = Environment.ExpandEnvironmentVariables("%NDKROOT%/ndk-build" + (Utils.IsRunningOnMono ? "" : ".cmd"));
// set up some directory info
string IntermediateAndroidPath = Path.Combine(ProjectDirectory, "Intermediate/Android/");
string UE4BuildPath = Path.Combine(IntermediateAndroidPath, "APK");
string UE4JavaFilePath = Path.Combine(ProjectDirectory, "Build", "Android", GetUE4JavaSrcPath());
string UE4BuildFilesPath = GetUE4BuildFilePath(EngineDirectory);
string GameBuildFilesPath = Path.Combine(ProjectDirectory, "Build/Android");
// Generate Java files
string PackageName = GetPackageName(ProjectName);
string TemplateDestinationBase = Path.Combine(ProjectDirectory, "Build", "Android", "src", PackageName.Replace('.', Path.DirectorySeparatorChar));
MakeDirectoryIfRequired(TemplateDestinationBase);
// We'll be writing the OBB data into the same location as the download service files
string UE4OBBDataFileName = GetUE4JavaOBBDataFileName(TemplateDestinationBase);
string UE4DownloadShimFileName = GetUE4JavaDownloadShimFileName(UE4JavaFilePath);
// Template generated files
string JavaTemplateSourceDir = GetUE4TemplateJavaSourceDir(EngineDirectory);
var templates = from template in Directory.EnumerateFiles(JavaTemplateSourceDir, "*.template")
let RealName = Path.GetFileNameWithoutExtension(template)
select new TemplateFile { SourceFile = template, DestinationFile = GetUE4TemplateJavaDestination(TemplateDestinationBase, RealName) };
// Generate the OBB and Shim files here
string ObbFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + ".obb";
// This is kind of a small hack to get around a rewrite problem
// We need to make sure the file is there but if the OBB file doesn't exist then we don't want to replace it
if (File.Exists(ObbFileLocation) || !File.Exists(UE4OBBDataFileName))
{
WriteJavaOBBDataFile(UE4OBBDataFileName, PackageName, new List<string> { ObbFileLocation });
}
// Make sure any existing proguard file in project is NOT used (back it up)
string ProjectBuildProguardFile = Path.Combine(GameBuildFilesPath, "proguard-project.txt");
if (File.Exists(ProjectBuildProguardFile))
{
string ProjectBackupProguardFile = Path.Combine(GameBuildFilesPath, "proguard-project.backup");
File.Move(ProjectBuildProguardFile, ProjectBackupProguardFile);
}
WriteJavaDownloadSupportFiles(UE4DownloadShimFileName, templates, new Dictionary<string, string>{
{ "$$GameName$$", ProjectName },
{ "$$PublicKey$$", GetPublicKey() },
{ "$$PackageName$$",PackageName }
});
// Sometimes old files get left behind if things change, so we'll do a clean up pass
{
string CleanUpBaseDir = Path.Combine(ProjectDirectory, "Build", "Android", "src");
var files = Directory.EnumerateFiles(CleanUpBaseDir, "*.java", SearchOption.AllDirectories);
Log.TraceInformation("Cleaning up files based on template dir {0}", TemplateDestinationBase);
// Make a set of files that are okay to clean up
var cleanFiles = new HashSet<string>();
cleanFiles.Add("OBBData.java");
foreach (var template in templates)
{
cleanFiles.Add(Path.GetFileName(template.DestinationFile));
}
foreach (var filename in files)
{
if (filename == UE4DownloadShimFileName) // we always need the shim, and it'll get rewritten if needed anyway
continue;
string filePath = Path.GetDirectoryName(filename); // grab the file's path
if (filePath != TemplateDestinationBase) // and check to make sure it isn't the same as the Template directory we calculated earlier
{
// Only delete the files in the cleanup set
if (!cleanFiles.Contains(Path.GetFileName(filename)))
continue;
Log.TraceInformation("Cleaning up file {0} with path {1}", filename, filePath);
File.Delete(filename);
// Check to see if this file also exists in our target destination, and if so nuke it too
string DestFilename = Path.Combine(UE4BuildPath, Utils.MakePathRelativeTo(filePath, UE4BuildFilesPath));
if (File.Exists(filename))
{
File.Delete(filename);
}
}
}
// Directory clean up code
var directories = Directory.EnumerateDirectories(CleanUpBaseDir, "*", SearchOption.AllDirectories).OrderByDescending(x => x);
foreach (var directory in directories)
{
if (Directory.Exists(directory) && Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Count() == 0)
{
Log.TraceInformation("Cleaning Directory {0} as empty.", directory);
Directory.Delete(directory, true);
}
};
}
// cache if we want data in the Apk
bool bPackageDataInsideApk = PackageDataInsideApk(bDisallowPackagingDataInApk);
bool bDisableVerifyOBBOnStartUp = DisableVerifyOBBOnStartUp();
// check to see if any "meta information" is newer than last time we build
string CurrentBuildSettings = GetAllBuildSettings(ToolChain, UE4BuildPath, bForDistribution, bMakeSeparateApks, bPackageDataInsideApk, bDisableVerifyOBBOnStartUp);
string BuildSettingsCacheFile = Path.Combine(UE4BuildPath, "UEBuildSettings.txt");
// do we match previous build settings?
bool bBuildSettingsMatch = true;
// get application name and whether it changed, needing to force repackage
string ApplicationDisplayName;
if (CheckApplicationName(UE4BuildPath, ProjectName, out ApplicationDisplayName))
{
bBuildSettingsMatch = false;
Log.TraceInformation("Application display name is different than last build, forcing repackage.");
}
// if the manifest matches, look at other settings stored in a file
if (bBuildSettingsMatch)
{
if (File.Exists(BuildSettingsCacheFile))
{
string PreviousBuildSettings = File.ReadAllText(BuildSettingsCacheFile);
if (PreviousBuildSettings != CurrentBuildSettings)
{
bBuildSettingsMatch = false;
Log.TraceInformation("Previous .apk file(s) were made with different build settings, forcing repackage.");
}
}
}
// only check input dependencies if the build settings already match
if (bBuildSettingsMatch)
{
// check if so's are up to date against various inputs
var JavaFiles = new List<string>{
UE4OBBDataFileName,
UE4DownloadShimFileName
};
// Add the generated files too
JavaFiles.AddRange(from t in templates select t.SourceFile);
JavaFiles.AddRange(from t in templates select t.DestinationFile);
bBuildSettingsMatch = CheckDependencies(ToolChain, ProjectName, ProjectDirectory, UE4BuildFilesPath, GameBuildFilesPath,
EngineDirectory, JavaFiles, CookFlavor, OutputPath, UE4BuildPath, bMakeSeparateApks, bPackageDataInsideApk);
}
var Arches = ToolChain.GetAllArchitectures();
var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
// Initialize APL contexts for each architecture enabled
List<string> NDKArches = new List<string>();
foreach (var Arch in Arches)
{
string NDKArch = GetNDKArch(Arch);
if (!NDKArches.Contains(NDKArch))
{
NDKArches.Add(NDKArch);
}
}
UPL.Init(NDKArches, bForDistribution, EngineDirectory, UE4BuildPath);
IEnumerable<Tuple<string, string, string>> BuildList = null;
if (!bBuildSettingsMatch)
{
BuildList = from Arch in Arches
from GPUArch in GPUArchitectures
let manifest = GenerateManifest(ToolChain, ProjectName, bForDistribution, bPackageDataInsideApk, GameBuildFilesPath, RequiresOBB(bDisallowPackagingDataInApk, ObbFileLocation), bDisableVerifyOBBOnStartUp, Arch, GPUArch)
select Tuple.Create(Arch, GPUArch, manifest);
}
else
{
BuildList = from Arch in Arches
from GPUArch in GPUArchitectures
let manifestFile = Path.Combine(IntermediateAndroidPath, Arch + "_" + GPUArch + "_AndroidManifest.xml")
let manifest = GenerateManifest(ToolChain, ProjectName, bForDistribution, bPackageDataInsideApk, GameBuildFilesPath, RequiresOBB(bDisallowPackagingDataInApk, ObbFileLocation), bDisableVerifyOBBOnStartUp, Arch, GPUArch)
let OldManifest = File.Exists(manifestFile) ? File.ReadAllText(manifestFile) : ""
where manifest != OldManifest
select Tuple.Create(Arch, GPUArch, manifest);
}
// Now we have to spin over all the arch/gpu combinations to make sure they all match
int BuildListComboTotal = BuildList.Count();
if (BuildListComboTotal == 0)
{
Log.TraceInformation("Output .apk file(s) are up to date (dependencies and build settings are up to date)");
return;
}
// Once for all arches code:
// make up a dictionary of strings to replace in xml files (strings.xml)
Dictionary<string, string> Replacements = new Dictionary<string, string>();
Replacements.Add("${EXECUTABLE_NAME}", ApplicationDisplayName);
if (!bIncrementalPackage)
{
// Wipe the Intermediate/Build/APK directory first, except for dexedLibs, because Google Services takes FOREVER to predex, and it almost never changes
// so allow the ANT checking to win here - if this grows a bit with extra libs, it's fine, it _should_ only pull in dexedLibs it needs
Log.TraceInformation("Performing complete package - wiping {0}, except for predexedLibs", UE4BuildPath);
DeleteDirectory(UE4BuildPath, "dexedLibs");
}
// If we are packaging for Amazon then we need to copy the file to the correct location
Log.TraceInformation("bPackageDataInsideApk = {0}", bPackageDataInsideApk);
if (bPackageDataInsideApk)
{
Console.WriteLine("Obb location {0}", ObbFileLocation);
string ObbFileDestination = UE4BuildPath + "/assets";
Console.WriteLine("Obb destination location {0}", ObbFileDestination);
if (File.Exists(ObbFileLocation))
{
Directory.CreateDirectory(UE4BuildPath);
Directory.CreateDirectory(ObbFileDestination);
Console.WriteLine("Obb file exists...");
var DestFileName = Path.Combine(ObbFileDestination, "main.obb.png"); // Need a rename to turn off compression
var SrcFileName = ObbFileLocation;
if (!File.Exists(DestFileName) || File.GetLastWriteTimeUtc(DestFileName) < File.GetLastWriteTimeUtc(SrcFileName))
{
Console.WriteLine("Copying {0} to {1}", SrcFileName, DestFileName);
File.Copy(SrcFileName, DestFileName);
}
}
}
else // try to remove the file it we aren't packaging inside the APK
{
string ObbFileDestination = UE4BuildPath + "/assets";
var DestFileName = Path.Combine(ObbFileDestination, "main.obb.png");
if (File.Exists(DestFileName))
{
File.Delete(DestFileName);
}
}
//Copy build files to the intermediate folder in this order (later overrides earlier):
// - Shared Engine
// - Shared Engine NoRedist (for Epic secret files)
// - Game
// - Game NoRedist (for Epic secret files)
CopyFileDirectory(UE4BuildFilesPath, UE4BuildPath, Replacements);
CopyFileDirectory(UE4BuildFilesPath + "/NotForLicensees", UE4BuildPath, Replacements);
CopyFileDirectory(UE4BuildFilesPath + "/NoRedist", UE4BuildPath, Replacements);
CopyFileDirectory(GameBuildFilesPath, UE4BuildPath, Replacements);
CopyFileDirectory(GameBuildFilesPath + "/NotForLicensees", UE4BuildPath, Replacements);
CopyFileDirectory(GameBuildFilesPath + "/NoRedist", UE4BuildPath, Replacements);
//Extract AAR and Jar files with dependencies
ExtractAARAndJARFiles(EngineDirectory, UE4BuildPath, NDKArches);
//Now validate GooglePlay app_id if enabled
ValidateGooglePlay(UE4BuildPath);
//determine which orientation requirements this app has
bool bNeedLandscape = false;
bool bNeedPortrait = false;
DetermineScreenOrientationRequirements(out bNeedPortrait, out bNeedLandscape);
//Now keep the splash screen images matching orientation requested
PickSplashScreenOrientation(UE4BuildPath, bNeedPortrait, bNeedLandscape);
//Similarly, keep only the downloader screen image matching the orientation requested
PickDownloaderScreenOrientation(UE4BuildPath, bNeedPortrait, bNeedLandscape);
// at this point, we can write out the cached build settings to compare for a next build
File.WriteAllText(BuildSettingsCacheFile, CurrentBuildSettings);
// at this point, we can write out the cached build settings to compare for a next build
File.WriteAllText(BuildSettingsCacheFile, CurrentBuildSettings);
///////////////
// in case the game had an AndroidManifest.xml file, we overwrite it now with the generated one
//File.WriteAllText(ManifestFile, NewManifest);
///////////////
Log.TraceInformation("\n===={0}====PREPARING NATIVE CODE=================================================================", DateTime.Now.ToString());
bool HasNDKPath = File.Exists(NDKBuildPath);
// get Ant verbosity level
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string AntVerbosity;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "AntVerbosity", out AntVerbosity);
foreach (var build in BuildList)
{
string Arch = build.Item1;
string GPUArchitecture = build.Item2;
string Manifest = build.Item3;
string NDKArch = GetNDKArch(Arch);
string SourceSOName = AndroidToolChain.InlineArchName(OutputPath, Arch, GPUArchitecture);
// if the source binary was UE4Game, replace it with the new project name, when re-packaging a binary only build
string ApkFilename = Path.GetFileNameWithoutExtension(OutputPath).Replace("UE4Game", ProjectName);
string DestApkName = Path.Combine(ProjectDirectory, "Binaries/Android/") + ApkFilename + ".apk";
// As we are always making seperate APKs we need to put the architecture into the name
DestApkName = AndroidToolChain.InlineArchName(DestApkName, Arch, GPUArchitecture);
// Write the manifest to the correct locations (cache and real)
String ManifestFile = Path.Combine(IntermediateAndroidPath, Arch + "_" + GPUArchitecture + "_AndroidManifest.xml");
File.WriteAllText(ManifestFile, Manifest);
ManifestFile = Path.Combine(UE4BuildPath, "AndroidManifest.xml");
File.WriteAllText(ManifestFile, Manifest);
// copy prebuild plugin files
UPL.ProcessPluginNode(NDKArch, "prebuildCopies", "");
// update metadata files (like project.properties, build.xml) if we are missing a build.xml or if we just overwrote project.properties with a bad version in it (from game/engine dir)
UpdateProjectProperties(ToolChain, UE4BuildPath, ProjectName);
// update GameActivity.java if out of date
UpdateGameActivity(Arch, NDKArch, EngineDirectory, UE4BuildPath);
// Copy the generated .so file from the binaries directory to the jni folder
if (!File.Exists(SourceSOName))
{
throw new BuildException("Can't make an APK without the compiled .so [{0}]", SourceSOName);
}
if (!Directory.Exists(UE4BuildPath + "/jni"))
{
throw new BuildException("Can't make an APK without the jni directory [{0}/jni]", UE4BuildFilesPath);
}
String FinalSOName;
if (HasNDKPath)
{
string LibDir = UE4BuildPath + "/jni/" + GetNDKArch(Arch);
FinalSOName = LibDir + "/libUE4.so";
// check to see if libUE4.so needs to be copied
if (BuildListComboTotal > 1 || FilesAreDifferent(SourceSOName, FinalSOName))
{
Log.TraceInformation("\nCopying new .so {0} file to jni folder...", SourceSOName);
Directory.CreateDirectory(LibDir);
// copy the binary to the standard .so location
File.Copy(SourceSOName, FinalSOName, true);
}
}
else
{
// if no NDK, we don't need any of the debugger stuff, so we just copy the .so to where it will end up
FinalSOName = UE4BuildPath + "/libs/" + GetNDKArch(Arch) + "/libUE4.so";
// check to see if libUE4.so needs to be copied
if (BuildListComboTotal > 1 || FilesAreDifferent(SourceSOName, FinalSOName))
{
Log.TraceInformation("\nCopying .so {0} file to jni folder...", SourceSOName);
Directory.CreateDirectory(Path.GetDirectoryName(FinalSOName));
File.Copy(SourceSOName, FinalSOName, true);
}
}
// remove any read only flags
FileInfo DestFileInfo = new FileInfo(FinalSOName);
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
File.SetLastWriteTimeUtc(FinalSOName, File.GetLastWriteTimeUtc(SourceSOName));
// if we need to run ndk-build, do it now
if (HasNDKPath)
{
string LibSOName = UE4BuildPath + "/libs/" + GetNDKArch(Arch) + "/libUE4.so";
// always delete libs up to this point so fat binaries and incremental builds work together (otherwise we might end up with multiple
// so files in an apk that doesn't want them)
// note that we don't want to delete all libs, just the ones we copied
TimeSpan Diff = File.GetLastWriteTimeUtc(LibSOName) - File.GetLastWriteTimeUtc(FinalSOName);
if (!File.Exists(LibSOName) || Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1)
{
foreach (string Lib in Directory.EnumerateFiles(UE4BuildPath + "/libs", "libUE4*.so", SearchOption.AllDirectories))
{
File.Delete(Lib);
}
string CommandLine = "APP_ABI=\"" + GetNDKArch(Arch) + " " + "\"";
if (!bForDistribution)
{
CommandLine += " NDK_DEBUG=1";
}
RunCommandLineProgramWithException(UE4BuildPath, NDKBuildPath, CommandLine, "Preparing native code for debugging...", true);
File.SetLastWriteTimeUtc(LibSOName, File.GetLastWriteTimeUtc(FinalSOName));
}
}
// after ndk-build is called, we can now copy in the stl .so (ndk-build deletes old files)
// copy libgnustl_shared.so to library (use 4.8 if possible, otherwise 4.6)
CopySTL(ToolChain, UE4BuildPath, Arch, NDKArch, bForDistribution);
CopyGfxDebugger(UE4BuildPath, Arch, NDKArch);
// copy postbuild plugin files
UPL.ProcessPluginNode(NDKArch, "resourceCopies", "");
Log.TraceInformation("\n===={0}====PERFORMING FINAL APK PACKAGE OPERATION================================================", DateTime.Now.ToString());
string AntBuildType = "debug";
string AntOutputSuffix = "-debug";
if (bForDistribution)
{
// Generate the Proguard file contents and write it
string ProguardContents = GenerateProguard(NDKArch, UE4BuildFilesPath, GameBuildFilesPath);
string ProguardFilename = Path.Combine(UE4BuildPath, "proguard-project.txt");
if (File.Exists(ProguardFilename))
{
File.Delete(ProguardFilename);
}
File.WriteAllText(ProguardFilename, ProguardContents);
// this will write out ant.properties with info needed to sign a distribution build
PrepareToSignApk(UE4BuildPath);
AntBuildType = "release";
AntOutputSuffix = "-release";
}
// Use ant to build the .apk file
string ShellExecutable = Utils.IsRunningOnMono ? "/bin/sh" : "cmd.exe";
string ShellParametersBegin = Utils.IsRunningOnMono ? "-c '" : "/c ";
string ShellParametersEnd = Utils.IsRunningOnMono ? "'" : "";
switch (AntVerbosity.ToLower())
{
default:
case "quiet":
if (RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" -quiet " + AntBuildType + ShellParametersEnd, "Making .apk with Ant... (note: it's safe to ignore javac obsolete warnings)") != 0)
{
RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
}
break;
case "normal":
RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
break;
case "verbose":
RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" -verbose " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
break;
}
// make sure destination exists
Directory.CreateDirectory(Path.GetDirectoryName(DestApkName));
// now copy to the final location
File.Copy(UE4BuildPath + "/bin/" + ProjectName + AntOutputSuffix + ".apk", DestApkName, true);
}
}
private void PrepareToSignApk(string BuildPath)
{
// ini file to get settings from
ConfigCacheIni Ini = GetConfigCacheIni("Engine");
string KeyAlias, KeyStore, KeyStorePassword, KeyPassword;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "KeyAlias", out KeyAlias);
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "KeyStore", out KeyStore);
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "KeyStorePassword", out KeyStorePassword);
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "KeyPassword", out KeyPassword);
if (string.IsNullOrEmpty(KeyAlias) || string.IsNullOrEmpty(KeyStore) || string.IsNullOrEmpty(KeyStorePassword))
{
throw new BuildException("DistributionSigning settings are not all set. Check the DistributionSettings section in the Andriod tab of Project Settings");
}
string[] AntPropertiesLines = new string[4];
AntPropertiesLines[0] = "key.store=" + KeyStore;
AntPropertiesLines[1] = "key.alias=" + KeyAlias;
AntPropertiesLines[2] = "key.store.password=" + KeyStorePassword;
AntPropertiesLines[3] = "key.alias.password=" + ((string.IsNullOrEmpty(KeyPassword) || KeyPassword == "_sameaskeystore_") ? KeyStorePassword : KeyPassword);
// now write out the properties
File.WriteAllLines(Path.Combine(BuildPath, "ant.properties"), AntPropertiesLines);
}
private List<string> CollectPluginDataPaths(TargetReceipt Receipt)
{
List<string> PluginExtras = new List<string>();
if (Receipt == null)
{
Log.TraceInformation("Receipt is NULL");
return PluginExtras;
}
// collect plugin extra data paths from target receipt
var Results = Receipt.AdditionalProperties.Where(x => x.Name == "AndroidPlugin");
foreach (var Property in Results)
{
// Keep only unique paths
string PluginPath = Property.Value;
if (PluginExtras.FirstOrDefault(x => x == PluginPath) == null)
{
PluginExtras.Add(PluginPath);
Log.TraceInformation("AndroidPlugin: {0}", PluginPath);
}
}
return PluginExtras;
}
public override bool PrepTargetForDeployment(UEBuildTarget InTarget)
{
//Log.TraceInformation("$$$$$$$$$$$$$$ PrepTargetForDeployment $$$$$$$$$$$$$$$$$");
AndroidToolChain ToolChain = new AndroidToolChain(InTarget.ProjectFile);
// we need to strip architecture from any of the output paths
string BaseSoName = ToolChain.RemoveArchName(InTarget.OutputPaths[0].FullName);
// get the receipt
UnrealTargetPlatform Platform = InTarget.Platform;
UnrealTargetConfiguration Configuration = InTarget.Configuration;
string ProjectBaseName = Path.GetFileName(BaseSoName).Replace("-" + Platform, "").Replace("-" + Configuration, "").Replace(".so", "");
string ReceiptFilename = TargetReceipt.GetDefaultPath(InTarget.ProjectDirectory.FullName, ProjectBaseName, Platform, Configuration, "");
Log.TraceInformation("Receipt Filename: {0}", ReceiptFilename);
SetAndroidPluginData(ToolChain.GetAllArchitectures(), CollectPluginDataPaths(TargetReceipt.Read(ReceiptFilename)));
// make an apk at the end of compiling, so that we can run without packaging (debugger, cook on the fly, etc)
MakeApk(ToolChain, InTarget.AppName, InTarget.ProjectDirectory.FullName, BaseSoName, BuildConfiguration.RelativeEnginePath, bForDistribution: false, CookFlavor: "",
bMakeSeparateApks: ShouldMakeSeparateApks(), bIncrementalPackage: true, bDisallowPackagingDataInApk: false);
// if we made any non-standard .apk files, the generated debugger settings may be wrong
if (ShouldMakeSeparateApks() && (InTarget.OutputPaths.Count > 1 || !InTarget.OutputPaths[0].FullName.Contains("-armv7-es2")))
{
Console.WriteLine("================================================================================================================================");
Console.WriteLine("Non-default apk(s) have been made: If you are debugging, you will need to manually select one to run in the debugger properties!");
Console.WriteLine("================================================================================================================================");
}
return true;
}
public static bool ShouldMakeSeparateApks()
{
// @todo android fat binary: Currently, there isn't much utility in merging multiple .so's into a single .apk except for debugging,
// but we can't properly handle multiple GPU architectures in a single .apk, so we are disabling the feature for now
// The user will need to manually select the apk to run in their Visual Studio debugger settings (see Override APK in TADP, for instance)
// If we change this, pay attention to <OverrideAPKPath> in AndroidProjectGenerator
return true;
// check to see if the project wants separate apks
// ConfigCacheIni Ini = nGetConfigCacheIni("Engine");
// bool bSeparateApks = false;
// Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bSplitIntoSeparateApks", out bSeparateApks);
//
// return bSeparateApks;
}
public override bool PrepForUATPackageOrDeploy(FileReference ProjectFile, string ProjectName, string ProjectDirectory, string ExecutablePath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bIsDataDeploy)
{
//Log.TraceInformation("$$$$$$$$$$$$$$ PrepForUATPackageOrDeploy $$$$$$$$$$$$$$$$$");
// note that we cannot allow the data packaged into the APK if we are doing something like Launch On that will not make an obb
// file and instead pushes files directly via deploy
AndroidToolChain ToolChain = new AndroidToolChain(ProjectFile);
MakeApk(ToolChain, ProjectName, ProjectDirectory, ExecutablePath, EngineDirectory, bForDistribution: bForDistribution, CookFlavor: CookFlavor,
bMakeSeparateApks: ShouldMakeSeparateApks(), bIncrementalPackage: false, bDisallowPackagingDataInApk: bIsDataDeploy);
return true;
}
public static void OutputReceivedDataEventHandler(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null))
{
Log.TraceInformation(Line.Data);
}
}
private void UpdateGameActivity(string UE4Arch, string NDKArch, string EngineDir, string UE4BuildPath)
{
string SourceFilename = Path.Combine(EngineDir, "Build", "Android", "Java", "src", "com", "epicgames", "ue4", "GameActivity.java");
string DestFilename = Path.Combine(UE4BuildPath, "src", "com", "epicgames", "ue4", "GameActivity.java");
Dictionary<string, string> Replacements = new Dictionary<string, string>{
{ "//$${gameActivityImportAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityImportAdditions", "")},
{ "//$${gameActivityClassAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityClassAdditions", "")},
{ "//$${gameActivityReadMetadataAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityReadMetadataAdditions", "")},
{ "//$${gameActivityOnCreateAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnCreateAdditions", "")},
{ "//$${gameActivityOnDestroyAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnDestroyAdditions", "")},
{ "//$${gameActivityOnStartAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnStartAdditions", "")},
{ "//$${gameActivityOnStopAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnStopAdditions", "")},
{ "//$${gameActivityOnPauseAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnPauseAdditions", "")},
{ "//$${gameActivityOnResumeAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnResumeAdditions", "")},
{ "//$${gameActivityOnActivityResultAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityOnActivityResultAdditions", "")},
{ "//$${soLoadLibrary}$$", UPL.ProcessPluginNode(NDKArch, "soLoadLibrary", "")}
};
string[] TemplateSrc = File.ReadAllLines(SourceFilename);
string[] TemplateDest = File.Exists(DestFilename) ? File.ReadAllLines(DestFilename) : null;
for (int LineIndex = 0; LineIndex < TemplateSrc.Length; ++LineIndex)
{
string SrcLine = TemplateSrc[LineIndex];
bool Changed = false;
foreach (var KVP in Replacements)
{
if(SrcLine.Contains(KVP.Key))
{
SrcLine = SrcLine.Replace(KVP.Key, KVP.Value);
Changed = true;
}
}
if (Changed)
{
TemplateSrc[LineIndex] = SrcLine;
}
}
if (TemplateDest == null || TemplateSrc.Length != TemplateDest.Length || !TemplateSrc.SequenceEqual(TemplateDest))
{
Log.TraceInformation("\n==== Writing new GameActivity.java file to {0} ====", DestFilename);
File.WriteAllLines(DestFilename, TemplateSrc);
}
}
private void ExtractAARAndJARFiles(string EngineDir, string UE4BuildPath, List<string> NDKArches)
{
AndroidAARHandler AARHandler = new AndroidAARHandler();
string ImportList = "";
// Get some common paths
string AndroidHome = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%").TrimEnd('/', '\\');
EngineDir = EngineDir.TrimEnd('/', '\\');
// Add the AARs from the default aar-imports.txt
// format: Package,Name,Version
string ImportsFile = Path.Combine(UE4BuildPath, "aar-imports.txt");
if (File.Exists(ImportsFile))
{
ImportList = File.ReadAllText(ImportsFile);
}
// Run the UPL imports section for each architecture and add any new imports (duplicates will be removed)
foreach (string NDKArch in NDKArches)
{
ImportList = UPL.ProcessPluginNode(NDKArch, "AARImports", ImportList);
}
// Add the final list of imports and get dependencies
foreach (string Line in ImportList.Split('\n'))
{
string Trimmed = Line.Trim(' ', '\r');
if (Trimmed.StartsWith("repository "))
{
string DirectoryPath = Trimmed.Substring(11).Trim(' ').TrimEnd('/', '\\');
DirectoryPath = DirectoryPath.Replace("$(ENGINEDIR)", EngineDir);
DirectoryPath = DirectoryPath.Replace("$(ANDROID_HOME)", AndroidHome);
DirectoryPath = DirectoryPath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
AARHandler.AddRepository(DirectoryPath);
}
else if (Trimmed.StartsWith("repositories "))
{
string DirectoryPath = Trimmed.Substring(13).Trim(' ').TrimEnd('/', '\\');
DirectoryPath = DirectoryPath.Replace("$(ENGINEDIR)", EngineDir);
DirectoryPath = DirectoryPath.Replace("$(ANDROID_HOME)", AndroidHome);
DirectoryPath = DirectoryPath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
AARHandler.AddRepositories(DirectoryPath, "m2repository");
}
else
{
string[] Sections = Trimmed.Split(',');
if (Sections.Length == 3)
{
string PackageName = Sections[0].Trim(' ');
string BaseName = Sections[1].Trim(' ');
string Version = Sections[2].Trim(' ');
Log.TraceInformation("AARImports: {0}, {1}, {2}", PackageName, BaseName, Version);
AARHandler.AddNewAAR(PackageName, BaseName, Version);
}
}
}
// Finally, extract the AARs and copy the JARs
AARHandler.ExtractAARs(UE4BuildPath);
AARHandler.CopyJARs(UE4BuildPath);
}
}
}