Files
UnrealEngineUWP/Engine/Source/Programs/IOS/MobileDeviceInterface/MobileDeviceInstance.cs
Josh Adams 1b81c281d4 Copying //UE4/Dev-Platform to //UE4/Dev-Main (Source: //UE4/Dev-Platform @ 3120366)
#lockdown Nick.Penwarden
#rb none

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

Change 2714591 on 2015/10/02 by Ben.Marsh

	Initial branch of files from Engine-Main (//UE4/Engine-Main) to Dev-Platform (//UE4/Dev-Platform)

Change 2916715 on 2016/03/21 by Daniel.Lamb

	First pass at splitting out build cook run into into seperate scripts.

Change 2948322 on 2016/04/19 by Nick.Shin

	update libwebsockets to v1.7.4

	part 4 of 4 - doing this in stages for tracking purposes

	#jira UEPLAT-1246  -  Update libWebsockets
	#jira UEPLAT-1221  -  update websocket library
	#jira UEPLAT-1204  -  Rebuild libwebsockets with SSL

Change 2970016 on 2016/05/07 by Nick.Shin

	undo all of the following upgrades:
	- zlib
	- openssl
	- libcurl
	- libwebsockets

	and reset webrtc

	#jira UE-30298 - Fortnite and Orion crash on login

Change 3059693 on 2016/07/21 by Josh.Adams

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

Change 3061151 on 2016/07/22 by Niklas.Smedberg

	Fast ASTC texture compression, using ISPC.

	#jira UE-32308

Change 3061428 on 2016/07/22 by Peter.Sauerbrei

	Back out changelist 3061151 as it wasn't approved for submission

Change 3061970 on 2016/07/22 by Steve.Cano

	Adding AdMob interstitital ad support for Android, including Blueprint functions

	#jira UE-33286
	#ue4
	#android

Change 3062160 on 2016/07/22 by Mark.Satterthwaite

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

Change 3062770 on 2016/07/24 by Brent.Pease

	UE-32397 Error Message displays as Unknown Error when failing to supply a Remote Build server for ios on Windows

Change 3063227 on 2016/07/25 by Dmitry.Rekman

	Update hlslcc cross-compile after libc++ change.

Change 3063314 on 2016/07/25 by Jeff.Campeau

	Xbox One DLL loading
	Receipts can be read back by request for target info

Change 3063329 on 2016/07/25 by Mark.Satterthwaite

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

Change 3063492 on 2016/07/25 by Brent.Pease

	UE-23846 - iOS Movie Player can't handle videos at resolutions that aren't multiples of 16
	UE-33200 - A movie isn't played on iOS occasionally.

Change 3063729 on 2016/07/25 by Dmitry.Rekman

	Linux: enable XGE on all platforms.

	#tests Cross-compiled a number of Linux targets on Windows.

Change 3063732 on 2016/07/25 by Dmitry.Rekman

	Fixed formatting (spaces->tabs) in previous change.

Change 3063750 on 2016/07/25 by Daniel.Lamb

	Added code to dump the cook modification delegate loads to log.
	Fixed the memory usage output log.
	#test cook paragon.

Change 3063804 on 2016/07/25 by Daniel.Lamb

	Added cookpartialgc additional commandline option to uat.
	#test UFE

Change 3064008 on 2016/07/25 by Mark.Satterthwaite

	For non-shipping builds conditionally bind a default uniform buffer in Metal and report an error if the slot was unbound, if our validation layer is enabled attempt to report the shader source in question. This relies on the shader compiler providing accurate information about uniform buffer bindings and won't fix all occurances of bad uniform usage (if a buffer is bound but too short the result will be GPU restarts or an error in Apple's validation layer - we can't detect this case) but will help debug the typical error of leaving an active slot unbound.
	#jira FORT-27685

Change 3064141 on 2016/07/25 by Jeff.Campeau

	Rebuild vpxmd.lib with delayed codegen disabled (fixes linker warning building Win64).

Change 3065024 on 2016/07/26 by Nick.Shin

	Change filetype
	remove exclusive check out bit

	requested by or.coheni & nick.penwarden

Change 3065274 on 2016/07/26 by Jonathan.Fitzpatrick

	DirectoriesToAlwaysStageAsUFS now properly filters out *.uasset and *.umap files
	This prevents the bug where cooked assets get trampled by staging their uncooked version on top of them during the UFS step

	Added a file filter to DirectoriesToAlwaysStageAsUFS for uasset and umap.

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

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

Change 3066789 on 2016/07/27 by Daniel.Lamb

	Realtime mode does not save any packages anymore unless they are ready.
	#test cookontheside, cookbythebook shooter game

Change 3066847 on 2016/07/27 by Jeff.Campeau

	Fix define
	#2634
	#jira UE-33813

Change 3068868 on 2016/07/28 by Mark.Satterthwaite

	Extend hlslcc's handling of switch-statements to allow implict casts from scalar bool, half & float as HLSL itself permits while also making sure it errors if the expression input is not scalar. This fixes shader compile errors in UT.

Change 3070040 on 2016/07/29 by Dmitry.Rekman

	Delete Nadzorca.

Change 3070947 on 2016/07/29 by Jeff.Campeau

	Perforce C++ API 2015.2 (includes debug libraries)

Change 3073707 on 2016/08/02 by Daniel.Lamb

	Derived data cache commandlet runs resolve string asset references to load any string asset refereced packages from the map.
	Also process async results from shaders being compiled so they can have their memory resources released.
	#test DerivedDataCache commandlet shootergame.

Change 3076613 on 2016/08/03 by Brent.Pease

	 + UnrealTargetConfiguration is now passed into deploy and package methods
	 + The UIRequiredDeviceCapabilities  plist key now only considers the architectures from the corresponding target configuration (shipping or development)

Change 3076668 on 2016/08/03 by Brent.Pease

	Back out changelist 3076613

Change 3077157 on 2016/08/04 by Daniel.Lamb

	Fixed up DLC staging so that it stages to the proper mount point.
	Fixes up include engine content in DLC staging paths.
	#test Made up shooter game DLC

Change 3077191 on 2016/08/04 by Daniel.Lamb

	More smartly process async shader compilation if we are waiting for it.
	#test cook on the side shooter game cook by the book shooter game.

Change 3077412 on 2016/08/04 by Mark.Satterthwaite

	Fix "iOS Metal-based build crashes at launch with sub-levels":
	- Slate should not bind the null RHI texture from an unitialised texture atlas - atlases only have a valid texture pointer once an entry has been added to them and in the template projects an empty sub-level doesn't add anything.
	- To prevent this kind of bug resurfacing and being so hard to track down add Metal shader binding validation to our validation layer as Apple's is incomplete on iOS and won't warn us about nil texture usage which causes these GPU restarts. This requires reworking our vertex declaration handling to be more efficient so that we can cache the pipeline reflection data as well as the pipeline objects.
	- Fix validation error of texture reallocation on loading template projects under Metal.
	#jira UE-30847

Change 3077958 on 2016/08/04 by Brent.Pease

	 + UnrealTargetConfiguration is now passed into deploy and package methods
	 + The UIRequiredDeviceCapabilities  plist key now only considers the architectures from the corresponding target configuration (shipping or development)

Change 3079503 on 2016/08/05 by Mark.Satterthwaite

	Initialise more variable types to 0 in Metal shaders to workaround Xcode 8 toolchain no longer doing this for us for "threadgroup shared" variables. Everything but structs and atomic's will now be initialised.
	#jira UE-33856

Change 3079737 on 2016/08/05 by Jeff.Campeau

	Add support for delay load DLLs on Xbox One
	Turn off warnging for missing PDBs to match VCToolchain.cs

Change 3081005 on 2016/08/08 by Mark.Satterthwaite

	Fix-up Metal device name on AMD for macOS 10.12 which reports it correctly and enable tiled reflections on Intel from macOS 10.12 too as they now work.

Change 3081557 on 2016/08/08 by Daniel.Lamb

	File-> Package saves all packages before starting packaging.
	#test File package first person template

Change 3082215 on 2016/08/09 by Lee.Clark

	PS4 - Added 4k profile

Change 3082412 on 2016/08/09 by Daniel.Lamb

	Fixed cook on the fly server not handling cook requests.
	#test Cook on the fly shooter game.

Change 3082955 on 2016/08/09 by Dmitry.Rekman

	Linux: convert existing Strcat() uses to Strncat().

	- Strcat() does not check destination size so can silently corrupt memory. While this was not observed, this conversion removes this concern altogether.

Change 3083772 on 2016/08/10 by Luke.Thatcher

	[PLATFORM] [PS4] [+]
	Checking in PS4CrashHandler files so we have a copy in Perforce rather than just on the server.
	 - Taken from \\devweb-02 and removed all the unused files/dependencies.
	 - Created a publish profile pointing to \\devweb-02\Sites\PS4Services\PS4CrashHandlerDev so I'm not writing over the existing deployed crash handler.
	 - Moved all code in the Page_Load event to inside the check for the HTTP method (POST) otherwise GET'ing the page from a browser will generate crash folders that hang around forever.

Change 3085450 on 2016/08/11 by Lee.Clark

	PS4 - Fix mediaplayer pipeline allocation

Change 3086360 on 2016/08/11 by Michael.Trepka

	Fixed a non-unity build error in Mac UnrealFrontend

Change 3087224 on 2016/08/12 by Luke.Thatcher

	[PLATFORM] [PS4] [~]
	Refactor PS4 Crash Handler site
	 - Removed CoreDumpHandler. Processing dump files is handled directly by an async thread within the aspx process.
	 - Separated configuration values into their own class. Currently set to output to a testing directory, rather than the actual crash reporter landing zone.
	 - Added a debug upload page to allow manual submission of .orbisdmp/.txt settings files, accessible by GET'ing Default.aspx.
	 - Added logging. Logs self-delete after 30 days.

	Testing required before we switch to the new system.

	#jira UE-34504
	#jira OR-26886

Change 3087626 on 2016/08/12 by Dmitry.Rekman

	PR #2689: Fix copying/duplicating failing on Linux (UE-34586).

	- Contributed by Web-eWorks.

Change 3087991 on 2016/08/12 by Mark.Satterthwaite

	Initial AVFoundation implementation of Media Framework for Mac, iOS & tvOS.
	- Video playback occurs via AVPlayerItemVideoOutput's attached to the AVPlayerItem's output. This means gathering video samples is trivial.
	- Metal texture updates occur by wrapping the texture object provided by AVF - for Mac this is simple as it can bind to the IOSurface directly, for iOS/tvOS we have to create a CVMetalTextureCache and allocate our texture from there.
	- OpenGL and OpenGLES currently have to lock the pixel buffer and upload to a texture the old fashioned way - this should be revisited when there is time.
	- Subtitles/Captions are captured using AVPlayerItemLegibleOutput which also connects to the AVPlayerItem's output.
	- On Mac audio samples are returned by manually reading from the stream using an AVAssetReaderTrackOutput, including manual seeking and synching.
	- On iOS/tvOS the audio is played directly by AVPlayer because the IOSAudio system can't handle procedural buffers - otherwise it could reuse the Mac code.
	- AVFoundation does not support AVI - that's an obsolete Microsoft/Windows file-format.
	- Only 'file://' URLs are supported - streaming would require a totally different audio solution (using MTAudioProcessingTap) and has many more edge and failure cases that would need to be handled.
	#jira UE-34315

Change 3088790 on 2016/08/15 by Luke.Thatcher

	[PLATFORM] [PS4] [~]
	Hook new PS4 crash handler up to the crash reporter website.
	 - Removed indentation from generated crash context XML file. The crash reporter process does manual XML parsing which doesn't correctly handle whitespace at the start of lines.
	 - Switched the final output folder to match the one the crash reporter process is watching.
	 - Hide upload form on a config variable.

	#jira UE-34504
	#jira OR-26886

Change 3089060 on 2016/08/15 by Luke.Thatcher

	[PLATFORM] [PS4] [!]
	Change PS4 crash handler log file extension to ".ps4chlog", otherwise the crash reporter site attaches the wrong log file to the crash report.
	Allowed showing of debug upload form via "Default.aspx?showform=1" query string.

	#jira UE-34504
	#jira OR-26886

Change 3089089 on 2016/08/15 by Mark.Satterthwaite

	Duplicated changes to AppleMovieStreamer from CL #3088149.
	#jira UE-34315

Change 3089460 on 2016/08/15 by Mark.Satterthwaite

	Duplicate CL #3080971:
	Workaround a macOS 10.12 Beta bug on some Metal drivers that can't initialise temporary/local variable arrays, only those that are marked threadgroup shared.
	#jira UE-34355

Change 3089465 on 2016/08/15 by Mark.Satterthwaite

	For Metal shader translation retain more precision for float constants -1.0f >< 1.0f by emitting them in scientific notation - prevents Hammersley constant amongst others from being flushed to 0.

Change 3089902 on 2016/08/15 by Daniel.Lamb

	Changed the next compiling ID to the correct compiling ID.
	#test Cook

Change 3089903 on 2016/08/15 by Daniel.Lamb

	Cooker monitors config useage during cook and uses those settings to invalidate cooked content instead of all config settings.

Change 3090114 on 2016/08/16 by Luke.Thatcher

	[PLATFORM] [PS4] [~]
	Minor change to PS4 settings text on crash handler site.

Change 3090949 on 2016/08/16 by Nick.Shin

	WebSocketNetDriver crash fix

	filled in missing chunk of code that calls PacketHandler's "packet modifiers"

	#jira UE-25492   HTML5 Client cannot connect to Windows Server
	#jira UE-30880   WinServer crashes when NetDriver is set to WebSocket and Client attempts to connect via websocket
	#code.review john.pollard john.barrett

Change 3091265 on 2016/08/16 by Brent.Pease

	Add IOS support to HarfBuzz

Change 3091267 on 2016/08/16 by Brent.Pease

	Add references to fix mono build

Change 3091291 on 2016/08/16 by Nick.Shin

	CIS warning fix

	#jira UE-25492   HTML5 Client cannot connect to Windows Server
	#jira UE-30880   WinServer crashes when NetDriver is set to WebSocket and Client attempts to connect via websocket

Change 3091781 on 2016/08/17 by Joe.Barnes

	UE-33640: Exposed UPrimitiveComponent::IsAnyRigidBodyAwake() to Blueprints.

Change 3092687 on 2016/08/17 by Daniel.Lamb

	Added support for using binned allocator in cooker instead of tbb.
	#test Cook shootergame.

Change 3093867 on 2016/08/18 by Mark.Satterthwaite

	Use a read/write mutex to protect access to Metal's internal shader pipeline caches so that parallel threads can progress in the common case where new shaders are not being compiled.

Change 3093950 on 2016/08/18 by Mark.Satterthwaite

	Change the Mac GPU identification code to cope with AMD's new naming scheme on 10.12.

Change 3093951 on 2016/08/18 by Mark.Satterthwaite

	More SCW threads on Mac - they work now.

Change 3093960 on 2016/08/18 by Mark.Satterthwaite

	Increase the default number of command-buffers on Mac because bigger games overflow the current limit of 64 per queue.

Change 3096493 on 2016/08/22 by Jeff.Campeau

	Use Xbox version of DirectX include.

Change 3097509 on 2016/08/23 by Luke.Thatcher

	[PLATFORM] [PS4] [+]
	Refactor PS4 Symbol Publish
	 - Moved the SymStore task from Win.Automation to BuildGraph.Automation, and made it more generic.
	 - The specifics of uploading symbols are now implemented in the platform tool chain, alongside StripSymbols().
	 - Re-generated the build graph schema file.
	 - Removed the old PS4 symbols upload path in the package step.

	Modified OrionBuild.xml to publish symbols for all PS4 dev, test and shipping config builds of OrionClient.

Change 3097635 on 2016/08/23 by Luke.Thatcher

	[PLATFORM] [PS4] [+]
	Refactor Age Symbols task in UAT.
	 - Moved the AgeStore task from Win.Automation to BuildGraph.Automation and made it more generic.
	 - Symbol server directory structure is now defined by the platform tool chain, which the common task uses to clean out old symbols.
	 - Added a "filter" parameter to prevent age tasks deleting symbols from unrelated builds in shared symbol servers.

	Modified OrionBuild.xml to age both the Windows and PS4 symbol stores.

Change 3097713 on 2016/08/23 by Luke.Thatcher

	[PLATFORM] [PS4] [+]
	Enable new PS4 crash handler server
	 - Created live deployment profile and applied the required config file changes.

Change 3099214 on 2016/08/24 by Luke.Thatcher

	[PLATFORM] [PS4] [!]
	Fix compile error in PS4 tool chain. For some reason, this only breaks ocassionally. Maybe we're alternating between the 2013 and 2015 C# compilers depending on what initiates the build (e.g. Visual Studio vs GenerateProjectFiles)?

Change 3099222 on 2016/08/24 by Luke.Thatcher

	[PLATFORM] [PS4] [+]
	Added PS4 support for FPlatformMisc::MessageBoxExt using the MsgDialog library.
	 - Note, only one and two button message dialogs are supported (limitation of MsgDialog).

Change 3099260 on 2016/08/24 by Luke.Thatcher

	[PLATFORM] [PS4] [~]
	Better PS4 exit function. Calls quick_exit instead of abort when we've not asserted. Allows for a "cleaner" forced exit without generating a crash dump.

Change 3101192 on 2016/08/25 by Josh.Adams

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

Change 3101944 on 2016/08/25 by Daniel.Lamb

	Ask to save the current level when we are using launch on.

Change 3102036 on 2016/08/25 by Nick.Shin

	check for minimum expected size upon data received from network

	#jira UE-13657 - HTML5 plugin OnRawRecieve overflow

Change 3102115 on 2016/08/25 by Brent.Pease

	 - Fix small errors that probably only show up in the mac mono build

	#code.review peter.sauerbrei

Change 3102747 on 2016/08/26 by Jeremiah.Waldron

	Re-submitting OnlineSubsystemGameCircle with TPS permission for Amazon SDK
	 - Also fixed the plugin to remove any related files from the final package when IAP is disabled or GameCircle support itself is disabled with the plugin still enabled
	 - Added support for new AlreadyOwned IAP response code as well which is already used for GooglePlay and IOS

Change 3102900 on 2016/08/26 by Nick.Shin

	since last checkin (CL: 2981945) - prints are crashing the browser - this change will allow browser to print the details via console.log()

	#jira UE-26047 - HTML5 HTTP Response Headers not implemented

Change 3103130 on 2016/08/26 by Brent.Pease

	UE-24679 - Enable the ability to change ports for a firewall to pass them through rsync

Change 3103225 on 2016/08/26 by Daniel.Lamb

	Fixed issue with warning which would cause crash.

Change 3103425 on 2016/08/26 by Dmitry.Rekman

	Enable offscreen GL rendering without X.

	- Added new video subsystem to SDL that is uses EGL to initialize the context.
	- Most windoing functions stubbed.
	- Also added a new test case to TestPAL for easier debugging.

Change 3104743 on 2016/08/29 by Brent.Pease

	Support remote offline metal shader compilation

Change 3105051 on 2016/08/29 by Brent.Pease

	UE-2382 - TASK: MobleMVP: iOS: Add ability to view iOS device console output in the editor UFE

	 - IOS Automation will now create a thread to collect the console logs from the device and send them to C# Console output while the app is running on device
	 - Made ProcessResult an interface (IProcessResult) which ProcessResult implements. This allows platforms to provide their own implementation if needed.
	 - Moved the RunLoop related parts of CoreFoundation into MobileDevice.cs

Change 3105053 on 2016/08/29 by Brent.Pease

	 - IOS dll's as part of the last check-in

Change 3106853 on 2016/08/30 by Jeff.Campeau

	Implement FD3D11DynamicRHI::RHICreateComputeFence to prevent memory overwrite

Change 3107361 on 2016/08/30 by Dmitry.Rekman

	Renderer: changes to allow postproc delegates.

Change 3107362 on 2016/08/30 by Dmitry.Rekman

	Plugin with a CUDA postproc example.

	- Linux version only. Runs under a headless GL too (without X).
	- Disabled during cross-compilation, can be compiled natively only.
	- CUDA kernel should be compiled separately, CMakefile with compilation attached (can be used to generate VStudio projects as well).
	- To test the output, run under the debugger, interrupt and set global variable GSaveTheOutput to 50 (this will write out kernel output buffer 50 times as .bmp files into working directory).

Change 3107913 on 2016/08/31 by Daniel.Lamb

	Fixed loading of cooked content in the editor.

Change 3107916 on 2016/08/31 by Daniel.Lamb

	Added error case when shader compilation fails to notify shader.
	#test Cook shooter game.

Change 3108080 on 2016/08/31 by Josh.Adams

	- Fixed PS4Automation compile errors

Change 3109077 on 2016/08/31 by Brent.Pease

	Fix C# debug builds by specifying x64 and add reference to MobileDeviceInterface

Change 3110086 on 2016/09/01 by Dmitry.Rekman

	Fix race condition in PThread runnable (UE-35074).

	- Instead of relying on busy-wait, join the threads. This prevents race between PostRun() (executed in the context of the thread) and FPThreadRunnableThread() destructor (see UE-34909).
	- Do not use an invalid value for pthread_t, since there's none.

Change 3110172 on 2016/09/01 by Dmitry.Rekman

	Fixed a crash exiting VR Preview on Windows GL4 (UE-28708).

	- PR #2188 submitted by ardneran.

Change 3110313 on 2016/09/01 by Josh.Adams

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

Change 3111134 on 2016/09/01 by Dmitry.Rekman

	UBT: prevent mono from hanging on Ctrl-C.

	- Sometimes Ctrl-C can cause thread creation to fail. Without this change, UBT would lock up.

Change 3111171 on 2016/09/01 by Brent.Pease

	Move all C# projects to use the x64 Platform for consistency with other changes made to move to the x64 Platform

Change 3111177 on 2016/09/01 by Dmitry.Rekman

	Fix Linux build on systems without CUDA (UE-35460).

Change 3111548 on 2016/09/02 by Luke.Thatcher

	[PLATFORM] [PS4] [!]
	Fix for PS4 iterative deployment.
	 - Changes in Dev-Mobile broke the deployment manifests, as PS4 was now using the wrong filename when creating delta and obsolete file lists.

Change 3111863 on 2016/09/02 by Dmitry.Rekman

	Better fix for build without CUDA (UE-35460).

Change 3112738 on 2016/09/02 by Mark.Satterthwaite

	Fix the pausing particles on Metal - one line bug in the Metal query implementation meant that the first query wouldn't return the correct result for no good reason.
	#jira UE-34989

Change 3114579 on 2016/09/06 by Chris.Babcock

	Fix Vulkan include path in NDK check (contributed by geediiiiky)
	#jira UE-35490
	#github #2758
	#ue4
	#android

Change 3115115 on 2016/09/06 by Jeff.Campeau

	Calculate buffer size for paks using the bitwindow override as needed

Change 3115600 on 2016/09/07 by Luke.Thatcher

	[PLATFORM] [PS4] [~]
	Make the crash dump handler registration much earlier, to catch crashes in early engine init.
	 - Fixed up places in FGenericCrashContext::SerializeContentToBuffer which used the command line. If we crash early, the command line may not have been initialized yet.
	 - Added a GetNoInit function to FThreadHeartBeat to avoid creating the heart beat thread if we crash early, and the thread doesn't exist yet.

	Tested by calling abort() immediately inside int main(), and we get a valid crash dump that the crash handler service can consume.

Change 3115676 on 2016/09/07 by Luke.Thatcher

	[PLATFORM] [~]
	Dev-Platform integration fix for original CL 3064888 in //Orion/Release-29.1
	Add .exe and .dll to windows symbol upload file filters.

Change 3115811 on 2016/09/07 by Josh.Adams

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

Change 3115944 on 2016/09/07 by Michael.Trepka

	Implemented IsGamepadAttached() for Mac

Change 3115948 on 2016/09/07 by Michael.Trepka

	Don't try to restore Help menu item on Mac if MenuBlock does not contain it

Change 3116200 on 2016/09/07 by Jeff.Campeau

	Fix parameter ordering

Change 3117660 on 2016/09/08 by Josh.Adams

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

Change 3117728 on 2016/09/08 by Michael.Trepka

	Copy of CL 3117698 by Mike.Fricker

	Fixed regression with editor's Simulate mode where cursor would teleport back to the center of the viewport after every click
	- This bug was introduced in CL 3075932 from a borderless window cursor handling fix that was needed for games that capture the cursor

Change 3117797 on 2016/09/08 by Peter.Sauerbrei

	Shader Resource compression

Change 3117988 on 2016/09/08 by Brent.Pease

	 - Solutiion generator will now pick x64 instead of AnyCPU for the default platform configuration
	 - Fix what I think was a merge error in BuildGraph.cs

Change 3118296 on 2016/09/08 by Daniel.Lamb

	Fixed crash with launch on.  Couldnt' correctly detect previous generated ini settings.
	#test launch on QA game
	#jira UE-35741

Change 3118438 on 2016/09/08 by JohnHenry.Carawon

	Fix UAT compilation on Linux
	#UE-35745

Change 3118934 on 2016/09/08 by Jeff.Campeau

	Shader compression setting based on target platform instead of cooking host platform.

	#jira UE-35753

Change 3120190 on 2016/09/09 by Ben.Marsh

	Add missing Platform attribute to build script for Dev-Platform.

[CL 3120378 by Josh Adams in Main branch]
2016-09-09 20:13:41 -04:00

1532 lines
53 KiB
C#

// Software License Agreement (BSD License)
//
// Copyright (c) 2007, Peter Dennis Bartok <PeterDennisBartok@gmail.com>
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the
// following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// * Neither the name of Peter Dennis Bartok nor the names of its
// contributors may be used to endorse or promote products
// derived from this software without specific prior
// written permission of Yahoo! Inc.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
using MobileDeviceInterface;
namespace Manzana
{
public class MobileDeviceInstanceManager
{
/// <summary>
/// Registered device notification callback
/// </summary>
private static DeviceNotificationCallback DeviceCallbackHandle;
/// <summary>
/// The <c>Connect</c> event is triggered when a iPhone is connected to the computer
/// </summary>
public static event ConnectEventHandler ConnectEH;
/// <summary>
/// The <c>Disconnect</c> event is triggered when the iPhone is disconnected from the computer
/// </summary>
public static event ConnectEventHandler DisconnectEH;
/// <summary>
/// List of connected devices (device ptr -> device instance)
/// </summary>
public static Dictionary<TypedPtr<AppleMobileDeviceConnection>, MobileDeviceInstance> ConnectedDevices = new Dictionary<TypedPtr<AppleMobileDeviceConnection>, MobileDeviceInstance>();
public static IEnumerable<MobileDeviceInstance> GetSnapshotInstanceList()
{
// Clone a copy to prevent problems from delayed enumeration
List<MobileDeviceInstance> Result = new List<MobileDeviceInstance>();
Result.AddRange(ConnectedDevices.Values);
return Result;
}
/// <summary>
/// Returns true if any devices are currently connected
/// </summary>
/// <returns></returns>
public static bool AreAnyDevicesConnected()
{
lock (ConnectedDevices)
{
return ConnectedDevices.Count > 0;
}
}
/// <summary>
/// Initialize the mobile device manager, which handles discovery of connected Apple mobile devices
/// </summary>
/// <param name="myConnectHandler"></param>
/// <param name="myDisconnectHandler"></param>
public static void Initialize(ConnectEventHandler myConnectHandler, ConnectEventHandler myDisconnectHandler)
{
ConnectEH += myConnectHandler;
DisconnectEH += myDisconnectHandler;
DeviceCallbackHandle = new DeviceNotificationCallback(NotifyCallback);
int ret = MobileDevice.DeviceImpl.NotificationSubscribe(DeviceCallbackHandle);
if (ret != 0)
{
throw new Exception("AMDeviceNotificationSubscribe failed with error " + ret);
}
}
/// <summary>
/// Raises the <see>Connect</see> event.
/// </summary>
/// <param name="args">A <see cref="ConnectEventArgs"/> that contains the event data.</param>
protected static void OnConnect(ConnectEventArgs args)
{
ConnectEventHandler handler = ConnectEH;
if (handler != null)
{
handler(null, args);
}
}
/// <summary>
/// Raises the <see>Disconnect</see> event.
/// </summary>
/// <param name="args">A <see cref="ConnectEventArgs"/> that contains the event data.</param>
protected static void OnDisconnect(ConnectEventArgs args)
{
ConnectEventHandler handler = DisconnectEH;
if (handler != null)
{
handler(null, args);
}
}
private static void NotifyCallback(ref AMDeviceNotificationCallbackInfo callback)
{
if (callback.msg == NotificationMessage.Connected)
{
MobileDeviceInstance Inst;
if (ConnectedDevices.TryGetValue(callback.dev, out Inst))
{
// Already connected, not sure why we got another message...
}
else
{
Inst = new MobileDeviceInstance(callback.dev);
ConnectedDevices.Add(callback.dev, Inst);
}
if (Inst.ConnectToPhone())
{
OnConnect(new ConnectEventArgs(callback));
}
}
else if (callback.msg == NotificationMessage.Disconnected)
{
MobileDeviceInstance Inst;
if (ConnectedDevices.TryGetValue(callback.dev, out Inst))
{
Inst.connected = false;
OnDisconnect(new ConnectEventArgs(callback));
ConnectedDevices.Remove(callback.dev);
}
}
}
}
/// <summary>
/// Exposes access to a mobile device running iOS
/// </summary>
public class MobileDeviceInstance
{
#region Locals
private DeviceRestoreNotificationCallback drn1;
private DeviceRestoreNotificationCallback drn2;
private DeviceRestoreNotificationCallback drn3;
private DeviceRestoreNotificationCallback drn4;
internal TypedPtr<AppleMobileDeviceConnection> iPhoneHandle;
internal TypedPtr<AFCCommConnection> AFCCommsHandle;
internal IntPtr hService;
internal IntPtr hSyslogService;
internal IntPtr hInstallService;
public bool connected;
private string current_directory;
#endregion // Locals
#region Constructors
/// <summary>
/// Initializes a new iPhone object.
/// </summary>
private void doConstruction()
{
drn1 = new DeviceRestoreNotificationCallback(DfuConnectCallback);
drn2 = new DeviceRestoreNotificationCallback(RecoveryConnectCallback);
drn3 = new DeviceRestoreNotificationCallback(DfuDisconnectCallback);
drn4 = new DeviceRestoreNotificationCallback(RecoveryDisconnectCallback);
int ret = MobileDevice.DeviceImpl.RestoreRegisterForDeviceNotifications(drn1, drn2, drn3, drn4, 0, IntPtr.Zero);
if (ret != 0)
{
throw new Exception("AMRestoreRegisterForDeviceNotifications failed with error " + ret);
}
current_directory = "/";
}
/// <summary>
/// Creates a new iPhone object. If an iPhone is connected to the computer, a connection will automatically be opened.
/// </summary>
public MobileDeviceInstance(TypedPtr<AppleMobileDeviceConnection> Connection)
{
iPhoneHandle = Connection;
doConstruction();
}
#endregion // Constructors
#region Properties
/// <summary>
/// Gets the current activation state of the phone
/// </summary>
public string ActivationState
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "ActivationState");
}
}
/// <summary>
/// Returns true if an iPhone is connected to the computer
/// </summary>
public bool IsConnected
{
get
{
return connected;
}
}
/// <summary>
/// Returns the Device information about the connected iPhone
/// </summary>
public TypedPtr<AppleMobileDeviceConnection> Device
{
get
{
return iPhoneHandle;
}
}
///<summary>
/// Returns the 40-character UUID of the device
///</summary>
public string DeviceId
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "UniqueDeviceID");
}
}
///<summary>
/// Returns the type of the device, should be either 'iPhone' or 'iPod'.
///</summary>
public string DeviceType
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "DeviceClass");
}
}
///<summary>
/// Returns the current OS version running on the device (2.0, 2.2, 3.0, 3.1, etc).
///</summary>
public string DeviceVersion
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "ProductVersion");
}
}
///<summary>
/// Returns the name of the device, like "Dan's iPhone"
///</summary>
public string DeviceName
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "DeviceName");
}
}
///<summary>
/// Returns the model number of the device, like "MA712"
///</summary>
public string ModelNumber
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "ModelNumber");
}
}
///<summary>
/// Returns the product type of the device, like "iPhone1,1"
///</summary>
public string ProductType
{
get
{
return MobileDevice.AMDeviceCopyValue(iPhoneHandle, "ProductType");
}
}
/// <summary>
/// Returns the handle to the iPhone com.apple.afc service
/// </summary>
public TypedPtr<AFCCommConnection> AFCHandle
{
get
{
return AFCCommsHandle;
}
}
/// <summary>
/// Gets/Sets the current working directory, used by all file and directory methods
/// </summary>
public string CurrentDirectory
{
get
{
return current_directory;
}
set
{
string new_path = FullPath(current_directory, value);
if (!IsDirectory(new_path))
{
throw new Exception("Invalid directory specified");
}
current_directory = new_path;
}
}
#endregion // Properties
#region Events
/// <summary>
/// Write Me
/// </summary>
public event EventHandler DfuConnect;
/// <summary>
/// Raises the <see>DfuConnect</see> event.
/// </summary>
/// <param name="args">A <see cref="DeviceNotificationEventArgs"/> that contains the event data.</param>
protected void OnDfuConnect(DeviceNotificationEventArgs args)
{
EventHandler handler = DfuConnect;
if (handler != null)
{
handler(this, args);
}
}
/// <summary>
/// Write Me
/// </summary>
public event EventHandler DfuDisconnect;
/// <summary>
/// Raises the <see>DfiDisconnect</see> event.
/// </summary>
/// <param name="args">A <see cref="DeviceNotificationEventArgs"/> that contains the event data.</param>
protected void OnDfuDisconnect(DeviceNotificationEventArgs args)
{
EventHandler handler = DfuDisconnect;
if (handler != null)
{
handler(this, args);
}
}
/// <summary>
/// The RecoveryModeEnter event is triggered when the attached iPhone enters Recovery Mode
/// </summary>
public event EventHandler RecoveryModeEnter;
/// <summary>
/// Raises the <see>RecoveryModeEnter</see> event.
/// </summary>
/// <param name="args">A <see cref="DeviceNotificationEventArgs"/> that contains the event data.</param>
protected void OnRecoveryModeEnter(DeviceNotificationEventArgs args)
{
EventHandler handler = RecoveryModeEnter;
if (handler != null)
{
handler(this, args);
}
}
/// <summary>
/// The RecoveryModeLeave event is triggered when the attached iPhone leaves Recovery Mode
/// </summary>
public event EventHandler RecoveryModeLeave;
/// <summary>
/// Raises the <see>RecoveryModeLeave</see> event.
/// </summary>
/// <param name="args">A <see cref="DeviceNotificationEventArgs"/> that contains the event data.</param>
protected void OnRecoveryModeLeave(DeviceNotificationEventArgs args)
{
EventHandler handler = RecoveryModeLeave;
if (handler != null)
{
handler(this, args);
}
}
#endregion // Events
#region Filesystem
/// <summary>
/// Sanitizes a filename for use on PC (just the filename, not a full path)
/// </summary>
static public string SanitizeFilename(string InputFilename)
{
char[] Filename = InputFilename.ToCharArray();
char[] BadChars = Path.GetInvalidFileNameChars();
for (int i = 0; i < Filename.Length; ++i)
{
foreach (char BadChar in BadChars)
{
if (Filename[i] == BadChar)
{
Filename[i] = '_';
break;
}
}
}
return new string(Filename);
}
/// <summary>
/// Sanitizes a path for use on PC (just the path, no filename)
/// </summary>
static public string SanitizePathNoFilename(string InputPath)
{
char[] DirectoryName = InputPath.ToCharArray();
char[] BadChars = Path.GetInvalidPathChars();
for (int i = 0; i < DirectoryName.Length; ++i)
{
foreach (char BadChar in BadChars)
{
if ((DirectoryName[i] == BadChar) || (DirectoryName[i] == ':'))
{
DirectoryName[i] = '_';
break;
}
}
}
return new string(DirectoryName);
}
void RecursiveBackup(string SourceFolderOnDevice, string TargetFolderOnPC)
{
string[] Directories = GetDirectories(SourceFolderOnDevice);
foreach (string Directory in Directories)
{
string NewSourceFolder = SourceFolderOnDevice + Directory + "/";
string NewTargetFolder = Path.Combine(TargetFolderOnPC, SanitizePathNoFilename(Directory));
RecursiveBackup(NewSourceFolder, NewTargetFolder);
}
string[] Filenames = GetFiles(SourceFolderOnDevice);
foreach (string Filename in Filenames)
{
string SourceFilename = SourceFolderOnDevice + Filename;
string DestFilename = Path.Combine(TargetFolderOnPC, SanitizeFilename(Filename));
WriteProgressLine("Copying '{0}' -> '{1}' ...", 0, SourceFilename, DestFilename);
CopyFileFromPhone(DestFilename, SourceFilename, 1024 * 1024);
}
}
void RecursiveCopy(string SourceFolderOnPC, string TargetFolderOnDevice)
{
string[] Directories = System.IO.Directory.GetDirectories(SourceFolderOnPC);
foreach (string Directory in Directories)
{
string NewSourceFolder = Directory;
string NewTargetFolder = TargetFolderOnDevice + Directory.Substring(Directory.LastIndexOf(Path.DirectorySeparatorChar)+1) + "/";
WriteProgressLine("Copying folder {0} to {1}", 0, NewSourceFolder, NewTargetFolder);
RecursiveCopy(NewSourceFolder, NewTargetFolder);
}
string[] Filenames = System.IO.Directory.GetFiles(SourceFolderOnPC);
foreach (string Filename in Filenames)
{
string SourceFilename = Filename;
string DestFilename = TargetFolderOnDevice + Path.GetFileName(Filename);
WriteProgressLine("Copying '{0}' -> '{1}' ...", 0, SourceFilename, DestFilename);
CopyFileToPhone(SourceFilename, DestFilename, 1024 * 1024);
}
}
public void DumpInstalledApplications()
{
Dictionary<string, object> AppBundles;
MobileDevice.DeviceImpl.LookupApplications(iPhoneHandle, IntPtr.Zero, out AppBundles);
foreach (var Bundle in AppBundles)
{
WriteProgressLine(String.Format("Application bundle {0} has the following pairs:", Bundle.Key), 0);
Dictionary<object, object> BundlePairs = (Dictionary<object, object>)Bundle.Value;
foreach (var KVP in BundlePairs)
{
WriteProgressLine(String.Format(" {0} -> {1}", KVP.Key, KVP.Value), 0);
}
}
}
/// <summary>
/// Tries to back up all of the files on a phone in a particular directory to the PC
/// (requires the bundle identifier to be able to mount that directory)
/// </summary>
public bool TryBackup(string BundleIdentifier, string SourceFolderOnDevice, string TargetFolderOnPC)
{
if (ConnectToBundle(BundleIdentifier))
{
WriteProgressLine("Connected to bundle '{0}'", 0, BundleIdentifier);
try
{
RecursiveBackup(SourceFolderOnDevice, TargetFolderOnPC);
return true;
}
catch (Exception ex)
{
WriteProgressLine("Failed to transfer a file, extended error is '{0}'", 100, ex.Message);
return false;
}
}
else
{
WriteProgressLine("Error: Failed to connect to bundle '{0}'", 100, BundleIdentifier);
return false;
}
}
/// <summary>
/// Tries to copy all of the files in a particular directory on the PC to the phone directory
/// (requires the bundle identifier to be able to mount that directory)
/// </summary>
public bool TryCopy(string BundleIdentifier, string SourceFolderOnPC, string TargetFolderOnDevice)
{
if (ConnectToBundle(BundleIdentifier))
{
WriteProgressLine("Connected to bundle '{0}'", 0, BundleIdentifier);
try
{
RecursiveCopy(SourceFolderOnPC, TargetFolderOnDevice);
return true;
}
catch (Exception ex)
{
WriteProgressLine("Failed to transfer a file, extended error is '{0}'", 100, ex.Message);
return false;
}
}
else
{
WriteProgressLine("Error: Failed to connect to bundle '{0}'", 100, BundleIdentifier);
return false;
}
}
/// <summary>
/// Tries to copy all of the files in the manifest on the PC to the documents directory
/// (requires the bundle identifier to be able to mount that directory)
/// </summary>
public bool TryCopy(string BundleIdentifier, string Manifest)
{
if (ConnectToBundle(BundleIdentifier))
{
WriteProgressLine("Connected to bundle '{0}'", 0, BundleIdentifier);
try
{
string BaseFolder = Path.GetDirectoryName(Manifest);
string Files = File.ReadAllText(Manifest);
string[] FileList = Files.Split('\n');
foreach (string Filename in FileList)
{
if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
{
string Trimmed = Filename.Trim();
string SourceFilename = BaseFolder + "\\" + Trimmed;
SourceFilename = SourceFilename.Replace('/', '\\');
string DestFilename = "/Documents/" + Trimmed.Replace("cookeddata/", "");
DestFilename = DestFilename.Replace('\\', '/');
SourceFilename = SourceFilename.Replace('\\', Path.DirectorySeparatorChar);
WriteProgressLine("Copying '{0}' -> '{1}' ...", 0, SourceFilename, DestFilename);
CopyFileToPhone(SourceFilename, DestFilename, 1024 * 1024);
}
}
return true;
}
catch (Exception ex)
{
WriteProgressLine("Failed to transfer a file, extended error is '{0}'", 100, ex.Message);
return false;
}
}
else
{
WriteProgressLine("Error: Failed to connect to bundle '{0}'", 100, BundleIdentifier);
return false;
}
}
/// <summary>
/// Tries to copy all of the files in a particular directory on the PC to the phone directory
/// (requires the bundle identifier to be able to mount that directory)
/// </summary>
public bool TryBackup(string BundleIdentifier, string[] Files)
{
if (ConnectToBundle(BundleIdentifier))
{
WriteProgressLine("Connected to bundle '{0}'", 0, BundleIdentifier);
try
{
string SafeDeviceName = MobileDeviceInstance.SanitizePathNoFilename(DeviceName);
foreach (string Filename in Files)
{
// string BaseFolder = Path.GetDirectoryName(Filename);
string Manifest = Path.GetDirectoryName(Filename) + "\\" + SafeDeviceName + "_" + Path.GetFileName(Filename);
CopyFileFromPhone(Manifest, "/Documents/" + Path.GetFileName(Filename), 1024 * 1024);
}
return true;
}
catch (Exception ex)
{
WriteProgressLine("Failed to transfer a file, extended error is '{0}'", 100, ex.Message);
return false;
}
}
else
{
string SafeDeviceName = MobileDeviceInstance.SanitizePathNoFilename(DeviceName);
foreach (string Filename in Files)
{
// string BaseFolder = Path.GetDirectoryName(Filename);
string Manifest = Path.GetDirectoryName (Filename) + "\\" + SafeDeviceName + "_" + Path.GetFileName (Filename);
WriteProgressLine("File to be written '{0}'", 100, Manifest);
File.WriteAllText (Manifest, "");
}
WriteProgressLine("Error: Failed to connect to bundle '{0}'", 100, BundleIdentifier);
return false;
}
}
/// <summary>
/// Returns the names of files in a specified directory
/// </summary>
/// <param name="path">The directory from which to retrieve the files.</param>
/// <returns>A <c>String</c> array of file names in the specified directory. Names are relative to the provided directory</returns>
public string[] GetFiles(string path)
{
return GetFiles(path, false);
}
public string[] GetFiles(string path, bool bIncludeDirs)
{
if (!IsConnected)
{
throw new Exception("Not connected to phone");
}
string full_path = FullPath(CurrentDirectory, path);
IntPtr hAFCDir = IntPtr.Zero;
if (MobileDevice.DeviceImpl.DirectoryOpen(AFCCommsHandle, full_path, ref hAFCDir) != 0)
{
throw new Exception("Path does not exist");
}
string buffer = null;
ArrayList paths = new ArrayList();
MobileDevice.DeviceImpl.DirectoryRead(AFCCommsHandle, hAFCDir, ref buffer);
while (buffer != null)
{
if (!IsDirectory(FullPath(full_path, buffer)))
{
paths.Add(buffer);
}
else
{
if (bIncludeDirs)
{
paths.Add(buffer + "/");
}
}
MobileDevice.DeviceImpl.DirectoryRead(AFCCommsHandle, hAFCDir, ref buffer);
}
MobileDevice.DeviceImpl.DirectoryClose(AFCCommsHandle, hAFCDir);
return (string[])paths.ToArray(typeof(string));
}
/// <summary>
/// Returns the FileInfo dictionary
/// </summary>
/// <param name="path">The file or directory for which to retrieve information.</param>
public Dictionary<string, string> GetFileInfo(string path)
{
Dictionary<string, string> ans = new Dictionary<string, string>();
TypedPtr<AFCDictionary> Data;
int ret = MobileDevice.DeviceImpl.FileInfoOpen(AFCCommsHandle, path, out Data);
if ((ret == 0) && (Data.Handle != IntPtr.Zero))
{
IntPtr pname;
IntPtr pvalue;
while (MobileDevice.DeviceImpl.KeyValueRead(Data, out pname, out pvalue) == 0 && pname != IntPtr.Zero && pvalue != IntPtr.Zero)
{
string name = Marshal.PtrToStringAnsi(pname);
string value = Marshal.PtrToStringAnsi(pvalue);
if ((name != null) && (value != null))
{
ans.Add(name, value);
}
else
{
break;
}
}
MobileDevice.DeviceImpl.KeyValueClose(Data);
}
return ans;
}
/// <summary>
/// Returns the st_ifmt of a path
/// </summary>
/// <param name="path">Path to query</param>
/// <returns>string representing value of st_ifmt</returns>
private string Get_st_ifmt(string path)
{
Dictionary<string, string> fi = GetFileInfo(path);
return fi["st_ifmt"];
}
/// <summary>
/// Returns the size and type of the specified file or directory.
/// </summary>
/// <param name="path">The file or directory for which to retrieve information.</param>
/// <param name="size">Returns the size of the specified file or directory</param>
/// <param name="directory">Returns <c>true</c> if the given path describes a directory, false if it is a file.</param>
public void GetFileInfo(string path, out ulong size, out bool directory)
{
Dictionary<string, string> fi = GetFileInfo(path);
size = fi.ContainsKey("st_size") ? System.UInt64.Parse(fi["st_size"]) : 0;
bool SLink = false;
directory = false;
if (fi.ContainsKey("st_ifmt"))
{
switch (fi["st_ifmt"])
{
case "S_IFDIR": directory = true; break;
case "S_IFLNK": SLink = true; break;
}
}
if (SLink)
{
// test for symbolic directory link
IntPtr hAFCDir = IntPtr.Zero;
if (directory = (MobileDevice.DeviceImpl.DirectoryOpen(AFCCommsHandle, path, ref hAFCDir) == 0))
{
MobileDevice.DeviceImpl.DirectoryClose(AFCCommsHandle, hAFCDir);
}
}
}
/// <summary>
/// Returns the size of the specified file or directory.
/// </summary>
/// <param name="path">The file or directory for which to obtain the size.</param>
/// <returns></returns>
public ulong FileSize(string path)
{
bool is_dir;
ulong size;
GetFileInfo(path, out size, out is_dir);
return size;
}
/// <summary>
/// Creates the directory specified in path
/// </summary>
/// <param name="path">The directory path to create</param>
/// <returns>true if directory was created</returns>
public bool CreateDirectory(string path)
{
return !(MobileDevice.DeviceImpl.DirectoryCreate(AFCCommsHandle, FullPath(CurrentDirectory, path)) != 0);
}
/// <summary>
/// Gets the names of subdirectories in a specified directory.
/// </summary>
/// <param name="path">The path for which an array of subdirectory names is returned.</param>
/// <returns>An array of type <c>String</c> containing the names of subdirectories in <c>path</c>.</returns>
public string[] GetDirectories(string path)
{
if (!IsConnected)
{
Reconnect();
if (!IsConnected)
{
throw new Exception("Not connected to phone");
}
}
IntPtr hAFCDir = IntPtr.Zero;
string full_path = FullPath(CurrentDirectory, path);
//full_path = "/private"; // bug test
int res = MobileDevice.DeviceImpl.DirectoryOpen(AFCCommsHandle, full_path, ref hAFCDir);
if (res != 0)
{
throw new Exception("Path does not exist: " + res.ToString());
}
string buffer = null;
ArrayList paths = new ArrayList();
MobileDevice.DeviceImpl.DirectoryRead(AFCCommsHandle, hAFCDir, ref buffer);
while (buffer != null)
{
if ((buffer != ".") && (buffer != "..") && IsDirectory(FullPath(full_path, buffer)))
{
paths.Add(buffer);
}
MobileDevice.DeviceImpl.DirectoryRead(AFCCommsHandle, hAFCDir, ref buffer);
}
MobileDevice.DeviceImpl.DirectoryClose(AFCCommsHandle, hAFCDir);
return (string[])paths.ToArray(typeof(string));
}
/// <summary>
/// Moves a file or a directory and its contents to a new location or renames a file or directory if the old and new parent path matches.
/// </summary>
/// <param name="sourceName">The path of the file or directory to move or rename.</param>
/// <param name="destName">The path to the new location for <c>sourceName</c>.</param>
/// <remarks>Files cannot be moved across filesystem boundaries.</remarks>
public bool Rename(string sourceName, string destName)
{
return MobileDevice.DeviceImpl.RenamePath(AFCCommsHandle,
FullPath(CurrentDirectory, sourceName),
FullPath(CurrentDirectory, destName)) == 0;
}
/// <summary>
/// Returns the root information for the specified path.
/// </summary>
/// <param name="path">The path of a file or directory.</param>
/// <returns>A string containing the root information for the specified path. </returns>
public string GetDirectoryRoot(string path)
{
return "/";
}
/// <summary>
/// Determines whether the given path refers to an existing file or directory on the phone.
/// </summary>
/// <param name="path">The path to test.</param>
/// <returns><c>true</c> if path refers to an existing file or directory, otherwise <c>false</c>.</returns>
public bool Exists(string path)
{
TypedPtr<AFCDictionary> data = IntPtr.Zero;
int ret = MobileDevice.DeviceImpl.FileInfoOpen(AFCCommsHandle, path, out data);
if (ret == 0)
{
MobileDevice.DeviceImpl.KeyValueClose(data);
}
return ret == 0;
}
/// <summary>
/// Determines whether the given path refers to an existing directory on the phone.
/// </summary>
/// <param name="path">The path to test.</param>
/// <returns><c>true</c> if path refers to an existing directory or is a symbolic link to one, otherwise <c>false</c>.</returns>
public bool IsDirectory(string path)
{
bool is_dir;
ulong size;
GetFileInfo(path, out size, out is_dir);
return is_dir;
}
/// <summary>
/// Test if path represents a regular file
/// </summary>
/// <param name="path">path to query</param>
/// <returns>true if path refers to a regular file, false if path is a link or directory</returns>
public bool IsFile(string path)
{
return Get_st_ifmt(path) == "S_IFREG";
}
/// <summary>
/// Test if path represents a link
/// </summary>
/// <param name="path">path to test</param>
/// <returns>true if path is a symbolic link</returns>
public bool IsLink(string path)
{
return Get_st_ifmt(path) == "S_IFLNK";
}
/// <summary>
/// Deletes an empty directory from a specified path.
/// </summary>
/// <param name="path">The name of the empty directory to remove. This directory must be writable and empty.</param>
public void DeleteDirectory(string path)
{
string full_path = FullPath(CurrentDirectory, path);
if (IsDirectory(full_path))
{
MobileDevice.DeviceImpl.RemovePath(AFCCommsHandle, full_path);
}
}
/// <summary>
/// Deletes the specified directory and, if indicated, any subdirectories in the directory.
/// </summary>
/// <param name="path">The name of the directory to remove.</param>
/// <param name="recursive"><c>true</c> to remove directories, subdirectories, and files in path; otherwise, <c>false</c>. </param>
public void DeleteDirectory(string path, bool recursive)
{
if (!recursive)
{
DeleteDirectory(path);
return;
}
string full_path = FullPath(CurrentDirectory, path);
if (IsDirectory(full_path))
{
InternalDeleteDirectory(path);
}
}
/// <summary>
/// Deletes the specified file.
/// </summary>
/// <param name="path">The name of the file to remove.</param>
public void DeleteFile(string path)
{
string full_path = FullPath(CurrentDirectory, path);
if (Exists(full_path))
{
MobileDevice.DeviceImpl.RemovePath(AFCCommsHandle, full_path);
}
}
#endregion // Filesystem
#region Public Methods
/// <summary>
/// Close the AFC connection
/// </summary>
public void Disconnect()
{
if (AFCCommsHandle.Handle != IntPtr.Zero)
{
MobileDevice.DeviceImpl.ConnectionClose(AFCCommsHandle);
MobileDevice.DeviceImpl.StopSession(iPhoneHandle);
MobileDevice.DeviceImpl.Disconnect(iPhoneHandle);
}
AFCCommsHandle = IntPtr.Zero;
}
/// <summary>
/// Close and Reopen AFC Connection
/// </summary>
/// <returns>status from reopen</returns>
public void Reconnect()
{
Disconnect();
ConnectToPhone();
}
#endregion // public Methods
void CopyFileToPhone(string PathOnPC, string PathOnPhone)
{
CopyFileToPhone(PathOnPC, PathOnPhone, 1024 * 1024);
}
void CopyFileToPhone(string PathOnPC, string PathOnPhone, int ChunkSize)
{
DateTime StartTime = DateTime.Now;
byte[] buffer = new byte[ChunkSize];
// verify we are still connected
if (!IsConnected)
{
Reconnect();
}
// Make sure the directory exists on the phone
string DirectoryOnPhone = PathOnPhone.Remove(PathOnPhone.LastIndexOf('/'));
if (!IsDirectory(DirectoryOnPhone))
{
Console.WriteLine("Directory (" + DirectoryOnPhone + ") doesn't exist!");
if (!CreateDirectory(DirectoryOnPhone))
{
Console.WriteLine("CreateDirectory (" + DirectoryOnPhone + ") failed");
// throw new IOException("CreateDirectory (" + DirectoryOnPhone + ") failed");
}
}
FileStream SourceFile = File.OpenRead(PathOnPC);
iPhoneFile DestinationFile = iPhoneFile.OpenWrite(this, PathOnPhone);
long ProgressInterval = Math.Max(buffer.Length, (SourceFile.Length / TransferProgressDivisor));
long NextProgressPrintout = ProgressInterval;
long TotalBytesRead = 0;
int BytesRead = SourceFile.Read(buffer, 0, buffer.Length);
while (BytesRead > 0)
{
if (TotalBytesRead >= NextProgressPrintout)
{
NextProgressPrintout += ProgressInterval;
if (OnGenericProgress != null)
{
int PercentDone = (int)((100 * TotalBytesRead) / SourceFile.Length);
string Msg = "Transferred " + (SourceFile.Position / 1024) + " KB of " + (SourceFile.Length / 1024) + " KB";
OnGenericProgress(Msg, PercentDone);
}
else
{
Console.WriteLine(" ... Transferred " + (SourceFile.Position / 1024) + " KB of " + (SourceFile.Length / 1024) + " KB");
}
}
DestinationFile.Write(buffer, 0, BytesRead);
BytesRead = SourceFile.Read(buffer, 0, buffer.Length);
TotalBytesRead += BytesRead;
}
// DestinationFile.Flush();
DestinationFile.Close();
SourceFile.Close();
TimeSpan CopyLength = DateTime.Now - StartTime;
Console.WriteLine(" ... Finished copying to '{1}' in {0:0.00} s", CopyLength.TotalSeconds, PathOnPhone);
}
// Default level is 6, and 0,3,7 are regularly used. Bump the logging level up to 7 to get verbose logs.
void SetLoggingLevel(int Threshold)
{
Int32 LoggingThreshold = Math.Min(Math.Max(0, Threshold), 7);
MobileDevice.CoreImpl.CFPreferencesSetAppValue(
(IntPtr)MobileDevice.CFStringMakeConstantString("LogLevel"),
(IntPtr)MobileDevice.CFNumberCreate(LoggingThreshold),
(IntPtr)MobileDevice.CFStringMakeConstantString("com.apple.MobileDevice"));
}
void WriteProgressLine(string Fmt, int ProgressCount, params object[] Args)
{
string Line = String.Format(Fmt, Args);
if (OnGenericProgress != null)
{
OnGenericProgress(Line, ProgressCount);
}
else
{
Console.WriteLine(Line);
}
}
/// <summary>
/// Copies a file from the phone to the PC
/// </summary>
void CopyFileFromPhone(string PathOnPC, string PathOnPhone, int ChunkSize)
{
DateTime StartTime = DateTime.Now;
byte[] buffer = new byte[ChunkSize];
// Make sure the directory exists on the PC
string DirectoryOnPC = Path.GetDirectoryName(PathOnPC);
Directory.CreateDirectory(DirectoryOnPC);
iPhoneFile SourceFile = iPhoneFile.OpenRead(this, PathOnPhone);
FileStream DestinationFile = File.OpenWrite(PathOnPC);
long TotalBytesRead = 0;
int BytesRead = SourceFile.Read(buffer, 0, buffer.Length);
while (BytesRead > 0)
{
DestinationFile.Write(buffer, 0, BytesRead);
BytesRead = SourceFile.Read(buffer, 0, buffer.Length);
TotalBytesRead += BytesRead;
}
DestinationFile.Flush();
DestinationFile.Close();
SourceFile.Close();
TimeSpan CopyLength = DateTime.Now - StartTime;
WriteProgressLine(" ... Finished copying to '{1}' in {0:0.00} s", 100, CopyLength.TotalSeconds, PathOnPC);
}
string MakeUnixPath(string PlatformPath)
{
string Result = Path.GetFullPath(PlatformPath);
// Convert C:\ to /C\, the \ will get converted to / in the next step
if (Path.IsPathRooted(Result))
{
string Root = Path.GetPathRoot(Result);
Result = '/' + Root.Replace(":", "") + Result.Substring(Root.Length);
}
// Convert slash directions
if (Path.DirectorySeparatorChar != '/')
{
Result = Result.Replace(Path.DirectorySeparatorChar, '/');
}
return Result;
}
void ReconnectWithInstallProxy()
{
Reconnect();
if (MobileDevice.DeviceImpl.StartService(iPhoneHandle, MobileDevice.CFStringMakeConstantString("com.apple.mobile.installation_proxy"), ref hInstallService, IntPtr.Zero) != 0)
{
Console.WriteLine("Unable to start installation_proxy service!");
}
}
public delegate void ProgressCallback(string Message, int PercentDone);
public ProgressCallback OnGenericProgress = null;
public int TransferProgressDivisor = 25;
/// <summary>
/// Generic progress callback implementation (used for install/uninstall/etc...)
/// </summary>
void HandleProgressCallback(string OuterFunction, TypedPtr<CFDictionary> SourceDict)
{
Dictionary<string, object> Dict = MobileDevice.ConvertCFDictionaryToDictionaryString(SourceDict);
// Expecting:
// string,string -> "Status",PhaseOfInstaller
// string,number -> "PercentComplete",%Done
try
{
string Phase = Dict["Status"] as string;
int PercentDone = (int)((Double)(Dict["PercentComplete"]));
if (OnGenericProgress != null)
{
string Msg = String.Format("{0} is {1}% complete at phase '{2}'", OuterFunction, PercentDone, Phase);
OnGenericProgress(Msg, PercentDone);
}
else
{
Console.WriteLine(" ... {0} {1}% complete (phase '{2}')", OuterFunction, PercentDone, Phase);
}
}
catch (System.Exception)
{
}
}
/// <summary>
/// Progress callback for upgrading or installing
/// </summary>
void InstallProgressCallback(IntPtr UntypedSourceDict, IntPtr UserData)
{
HandleProgressCallback("Install", UntypedSourceDict);
}
/// <summary>
/// Progress callback for uninstalling
/// </summary>
void UninstallProgressCallback(IntPtr UntypedSourceDict, IntPtr UserData)
{
HandleProgressCallback("Uninstall", UntypedSourceDict);
}
/// <summary>
/// Try to install an IPA on the mobile device (it must have already been copied to the PublicStaging directory, with the same filename as the path passed in)
/// </summary>
public bool TryInstall(string IPASourcePathOnPC)
{
DateTime StartTime = DateTime.Now;
ReconnectWithInstallProxy();
IntPtr LiveConnection = IntPtr.Zero;
IntPtr ClientOptions = IntPtr.Zero;
TypedPtr<CFURL> UrlPath = MobileDevice.CreateFileUrlHelper(IPASourcePathOnPC, false);
// string UrlPathAsString = MobileDevice.GetStringForUrl(UrlPath);
int Result = MobileDevice.DeviceImpl.SecureInstallApplication(LiveConnection, iPhoneHandle, UrlPath, ClientOptions, InstallProgressCallback, IntPtr.Zero);
if (Result == 0)
{
Console.WriteLine("Install of \"{0}\" completed successfully in {2:0.00} seconds", Path.GetFileName(IPASourcePathOnPC), Result, (DateTime.Now - StartTime).TotalSeconds);
}
else
{
Console.WriteLine("Install of \"{0}\" failed with error code {1} in {2:0.00} seconds", Path.GetFileName(IPASourcePathOnPC), MobileDevice.GetErrorString(Result), (DateTime.Now - StartTime).TotalSeconds);
}
return Result == 0;
}
/// <summary>
/// Try to update an IPA on the mobile device (it must have already been copied to the PublicStaging directory, with the same filename as the path passed in)
/// </summary>
public bool TryUpgrade(string IPASourcePathOnPC)
{
DateTime StartTime = DateTime.Now;
ReconnectWithInstallProxy();
IntPtr LiveConnection = IntPtr.Zero;
IntPtr ClientOptions = IntPtr.Zero;
TypedPtr<CFURL> UrlPath = MobileDevice.CreateFileUrlHelper(IPASourcePathOnPC, false);
// string UrlPathAsString = MobileDevice.GetStringForUrl(UrlPath);
int Result = MobileDevice.DeviceImpl.SecureUpgradeApplication(LiveConnection, iPhoneHandle, UrlPath, ClientOptions, InstallProgressCallback, IntPtr.Zero);
if (Result == 0)
{
Console.WriteLine("Install \\ Update of \"{0}\" finished in {2:0.00} seconds", Path.GetFileName(IPASourcePathOnPC), Result, (DateTime.Now - StartTime).TotalSeconds);
}
else
{
Console.WriteLine("Install \\ Update of \"{0}\" failed with {1} in {2:0.00} seconds", Path.GetFileName(IPASourcePathOnPC), MobileDevice.GetErrorString(Result), (DateTime.Now - StartTime).TotalSeconds);
}
return Result == 0;
}
public bool TryUninstall(string ApplicationIdentifier)
{
DateTime StartTime = DateTime.Now;
ReconnectWithInstallProxy();
TypedPtr<CFString> CF_ApplicationIdentifier = MobileDevice.CFStringMakeConstantString(ApplicationIdentifier);
IntPtr CF_ClientOptions = IntPtr.Zero;
// IntPtr ExtraKey = IntPtr.Zero;
// IntPtr ExtraValue = IntPtr.Zero;
IntPtr ConnectionHandle = IntPtr.Zero;
int Result = MobileDevice.DeviceImpl.SecureUninstallApplication(ConnectionHandle, iPhoneHandle, CF_ApplicationIdentifier, CF_ClientOptions, UninstallProgressCallback, IntPtr.Zero);
if (Result == 0)
{
Console.WriteLine("Uninstall of \"{0}\" completed successfully in {2:0.00} seconds", ApplicationIdentifier, Result, (DateTime.Now - StartTime).TotalSeconds);
}
else
{
Console.WriteLine("Uninstall of \"{0}\" failed with {1} in {2:0.00} seconds", ApplicationIdentifier, MobileDevice.GetErrorString(Result), (DateTime.Now - StartTime).TotalSeconds);
}
return Result == 0;
}
/// <summary>
/// Copies a file to the PublicStaging directory on the mobile device, preserving the filename
/// </summary>
/// <param name="SourceFile"></param>
public void CopyFileToPublicStaging(string SourceFile)
{
string IpaFilename = Path.GetFileName(SourceFile);
CopyFileToPhone(SourceFile, "PublicStaging/" + IpaFilename);
//Dictionary<string, string> fi = GetFileInfo("/PublicStaging/" + IpaFilename);
}
public bool StartSyslogService()
{
if (MobileDevice.DeviceImpl.Connect(iPhoneHandle) != 0)
{
Console.WriteLine("Connect: Failed to Connect");
return false;
}
if (MobileDevice.DeviceImpl.StartSession(iPhoneHandle) == 1)
{
Console.WriteLine("Connect: Couldn't start session");
return false;
}
if (MobileDevice.DeviceImpl.SecureStartService(iPhoneHandle, MobileDevice.CFStringMakeConstantString("com.apple.syslog_relay"), 0, ref hSyslogService) != 0)
{
Console.WriteLine("Connect: Couldn't Start syslog relay service");
return false;
}
return true;
}
public string GetSyslogData()
{
return MobileDevice.DeviceImpl.ServiceConnectionReceive(hSyslogService);
}
public void StopSyslogService()
{
MobileDevice.DeviceImpl.StopSession(iPhoneHandle);
MobileDevice.DeviceImpl.Disconnect(iPhoneHandle);
}
#region Private Methods
public bool ConnectToPhone()
{
SetLoggingLevel(7);
// Make sure we can connect to the phone and that it has been paired with this machine previously
if (MobileDevice.DeviceImpl.Connect(iPhoneHandle) != 0)
{
Console.WriteLine("Connect: Failed to Connect");
return false;
}
if (MobileDevice.DeviceImpl.IsPaired(iPhoneHandle) != 1)
{
Console.WriteLine("Connect: Device is not paired");
return false;
}
if (MobileDevice.DeviceImpl.ValidatePairing(iPhoneHandle) != 0)
{
Console.WriteLine("Connect: Pairing not valid");
return false;
}
// Start a session
if (MobileDevice.DeviceImpl.StartSession(iPhoneHandle) == 1)
{
Console.WriteLine("Connect: Couldn't start session");
return false;
}
if (MobileDevice.DeviceImpl.StartService(iPhoneHandle, MobileDevice.CFStringMakeConstantString("com.apple.afc"), ref hService, IntPtr.Zero) != 0)
{
Console.WriteLine("Connect: Couldn't Start AFC service");
return false;
}
// Open a file sharing connection
if (MobileDevice.DeviceImpl.ConnectionOpen(hService, 0, out AFCCommsHandle) != 0)
{
Console.WriteLine("Connect: Couldn't Open File Sharing Connection");
return false;
}
connected = true;
return true;
}
public bool ConnectToBundle(string BundleName)
{
Reconnect();
TypedPtr<CFString> CFBundleName = MobileDevice.CFStringMakeConstantString(BundleName);
// Open the bundle
int Result = MobileDevice.DeviceImpl.StartHouseArrestService(iPhoneHandle, CFBundleName, IntPtr.Zero, ref hService, 0);
if (Result != 0)
{
Console.WriteLine("Failed to connect to bundle '{0}' with {1}", BundleName, MobileDevice.GetErrorString(Result));
return false;
}
// Open a file sharing connection
if (MobileDevice.DeviceImpl.ConnectionOpen(hService, 0, out AFCCommsHandle) != 0)
{
return false;
}
return true;
}
private void DfuConnectCallback(ref AMRecoveryDevice callback)
{
OnDfuConnect(new DeviceNotificationEventArgs(callback));
}
private void DfuDisconnectCallback(ref AMRecoveryDevice callback)
{
OnDfuDisconnect(new DeviceNotificationEventArgs(callback));
}
private void RecoveryConnectCallback(ref AMRecoveryDevice callback)
{
OnRecoveryModeEnter(new DeviceNotificationEventArgs(callback));
}
private void RecoveryDisconnectCallback(ref AMRecoveryDevice callback)
{
OnRecoveryModeLeave(new DeviceNotificationEventArgs(callback));
}
private void InternalDeleteDirectory(string path)
{
string full_path = FullPath(CurrentDirectory, path);
string[] contents = GetFiles(path);
for (int i = 0; i < contents.Length; i++)
{
DeleteFile(full_path + "/" + contents[i]);
}
contents = GetDirectories(path);
for (int i = 0; i < contents.Length; i++)
{
InternalDeleteDirectory(full_path + "/" + contents[i]);
}
DeleteDirectory(path);
}
static char[] path_separators = { '/' };
internal string FullPath(string path1, string path2)
{
if ((path1 == null) || (path1 == String.Empty))
{
path1 = "/";
}
if ((path2 == null) || (path2 == String.Empty))
{
path2 = "/";
}
string[] path_parts;
if (path2[0] == '/')
{
path_parts = path2.Split(path_separators);
}
else if (path1[0] == '/')
{
path_parts = (path1 + "/" + path2).Split(path_separators);
}
else
{
path_parts = ("/" + path1 + "/" + path2).Split(path_separators);
}
string[] result_parts = new string[path_parts.Length];
int target_index = 0;
for (int i = 0; i < path_parts.Length; i++)
{
if (path_parts[i] == "..")
{
if (target_index > 0)
{
target_index--;
}
}
else if ((path_parts[i] == ".") || (path_parts[i] == ""))
{
// Do nothing
}
else
{
result_parts[target_index++] = path_parts[i];
}
}
return "/" + String.Join("/", result_parts, 0, target_index);
}
#endregion // Private Methods
}
}