Files
UnrealEngineUWP/Engine/Source/Runtime/NetworkFileSystem/Private/NetworkFileServerConnection.cpp
Josh Adams b3cf2c5fd5 Copying //UE4/Dev-Platform to Dev-Main (//UE4/Dev-Main) (Source: //UE4/Dev-Platform @ 3061622)
#rb none
#lockdown nick.penwarden

Change 3046743 on 2016/07/12 by Mark.Satterthwaite

	Revert Metal workaround for AtmosphericFog rendering on Intel & AMD from 2897082 and instead change the MetalBackend to emit a precise::sqrt(max(0.0, value)) instruction instead of sqrt(value) to avoid the NaN from -ve values. This may still be technically incorrect versus D3D, but it matches the existing OpenGL appearance.
	#rb ben.woodhouse
	#jira UE-33028

Change 3046820 on 2016/07/12 by Peter.Sauerbrei

	PR#2594 - fix for analog input, courtesy of CleanCut
	#rb daniel.lamb

Change 3046826 on 2016/07/12 by Peter.Sauerbrei

	PR#2561 - addition of code to limit architecture in required caps for IOS, courtesy of derekvanvliet
	#rb daniel.lamb

Change 3046835 on 2016/07/12 by Peter.Sauerbrei

	PR#2559 - Increase the stack size on IOS and Mac, courtesy of derekvanvliet
	PR#2552 -  Addition for Apple ReplayKit Framework, courtesy of JoshuaKaiser
	#rb daniel.lamb

Change 3046838 on 2016/07/12 by Peter.Sauerbrei

	PR#2548 - Adding Log information when an unsupported audio type is used, courtesy of derekvanvliet
	#rb daniel.lamb

Change 3046854 on 2016/07/12 by Peter.Sauerbrei
	PR#2547 - fix for unrecognize selector crash on iOS, couretesy of derekvanvliet
	PR#2384 - prevent crashes when initializing push notifications on IOS 7, courtesy of alk3ovation
	#rb daniel.lamb

Change 3046858 on 2016/07/12 by Peter.Sauerbrei

	PR#2475, #1868 - fix for mapping of iOS device name, courtesy of wingedrobin, derekvanvliet
	PR#2567 - fix name of IPhoneSE in names array, courtesy of rohanliston
	#rb daniel.lamb

Change 3046862 on 2016/07/12 by Peter.Sauerbrei

	fix for type in tooltip
	#jira UE-27123
	#rb daniel.lamb

Change 3046919 on 2016/07/12 by Daniel.Lamb

	Stop texture derived data from loading it's bulk data when the linker is destoryed.
	#rb Peter.Sauerbrei

Change 3046922 on 2016/07/12 by Daniel.Lamb

	Updated the default cooker gc settings so that it can have more resources.
	Added support for cooker markup package and objects as (new flag) disregard for gc if it's still in use by the cooker.
	Changed the way reentry data is stored in the cooker.
	Cook only editor content flag in project settings now works again.
	#rb Josh.Adams
	#test cook Paragon

Change 3046924 on 2016/07/12 by Daniel.Lamb

	Added support for encrypting ini files.
	Added new project setting in the editor and setting in ufe.
	Also added ForDistribution flag to ufe.
	#rb Peter.Sauerbrei

Change 3046936 on 2016/07/12 by Mark.Satterthwaite

	Fix compute shader TLV clear for async. compute on Mac.
	#rb chris.babcock

Change 3047207 on 2016/07/12 by Mark.Satterthwaite

	It is illegal to use a reference to an element within a TMap to initialise a new value that is to be added to the TMap as it causes heap-use-after-free.
	#rb chris.babcock

Change 3047208 on 2016/07/12 by Mark.Satterthwaite

	When removing a vertex don't attempt to copy from one element beyond the end of the array to fill the last element - that's a heap-buffer-overflow and is unnecessary because that element will no longer be used.
	#rb chris.babcock

Change 3047209 on 2016/07/12 by Mark.Satterthwaite

	Don't attempt to update Metal class counts if the MetalRHI is uninitalised - it will attempt to double-free the TMap.
	#rb chris.babcock

Change 3047641 on 2016/07/13 by Lee.Clark

	PS4 - Improve SDK Version checking messages

	#rb none

Change 3047663 on 2016/07/13 by Keith.Judge

	Orion - Various minor PS4-only things activated for XB1.

	#rb none

Change 3047664 on 2016/07/13 by Keith.Judge

	XB1 - Fix analysis warning of shadowing a member variable.

	#rb none

Change 3047784 on 2016/07/13 by Keith.Judge

	Xbox One - Memory and perf saving in query handling. Store 8 queries per allocation, rather than 1 so we're making the maximum use of the 256byte allocation granularity.

	#rb None

Change 3047834 on 2016/07/13 by Keith.Judge

	XB1 - Release underlying memory of 3D textures when destroying them. Oops!

	#rb none

Change 3048190 on 2016/07/13 by Josh.Adams

	- Now leave around the ASTC encoder input file on error, for reproing outside of the engine
	#rb none

Change 3048256 on 2016/07/13 by Daniel.Lamb

	Removed warning about missing file when using cook on the fly.
	#rb Peter.Sauerbrei

Change 3048409 on 2016/07/13 by Daniel.Lamb

	Improved output for saving packages in unattended builds.
	#rb Jonathan.Fitzpatrick

Change 3048763 on 2016/07/13 by Peter.Sauerbrei

	switch AppleTV to tvOS in the editor
	#jira UE-30532
	#rb michael.trepka

Change 3049608 on 2016/07/14 by Keith.Judge

	XB1 - Optimize vertex/index buffer dynamic memory usage.

	#rb none

Change 3049609 on 2016/07/14 by Keith.Judge

	Xbox One CPU Perf - Add _RenderThread versions of Lock/Unlock Texture 2D to stop more RHI thread stalls.

	#rb None

Change 3049610 on 2016/07/14 by Keith.Judge

	Xbox One - Reduce latency of deferred deletions to two frames.

	#rb None

Change 3049730 on 2016/07/14 by Keith.Judge

	Xbox One - Disable _RenderThread versions of Lock/Unlock Texture 2D for now as they're causing hangs.

	#rb None

Change 3049732 on 2016/07/14 by Keith.Judge

	Xbox One - Add critical section to the query slot incrementing code as this wa  causing a hang after running for a while as it can be done on any of the parallel rendering threads (not just the RHI thread.

	Also remove optimization pragmas accidentally left in.

	#rb none

Change 3049791 on 2016/07/14 by Keith.Judge

	Xbox One - Made the occlusion query multithreading even more robust. Can play for ages now in a large level without a crash.

	#rb None

Change 3049968 on 2016/07/14 by Jeremiah.Waldron

	Adding AndroidDisableThreadedRendering CVar and device profiles for 4 specific devices that need to have threaded rendering disabled on them due to swap buffer issues.
	Leaving previous checks in FAndroidMisc::AllowRenderThread as they are, but any new devices that need threaded rendering disabled should use the CVar

	#jira UE-24954, UE-27685, UE-20067
	#rb chris.babcock

Change 3050428 on 2016/07/14 by Jeremiah.Waldron

	Fix for application window being terminated if an AlertDialog is showing onPause
	Repro'd and fix tested on Samsung Galaxy Note 3
	#android
	#jira UE-32998
	#rb chris.babcock

Change 3050642 on 2016/07/14 by Peter.Sauerbrei

	fix for invalid generated plist
	#rb daniel.lamb

Change 3050718 on 2016/07/14 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	#rb none

Change 3051327 on 2016/07/15 by Keith.Judge

	Xbox One - Save memory when locking 2D textures by only allocating a linear copy of the mip/array slice we're locking, rather than the entire mip chain. I'll do the same for 3D textures next.

	#rb None

Change 3051346 on 2016/07/15 by Keith.Judge

	Xbox One - Same memory savings for UpdateTexture2D/3D. Only allocate for the mip/slice that we're updating, not the entire texture.

	#rb None.

Change 3051530 on 2016/07/15 by Nick.Shin

	github: minor typo fixes

	#jira UE-32129 - GitHub 2513 : Update default output of HTML5 packaging
	#rb none

Change 3053631 on 2016/07/18 by Mark.Satterthwaite

	Don't attempt to bind a 2D texture to the FOnePassPointShadowProjectionShaderParameters  because it just won't work on Metal - instead bind the black-cube. This fixes validation errors that prevent running projects under the debugger.
	#codereview daniel.wright
	#rb josh.adams
	#jira UE-33350

Change 3053816 on 2016/07/18 by Mark.Satterthwaite

	Fixes for iOS Metal:
	- Depth-clip mode was erroneously exported on iOS SDK 9, it wasn't ever actually available.
	- Stencil texture views are only required on Mac.
	- State cache shouldn't suggest a render target change is required if the current state is clear and the new state is load/don't care as this breaks iOS rendering with MSAA.
	- Instead the debug submissions should just directly invoke submit and switch to rendering so that its SetRenderTarget call always succeeds.
	#rb michael.trepka

Change 3053818 on 2016/07/18 by Mark.Satterthwaite

	Explicit casts for Metal precise::sqrt required for iOS to work with ffast-math workaround.
	#rb michael.trepka

Change 3054426 on 2016/07/18 by Dmitry.Rekman

	Fix case-sensitive compilation problems (UE-33420).

	#codereview Olaf.Piesche
	#rb none

Change 3054434 on 2016/07/18 by Mark.Satterthwaite

	Silence delete-non-virtual-dtor  warnings on iOS as we do on Mac.
	#rb none

Change 3054719 on 2016/07/18 by Jeremiah.Waldron

	Adding ShowHiddenAlertDialog JNI function to be called from native code after the render thread is resumed after pausing.
	Tested locally on Galaxy Note 3. Tested on LG G4 by nick.shin. Tested on Galaxy S6 by chris.babcock
	#jira UE-32998
	#android
	#rb chris.babcock

Change 3054742 on 2016/07/18 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	#rb none

Change 3054850 on 2016/07/18 by Dmitry.Rekman

	Replace Fatal->Error so messagebox can be shown (UE-22818).

	- Incorporates PR #1714 by zaps166.

	#rb none
	#tests Tried to create an invalid context, made sure messagebox is popping up.

Change 3055317 on 2016/07/19 by Lee.Clark

	PS4 - Fix render target memory allocation

	#jira UE-32988
	#rb Marcus.Wassmer

Change 3055682 on 2016/07/19 by Brent.Pease

	 + Fix Debug builds by removing force inline attribute only on debug builds to prevent a warning that is treated as an error

	#rb michael.trepka

Change 3056065 on 2016/07/19 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	#rb none

Change 3056256 on 2016/07/19 by Chris.Babcock

	Add optional log spew filtering callback to Run
	#jira UE-33468
	#ue4
	#android
	#rb Ben.Marsh
	#codereview Jack.Porter

Change 3056727 on 2016/07/19 by Chris.Babcock

	Added addition scope (plus.login) to Google Play Games builder
	#jira UE-33480
	#ue4
	#android
	#rb none
	#codereview ryan.gerleve

Change 3056811 on 2016/07/19 by Jeff.Campeau

	Xbox One now accepts client configs.
	#rb none

Change 3057152 on 2016/07/20 by Dmitry.Rekman

	Linux: use libc++ instead of libstdc++.

	- Needed to solve problems with third-party C++ libraries (e.g. WebRTC).
	- Bundled libc++ 3.8.1 (TPS cleared).
	- Turned off ICU compilation (needs recompile against libc++).
	- Some libraries (e.g. FBX sdk) still need libstdc++, so in practice it is going to be a mix.

	#rb none
	#tests Built and ran a number of Linux targets.

Change 3057362 on 2016/07/20 by Keith.Judge

	XB1 - Fix busted merge from yesterday

	#rb None

Change 3057647 on 2016/07/20 by Josh.Adams

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	#rb none

Change 3057655 on 2016/07/20 by Daniel.Lamb

	Added test cooking flag.
	#rb Peter.Sauerbrei
	#test Cook paragon.

Change 3058779 on 2016/07/20 by Dmitry.Rekman

	Fix crash on cooker exit (UE-33583).

	- Global/static tickable objects could outlive the collection and trigger asserts when removing themselves from it.

	#rb Josh.Adams
	#codereview Josh.Adams, Jamie.Dale
	#tests Compiled and ran Linux editor.
	#lockdown Josh.Adams

Change 3058835 on 2016/07/20 by Chris.Babcock

	Enable GooglePlay and GameCenter plugins by default
	#jira UE-33605
	#ue4
	#android
	#ios
	#rb mark.satterthwaite
	#codereview Peter.Sauerbrei
	#lockdown Josh.Adams

Change 3058847 on 2016/07/20 by Chris.Babcock

	Fix Android device rule for AlcatelPixi3
	#jira UE-33606
	#ue4
	#android
	#rb none
	#codereview Jeremiah.Walron
	#lockdown Josh.Adams

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

	Merging //UE4/Dev-Main to Dev-Platform (//UE4/Dev-Platform)
	#rb none
	#lockdown nick.penwarden

Change 3060139 on 2016/07/21 by Chris.Babcock

	Fix proguard entry for Android mediaplayer tracks
	#jira UE-33644
	#ue4
	#android
	#rb Josh.Adams
	#lockdown Josh.Adams

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

	Fast ASTC texture compression, using ISPC.

	#jira UE-32308
	#rb chris.babcock
	#lockdown josh.adams

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

	Back out changelist 3061151 as it wasn't approved for submission
	#rb none
	#lockdown josh.adams

Change 3061436 on 2016/07/22 by Lee.Clark

	PS4 - Back out render target mem allocation changes and put in a temp hack

	#jira UE-33657
	#codereview Marcus.Wassmer
	#lockdown josh.adams
	#rb none

[CL 3061637 by Josh Adams in Main branch]
2016-07-22 11:36:47 -04:00

1035 lines
31 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "NetworkFileSystemPrivatePCH.h"
#include "PackageName.h"
#include "TargetPlatform.h"
/* FNetworkFileServerClientConnection structors
*****************************************************************************/
FNetworkFileServerClientConnection::FNetworkFileServerClientConnection( const FFileRequestDelegate& InFileRequestDelegate,
const FRecompileShadersDelegate& InRecompileShadersDelegate, const TArray<ITargetPlatform*>& InActiveTargetPlatforms )
: LastHandleId(0)
, Sandbox(NULL)
, ActiveTargetPlatforms(InActiveTargetPlatforms)
{
if (InFileRequestDelegate.IsBound())
{
FileRequestDelegate = InFileRequestDelegate;
}
if (InRecompileShadersDelegate.IsBound())
{
RecompileShadersDelegate = InRecompileShadersDelegate;
}
}
FNetworkFileServerClientConnection::~FNetworkFileServerClientConnection( )
{
// close all the files the client had opened through us when the client disconnects
for (TMap<uint64, IFileHandle*>::TIterator It(OpenFiles); It; ++It)
{
delete It.Value();
}
delete Sandbox;
Sandbox = NULL;
}
/* FStreamingNetworkFileServerConnection implementation
*****************************************************************************/
void FNetworkFileServerClientConnection::ConvertClientFilenameToServerFilename(FString& FilenameToConvert)
{
if (FilenameToConvert.StartsWith(ConnectedEngineDir))
{
FilenameToConvert = FilenameToConvert.Replace(*ConnectedEngineDir, *(FPaths::EngineDir()));
}
else if (FilenameToConvert.StartsWith(ConnectedGameDir))
{
if ( FPaths::IsProjectFilePathSet() )
{
FilenameToConvert = FilenameToConvert.Replace(*ConnectedGameDir, *(FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/")));
}
else
{
#if !IS_PROGRAM
// UnrealFileServer has a GameDir of ../../../Engine/Programs/UnrealFileServer.
// We do *not* want to replace the directory in that case.
FilenameToConvert = FilenameToConvert.Replace(*ConnectedGameDir, *(FPaths::GameDir()));
#endif
}
}
}
/**
* Fixup sandbox paths to match what package loading will request on the client side. e.g.
* Sandbox path: "../../../Elemental/Content/Elemental/Effects/FX_Snow_Cracks/Crack_02/Materials/M_SnowBlast.uasset ->
* client path: "../../../Samples/Showcases/Elemental/Content/Elemental/Effects/FX_Snow_Cracks/Crack_02/Materials/M_SnowBlast.uasset"
* This ensures that devicelocal-cached files will be properly timestamp checked before deletion.
*/
static TMap<FString, FDateTime> FixupSandboxPathsForClient(FSandboxPlatformFile* Sandbox, const TMap<FString, FDateTime>& SandboxPaths, const FString& LocalEngineDir, const FString& LocalGameDir, bool bLowerCaseFiles)
{
TMap<FString, FDateTime> FixedFiletimes;
FString SandboxEngine = Sandbox->ConvertToSandboxPath(*LocalEngineDir);
if (SandboxEngine.EndsWith(TEXT("/"), ESearchCase::CaseSensitive) == false)
{
SandboxEngine += TEXT("/");
}
// we need to add an extra bit to the game path to make the sandbox convert it correctly (investigate?)
// @todo: double check this
FString SandboxGame = Sandbox->ConvertToSandboxPath(*(LocalGameDir + TEXT("a.txt"))).Replace(TEXT("a.txt"), TEXT(""));
// since the sandbox remaps from A/B/C to C, and the client has no idea of this, we need to put the files
// into terms of the actual LocalGameDir, which is all that the client knows about
for (TMap<FString, FDateTime>::TConstIterator It(SandboxPaths); It; ++It)
{
FString Fixed = Sandbox->ConvertToSandboxPath(*It.Key());
Fixed = Fixed.Replace(*SandboxEngine, *LocalEngineDir);
Fixed = Fixed.Replace(*SandboxGame, *LocalGameDir);
if (bLowerCaseFiles)
{
Fixed = Fixed.ToLower();
}
FixedFiletimes.Add(Fixed, It.Value());
}
return FixedFiletimes;
}
void FNetworkFileServerClientConnection::ConvertServerFilenameToClientFilename(FString& FilenameToConvert)
{
if (FilenameToConvert.StartsWith(FPaths::EngineDir()))
{
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::EngineDir()), *ConnectedEngineDir);
}
else if (FPaths::IsProjectFilePathSet())
{
if (FilenameToConvert.StartsWith(FPaths::GetPath(FPaths::GetProjectFilePath())))
{
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/")), *ConnectedGameDir);
}
}
#if !IS_PROGRAM
else if (FilenameToConvert.StartsWith(FPaths::GameDir()))
{
// UnrealFileServer has a GameDir of ../../../Engine/Programs/UnrealFileServer.
// We do *not* want to replace the directory in that case.
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::GameDir()), *ConnectedGameDir);
}
#endif
}
static FCriticalSection SocketCriticalSection;
bool FNetworkFileServerClientConnection::ProcessPayload(FArchive& Ar)
{
FBufferArchive Out;
bool Result = true;
// first part of the payload is always the command
uint32 Cmd;
Ar << Cmd;
UE_LOG(LogFileServer, Verbose, TEXT("Processing payload with Cmd %d"), Cmd);
// what type of message is this?
NFS_Messages::Type Msg = NFS_Messages::Type(Cmd);
// make sure the first thing is GetFileList which initializes the game/platform
checkf(Msg == NFS_Messages::GetFileList || Msg == NFS_Messages::Heartbeat || Sandbox != NULL, TEXT("The first client message MUST be GetFileList, not %d"), (int32)Msg);
// process the message!
bool bSendUnsolicitedFiles = false;
{
FScopeLock SocketLock(&SocketCriticalSection);
switch (Msg)
{
case NFS_Messages::OpenRead:
ProcessOpenFile(Ar, Out, false);
break;
case NFS_Messages::OpenWrite:
ProcessOpenFile(Ar, Out, true);
break;
case NFS_Messages::Read:
ProcessReadFile(Ar, Out);
break;
case NFS_Messages::Write:
ProcessWriteFile(Ar, Out);
break;
case NFS_Messages::Seek:
ProcessSeekFile(Ar, Out);
break;
case NFS_Messages::Close:
ProcessCloseFile(Ar, Out);
break;
case NFS_Messages::MoveFile:
ProcessMoveFile(Ar, Out);
break;
case NFS_Messages::DeleteFile:
ProcessDeleteFile(Ar, Out);
break;
case NFS_Messages::GetFileInfo:
ProcessGetFileInfo(Ar, Out);
break;
case NFS_Messages::CopyFile:
ProcessCopyFile(Ar, Out);
break;
case NFS_Messages::SetTimeStamp:
ProcessSetTimeStamp(Ar, Out);
break;
case NFS_Messages::SetReadOnly:
ProcessSetReadOnly(Ar, Out);
break;
case NFS_Messages::CreateDirectory:
ProcessCreateDirectory(Ar, Out);
break;
case NFS_Messages::DeleteDirectory:
ProcessDeleteDirectory(Ar, Out);
break;
case NFS_Messages::DeleteDirectoryRecursively:
ProcessDeleteDirectoryRecursively(Ar, Out);
break;
case NFS_Messages::ToAbsolutePathForRead:
ProcessToAbsolutePathForRead(Ar, Out);
break;
case NFS_Messages::ToAbsolutePathForWrite:
ProcessToAbsolutePathForWrite(Ar, Out);
break;
case NFS_Messages::ReportLocalFiles:
ProcessReportLocalFiles(Ar, Out);
break;
case NFS_Messages::GetFileList:
Result = ProcessGetFileList(Ar, Out);
break;
case NFS_Messages::Heartbeat:
ProcessHeartbeat(Ar, Out);
break;
case NFS_Messages::SyncFile:
ProcessSyncFile(Ar, Out);
bSendUnsolicitedFiles = true;
break;
case NFS_Messages::RecompileShaders:
ProcessRecompileShaders(Ar, Out);
break;
default:
UE_LOG(LogFileServer, Error, TEXT("Bad incomming message tag (%d)."), (int32)Msg);
}
}
// send back a reply if the command wrote anything back out
if (Out.Num() && Result )
{
int32 NumUnsolictedFiles = 0;
if (bSendUnsolicitedFiles)
{
int64 MaxMemoryAllowed = 50 * 1024 * 1024;
for (const auto& Filename : UnsolictedFiles)
{
// get file timestamp and send it to client
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
TArray<uint8> Contents;
// open file
int64 FileSize = Sandbox->FileSize(*Filename);
if (MaxMemoryAllowed > FileSize)
{
MaxMemoryAllowed -= FileSize;
++NumUnsolictedFiles;
}
}
Out << NumUnsolictedFiles;
}
UE_LOG(LogFileServer, Verbose, TEXT("Returning payload with %d bytes"), Out.Num());
// send back a reply
Result &= SendPayload( Out );
TArray<FString> UnprocessedUnsolictedFiles;
UnprocessedUnsolictedFiles.Empty(NumUnsolictedFiles);
if (bSendUnsolicitedFiles && Result )
{
for (int32 Index = 0; Index < NumUnsolictedFiles; Index++)
{
FBufferArchive OutUnsolicitedFile;
PackageFile(UnsolictedFiles[Index], OutUnsolicitedFile);
UE_LOG(LogFileServer, Display, TEXT("Returning unsolicited file %s with %d bytes"), *UnsolictedFiles[Index], OutUnsolicitedFile.Num());
Result &= SendPayload(OutUnsolicitedFile);
}
UnsolictedFiles.RemoveAt(0, NumUnsolictedFiles);
}
}
UE_LOG(LogFileServer, Verbose, TEXT("Done Processing payload with Cmd %d Total Size sending %d "), Cmd,Out.TotalSize());
return Result;
}
void FNetworkFileServerClientConnection::ProcessOpenFile( FArchive& In, FArchive& Out, bool bIsWriting )
{
// Get filename
FString Filename;
In << Filename;
bool bAppend = false;
bool bAllowRead = false;
if (bIsWriting)
{
In << bAppend;
In << bAllowRead;
}
// todo: clients from the same ip address "could" be trying to write to the same file in the same sandbox (for example multiple windows clients)
// should probably have the sandbox write to separate files for each client
// not important for now
ConvertClientFilenameToServerFilename(Filename);
if (bIsWriting)
{
// Make sure the directory exists...
Sandbox->CreateDirectoryTree(*(FPaths::GetPath(Filename)));
}
TArray<FString> NewUnsolictedFiles;
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
int64 ServerFileSize = 0;
IFileHandle* File = bIsWriting ? Sandbox->OpenWrite(*Filename, bAppend, bAllowRead) : Sandbox->OpenRead(*Filename);
if (!File)
{
UE_LOG(LogFileServer, Display, TEXT("Open request for %s failed for file %s."), bIsWriting ? TEXT("Writing") : TEXT("Reading"), *Filename);
ServerTimeStamp = FDateTime::MinValue(); // if this was a directory, this will make sure it is not confused with a zero byte file
}
else
{
ServerFileSize = File->Size();
}
uint64 HandleId = ++LastHandleId;
OpenFiles.Add( HandleId, File );
Out << HandleId;
Out << ServerTimeStamp;
Out << ServerFileSize;
}
void FNetworkFileServerClientConnection::ProcessReadFile( FArchive& In, FArchive& Out )
{
// Get Handle ID
uint64 HandleId = 0;
In << HandleId;
int64 BytesToRead = 0;
In << BytesToRead;
int64 BytesRead = 0;
IFileHandle* File = FindOpenFile(HandleId);
if (File)
{
uint8* Dest = (uint8*)FMemory::Malloc(BytesToRead);
if (File->Read(Dest, BytesToRead))
{
BytesRead = BytesToRead;
Out << BytesRead;
Out.Serialize(Dest, BytesRead);
}
else
{
Out << BytesRead;
}
FMemory::Free(Dest);
}
else
{
Out << BytesRead;
}
}
void FNetworkFileServerClientConnection::ProcessWriteFile( FArchive& In, FArchive& Out )
{
// Get Handle ID
uint64 HandleId = 0;
In << HandleId;
int64 BytesWritten = 0;
IFileHandle* File = FindOpenFile(HandleId);
if (File)
{
int64 BytesToWrite = 0;
In << BytesToWrite;
uint8* Source = (uint8*)FMemory::Malloc(BytesToWrite);
In.Serialize(Source, BytesToWrite);
if (File->Write(Source, BytesToWrite))
{
BytesWritten = BytesToWrite;
}
FMemory::Free(Source);
}
Out << BytesWritten;
}
void FNetworkFileServerClientConnection::ProcessSeekFile( FArchive& In, FArchive& Out )
{
// Get Handle ID
uint64 HandleId = 0;
In << HandleId;
int64 NewPosition;
In << NewPosition;
int64 SetPosition = -1;
IFileHandle* File = FindOpenFile(HandleId);
if (File && File->Seek(NewPosition))
{
SetPosition = File->Tell();
}
Out << SetPosition;
}
void FNetworkFileServerClientConnection::ProcessCloseFile( FArchive& In, FArchive& Out )
{
// Get Handle ID
uint64 HandleId = 0;
In << HandleId;
uint32 Closed = 0;
IFileHandle* File = FindOpenFile(HandleId);
if (File)
{
Closed = 1;
OpenFiles.Remove(HandleId);
delete File;
}
Out << Closed;
}
void FNetworkFileServerClientConnection::ProcessGetFileInfo( FArchive& In, FArchive& Out )
{
// Get filename
FString Filename;
In << Filename;
ConvertClientFilenameToServerFilename(Filename);
FFileInfo Info;
Info.FileExists = Sandbox->FileExists(*Filename);
// if the file exists, cook it if necessary (the FileExists flag won't change value based on this callback)
// without this, the server can return the uncooked file size, which can cause reads off the end
if (Info.FileExists)
{
TArray<FString> NewUnsolictedFiles;
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
}
// get the rest of the info
Info.ReadOnly = Sandbox->IsReadOnly(*Filename);
Info.Size = Sandbox->FileSize(*Filename);
Info.TimeStamp = Sandbox->GetTimeStamp(*Filename);
Info.AccessTimeStamp = Sandbox->GetAccessTimeStamp(*Filename);
Out << Info.FileExists;
Out << Info.ReadOnly;
Out << Info.Size;
Out << Info.TimeStamp;
Out << Info.AccessTimeStamp;
}
void FNetworkFileServerClientConnection::ProcessMoveFile( FArchive& In, FArchive& Out )
{
FString From;
In << From;
FString To;
In << To;
ConvertClientFilenameToServerFilename(From);
ConvertClientFilenameToServerFilename(To);
uint32 Success = Sandbox->MoveFile(*To, *From);
Out << Success;
}
void FNetworkFileServerClientConnection::ProcessDeleteFile( FArchive& In, FArchive& Out )
{
FString Filename;
In << Filename;
ConvertClientFilenameToServerFilename(Filename);
uint32 Success = Sandbox->DeleteFile(*Filename);
Out << Success;
}
void FNetworkFileServerClientConnection::ProcessReportLocalFiles( FArchive& In, FArchive& Out )
{
// get the list of files on the other end
TMap<FString, FDateTime> ClientFileTimes;
In << ClientFileTimes;
// go over them and compare times to this side
TArray<FString> OutOfDateFiles;
for (TMap<FString, FDateTime>::TIterator It(ClientFileTimes); It; ++It)
{
FString ClientFile = It.Key();
ConvertClientFilenameToServerFilename(ClientFile);
// get the local timestamp
FDateTime Timestamp = Sandbox->GetTimeStamp(*ClientFile);
// if it's newer than the client/remote timestamp, it's newer here, so tell the other side it's out of date
if (Timestamp > It.Value())
{
OutOfDateFiles.Add(ClientFile);
}
}
UE_LOG(LogFileServer, Display, TEXT("There were %d out of date files"), OutOfDateFiles.Num());
}
/** Copies file. */
void FNetworkFileServerClientConnection::ProcessCopyFile( FArchive& In, FArchive& Out )
{
FString To;
FString From;
In << To;
In << From;
ConvertClientFilenameToServerFilename(To);
ConvertClientFilenameToServerFilename(From);
bool Success = Sandbox->CopyFile(*To, *From);
Out << Success;
}
void FNetworkFileServerClientConnection::ProcessSetTimeStamp( FArchive& In, FArchive& Out )
{
FString Filename;
FDateTime Timestamp;
In << Filename;
In << Timestamp;
ConvertClientFilenameToServerFilename(Filename);
Sandbox->SetTimeStamp(*Filename, Timestamp);
// Need to sends something back otherwise the response won't get sent at all.
bool Success = true;
Out << Success;
}
void FNetworkFileServerClientConnection::ProcessSetReadOnly( FArchive& In, FArchive& Out )
{
FString Filename;
bool bReadOnly;
In << Filename;
In << bReadOnly;
ConvertClientFilenameToServerFilename(Filename);
bool Success = Sandbox->SetReadOnly(*Filename, bReadOnly);
Out << Success;
}
void FNetworkFileServerClientConnection::ProcessCreateDirectory( FArchive& In, FArchive& Out )
{
FString Directory;
In << Directory;
ConvertClientFilenameToServerFilename(Directory);
bool bSuccess = Sandbox->CreateDirectory(*Directory);
Out << bSuccess;
}
void FNetworkFileServerClientConnection::ProcessDeleteDirectory( FArchive& In, FArchive& Out )
{
FString Directory;
In << Directory;
ConvertClientFilenameToServerFilename(Directory);
bool bSuccess = Sandbox->DeleteDirectory(*Directory);
Out << bSuccess;
}
void FNetworkFileServerClientConnection::ProcessDeleteDirectoryRecursively( FArchive& In, FArchive& Out )
{
FString Directory;
In << Directory;
ConvertClientFilenameToServerFilename(Directory);
bool bSuccess = Sandbox->DeleteDirectoryRecursively(*Directory);
Out << bSuccess;
}
void FNetworkFileServerClientConnection::ProcessToAbsolutePathForRead( FArchive& In, FArchive& Out )
{
FString Filename;
In << Filename;
ConvertClientFilenameToServerFilename(Filename);
Filename = Sandbox->ConvertToAbsolutePathForExternalAppForRead(*Filename);
Out << Filename;
}
void FNetworkFileServerClientConnection::ProcessToAbsolutePathForWrite( FArchive& In, FArchive& Out )
{
FString Filename;
In << Filename;
ConvertClientFilenameToServerFilename(Filename);
Filename = Sandbox->ConvertToAbsolutePathForExternalAppForWrite(*Filename);
Out << Filename;
}
bool FNetworkFileServerClientConnection::ProcessGetFileList( FArchive& In, FArchive& Out )
{
// get the list of directories to process
TArray<FString> TargetPlatformNames;
FString GameName;
FString EngineRelativePath;
FString GameRelativePath;
TArray<FString> RootDirectories;
bool bIsStreamingRequest = false;
In << TargetPlatformNames;
In << GameName;
In << EngineRelativePath;
In << GameRelativePath;
In << RootDirectories;
In << bIsStreamingRequest;
ConnectedPlatformName = TEXT("");
bool bSendLowerCase = false;
// if we didn't find one (and this is a dumb server - no active platforms), then just use what was sent
if (ActiveTargetPlatforms.Num() == 0)
{
ConnectedPlatformName = TargetPlatformNames[0];
}
// we only need to care about validating the connected platform if there are active targetplatforms
else
{
// figure out the best matching target platform for the set of valid ones
for (int32 TPIndex = 0; TPIndex < TargetPlatformNames.Num() && ConnectedPlatformName == TEXT(""); TPIndex++)
{
UE_LOG(LogFileServer, Display, TEXT(" Possible Target Platform from client: %s"), *TargetPlatformNames[TPIndex]);
// look for a matching target platform
for (int32 ActiveTPIndex = 0; ActiveTPIndex < ActiveTargetPlatforms.Num(); ActiveTPIndex++)
{
UE_LOG(LogFileServer, Display, TEXT(" Checking against: %s"), *ActiveTargetPlatforms[ActiveTPIndex]->PlatformName());
if (ActiveTargetPlatforms[ActiveTPIndex]->PlatformName() == TargetPlatformNames[TPIndex])
{
bSendLowerCase = ActiveTargetPlatforms[ActiveTPIndex]->SendLowerCaseFilePaths();
ConnectedPlatformName = ActiveTargetPlatforms[ActiveTPIndex]->PlatformName();
break;
}
}
}
// if we didn't find one, reject client and also print some warnings
if (ConnectedPlatformName == TEXT(""))
{
// reject client we can't cook/compile shaders for you!
UE_LOG(LogFileServer, Warning, TEXT("Unable to find target platform for client, terminating client connection!"));
for (int32 TPIndex = 0; TPIndex < TargetPlatformNames.Num() && ConnectedPlatformName == TEXT(""); TPIndex++)
{
UE_LOG(LogFileServer, Warning, TEXT(" Target platforms from client: %s"), *TargetPlatformNames[TPIndex]);
}
for (int32 ActiveTPIndex = 0; ActiveTPIndex < ActiveTargetPlatforms.Num(); ActiveTPIndex++)
{
UE_LOG(LogFileServer, Warning, TEXT(" Active target platforms on server: %s"), *ActiveTargetPlatforms[ActiveTPIndex]->PlatformName());
}
return false;
}
}
ConnectedEngineDir = EngineRelativePath;
ConnectedGameDir = GameRelativePath;
FString LocalEngineDir = FPaths::EngineDir();
FString LocalGameDir = FPaths::GameDir();
if ( FPaths::IsProjectFilePathSet() )
{
LocalGameDir = FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/");
}
UE_LOG(LogFileServer, Display, TEXT(" Connected EngineDir = %s"), *ConnectedEngineDir);
UE_LOG(LogFileServer, Display, TEXT(" Local EngineDir = %s"), *LocalEngineDir);
UE_LOG(LogFileServer, Display, TEXT(" Connected GameDir = %s"), *ConnectedGameDir);
UE_LOG(LogFileServer, Display, TEXT(" Local GameDir = %s"), *LocalGameDir);
// Remap the root directories requested...
for (int32 RootDirIdx = 0; RootDirIdx < RootDirectories.Num(); RootDirIdx++)
{
FString CheckRootDir = RootDirectories[RootDirIdx];
ConvertClientFilenameToServerFilename(CheckRootDir);
RootDirectories[RootDirIdx] = CheckRootDir;
}
// figure out the sandbox directory
// @todo: This should use FPlatformMisc::SavedDirectory(GameName)
FString SandboxDirectory;
if ( FPaths::IsProjectFilePathSet() )
{
FString ProjectDir = FPaths::GetPath(FPaths::GetProjectFilePath());
SandboxDirectory = FPaths::Combine(*ProjectDir, TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
if( bIsStreamingRequest )
{
RootDirectories.Add(ProjectDir);
}
}
else
{
if (FPaths::GetExtension(GameName) == FProjectDescriptor::GetExtension())
{
SandboxDirectory = FPaths::Combine(*FPaths::GetPath(GameName), TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
}
else
{
//@todo: This assumes the game is located in the UE4 Root directory
SandboxDirectory = FPaths::Combine(*FPaths::GetRelativePathToRoot(), *GameName, TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
}
}
// Convert to full path so that the sandbox wrapper doesn't re-base to Saved/Sandboxes
SandboxDirectory = FPaths::ConvertRelativePathToFull(SandboxDirectory);
// delete any existing one first, in case game name somehow changed and client is re-asking for files (highly unlikely)
delete Sandbox;
Sandbox = new FSandboxPlatformFile(false);
Sandbox->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), *FString::Printf(TEXT("-sandbox=\"%s\""), *SandboxDirectory));
// make sure the global shaders are up to date before letting the client read any shaders
// @todo: This will probably add about 1/2 second to the boot-up time of the client while the server does this
// @note: We assume the delegate will write to the proper sandbox directory, should we pass in SandboxDirectory, or Sandbox?
FShaderRecompileData RecompileData;
RecompileData.PlatformName = ConnectedPlatformName;
// All target platforms
RecompileData.ShaderPlatform = -1;
RecompileData.ModifiedFiles = NULL;
RecompileData.MeshMaterialMaps = NULL;
RecompileShadersDelegate.ExecuteIfBound(RecompileData);
UE_LOG(LogFileServer, Display, TEXT("Getting files for %d directories, game = %s, platform = %s"), RootDirectories.Num(), *GameName, *ConnectedPlatformName);
UE_LOG(LogFileServer, Display, TEXT(" Sandbox dir = %s"), *SandboxDirectory);
for (int32 DumpIdx = 0; DumpIdx < RootDirectories.Num(); DumpIdx++)
{
UE_LOG(LogFileServer, Display, TEXT("\t%s"), *(RootDirectories[DumpIdx]));
}
TArray<FString> DirectoriesToAlwaysStageAsUFS;
if ( GConfig->GetArray(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("DirectoriesToAlwaysStageAsUFS"), DirectoriesToAlwaysStageAsUFS, GGameIni) )
{
for ( const auto& DirectoryToAlwaysStage : DirectoriesToAlwaysStageAsUFS )
{
RootDirectories.Add( DirectoryToAlwaysStage );
}
}
// list of directories to skip
TArray<FString> DirectoriesToSkip;
TArray<FString> DirectoriesToNotRecurse;
// @todo: This should really be FPlatformMisc::GetSavedDirForGame(ClientGameName), etc
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
{
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Backup")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Config")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Logs")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Sandboxes")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Cooked")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/ShaderDebugInfo")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/StagedBuilds")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Intermediate")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Documentation")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Extras")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Binaries")));
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Source")));
DirectoriesToNotRecurse.Add(FString(RootDirectories[DirIndex] / TEXT("DerivedDataCache")));
}
// use the timestamp grabbing visitor (include directories)
FLocalTimestampDirectoryVisitor Visitor(*Sandbox, DirectoriesToSkip, DirectoriesToNotRecurse, true);
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
{
Sandbox->IterateDirectory(*RootDirectories[DirIndex], Visitor);
}
// report the package version information
// The downside of this is that ALL cooked data will get tossed on package version changes
int32 PackageFileUE4Version = GPackageFileUE4Version;
Out << PackageFileUE4Version;
int32 PackageFileLicenseeUE4Version = GPackageFileLicenseeUE4Version;
Out << PackageFileLicenseeUE4Version;
// Send *our* engine and game dirs
Out << LocalEngineDir;
Out << LocalGameDir;
// return the files and their timestamps
TMap<FString, FDateTime> FixedTimes = FixupSandboxPathsForClient(Sandbox, Visitor.FileTimes, LocalEngineDir, LocalGameDir, bSendLowerCase);
Out << FixedTimes;
// Do it again, preventing access to non-cooked files
if( bIsStreamingRequest == false )
{
TArray<FString> RootContentPaths;
FPackageName::QueryRootContentPaths(RootContentPaths);
TArray<FString> ContentFolders;
for (const auto& RootPath : RootContentPaths)
{
const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath);
FString ConnectedContentFolder = ContentFolder;
ConnectedContentFolder.ReplaceInline(*LocalEngineDir, *ConnectedEngineDir);
int32 ReplaceCount = 0;
// If one path is relative and the other isn't, convert both to absolute paths before trying to replace
if (FPaths::IsRelative(LocalGameDir) != FPaths::IsRelative(ConnectedContentFolder))
{
FString AbsoluteLocalGameDir = FPaths::ConvertRelativePathToFull(LocalGameDir);
FString AbsoluteConnectedContentFolder = FPaths::ConvertRelativePathToFull(ConnectedContentFolder);
ReplaceCount = AbsoluteConnectedContentFolder.ReplaceInline(*AbsoluteLocalGameDir, *ConnectedGameDir);
if (ReplaceCount > 0)
{
ConnectedContentFolder = AbsoluteConnectedContentFolder;
}
}
else
{
ReplaceCount = ConnectedContentFolder.ReplaceInline(*LocalGameDir, *ConnectedGameDir);
}
if (ReplaceCount == 0)
{
int32 GameDirOffset = ConnectedContentFolder.Find(ConnectedGameDir, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
if (GameDirOffset != INDEX_NONE)
{
ConnectedContentFolder = ConnectedContentFolder.RightChop(GameDirOffset);
}
}
ContentFolders.Add(ConnectedContentFolder);
}
Out << ContentFolders;
// Do it again, preventing access to non-cooked files
const int32 NUM_EXCLUSION_WILDCARDS = 2;
FString ExclusionWildcard[NUM_EXCLUSION_WILDCARDS];
ExclusionWildcard[0] = FString(TEXT("*")) + FPackageName::GetAssetPackageExtension();
ExclusionWildcard[1] = FString(TEXT("*")) + FPackageName::GetMapPackageExtension();
for (int32 i=0; i < NUM_EXCLUSION_WILDCARDS; ++i)
{
Sandbox->AddExclusion(*ExclusionWildcard[i]);
UE_LOG(LogFileServer, Display, TEXT("Excluding %s from non-sandboxed directories"),
*ExclusionWildcard[i]);
}
FLocalTimestampDirectoryVisitor VisitorForCacheDates(*Sandbox, DirectoriesToSkip, DirectoriesToNotRecurse, true);
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
{
Sandbox->IterateDirectory(*RootDirectories[DirIndex], VisitorForCacheDates);
}
// return the cached files and their timestamps
FixedTimes = FixupSandboxPathsForClient(Sandbox, VisitorForCacheDates.FileTimes, LocalEngineDir, LocalGameDir, bSendLowerCase);
Out << FixedTimes;
}
return true;
}
void FNetworkFileServerClientConnection::ProcessHeartbeat( FArchive& In, FArchive& Out )
{
// Protect the array
FScopeLock Lock(&ModifiedFilesSection);
// return the list of modified files
Out << ModifiedFiles;
// @todo: note the last received time, and toss clients that don't heartbeat enough!
// @todo: Right now, there is no directory watcher adding to ModifiedFiles. It had to be pulled from this thread (well, the ModuleManager part)
// We should have a single directory watcher that pushes the changes to all the connections - or possibly pass in a shared DirectoryWatcher
// and have each connection set up a delegate (see p4 history for HandleDirectoryWatcherDirectoryChanged)
}
/* FStreamingNetworkFileServerConnection callbacks
*****************************************************************************/
bool FNetworkFileServerClientConnection::PackageFile( FString& Filename, FArchive& Out )
{
// get file timestamp and send it to client
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
TArray<uint8> Contents;
// open file
IFileHandle* File = Sandbox->OpenRead(*Filename);
if (!File)
{
ServerTimeStamp = FDateTime::MinValue(); // if this was a directory, this will make sure it is not confused with a zero byte file
}
else
{
if (!File->Size())
{
UE_LOG(LogFileServer, Warning, TEXT("Sending empty file %s...."), *Filename);
}
else
{
// read it
Contents.AddUninitialized(File->Size());
File->Read(Contents.GetData(), Contents.Num());
}
// close it
delete File;
UE_LOG(LogFileServer, Display, TEXT("Read %s, %d bytes"), *Filename, Contents.Num());
}
Out << Filename;
Out << ServerTimeStamp;
uint64 FileSize = Contents.Num();
Out << FileSize;
Out.Serialize(Contents.GetData(), FileSize);
return true;
}
void FNetworkFileServerClientConnection::ProcessRecompileShaders( FArchive& In, FArchive& Out )
{
TArray<FString> RecompileModifiedFiles;
TArray<uint8> MeshMaterialMaps;
FShaderRecompileData RecompileData;
RecompileData.PlatformName = ConnectedPlatformName;
RecompileData.ModifiedFiles = &RecompileModifiedFiles;
RecompileData.MeshMaterialMaps = &MeshMaterialMaps;
// tell other side all the materials to load, by pathname
In << RecompileData.MaterialsToLoad;
In << RecompileData.ShaderPlatform;
In << RecompileData.SerializedShaderResources;
In << RecompileData.bCompileChangedShaders;
RecompileShadersDelegate.ExecuteIfBound(RecompileData);
// tell other side what to do!
Out << RecompileModifiedFiles;
Out << MeshMaterialMaps;
}
void FNetworkFileServerClientConnection::ProcessSyncFile( FArchive& In, FArchive& Out )
{
// get filename
FString Filename;
In << Filename;
ConvertClientFilenameToServerFilename(Filename);
//FString AbsFile(FString(*Sandbox->ConvertToAbsolutePathForExternalApp(*Filename)).MakeStandardFilename());
// ^^ we probably in general want that filename, but for cook on the fly, we want the un-sandboxed name
TArray<FString> NewUnsolictedFiles;
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
for (int32 Index = 0; Index < NewUnsolictedFiles.Num(); Index++)
{
if (NewUnsolictedFiles[Index] != Filename)
{
UnsolictedFiles.AddUnique(NewUnsolictedFiles[Index]);
}
}
PackageFile(Filename, Out);
}
FString FNetworkFileServerClientConnection::GetDescription() const
{
return FString("Client For " ) + ConnectedPlatformName;
}