Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Android/UEDeployAndroid.cs
Max Preussner 8e9b51aeff Copying //UE4/Dev-Sequencer to //UE4/Dev-Main (Source: //UE4/Dev-Sequencer @ 3237992)
#lockdown Nick.Penwarden
#rb none

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

Change 3136778 on 2016/09/22 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3179199 on 2016/10/29 by Max.Chen

	Sequencer: Fade only oin the current player context, not on all worlds.

	Copy from Release-4.14. Copied fix to FadeTrackInstance to FadeTemplate.

	#jira UE-37939

Change 3179340 on 2016/10/29 by Max.Preussner

	PS4Media: Fixed audio track dropping first frame

Change 3180391 on 2016/10/31 by Max.Preussner

	UdpMessaging: nulling out message processor in destructor

Change 3180459 on 2016/10/31 by Max.Chen

	Sequencer: Fix copy/paste crash in UMG.

Change 3180607 on 2016/10/31 by Andrew.Rodham

	UMG: Fixed parent bindings not being adhered to correctly. Fixed slot widgets that get recreated not having their object bindings updated.

	#jira UE-38021
	#jira UE-38018

Change 3181405 on 2016/11/01 by Lina.Halper

	#ANIM/SEQUCNER: sequencer animation blending support including additive
	 - created multiway blend node - extension of two way blend
	 - created anim sequencer instance to be used in sequencer for blending multiple animations and additives
	 - hooked up to sequencer track players

	- renamed AnimationNode_TwoWay to AnimNode_TwoWay to be consistent with other node names.
	- Make sure you can't choose montage when selecting animation in Sequencer
	- Fixed Anim BP playing with multi group montages

	#code review: Max.Chen

Change 3181870 on 2016/11/01 by Andrew.Rodham

	Sequencer: Made sequence pointers stored in sequence template instances weak object ptrs

	  - We can't guarantee the lifetime of the objects here

	#jira UE-38051

Change 3182851 on 2016/11/02 by Andrew.Rodham

	Sequencer: Assert that a GetScriptStructImpl has been overridden correctly on templates

Change 3182852 on 2016/11/02 by Andrew.Rodham

	Sequencer: Added 'Restore Animated State' command (CTRL+R) and button to sequencer toolbar

Change 3183161 on 2016/11/02 by Max.Preussner

	Media: Added supported file extensions & URL schemes

Change 3183476 on 2016/11/02 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3185181 on 2016/11/03 by Max.Chen

	Sequencer: Refactor general options button menu into play options and select options. Add Select Sections in Selection Range and Select All in Selection Range.

	Fix issues with convert to spawanble and convert to possessable. Convert to possessable now deletes the spawn track so that it's not left lying around, which when deleted would end up deleting the converted possessable actor.

	#jira UE-37854

Change 3185184 on 2016/11/03 by Max.Chen

	Sequencer: Add hotkey to toggle camera cut track lock/unlock camera.

Change 3185409 on 2016/11/03 by Max.Chen

	Sequencer: Fix crash in skeletal mesh section drawing.

	#jira UE-38090

Change 3185444 on 2016/11/03 by Max.Chen

	UMG: Expose label browser for UMG

Change 3185662 on 2016/11/03 by Max.Chen

	Sequencer: Paste track fixes.

	 - Loosen restrictions on paste track destination.  This allows the paste to operate on spawnables and on properties that don't have an explicit Set function.
	 - Allow pasting onto all types of tracks, not just property tracks.
	 - Fix when pasting the copied tracks onto multiple objects.

	Tested pasting transform tracks from possessable to spawnables.
	Tested pasting skeletal animation tracks from spawnable to possessables.

	#jira UETOOL-1206

Change 3185920 on 2016/11/03 by Andrew.Porter

	Adding test content for multiple audio video tracks.

Change 3186404 on 2016/11/03 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3187957 on 2016/11/04 by Max.Preussner

	MediaAssets: Exposed CanPlaySource in BP

Change 3187988 on 2016/11/05 by Max.Preussner

	Fixed documentation

Change 3188035 on 2016/11/05 by Max.Chen

	Sequencer: Show camera name in cinematic viewport.

	#jira UE-28115

Change 3188603 on 2016/11/07 by Max.Preussner

	WmfMedia: Added missing nullptr check

Change 3188788 on 2016/11/07 by Max.Preussner

	MediaPlayerEditor: Removed property buttons from PlatformMediaSource customization (UE-37948)

	#jira UE-37948

Change 3188808 on 2016/11/07 by Max.Preussner

	MediaAssets: Moved media player implementation into reusable class
	Also moved overlay text handling into separate asset.

Change 3188919 on 2016/11/07 by Max.Preussner

	Media: Changed the handling of invalid media and media that failed to open (UE-38014)

	#jira UE-38014

Change 3189112 on 2016/11/07 by Max.Preussner

	WmfMedia: Added rudimentary H.265 HEVC support for Windows 10 (UE-38324)

	#jira UE-38324

Change 3189376 on 2016/11/07 by Max.Preussner

	WmfMedia: Removed Windows specific code from factory module

Change 3189381 on 2016/11/07 by Max.Preussner

	Atrac9Audio: Fixed log category

Change 3189497 on 2016/11/07 by Max.Preussner

	Media: Added binary sinks support

Change 3189666 on 2016/11/07 by Max.Chen

	Curve Editor: Add option to show time in frame numbers

	#jira UE-27210

Change 3190339 on 2016/11/08 by Max.Preussner

	MediaAssets: Removed SetDesiredPlayerName since the field is public

Change 3190342 on 2016/11/08 by Andrew.Porter

	Adding sequencer test content for animation blueprint

Change 3190398 on 2016/11/08 by Max.Preussner

	Media: Renamed binary tracks to metadata tracks

Change 3190458 on 2016/11/08 by andrew.porter

	Updating Skeleton with new slots.

Change 3191167 on 2016/11/08 by Max.Chen

	Sequencer: Fix crash in validating paste tracks buffer. Validate the tracks instead of actually pasting into temp.

	#jira UE-38353

Change 3191336 on 2016/11/09 by Andrew.Rodham

	Slate: Added the ability to set and retrieve a host tab manager from a details view

Change 3191338 on 2016/11/09 by Andrew.Rodham

	Editor: Added the ability to extend default layouts

	  - FLayoutExtender can be used to provide basic tab layout extensions on default themes.
	  - This can be used by external plugins to inject tabs to other interfaces where necessary.
	  - Currently this is supported by the blueprint editor's unified component layout, and the level editor layout.

Change 3191346 on 2016/11/09 by Andrew.Rodham

	Sequencer: Added new (experimental) ActorSequence module and editor

	  - Sequences can now be added to actors via the UActorSequenceComponent.
	  - An embedded sequencer will appear on details panels, with the option to break it out into a tab.
	  - Separated common playback elements from ULevelSequencePlayer into UMovieSceneSequencePlayer, from which specific players can derive.
	  - The majority of level editorintegration with sequencer has been separated out into a separate singleton class that can manage multiple sequencers.
	  - All movie scene data now defaults to instanced, such that it can be duplicated and instanced correctly.
	  - Added read-only mode for sequencer which is used for actor sequence components that come from a blueprint archetype to prevent erroneous editing.

Change 3191387 on 2016/11/09 by Andrew.Rodham

	Orion: Fixed deprecation warnings

Change 3191388 on 2016/11/09 by Andrew.Rodham

	Orion: Added dependency on MovieScene module

Change 3191403 on 2016/11/09 by Andrew.Rodham

	Sequencer: Fix initialization order warning

Change 3191428 on 2016/11/09 by Andrew.Rodham

	Sequencer: Added missing include

Change 3191510 on 2016/11/09 by Andrew.Rodham

	Header include fixes

Change 3191599 on 2016/11/09 by Max.Chen

	Sequencer: Add option to lock the playback range per movie scene. The toggle is stored as editor only and should be a saved value so that it can persist as the asset is passed from user to user.

	#jira UE-34677

Change 3191664 on 2016/11/09 by Andrew.Rodham

	Sequencer: Ensure keyframe handlers are only added once

Change 3192373 on 2016/11/09 by Max.Preussner

	MediaAssets: Fixed regression: playlists no longer open

Change 3192408 on 2016/11/09 by Max.Preussner

	MediaAssets: Fixed OpenPlaylistIndex crashing

Change 3192878 on 2016/11/09 by Max.Chen

	Camera Rig: Fix log spam trying to unregister component.

	#jira UE-38435

Change 3192989 on 2016/11/10 by Andrew.Rodham

	Slate: Added constructor to appease old VS2013 compiler warning about non-constructible type

Change 3192991 on 2016/11/10 by Andrew.Rodham

	Sequencer: Moved lambda out-of-line to fix static analysis warning

Change 3193420 on 2016/11/10 by Max.Preussner

	MediaAssets: Replaced CopyToResolveTarget with new TransitionTarget API

Change 3193478 on 2016/11/10 by Max.Chen

	Sequencer: Moved Fix Actor References back under the General Options menu.

Change 3193870 on 2016/11/10 by Max.Preussner

	MediaPlayerEditor: Removed additional buttons in per-platform overrides (UE-37948)

	#jira UE-37948

Change 3193873 on 2016/11/10 by Lina.Halper

	- Sequencer fix with anim instance reinit
	- Fixed TMap issue with memory by changing to pointer from ref.

	#code review: Max.Chen

Change 3194184 on 2016/11/10 by Max.Chen

	Sequencer: Only expand section when setting keys when there are keys. Otherwise if you set the default value while the time position is outside of the section range, the section will expand, which seems undesirable.

Change 3194187 on 2016/11/10 by Max.Chen

	Sequencer: Backwards compatibility if a track no longer supports multiple rows, its sections are split to other duplicate tracks.

Change 3194191 on 2016/11/10 by Max.Chen

	Sequencer: Add audio volume and pitch curves.

	#jira UE-30009

Change 3194256 on 2016/11/10 by Max.Chen

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

Change 3194282 on 2016/11/10 by Max.Chen

	Movie Capture: Add some frame rate bounds. Max frame rate for recording is 200. Min is 1.

	#jira UE-38502

Change 3194355 on 2016/11/11 by Max.Chen

	Sequencer: Minimum handle size for time slider scrubber.

	#jira UE-34676

Change 3194767 on 2016/11/11 by Max.Chen

	Sequencer: Mark duplicated tracks as changed so that their template gets regenerated.

Change 3195094 on 2016/11/11 by Max.Preussner

	Media: Removing game thread dependencies

	This change removes game thread dependencies from all media players so that we can use the media framework for startup movies where the game thread is block while loading the Engine. The players now have two new methods, TickPlayer and TickVideo, which need to be called from the external code that owns the players. On the Engine side, this is taken care of by UMediaPlayer, which calls TickPlayer from the game thread and TickVideo from the render thread. In startup movies, this will be taken care of by a special thread.

	AvfMedia: This change does not fully remove game thread dependencies in AvfMediaPlayer yet. There are some async callbacks scheduled to execute on the game thread that need to be refactored. The execution of these events should be performed in TickPlayer instead.

	All platform owners, please review these changes for your platform and make sure that everything still works. I have not had time to test all platforms yet.

Change 3195396 on 2016/11/11 by Max.Preussner

	AvfMedia: Removed remaining game thread dependencies

Change 3195670 on 2016/11/11 by Max.Preussner

	MediaUtils: Renamed function

Change 3195690 on 2016/11/11 by Max.Preussner

	MediaAssets: MediaPlayerBase instance is now a field instead of pointer.

Change 3195802 on 2016/11/11 by Max.Preussner

	Media: Removed UMediaPlayer::GetNativePlayer

Change 3195843 on 2016/11/11 by Max.Preussner

	Kismet: Fixed non-unity

Change 3195851 on 2016/11/11 by Max.Preussner

	Fixed typo.

Change 3195854 on 2016/11/11 by Max.Preussner

	MediaUtils: Added missing forward declaration

Change 3195937 on 2016/11/11 by Max.Chen

	Media: CIS Fix

Change 3196120 on 2016/11/13 by Max.Chen

	Sequencer: Weight curve for skeletal animation section.

	Changed skeletal template evaluation so that it works with multiple animation tracks. The shared track clears all the weights, the section gathers up all the data, and the shared track evaluates the data. Otherwise, the multiple track evaluations would conflict with each other in setting states back and forth.

	#jira UE-38374, UEFW-128

Change 3196265 on 2016/11/13 by Max.Chen

	Sequencer: Fix audio waveforms so that they're regenrated when audio start time is changed.

	#jira UE-38543

Change 3196421 on 2016/11/14 by Andrew.Rodham

	Sequencer: Fixed modified tracks not being written to the transaction buffer when replacing object bindings

	#jira UE-38423

Change 3197131 on 2016/11/14 by Max.Chen

	Sequencer: Null checks.

	#jira UE-38570, UE-38593

Change 3197209 on 2016/11/14 by Max.Chen

	Cine Camera: Reset focus smoothing interpolation on PostEditChangeProperty. This fixes an issue where if you enable focus smoothing, the manual focus distance that is input isn't used since the interpolation happens from the last current focus distance.

	#jira UE-27055

Change 3198691 on 2016/11/15 by Max.Chen

	Sequence Recorder: Optimize record transforms by setting all the keyframes at once.  Also, added option to toggle removing redundant keyframes from the recorded tracks.

	#jira UE-38489

Change 3198711 on 2016/11/15 by andrew.porter

	Adding test content for MEdia Framework Track Switching.

Change 3199174 on 2016/11/15 by Lina.Halper

	Sequencer backward compatibility fix with root motion
	Make sure you could remove root motion fine

	#jira : UE-38591

Change 3199260 on 2016/11/15 by tim.gautier

	Updated QA-Media_TrackSwitch - changed Trigger Collision to only detect overlap from PlayerPawn

Change 3199663 on 2016/11/15 by Max.Chen

	Anim Sequencer: Fix deprecation warning for bCanUseParallelUpdateAnimation. Updated to use bUseMultiThreadedAnimationUpdate.

Change 3199727 on 2016/11/15 by Max.Chen

	Matinee to Level Sequence: Set default scale when converting matinee move tracks to sequencer.

	#jira UE-38688

Change 3199847 on 2016/11/16 by Max.Chen

	Sequencer: Add menu option to reduce keys of all sections in the current level sequence

Change 3200351 on 2016/11/16 by Max.Chen

	Level Editor/Sequencer: Fixes to allow for component keyframing. The transform track operates on the components that changed, not the actor. The level editor viewport broadcasts begin/end movement on the components that changed.

	#jira UE-38649, UE-38646

Change 3200474 on 2016/11/16 by Max.Chen

	Sequencer: Move reduce keys to section context menu.

Change 3200888 on 2016/11/16 by Max.Chen

	Sequencer: Clamp skeletal animation evaluation remapping of time to section bounds. This is necessary when evaluating nearest is enabled and the time is beyond the section bounds.

	Also, set the shared track template to have higher priority so that it always clears/initializes weights before each section's template adds section params for evaluation.

Change 3201633 on 2016/11/17 by Max.Chen

	Matinee to Level Sequence: Fix matinee 3d scale track conversion to level sequence.

	Also, added paste matinee vector track to sequencer's vector track.

	#jira UE-38688

Change 3202458 on 2016/11/17 by Max.Chen

	Sequencer: Fix track editor commands getting unregistered when switching from one level sequence to another.  The sequence of events is: track editor commands get bound when a level sequence is edited. When switching to another level sequence, the existing track editor is released after the new one is registered, causing the commands to ultimately get unbound.

	#jira UE-38693

Change 3202606 on 2016/11/17 by Max.Chen

	Actor Sequence: Null check in CanPossessObject for a component's owner.

	#jira UE-38514

Change 3203522 on 2016/11/17 by Max.Chen

	Sequencer: Audio start time deprecated in favor of start offset which is an offset into the audio clip. Also, limit the start offset to positive values since you can just crop into the audio clip by dragging the section's start time.

	Audio track no longer supports multiple rows (should have been checked in along with the audio volume and pitch multiplier curves).

	#jira UE-38549, UE-38554, UE-38547

Change 3203863 on 2016/11/18 by Andrew.Rodham

	Engine: Ensure that world settings actor is considered by network object list when sorting the actor list for a level

Change 3203865 on 2016/11/18 by Andrew.Rodham

	Sequencer: Fixed play rate track interaction between servers and clients
	  - The logic for evaluation was previously flawed (it would only run in editor builds). Play rate is now only evaluated on servers and standalone clients, with the time dilation being replicated to network clients.

Change 3203900 on 2016/11/18 by Andrew.Rodham

	Sequencer: Changed CreateLevelSequencePlayer to create a transient level sequence actor

	#jira UE-37277

Change 3205038 on 2016/11/18 by Max.Preussner

	Slate: Corrected comment

Change 3205046 on 2016/11/18 by Max.Preussner

	WmfMedia: Added missing nullptr check

	#jira UE-38825

Change 3205073 on 2016/11/18 by Max.Chen

	Sequencer: Fix audio upgrade case when start time is 0.

Change 3205277 on 2016/11/19 by Max.Preussner

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

	Please take a look at SequencerEdMode.cpp and Sequencer.cpp. I ended up accepting latest Dev-Sequencer, which seemed to be the right thing to do.

Change 3205465 on 2016/11/20 by Max.Preussner

	MovieScene: Fixed non-unity build

Change 3205467 on 2016/11/20 by Max.Preussner

	Engine: Fixed spelling

Change 3206264 on 2016/11/21 by Max.Preussner

	Kismet: Added missing forward declaration

Change 3206493 on 2016/11/21 by Max.Preussner

	PS4Media: Added remaining changes for removing game thread dependencies

Change 3206512 on 2016/11/21 by Andrew.Porter

	Adding test content to QAGame for Sequencer animation weight blending.

Change 3206529 on 2016/11/21 by Lina.Halper

	Fixed anim notifes to work in Sequencer Instance

	- Give proper delta in editor preview
	- Make sure not to recreate AnimInstance

	#jira: UE-38849
	#code review:Max.Chen

Change 3206552 on 2016/11/21 by Max.Preussner

	QAGame: Enabled looping by default

Change 3207462 on 2016/11/22 by andrew.porter

	QAGame: updating QA-Sequencer with changes to animation blending test cases

Change 3207499 on 2016/11/22 by tim.gautier

	Added Streaming Sources, added Streaming Source options for BP_MediaPlayer. Specified Media Option Categories with BP_MediaPlayer to clean up details panel.

	#jira none

Change 3207571 on 2016/11/22 by Max.Chen

	Curve Editor: Expose curve editor settings to Editor Preferences.

	#jira UE-38907

Change 3207690 on 2016/11/22 by Max.Chen

	Sequencer: Speculative crash fix for switching UMG animations.

	#jira UE-29333

Change 3207744 on 2016/11/22 by tim.gautier

	Removed unnecessary nodes from BP_MediaPlayer. Created a variable visible in the Details Panel to allow the user to specify a URL to Stream media without specifying a Source in-editor.

	#jira none

Change 3207935 on 2016/11/22 by Max.Chen

	Sequencer: Temporary fix for skeletal animation track scrubbing. Verified that anim notifies still fire when playing and scrubbing.

	#jira UE-38964

Change 3207938 on 2016/11/22 by Max.Chen

	Sequence Recorder: Set reduce keys back to true so that there's no change in current behavior. This should be toggled off for performance reasons but in general is nice to have reduced keys.

Change 3207950 on 2016/11/22 by Lina.Halper

	- Fixed so that mesh space additive won't show up in sequencer
	- Added warning if you change type later or existing ones

	#jira: UE-38062?

Change 3208278 on 2016/11/22 by andrew.porter

	QAGame: Adjusting level blueprint for test case.

Change 3208285 on 2016/11/22 by andrew.porter

	QAGame: adding SequencerBP animation blueprint.

Change 3208538 on 2016/11/23 by Max.Chen

	Actor Sequence: Fix plugin filename.

Change 3208916 on 2016/11/23 by Max.Chen

	Sequencer: Fix material parameter initialization so that the value is retrieved from the material instance and not the parent material.

	#jira UE-34317

Change 3208924 on 2016/11/23 by Max.Chen

	Save As: Cancel should not save over the existing asset. It should just return.

Change 3208939 on 2016/11/23 by andrew.porter

	QAGame: reset some content back to its default state for testing

Change 3209053 on 2016/11/23 by Max.Chen

	Sequencer: Ensure the section id is unique.

Change 3209161 on 2016/11/23 by Max.Chen

	Save As: Follow up fix for cancelling save as.

Change 3210540 on 2016/11/26 by Max.Preussner

	WmfMedia: Reworked fallback stride calculations to fix issues with some exotic video formats

Change 3210546 on 2016/11/26 by Max.Preussner

	WmfMedia: Fixed NV12 vertical buffer alignment

Change 3211567 on 2016/11/28 by Max.Preussner

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

	Step 1 of 2

Change 3212408 on 2016/11/28 by Max.Preussner

	Fixed fallout from Dev-Main merge

Change 3212456 on 2016/11/28 by Max.Preussner

	ActorSequenceEditor: Removed monolithic header dependencies

Change 3212562 on 2016/11/28 by Max.Preussner

	ActorSequenceEditor: Removed monolithic header usage

Change 3212649 on 2016/11/28 by Max.Chen

	Fix CIS

Change 3212671 on 2016/11/28 by Max.Chen

	Sequencer: Add option to restore to the pre animated state.

	#jira UE-38862
	#2953

Change 3212672 on 2016/11/28 by Max.Chen

	Sequencer: Select object binding node corresponding to selected components and vice versa (select components in level when object binding node is selected)

Change 3212673 on 2016/11/28 by Max.Chen

	Sequencer: Follow-up fix for component keyframing - key area needs to be updated by component.

	#jira UE-38649

Change 3212676 on 2016/11/28 by Max.Chen

	Level Editor: PostEditMove should only be called on the actor if it is moved.

	#jira UE-38646

Change 3212688 on 2016/11/29 by Max.Chen

	Sequencer: Force refresh event parameters customization when struct contents change but not a full refresh when struct child contents change.

	#jira UE-39094

Change 3212831 on 2016/11/29 by Andrew.Rodham

	Disabled ActorSequenceEditor plugin by default while it's experimental

Change 3213219 on 2016/11/29 by Max.Preussner

	AvfMedia: Added missing include

Change 3213333 on 2016/11/29 by Andrew.Rodham

	Sequencer: Added the ability to override bindings when playing back a level sequence on a level sequence actor

	#jira UETOOL-746

Change 3213905 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214203 on 2016/11/29 by Michael.Gay

	Some demo files to test Sequencer timing.

Change 3214205 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214548 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214564 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214567 on 2016/11/29 by Max.Chen

	More IWYU fixes for Win32

Change 3214573 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214576 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214621 on 2016/11/30 by Max.Preussner

	Atrac9Decoder: Fixed log category declaration

Change 3214630 on 2016/11/30 by Max.Preussner

	More IWYU fixes

Change 3214747 on 2016/11/30 by Andrew.Rodham

	Sequencer: Fixed shadow variable

Change 3214957 on 2016/11/30 by Andrew.Rodham

	Core: Changed Algo::Find to use TElementType
	  - This allows it to support c style arrays

Change 3215127 on 2016/11/30 by Andrew.Rodham

	Sequencer: Made burn-in options and init settings instanced

	  - This ensures they work correctly when defined on archetypes and blueprints

	#jira UE-38645

Change 3215754 on 2016/11/30 by Max.Chen

	Sequencer: Fix skeletal animation track evaluating tracks in the wrong time space. Cache the evalulation time and weight value in each section's template and then execute with those values in the shared track's template.

	#jira UE-39145

Change 3216603 on 2016/12/01 by Max.Chen

	Sequencer: Set audio volume/pitch only if changed.

Change 3216613 on 2016/12/01 by Max.Chen

	Sequencer: Add component selector when there are multiple components that have sockets. This fixes a crash when there are multiple components to attach to.

	#jira UE-39167

Change 3217175 on 2016/12/01 by Max.Chen

	Sequencer: Set skeletal animation track evaluation to be upper bound exclusive. This gives better behavior when two clips butt up against each other since the sections would overlap in time and evaluation would normalize they weighted contribution of each.

	#jira UE-37184

Change 3217292 on 2016/12/01 by Max.Chen

	Sequencer: Rework upgrading track rows to include overlapping sections. For skeletal animation sections, set weight values based on the evaluation bounds since there was no blending prior to 4.15.

Change 3217860 on 2016/12/01 by Max.Preussner

	Media: Fall-through for media options

Change 3217965 on 2016/12/01 by Max.Preussner

	MediaAssets: Renamed media option name

Change 3218470 on 2016/12/01 by Max.Chen

	Sequencer: Fix start time deprecation value so that negative values are supported.

	#jira UE-39259

Change 3218473 on 2016/12/01 by Max.Chen

	Sequencer: Fix crash if start seq length is negative.

Change 3219021 on 2016/12/02 by Max.Chen

	Sequencer: Add multiply and divide to transform box.

Change 3219374 on 2016/12/02 by Max.Chen

	Sequencer: Teleport simulating components when moving them through the transform track. This fixes bugs with recording simulating actors (ie. vehicle game) where recorded actors don't playback with the recorded positions and there are warnings about attempting to move a fully simulated skeletal mesh.

	#jira UE-38442, UE-38444, UE-38852

Change 3219638 on 2016/12/02 by Max.Preussner

	Projects: Fixed error message

Change 3220584 on 2016/12/03 by Andrew.Rodham

	Sequencer: Blueprint generated classes are now always removed from level sequences on load in the editor
	  - This ensures that old (and perhaps corrupt) BP generated classes are destroyed

	#jira UE-39173

Change 3220585 on 2016/12/03 by Andrew.Rodham

	Editor: Fix EditInstanceOnly properties that aren't variables on the generated class being editable in blueprints

Change 3220973 on 2016/12/04 by Max.Chen

	Fix CIS

Change 3222833 on 2016/12/05 by Max.Chen

	Sequencer: Fixed some recorded components not being generated.

	#jira UE-34289

Change 3224450 on 2016/12/06 by Max.Chen

	Sequencer: Fix convert spawnable to posessable. Logic for setting the parent was mistakenly removed in runtime eval.

	#jira UE-39419

Change 3225301 on 2016/12/07 by Max.Preussner

	AvfMedia: Added settings class

Change 3225304 on 2016/12/07 by Max.Preussner

	Fixed typo

Change 3225723 on 2016/12/07 by Max.Preussner

	Fixed typo.

Change 3225871 on 2016/12/07 by Max.Preussner

	Forgot to check in

Change 3225932 on 2016/12/07 by Max.Preussner

	Added missing header

Change 3226266 on 2016/12/07 by Max.Preussner

	Media: Fixed various module dependencies

Change 3226451 on 2016/12/07 by Max.Preussner

	Include fixes

Change 3226455 on 2016/12/07 by Max.Preussner

	LevelSequence: Added missing include

Change 3227135 on 2016/12/08 by Max.Preussner

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

Change 3227143 on 2016/12/08 by Max.Preussner

	LevelSequencer: Added missing header

Change 3227731 on 2016/12/08 by Max.Preussner

	LevelSequencer: Added missing include

Change 3228222 on 2016/12/08 by Max.Preussner

	UBT: Fixed delay load library support for remote compilation to macOS

Change 3228266 on 2016/12/08 by Max.Preussner

	PluginBrowser: Added missing includes

Change 3228755 on 2016/12/09 by Andrew.Rodham

	Sequencer: Fixed copy-paste of event keys
	  - Also added a key-value iterator to TCurveInterface (both const and non-const)

	#jira UE-39526

Change 3228777 on 2016/12/09 by Luke.Thatcher

	[PLATFORM] [PS4] [!] Reimplement fixes from Fortnite for PS4 media framework in //UE4/Dev-Sequencer.

	Based on Original CL 3227137

	 - Event callback from AvPlayer was enqueing the processing of events over to the player thread, so the "State" member of FPS4MediaPlayer doesn't get updated until the following frame. This breaks cases with multiple calls to SetRate within a single frame.
	 - Removed time check in FPS4MediavideoSampler::Tick. There are cases where the time check failed, even when a new frame was available from the AvPlayer libs. The video sampler now always calls sceAvPlayerGetVideoDataEx. This returns immediately if no frame data is available.
	 - FPS4MediaPlayer::Seek was failing if the video is in a playing/paused state. We now restart the stream if a seek command occurs after the video has stopped (e.g. due to EOF reached).
	 - Shared a single critical section between the FPS4MediaTracks, FPS4MediaVideoSampler and FPS4MediaPlayer objects. Fixes deadlocks between the decoder/player threads where each will be waiting on each others' critical section.

	[~] Enabled debug warnings from AvPlayer library in non-shipping builds.
	[~] Changed log levels of UE_LOGs to match their severity.

	-------------------------

	[!] Also, fixed rendering artifacts on videos using a cropping rectangle
	 - e.g. 1080p videos are actually decoded as 1920x1088, with an extra 8 pixels height, which contained garbage.
	 - We determine the final media texture size as the size of the cropping rectangle, and use modified UVs during the YCbCr->RGB converstion shader to do the mapping.

Change 3228793 on 2016/12/09 by Andrew.Rodham

	Sequencer: Edits to actor sequences now correctly mark their parent blueprints for compilation

	#jira UE-38723

Change 3228877 on 2016/12/09 by Luke.Thatcher

	[PLATFORM] [PS4] [!] Fix track switching issues in PS4 media player.
	 - Sony's AvPlayer library does not support switching tracks (audio or video) on-the-fly after a stream has begun playback.
	 - The higher level UMediaPlayer enables track 0 automatically, which would be committed to the AvPlayer, and therefore lock out other streams.
	 - Actual track selection is now deferred until the stream is started, after which changing tracks is prohibited.
	 - Tracks must be selected before calling SetRate for the first time.

	#jira UE-37225

Change 3229501 on 2016/12/09 by Max.Preussner

	Media: Better display names for media player plug-ins

Change 3229515 on 2016/12/09 by Max.Preussner

	MediaPlayerEditor: Sorting player plug-ins alphabetically; consistent display in both media player editor and media source customization

Change 3229716 on 2016/12/09 by andrew.porter

	Adding PlayRate sequence to my dev folder

Change 3230554 on 2016/12/12 by Andrew.Rodham

	Back out changelist 3220584
	  - Currently this causes actor instances to fail to load because they are instanced of dead classes. Need to think of a more robust solution here.

	#jira UE-39398

Change 3230922 on 2016/12/12 by Max.Preussner

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

Change 3232059 on 2016/12/12 by Max.Preussner

	MediaUtils: Better error message for when no suitable media player plug-in was found

Change 3232097 on 2016/12/13 by Max.Preussner

	Switch: Temp fix for borked folder name on case-sensitive platforms

Change 3232100 on 2016/12/13 by Max.Preussner

	MediaAssets: Split up UMediaSource into UBaseMediaSource

	Also added color space related properties

Change 3232101 on 2016/12/13 by Max.Preussner

	Media: Started to implement support for color spaces

Change 3232119 on 2016/12/13 by Max.Preussner

	MediaAssets: Fixed buffer not recreated if color space changed

Change 3232799 on 2016/12/13 by Max.Preussner

	PS4Media: Fixed build

	#jira UE-39706

Change 3233170 on 2016/12/13 by Max.Preussner

	Merging //UE4/Dev-Main to Dev-Sequencer (//UE4/Dev-Sequencer)

Change 3233250 on 2016/12/13 by Max.Preussner

	MediaPlayerEditor: Added separator in track menu

Change 3233309 on 2016/12/13 by andrew.porter

	QAGame: Edited text render actors in QA-Media_TrackSwitch

Change 3233439 on 2016/12/13 by Chris.Babcock

	Standardize Android media track DisplayName

Change 3233817 on 2016/12/13 by Chris.Babcock

	Fix virtual keyboard EditableTextBox update when comitted text matches current text from change updates
	#jira UE-39424
	#ue4
	#mobile

Change 3234421 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed nullptr crash

Change 3234423 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed incorrect copying of base-class from compiler rules

Change 3234429 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed empty space not being added between the last and penultimate segments when required

	#jira UE-39442

Change 3234635 on 2016/12/14 by Max.Preussner

	MediaAssets: Exposed UTexture properties in UMediaTexture

Change 3234681 on 2016/12/14 by Max.Preussner

	MediaAssets: Made MediaTextureResources support -onethread

Change 3234878 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed crash with "Evaluate Sub Sequences in Isolation" enabled
	  - This occurred when there were tracks at the root level of the sub sequence, because it would incorrectly hash in the parent ID, rather than just using it directly

Change 3234901 on 2016/12/14 by Max.Preussner

	MediaPlayerEditor: Detail customization improvements

Change 3235275 on 2016/12/14 by Chris.Babcock

	Fix WMF stream ordering to match other players
	#jira UE-39703
	#ue4
	#mediaframework

Change 3235390 on 2016/12/14 by Max.Preussner

	DesktopPlatform: Added IniPlatformName to FPlatformInfo; fixed up indentation

Change 3235402 on 2016/12/14 by Max.Preussner

	MediaAssets: Fixed platform player name overrides ignored in packaged builds (UE-39771)

	#jira UE-39771

Change 3235667 on 2016/12/14 by Max.Preussner

	Media: Moved enums into separate header file, so they can be shared

Change 3235984 on 2016/12/14 by Max.Preussner

	Back out changelist 3235667

Change 3236040 on 2016/12/14 by Max.Preussner

	Core: Added modulus operator to FTimespan

Change 3236139 on 2016/12/15 by Max.Preussner

	Core: Added FTimespan::IsZero

Change 3236527 on 2016/12/15 by Max.Preussner

	Fixed initialization order

Change 3237101 on 2016/12/15 by Andrew.Rodham

	Sequencer: Skeletal animation and audio tracks now support multiple rows again.
	  - In practice there were too many edge-cases to account for whilst considering backwards compatability
	  - The impossible scenario was 2 sections on different rows, but evaluating nearest section - this cannot be represented as separate tracks.
	  - Reorganised animation runtime template to use execution tokens rather than ::Initialize to ensure that animation operates correctly on the first frame for spawned objects

	#jira UE-39442
	#jira UE-39725

Change 3237213 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed crash when setting event key properties

	#jira UE-39347

Change 3237255 on 2016/12/15 by Chris.Babcock

	Fix Multi with ETC2 and PVRTC selecting ES3.0 instead of 2.0
	#jira UE-39839
	#ue4
	#android

Change 3237294 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed shadowed variable warnings

Change 3237366 on 2016/12/15 by Max.Preussner

	Media: Removed color space changes; we'll do these in material graphs instead

Change 3237436 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed montages not being stopped for specific animation slots when animation sections were no longer evaluated

	#jira UE-39847

Change 3237458 on 2016/12/15 by Andrew.Rodham

	Sequencer: Always force regeneration of templates when PIE  to eliminate the posibility of combining stale data

Change 3237516 on 2016/12/15 by Max.Preussner

	Media: Attempting to fix Crash in fortnite just before exiting onboarding (UE-39841)

	#jira UE-39841

Change 3237532 on 2016/12/15 by Max.Preussner

	Added missing scope lock

Change 3237991 on 2016/12/16 by Max.Preussner

	PS4Media: Fixed build

[CL 3238204 by Max Preussner in Main branch]
2016-12-16 11:17:44 -05:00

2602 lines
100 KiB
C#

// Copyright 1998-2017 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, "http://schemas.android.com/apk/res/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 ConfigHierarchy GetConfigCacheIni(ConfigHierarchyType Type)
{
return ConfigCache.ReadHierarchy(Type, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.Android);
}
private string CachedSDKLevel = null;
private string GetSdkApiLevel(AndroidToolChain ToolChain)
{
if (CachedSDKLevel == null)
{
// ask the .ini system for what version to use
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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 + "/platforms/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, ConfigHierarchy Ini = null)
{
if (bDisallowPackagingDataInApk)
{
return false;
}
// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
}
// we check this a lot, so make it easy
bool bPackageDataInsideApk;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageDataInsideApk", out bPackageDataInsideApk);
return bPackageDataInsideApk;
}
public bool UseExternalFilesDir(bool bDisallowExternalFilesDir, ConfigHierarchy Ini = null)
{
if (bDisallowExternalFilesDir)
{
return false;
}
// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
}
// we check this a lot, so make it easy
bool bUseExternalFilesDir;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bUseExternalFilesDir", out bUseExternalFilesDir);
return bUseExternalFilesDir;
}
public bool DisableVerifyOBBOnStartUp(ConfigHierarchy Ini = null)
{
// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni(ConfigHierarchyType.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(ConfigHierarchyType.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);
}
}
}
private void CopyGfxDebugger(string UE4BuildPath, string UE4Arch, string NDKArch)
{
string AndroidGraphicsDebugger;
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "AndroidGraphicsDebugger", out AndroidGraphicsDebugger);
switch (AndroidGraphicsDebugger.ToLower())
{
case "mali":
{
string MaliGraphicsDebuggerPath;
AndroidPlatformSDK.GetPath(Ini, "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "MaliGraphicsDebuggerPath", out MaliGraphicsDebuggerPath);
if (Directory.Exists(MaliGraphicsDebuggerPath))
{
Directory.CreateDirectory(Path.Combine(UE4BuildPath, "libs", NDKArch));
string MaliLibSrcPath = Path.Combine(MaliGraphicsDebuggerPath, @"target\android-non-root\arm", NDKArch, "libMGD.so");
string MaliLibDstPath = Path.Combine(UE4BuildPath, "libs", NDKArch, "libMGD.so");
Console.WriteLine("Copying {0} to {1}", MaliLibSrcPath, MaliLibDstPath);
File.Copy(MaliLibSrcPath, MaliLibDstPath, true);
}
}
break;
// @TODO: Add NVIDIA Gfx Debugger
/*
case "nvidia":
{
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);
}
*/
default:
break;
}
}
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;
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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, bool bUseExternalFilesDir)
{
// 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));
CurrentSettings.AppendLine(string.Format("bUseExternalFilesDir={0}", bUseExternalFilesDir));
// all AndroidRuntimeSettings ini settings in here
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
ConfigHierarchySection Section = Ini.FindSection("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings");
if (Section != null)
{
foreach (string Key in Section.KeyNames)
{
IEnumerable<string> Values;
Section.TryGetValues(Key, out Values);
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.KeyNames)
{
IEnumerable<string> Values;
Section.TryGetValues(Key, out Values);
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)
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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 PackageForDaydream(string UE4BuildPath)
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
bool bPackageForDaydream;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForDaydream", out bPackageForDaydream);
if (!bPackageForDaydream)
{
// If this isn't a Daydream App, we need to make sure to remove
// Daydream specific assets.
// Remove the Daydream app tile background.
string AppTileBackgroundPath = UE4BuildPath + "/res/drawable-nodpi/vr_icon_background.png";
if (File.Exists(AppTileBackgroundPath))
{
File.Delete(AppTileBackgroundPath);
}
// Remove the Daydream app tile icon.
string AppTileIconPath = UE4BuildPath + "/res/drawable-nodpi/vr_icon.png";
if (File.Exists(AppTileIconPath))
{
File.Delete(AppTileIconPath);
}
}
}
private void PickSplashScreenOrientation(string UE4BuildPath, bool bNeedPortrait, bool bNeedLandscape)
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
bool bShowLaunchImage = false;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bShowLaunchImage", out bShowLaunchImage);
bool bPackageForGearVR;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForGearVR", out bPackageForGearVR);
bool bPackageForDaydream;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForDaydream", out bPackageForDaydream);
//override the parameters if we are not showing a launch image or are packaging for GearVR and Daydream
if (bPackageForGearVR || bPackageForDaydream || !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)
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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()
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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 CookFlavor, bool bUseExternalFilesDir)
{
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
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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 bPackageForDaydream;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bPackageForDaydream", out bPackageForDaydream);
bool bEnableIAP = false;
Ini.GetBool("OnlineSubsystemGooglePlay.Store", "bSupportsInAppPurchasing", out bEnableIAP);
bool bShowLaunchImage = false;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bShowLaunchImage", out bShowLaunchImage);
string AndroidGraphicsDebugger;
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "AndroidGraphicsDebugger", out AndroidGraphicsDebugger);
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;
}
}
// disable splash screen for daydream
if (bPackageForDaydream)
{
if (bShowLaunchImage)
{
Log.TraceInformation("Disabling Show Launch Image for Daydream enabled application");
bShowLaunchImage = false;
}
}
//figure out which texture compressions are supported
bool bETC1Enabled, bETC2Enabled, bDXTEnabled, bATCEnabled, bPVRTCEnabled, bASTCEnabled;
bETC1Enabled = bETC2Enabled = bDXTEnabled = bATCEnabled = bPVRTCEnabled = bASTCEnabled = false;
if (CookFlavor.Length < 1)
{
//All values supproted
bETC1Enabled = bETC2Enabled = bDXTEnabled = bATCEnabled = bPVRTCEnabled = bASTCEnabled = true;
}
else
{
switch(CookFlavor)
{
case "_Multi":
//need to check ini to determine which are supported
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_ETC1", out bETC1Enabled);
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_ETC2", out bETC2Enabled);
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_DXT", out bDXTEnabled);
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_ATC", out bATCEnabled);
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_PVRTC", out bPVRTCEnabled);
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bMultiTargetFormat_ASTC", out bASTCEnabled);
break;
case "_ETC1":
bETC1Enabled = true;
break;
case "_ETC2":
bETC2Enabled = true;
break;
case "_DXT":
bDXTEnabled = true;
break;
case "_ATC":
bATCEnabled = true;
break;
case "_PVRTC":
bPVRTCEnabled = true;
break;
case "_ASTC":
bASTCEnabled = true;
break;
default:
Log.TraceWarning("Invalid or unknown CookFlavor used in GenerateManifest: {0}", CookFlavor);
break;
}
}
bool bSupportingAllTextureFormats = bETC1Enabled && bETC2Enabled && bDXTEnabled && bATCEnabled && bPVRTCEnabled && bASTCEnabled;
// If it is only ETC2 we need to skip adding the texture format filtering and instead use ES 3.0 as minimum version (it requires ETC2)
bool bOnlyETC2Enabled = (bETC2Enabled && !(bETC1Enabled || bDXTEnabled || bATCEnabled || bPVRTCEnabled || bASTCEnabled));
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.bUseExternalFilesDir\" android:value=\"{0}\"/>", bUseExternalFilesDir ? "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\t<receiver android:name=\"com.epicgames.ue4.LocalNotificationReceiver\" />");
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, bOnlyETC2Enabled) + "\" 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);
}
}
if (AndroidGraphicsDebugger.ToLower() == "adreno")
{
Text.AppendLine("\t<uses-permission android:name=\"com.qti.permission.PROFILER\"/>");
}
if (!bSupportingAllTextureFormats)
{
Text.AppendLine("\t<!-- Supported texture compression formats (cooked) -->");
if (bETC1Enabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_OES_compressed_ETC1_RGB8_texture\" />");
}
if (bETC2Enabled && !bOnlyETC2Enabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_COMPRESSED_RGB8_ETC2\" />");
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_COMPRESSED_RGBA8_ETC2_EAC\" />");
}
if (bATCEnabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_AMD_compressed_ATC_texture\" />");
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_ATI_texture_compression_atitc\" />");
}
if (bDXTEnabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_EXT_texture_compression_dxt1\" />");
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_EXT_texture_compression_s3tc\" />");
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_NV_texture_compression_s3tc\" />");
}
if (bPVRTCEnabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_IMG_texture_compression_pvrtc\" />");
}
if (bASTCEnabled)
{
Text.AppendLine("\t<supports-gl-texture android:name=\"GL_KHR_texture_compression_astc_ldr\" />");
}
}
}
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)
{
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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, bool bDisallowExternalFilesDir)
{
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();
bool bUseExternalFilesDir = UseExternalFilesDir(bDisallowExternalFilesDir);
// check to see if any "meta information" is newer than last time we build
string CurrentBuildSettings = GetAllBuildSettings(ToolChain, UE4BuildPath, bForDistribution, bMakeSeparateApks, bPackageDataInsideApk, bDisableVerifyOBBOnStartUp, bUseExternalFilesDir);
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, ProjectDirectory);
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, CookFlavor, bUseExternalFilesDir)
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, CookFlavor, bUseExternalFilesDir)
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);
//Now package the app based on Daydream packaging settings
PackageForDaydream(UE4BuildPath);
//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
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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);
// modify the generated build.xml before the final include
UpdateBuildXML(Arch, NDKArch, EngineDirectory, UE4BuildPath);
// 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
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.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(UEBuildDeployTarget InTarget)
{
//Log.TraceInformation("$$$$$$$$$$$$$$ PrepTargetForDeployment $$$$$$$$$$$$$$$$$ {0}", InTarget.TargetName);
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.TargetName, InTarget.ProjectDirectory.FullName, BaseSoName, BuildConfiguration.RelativeEnginePath, bForDistribution: false, CookFlavor: "",
bMakeSeparateApks: ShouldMakeSeparateApks(), bIncrementalPackage: true, bDisallowPackagingDataInApk: false, bDisallowExternalFilesDir: true);
// 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 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, bDisallowExternalFilesDir: !bForDistribution || bIsDataDeploy );
return true;
}
public static void OutputReceivedDataEventHandler(Object Sender, DataReceivedEventArgs Line)
{
if ((Line != null) && (Line.Data != null))
{
Log.TraceInformation(Line.Data);
}
}
private void UpdateBuildXML(string UE4Arch, string NDKArch, string EngineDir, string UE4BuildPath)
{
string SourceFilename = Path.Combine(UE4BuildPath, "build.xml");
string DestFilename = SourceFilename;
Dictionary<string, string> Replacements = new Dictionary<string, string>{
{ "<import file=\"${sdk.dir}/tools/ant/build.xml\" />", UPL.ProcessPluginNode(NDKArch, "buildXmlPropertyAdditions", "")}
};
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))
{
// insert replacement before the <import>
SrcLine = SrcLine.Replace(KVP.Key, KVP.Value);
// then add the <import>
SrcLine += KVP.Key;
Changed = true;
}
}
if (Changed)
{
TemplateSrc[LineIndex] = SrcLine;
}
}
if (TemplateDest == null || TemplateSrc.Length != TemplateDest.Length || !TemplateSrc.SequenceEqual(TemplateDest))
{
Log.TraceInformation("\n==== Writing new build.xml file to {0} ====", DestFilename);
File.WriteAllLines(DestFilename, TemplateSrc);
}
}
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");
string LoadLibraryDefaults = "";
string AndroidGraphicsDebugger;
ConfigHierarchy Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "AndroidGraphicsDebugger", out AndroidGraphicsDebugger);
switch (AndroidGraphicsDebugger.ToLower())
{
case "mali":
LoadLibraryDefaults += "\t\ttry\n" +
"\t\t{\n" +
"\t\t\tSystem.loadLibrary(\"MGD\");\n" +
"\t\t}\n" +
"\t\tcatch (java.lang.UnsatisfiedLinkError e)\n" +
"\t\t{\n" +
"\t\t\tLog.debug(\"libMGD.so not loaded.\");\n" +
"\t\t}\n";
break;
}
Dictionary<string, string> Replacements = new Dictionary<string, string>{
{ "//$${gameActivityImportAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityImportAdditions", "")},
{ "//$${gameActivityPostImportAdditions}$$", UPL.ProcessPluginNode(NDKArch, "gameActivityPostImportAdditions", "")},
{ "//$${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", LoadLibraryDefaults)}
};
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);
}
}
}