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; }
FArchive* FFileManagerGeneric::CreateFileWriter( const TCHAR* Filename, uint32 Flags ) { // Only allow writes to files that are not signed // Except if the file is missing( that way corrupt ini files can be autogenerated by deleting them ) if( FSHA1::GetFileSHAHash( Filename, NULL ) && FileSize( Filename ) != -1 ) { UE_LOG( LogFileManager, Log, TEXT( "Can't write to signed game file: %s" ),Filename ); return new FArchiveFileWriterDummy(); } MakeDirectory( *FPaths::GetPath(Filename), true ); if( Flags & FILEWRITE_EvenIfReadOnly ) { GetLowLevel().SetReadOnly( Filename, false ); } IFileHandle* Handle = GetLowLevel().OpenWrite( Filename, !!( Flags & FILEWRITE_Append ), !!( Flags & FILEWRITE_AllowRead ) ); if( !Handle ) { if( Flags & FILEWRITE_NoFail ) { UE_LOG( LogFileManager, Fatal, TEXT( "Failed to create file: %s" ), Filename ); } return NULL; } return new FArchiveFileWriterGeneric( Handle, Filename, Handle->Tell() ); }
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; } }
/** * Write out the steam app id to the steam_appid.txt file before initializing the API * @param SteamAppId id assigned to the application by Steam */ static void WriteSteamAppIdToDisk(int32 SteamAppId) { if (SteamAppId > 0) { // Turn off sandbox temporarily to make sure file is where it's always expected FScopeSandboxContext ScopedSandbox(false); // Access the physical file writer directly so that we still write next to the executable in CotF builds. FString SteamAppIdFilename = GetSteamAppIdFilename(); IFileHandle* Handle = IPlatformFile::GetPlatformPhysical().OpenWrite(*SteamAppIdFilename, false, false); if (!Handle) { UE_LOG_ONLINE(Fatal, TEXT("Failed to create file: %s"), *SteamAppIdFilename); } else { FString AppId = FString::Printf(TEXT("%d"), SteamAppId); FBufferArchive Archive; Archive.Serialize((void*)TCHAR_TO_ANSI(*AppId), AppId.Len()); Handle->Write(Archive.GetData(), Archive.Num()); delete Handle; Handle = nullptr; } } }
bool FStreamingNetworkPlatformFile::InitializeInternal(IPlatformFile* Inner, const TCHAR* HostIP) { // look for the commandline that will read files from over the network if (HostIP == nullptr) { UE_LOG(LogStreamingPlatformFile, Error, TEXT("No Host IP specified in the commandline.")); bIsUsable = false; return false; } // optionally get the port from the command line int32 OverridePort; if (FParse::Value(FCommandLine::Get(), TEXT("fileserverport="), OverridePort)) { UE_LOG(LogStreamingPlatformFile, Display, TEXT("Overriding file server port: %d"), OverridePort); FileServerPort = OverridePort; } // Send the filenames and timestamps to the server. FNetworkFileArchive Payload(NFS_Messages::GetFileList); FillGetFileList(Payload, true); // Send the directories over, and wait for a response. FArrayReader Response; if(SendPayloadAndReceiveResponse(Payload,Response)) { // Receive the cooked version information. int32 ServerPackageVersion = 0; int32 ServerPackageLicenseeVersion = 0; ProcessServerInitialResponse(Response, ServerPackageVersion, ServerPackageLicenseeVersion); // Make sure we can sync a file. FString TestSyncFile = FPaths::Combine(*(FPaths::EngineDir()), TEXT("Config/BaseEngine.ini")); IFileHandle* TestFileHandle = OpenRead(*TestSyncFile); if (TestFileHandle != nullptr) { uint8* FileContents = (uint8*)FMemory::Malloc(TestFileHandle->Size()); if (!TestFileHandle->Read(FileContents, TestFileHandle->Size())) { UE_LOG(LogStreamingPlatformFile, Fatal, TEXT("Could not read test file %s."), *TestSyncFile); } FMemory::Free(FileContents); delete TestFileHandle; } else { UE_LOG(LogStreamingPlatformFile, Fatal, TEXT("Could not open test file %s."), *TestSyncFile); } FCommandLine::AddToSubprocessCommandline( *FString::Printf( TEXT("-StreamingHostIP=%s"), HostIP ) ); return true; } return false; }
bool CopyFileToDevice(const FString& IPAPath, FString PathOnDevice, int PacketSize) { bool Result = false; // reconnect to the device and start the AFC service Connect(); if (!AFC::StartService(DeviceHandle, &AFCHandle)) { if (!AFC::ConnectionOpen(AFCHandle, &AFCConnection)) { // ensure the directory on the phone exists FString DirectoryOnDevice = FPaths::GetPath(PathOnDevice); CreateDirectory(DirectoryOnDevice + TEXT("/")); // transfer the file IPlatformFile& PlatformFile = IPlatformFile::GetPlatformPhysical(); IFileHandle* SourceFile = PlatformFile.OpenRead(*IPAPath); if (SourceFile != NULL) { uint64 DestinationHandle = 0; if (AFC::FileRefOpen(AFCConnection, PathOnDevice, 3, &DestinationHandle) == 0) { int TotalBytes = 0; int PacketCount = SourceFile->Size() / PacketSize; uint8* buffer = new uint8[PacketSize]; for (int Index = 0; Index < PacketCount; ++Index) { if (SourceFile->Read(buffer, PacketSize)) { TotalBytes += PacketSize; AFC::FileRefWrite(AFCConnection, DestinationHandle, buffer, PacketSize); } } if (SourceFile->Read(buffer, SourceFile->Size() - TotalBytes)) { AFC::FileRefWrite(AFCConnection, DestinationHandle, buffer, SourceFile->Size() - TotalBytes); } // flush the destination and close AFC::FileRefClose(AFCConnection, DestinationHandle); Result = true; } delete SourceFile; } // stop the AFC service and disconnect from the device AFC::ConnectionClose(AFCConnection); AFCConnection = NULL; Disconnect(); } } return Result; }
FArchive* FFileManagerGeneric::CreateFileReader( const TCHAR* InFilename, uint32 Flags ) { IFileHandle* Handle = GetLowLevel().OpenRead( InFilename, !!(Flags & FILEREAD_AllowWrite) ); if( !Handle ) { if( Flags & FILEREAD_NoFail ) { UE_LOG( LogFileManager, Fatal, TEXT( "Failed to read file: %s" ), InFilename ); } return NULL; } return new FArchiveFileReaderGeneric( Handle, InFilename, Handle->Size() ); }
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; }
/// <summary> Writes a full line at the end of the file created for this session </summary> /// <param name="line"> The text line to append at the end of the file </param> void UMovementTracker::WriteToCurrentFile(FString line) { if (WritePosition && DirectoryExists) { IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); IFileHandle* handle = PlatformFile.OpenWrite(*FileName, true); if (handle) { handle->Write((const uint8 *)TCHAR_TO_ANSI(*line), line.Len()); handle->Write((const uint8 *)"\n", 1); delete handle; } } }
bool FPakPlatformFile::BufferedCopyFile(IFileHandle& Dest, IFileHandle& Source, const int64 FileSize, uint8* Buffer, const int64 BufferSize) const { int64 RemainingSizeToCopy = FileSize; // Continue copying chunks using the buffer while (RemainingSizeToCopy > 0) { const int64 SizeToCopy = FMath::Min(BufferSize, RemainingSizeToCopy); if (Source.Read(Buffer, SizeToCopy) == false) { return false; } if (Dest.Write(Buffer, SizeToCopy) == false) { return false; } RemainingSizeToCopy -= SizeToCopy; } return true; }
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::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 UE_LOG(LogFileServer, Warning, TEXT("Request for missing file %s."), *Filename ); } 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); }
void FNetworkPlatformFile::InitializeAfterSetActive() { double NetworkFileStartupTime = 0.0; { SCOPE_SECONDS_COUNTER(NetworkFileStartupTime); // send the filenames and timestamps to the server FNetworkFileArchive Payload(NFS_Messages::GetFileList); FillGetFileList(Payload, false); // send the directories over, and wait for a response FArrayReader Response; if (!SendPayloadAndReceiveResponse(Payload, Response)) { delete Transport; return; } else { // receive the cooked version information int32 ServerPackageVersion = 0; int32 ServerPackageLicenseeVersion = 0; ProcessServerInitialResponse(Response, ServerPackageVersion, ServerPackageLicenseeVersion); // receive a list of the cache files and their timestamps TMap<FString, FDateTime> ServerCachedFiles; Response << ServerCachedFiles; bool bDeleteAllFiles = true; // Check the stored cooked version FString CookedVersionFile = FPaths::GeneratedConfigDir() / TEXT("CookedVersion.txt"); if (InnerPlatformFile->FileExists(*CookedVersionFile) == true) { IFileHandle* FileHandle = InnerPlatformFile->OpenRead(*CookedVersionFile); if (FileHandle != NULL) { int32 StoredPackageCookedVersion; int32 StoredPackageCookedLicenseeVersion; if (FileHandle->Read((uint8*)&StoredPackageCookedVersion, sizeof(int32)) == true) { if (FileHandle->Read((uint8*)&StoredPackageCookedLicenseeVersion, sizeof(int32)) == true) { if ((ServerPackageVersion == StoredPackageCookedVersion) && (ServerPackageLicenseeVersion == StoredPackageCookedLicenseeVersion)) { bDeleteAllFiles = false; } else { UE_LOG(LogNetworkPlatformFile, Display, TEXT("Engine version mismatch: Server %d.%d, Stored %d.%d\n"), ServerPackageVersion, ServerPackageLicenseeVersion, StoredPackageCookedVersion, StoredPackageCookedLicenseeVersion); } } } delete FileHandle; } } else { UE_LOG(LogNetworkPlatformFile, Display, TEXT("Cooked version file missing: %s\n"), *CookedVersionFile); } if (bDeleteAllFiles == true) { // Make sure the config file exists... InnerPlatformFile->CreateDirectoryTree(*(FPaths::GeneratedConfigDir())); // Update the cooked version file IFileHandle* FileHandle = InnerPlatformFile->OpenWrite(*CookedVersionFile); if (FileHandle != NULL) { FileHandle->Write((const uint8*)&ServerPackageVersion, sizeof(int32)); FileHandle->Write((const uint8*)&ServerPackageLicenseeVersion, sizeof(int32)); delete FileHandle; } } // list of directories to skip TArray<FString> DirectoriesToSkip; TArray<FString> DirectoriesToNotRecurse; // use the timestamp grabbing visitor to get all the content times FLocalTimestampDirectoryVisitor Visitor(*InnerPlatformFile, DirectoriesToSkip, DirectoriesToNotRecurse, false); TArray<FString> RootContentPaths; FPackageName::QueryRootContentPaths( RootContentPaths ); for( TArray<FString>::TConstIterator RootPathIt( RootContentPaths ); RootPathIt; ++RootPathIt ) { const FString& RootPath = *RootPathIt; const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath); InnerPlatformFile->IterateDirectory( *ContentFolder, Visitor); } // delete out of date files using the server cached files for (TMap<FString, FDateTime>::TIterator It(ServerCachedFiles); It; ++It) { bool bDeleteFile = bDeleteAllFiles; FString ServerFile = It.Key(); // Convert the filename to the client version ConvertServerFilenameToClientFilename(ServerFile); // Set it in the visitor file times list Visitor.FileTimes.Add(ServerFile, FDateTime::MinValue()); if (bDeleteFile == false) { // Check the time stamps... // get local time FDateTime LocalTime = InnerPlatformFile->GetTimeStamp(*ServerFile); // If local time == MinValue than the file does not exist in the cache. if (LocalTime != FDateTime::MinValue()) { FDateTime ServerTime = It.Value(); // delete if out of date // We will use 1.0 second as the tolerance to cover any platform differences in resolution FTimespan TimeDiff = LocalTime - ServerTime; double TimeDiffInSeconds = TimeDiff.GetTotalSeconds(); bDeleteFile = (TimeDiffInSeconds > 1.0) || (TimeDiffInSeconds < -1.0); if (bDeleteFile == true) { if (InnerPlatformFile->FileExists(*ServerFile) == true) { UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: TimeDiff %5.3f, %s"), TimeDiffInSeconds, *It.Key()); } else { // It's a directory bDeleteFile = false; } } } } if (bDeleteFile == true) { InnerPlatformFile->DeleteFile(*ServerFile); } } // Any content files we have locally that were not cached, delete them for (TMap<FString, FDateTime>::TIterator It(Visitor.FileTimes); It; ++It) { if (It.Value() != FDateTime::MinValue()) { // This was *not* found in the server file list... delete it UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: %s"), *It.Key()); InnerPlatformFile->DeleteFile(*It.Key()); } } // make sure we can sync a file FString TestSyncFile = FPaths::Combine(*(FPaths::EngineDir()), TEXT("Config/BaseEngine.ini")); InnerPlatformFile->SetReadOnly(*TestSyncFile, false); InnerPlatformFile->DeleteFile(*TestSyncFile); if (InnerPlatformFile->FileExists(*TestSyncFile)) { UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not delete file sync test file %s."), *TestSyncFile); } EnsureFileIsLocal(TestSyncFile); if (!InnerPlatformFile->FileExists(*TestSyncFile) || InnerPlatformFile->FileSize(*TestSyncFile) < 1) { UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not sync test file %s."), *TestSyncFile); } } } FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Network file startup time: %5.3f seconds\n"), NetworkFileStartupTime); }
/** * Kicks directory watcher test/ */ int32 DirectoryWatcherTest(const TCHAR* CommandLine) { FPlatformMisc::SetCrashHandler(NULL); FPlatformMisc::SetGracefulTerminationHandler(); GEngineLoop.PreInit(CommandLine); UE_LOG(LogTestPAL, Display, TEXT("Running directory watcher test.")); IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); FString TestDir = FString::Printf(TEXT("%sDirectoryWatcherTest%d"), FPlatformProcess::UserTempDir(), FPlatformProcess::GetCurrentProcessId()); if (PlatformFile.CreateDirectory(*TestDir) && PlatformFile.CreateDirectory(*(TestDir + TEXT("/subtest")))) { FChangeDetector Detector; FDelegateHandle DirectoryChangedHandle; IDirectoryWatcher* DirectoryWatcher = FModuleManager::Get().LoadModuleChecked<FDirectoryWatcherModule>(TEXT("DirectoryWatcher")).Get(); if (DirectoryWatcher) { auto Callback = IDirectoryWatcher::FDirectoryChanged::CreateRaw(&Detector, &FChangeDetector::OnDirectoryChanged); DirectoryWatcher->RegisterDirectoryChangedCallback_Handle(TestDir, Callback, DirectoryChangedHandle); UE_LOG(LogTestPAL, Display, TEXT("Registered callback for changes in '%s'"), *TestDir); } else { UE_LOG(LogTestPAL, Fatal, TEXT("Could not get DirectoryWatcher module")); } FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // create and remove directory UE_LOG(LogTestPAL, Display, TEXT("Creating DIRECTORY '%s'"), *(TestDir + TEXT("/test"))); verify(PlatformFile.CreateDirectory(*(TestDir + TEXT("/test")))); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); UE_LOG(LogTestPAL, Display, TEXT("Deleting DIRECTORY '%s'"), *(TestDir + TEXT("/test"))); verify(PlatformFile.DeleteDirectory(*(TestDir + TEXT("/test")))); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // create and remove in a sub directory UE_LOG(LogTestPAL, Display, TEXT("Creating DIRECTORY '%s'"), *(TestDir + TEXT("/subtest/blah"))); verify(PlatformFile.CreateDirectory(*(TestDir + TEXT("/subtest/blah")))); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); UE_LOG(LogTestPAL, Display, TEXT("Deleting DIRECTORY '%s'"), *(TestDir + TEXT("/subtest/blah"))); verify(PlatformFile.DeleteDirectory(*(TestDir + TEXT("/subtest/blah")))); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); { // create file FString DummyFileName = TestDir + TEXT("/test file.bin"); UE_LOG(LogTestPAL, Display, TEXT("Creating FILE '%s'"), *DummyFileName); IFileHandle* DummyFile = PlatformFile.OpenWrite(*DummyFileName); check(DummyFile); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // modify file UE_LOG(LogTestPAL, Display, TEXT("Modifying FILE '%s'"), *DummyFileName); uint8 Contents = 0; DummyFile->Write(&Contents, sizeof(Contents)); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // close the file UE_LOG(LogTestPAL, Display, TEXT("Closing FILE '%s'"), *DummyFileName); delete DummyFile; DummyFile = nullptr; DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // delete file UE_LOG(LogTestPAL, Display, TEXT("Deleting FILE '%s'"), *DummyFileName); verify(PlatformFile.DeleteFile(*DummyFileName)); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); } // now the same in a grandchild directory { FString GrandChildDir = TestDir + TEXT("/subtest/grandchild"); UE_LOG(LogTestPAL, Display, TEXT("Creating DIRECTORY '%s'"), *GrandChildDir); verify(PlatformFile.CreateDirectory(*GrandChildDir)); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); { // create file FString DummyFileName = GrandChildDir + TEXT("/test file.bin"); UE_LOG(LogTestPAL, Display, TEXT("Creating FILE '%s'"), *DummyFileName); IFileHandle* DummyFile = PlatformFile.OpenWrite(*DummyFileName); check(DummyFile); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // modify file UE_LOG(LogTestPAL, Display, TEXT("Modifying FILE '%s'"), *DummyFileName); uint8 Contents = 0; DummyFile->Write(&Contents, sizeof(Contents)); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // close the file UE_LOG(LogTestPAL, Display, TEXT("Closing FILE '%s'"), *DummyFileName); delete DummyFile; DummyFile = nullptr; DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); // delete file UE_LOG(LogTestPAL, Display, TEXT("Deleting FILE '%s'"), *DummyFileName); PlatformFile.DeleteFile(*DummyFileName); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); } UE_LOG(LogTestPAL, Display, TEXT("Deleting DIRECTORY '%s'"), *GrandChildDir); verify(PlatformFile.DeleteDirectory(*GrandChildDir)); DirectoryWatcher->Tick(1.0f); FPlatformProcess::Sleep(1.0f); DirectoryWatcher->Tick(1.0f); } // clean up verify(DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(TestDir, DirectoryChangedHandle)); // remove dirs as well verify(PlatformFile.DeleteDirectory(*(TestDir + TEXT("/subtest")))); verify(PlatformFile.DeleteDirectory(*TestDir)); UE_LOG(LogTestPAL, Display, TEXT("End of test")); } else { UE_LOG(LogTestPAL, Fatal, TEXT("Could not create test directory %s."), *TestDir); } FEngineLoop::AppPreExit(); FEngineLoop::AppExit(); return 0; }