// Copyright Epic Games, Inc. All Rights Reserved. #include "StorageServerPlatformFile.h" #include "Algo/Replace.h" #include "CookOnTheFly.h" #include "CookOnTheFlyPackageStore.h" #include "HAL/FileManagerGeneric.h" #include "HAL/IPlatformFileModule.h" #include "Misc/App.h" #include "Misc/CommandLine.h" #include "Misc/CoreDelegates.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Misc/ScopeRWLock.h" #include "Misc/StringBuilder.h" #include "Modules/ModuleManager.h" #include "Modules/ModuleManager.h" #include "Serialization/CompactBinarySerialization.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "StorageServerConnection.h" #include "StorageServerIoDispatcherBackend.h" #include "StorageServerPackageStore.h" DEFINE_LOG_CATEGORY_STATIC(LogStorageServerPlatformFile, Log, All); #if !UE_BUILD_SHIPPING FStorageServerFileSystemTOC::~FStorageServerFileSystemTOC() { FWriteScopeLock _(TocLock); for (auto& KV : Directories) { delete KV.Value; } } FStorageServerFileSystemTOC::FDirectory* FStorageServerFileSystemTOC::AddDirectoriesRecursive(const FString& DirectoryPath) { FDirectory* Directory = new FDirectory(); Directories.Add(DirectoryPath, Directory); FString ParentDirectoryPath = FPaths::GetPath(DirectoryPath); FDirectory* ParentDirectory; if (ParentDirectoryPath.IsEmpty()) { ParentDirectory = &Root; } else { ParentDirectory = Directories.FindRef(ParentDirectoryPath); if (!ParentDirectory) { ParentDirectory = AddDirectoriesRecursive(ParentDirectoryPath); } } ParentDirectory->Directories.Add(DirectoryPath); return Directory; } void FStorageServerFileSystemTOC::AddFile(const FIoChunkId& FileChunkId, FStringView PathView) { FWriteScopeLock _(TocLock); const int32 FileIndex = Files.Num(); FFile& NewFile = Files.AddDefaulted_GetRef(); NewFile.FileChunkId = FileChunkId; NewFile.FilePath = PathView; FilePathToIndexMap.Add(NewFile.FilePath, FileIndex); FString DirectoryPath = FPaths::GetPath(NewFile.FilePath); FDirectory* Directory = Directories.FindRef(DirectoryPath); if (!Directory) { Directory = AddDirectoriesRecursive(DirectoryPath); } Directory->Files.Add(FileIndex); } bool FStorageServerFileSystemTOC::FileExists(const FString& Path) { FReadScopeLock _(TocLock); return FilePathToIndexMap.Contains(Path); } bool FStorageServerFileSystemTOC::DirectoryExists(const FString& Path) { FReadScopeLock _(TocLock); return Directories.Contains(Path); } const FIoChunkId* FStorageServerFileSystemTOC::GetFileChunkId(const FString& Path) { FReadScopeLock _(TocLock); if (const int32* FileIndex = FilePathToIndexMap.Find(Path)) { return &Files[*FileIndex].FileChunkId; } return nullptr; } bool FStorageServerFileSystemTOC::IterateDirectory(const FString& Path, TFunctionRef Callback) { UE_LOG(LogStorageServerPlatformFile, Verbose, TEXT("IterateDirectory '%s'"), *Path); FReadScopeLock _(TocLock); FDirectory* Directory = Directories.FindRef(Path); if (!Directory) { return false; } for (int32 FileIndex : Directory->Files) { const FFile& File = Files[FileIndex]; if (!Callback(File.FileChunkId, *File.FilePath)) { return false; } } for (const FString& ChildDirectoryPath : Directory->Directories) { if (!Callback(FIoChunkId(), *ChildDirectoryPath)) { return false; } } return true; } class FStorageServerFileHandle : public IFileHandle { enum { BufferSize = 64 << 10 }; FStorageServerPlatformFile& Owner; FIoChunkId FileChunkId; FString Filename; int64 FilePos = 0; int64 FileSize = -1; int64 BufferStart = -1; int64 BufferEnd = -1; uint8 Buffer[BufferSize]; public: FStorageServerFileHandle(FStorageServerPlatformFile& InOwner, FIoChunkId InFileChunkId, const TCHAR* InFilename) : Owner(InOwner) , FileChunkId(InFileChunkId) , Filename(InFilename) { } ~FStorageServerFileHandle() { } virtual int64 Size() override { if (FileSize < 0) { const FFileStatData FileStatData = Owner.SendGetStatDataMessage(FileChunkId); if (FileStatData.bIsValid) { FileSize = FileStatData.FileSize; } else { UE_LOG(LogStorageServerPlatformFile, Warning, TEXT("Failed to obtain size of file '%s'"), *Filename); FileSize = 0; } } return FileSize; } virtual int64 Tell() override { return FilePos; } virtual bool Seek(int64 NewPosition) override { FilePos = NewPosition; return true; } virtual bool SeekFromEnd(int64 NewPositionRelativeToEnd = 0) override { return Seek(Size() + NewPositionRelativeToEnd); } virtual bool Read(uint8* Destination, int64 BytesToRead) override { if (BytesToRead == 0) { return true; } if (BytesToRead > BufferSize) { const int64 BytesRead = Owner.SendReadMessage(Destination, FileChunkId, FilePos, BytesToRead); if (BytesRead == BytesToRead) { FilePos += BytesRead; return true; } return false; } int64 BytesReadFromBuffer = 0; if (FilePos >= BufferStart && FilePos < BufferEnd) { const int64 BufferOffset = FilePos - BufferStart; check(BufferOffset < BufferSize); BytesReadFromBuffer = FMath::Min(BufferSize - BufferOffset, BytesToRead); FMemory::Memcpy(Destination, Buffer + BufferOffset, BytesReadFromBuffer); if (BytesReadFromBuffer == BytesToRead) { FilePos += BytesReadFromBuffer; return true; } } const int64 BytesRead = Owner.SendReadMessage(Buffer, FileChunkId, FilePos + BytesReadFromBuffer, BufferSize); BufferStart = FilePos + BytesReadFromBuffer; BufferEnd = BufferStart + BytesRead; const int64 BytesToReadFromBuffer = FMath::Min(BytesRead, BytesToRead - BytesReadFromBuffer); FMemory::Memcpy(Destination + BytesReadFromBuffer, Buffer, BytesToReadFromBuffer); BytesReadFromBuffer += BytesToReadFromBuffer; if (BytesReadFromBuffer == BytesToRead) { FilePos += BytesReadFromBuffer; return true; } return false; } virtual bool Write(const uint8* Source, int64 BytesToWrite) override { check(false); return false; } virtual bool Flush(const bool bFullFlush = false) override { return false; } virtual bool Truncate(int64 NewSize) override { return false; } }; FStorageServerPlatformFile::FStorageServerPlatformFile() { } FStorageServerPlatformFile::~FStorageServerPlatformFile() { } TUniquePtr FStorageServerPlatformFile::TryFindProjectStoreMarkerFile(IPlatformFile* Inner) const { if (Inner == nullptr) { return nullptr; } FString RelativeStagedPath = TEXT("../../../"); FString RootPath = FPaths::RootDir(); FString PlatformName = FPlatformProperties::PlatformName(); FString CookedOutputPath = FPaths::Combine(FPaths::ProjectDir(), TEXT("Saved"), TEXT("Cooked"), PlatformName); TArray PotentialProjectStorePaths; PotentialProjectStorePaths.Add(RelativeStagedPath); PotentialProjectStorePaths.Add(CookedOutputPath); PotentialProjectStorePaths.Add(RootPath); for (const FString& ProjectStorePath : PotentialProjectStorePaths) { FString ProjectMarkerPath = ProjectStorePath / TEXT("ue.projectstore"); if (IFileHandle* ProjectStoreMarkerHandle = Inner->OpenRead(*ProjectMarkerPath); ProjectStoreMarkerHandle != nullptr) { UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Found '%s'"), *ProjectMarkerPath); return TUniquePtr(new FArchiveFileReaderGeneric(ProjectStoreMarkerHandle, *ProjectMarkerPath, ProjectStoreMarkerHandle->Size())); } } return nullptr; } bool FStorageServerPlatformFile::ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const { #if WITH_COTF UE::Cook::ICookOnTheFlyModule& CookOnTheFlyModule = FModuleManager::LoadModuleChecked(TEXT("CookOnTheFly")); TSharedPtr DefaultConnection = CookOnTheFlyModule.GetDefaultServerConnection(); if (DefaultConnection.IsValid() && !DefaultConnection->GetZenProjectName().IsEmpty()) { HostAddrs.Append(DefaultConnection->GetZenHostNames()); HostPort = DefaultConnection->GetZenHostPort(); return true; } #endif TUniquePtr ProjectStoreMarkerReader = TryFindProjectStoreMarkerFile(Inner); if (ProjectStoreMarkerReader != nullptr) { TSharedPtr ProjectStoreObject; TSharedRef> Reader = TJsonReaderFactory::Create(ProjectStoreMarkerReader.Get()); if (FJsonSerializer::Deserialize(Reader, ProjectStoreObject) && ProjectStoreObject.IsValid()) { const TSharedPtr* ZenServerObjectPtr = nullptr; if (ProjectStoreObject->TryGetObjectField(TEXT("zenserver"), ZenServerObjectPtr) && (ZenServerObjectPtr != nullptr)) { const TSharedPtr& ZenServerObject = *ZenServerObjectPtr; #if PLATFORM_DESKTOP || PLATFORM_ANDROID FString HostName; if (ZenServerObject->TryGetStringField(TEXT("hostname"), HostName) && !HostName.IsEmpty()) { HostAddrs.Add(HostName); } #endif const TArray>* RemoteHostNamesArrayPtr = nullptr; if (ZenServerObject->TryGetArrayField(TEXT("remotehostnames"), RemoteHostNamesArrayPtr) && (RemoteHostNamesArrayPtr != nullptr)) { for (TSharedPtr RemoteHostName : *RemoteHostNamesArrayPtr) { if (FString RemoteHostNameStr = RemoteHostName->AsString(); !RemoteHostNameStr.IsEmpty()) { HostAddrs.Add(RemoteHostNameStr); } } } uint16 SerializedHostPort = 0; if (ZenServerObject->TryGetNumberField(TEXT("hostport"), SerializedHostPort) && (SerializedHostPort != 0)) { HostPort = SerializedHostPort; } UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Using connection settings from ue.projectstore: HostAddrs='%s' and HostPort='%d'"), *FString::Join(HostAddrs, TEXT("+")), HostPort); } } else { UE_LOG(LogStorageServerPlatformFile, Error, TEXT("Failed to Deserialize ue.projectstore!'")); } } FString Host; if (FParse::Value(FCommandLine::Get(), TEXT("-ZenStoreHost="), Host)) { UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Adding connection settings from command line: -ZenStoreHost='%s'"), *Host); if (!Host.ParseIntoArray(HostAddrs, TEXT("+"), true)) { HostAddrs.Add(Host); } } if (FParse::Value(CmdLine, TEXT("-ZenStorePort="), HostPort)) { UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Using connection settings from command line: -ZenStorePort='%d'"), HostPort); } return HostAddrs.Num() > 0; } bool FStorageServerPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine) { LowerLevel = Inner; if (HostAddrs.Num() > 0) { // Don't initialize the connection yet because we want to incorporate project file path information into the initialization. TUniquePtr ProjectStoreMarkerReader = TryFindProjectStoreMarkerFile(Inner); if (ProjectStoreMarkerReader != nullptr) { TSharedPtr ProjectStoreObject; TSharedRef> Reader = TJsonReaderFactory::Create(ProjectStoreMarkerReader.Get()); if (FJsonSerializer::Deserialize(Reader, ProjectStoreObject) && ProjectStoreObject.IsValid()) { const TSharedPtr* ZenServerObjectPtr = nullptr; if (ProjectStoreObject->TryGetObjectField(TEXT("zenserver"), ZenServerObjectPtr) && (ZenServerObjectPtr != nullptr)) { const TSharedPtr& ZenServerObject = *ZenServerObjectPtr; ServerProject = ZenServerObject->GetStringField(TEXT("projectid")); ServerPlatform = ZenServerObject->GetStringField(TEXT("oplogid")); UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Using settings from ue.projectstore: ServerProject='%s' and ServerPlatform='%s'"), *ServerProject, *ServerPlatform); } } } if (FParse::Value(CmdLine, TEXT("-ZenStoreProject="), ServerProject)) { UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Using settings from command line: -ZenStoreProject='%s'"), *ServerProject); } if (FParse::Value(CmdLine, TEXT("-ZenStorePlatform="), ServerPlatform)) { UE_LOG(LogStorageServerPlatformFile, Display, TEXT("Using settings from command line: -ZenStorePlatform='%s'"), *ServerPlatform); } return true; } return false; } void FStorageServerPlatformFile::InitializeAfterProjectFilePath() { #if WITH_COTF UE::Cook::ICookOnTheFlyModule& CookOnTheFlyModule = FModuleManager::LoadModuleChecked(TEXT("CookOnTheFly")); CookOnTheFlyServerConnection = CookOnTheFlyModule.GetDefaultServerConnection(); if (CookOnTheFlyServerConnection) { CookOnTheFlyServerConnection->OnMessage().AddRaw(this, &FStorageServerPlatformFile::OnCookOnTheFlyMessage); ServerProject = CookOnTheFlyServerConnection->GetZenProjectName(); ServerPlatform = CookOnTheFlyServerConnection->GetPlatformName(); } #endif Connection.Reset(new FStorageServerConnection()); const TCHAR* ProjectOverride = ServerProject.IsEmpty() ? nullptr : *ServerProject; const TCHAR* PlatformOverride = ServerPlatform.IsEmpty() ? nullptr : *ServerPlatform; if (Connection->Initialize(HostAddrs, HostPort, ProjectOverride, PlatformOverride)) { if (SendGetFileListMessage()) { FIoDispatcher& IoDispatcher = FIoDispatcher::Get(); TSharedRef IoDispatcherBackend = MakeShared(*Connection.Get()); IoDispatcher.Mount(IoDispatcherBackend); #if WITH_COTF if (CookOnTheFlyServerConnection) { FPackageStore::Get().Mount(MakeShared(*CookOnTheFlyServerConnection.Get())); } else #endif { FPackageStore::Get().Mount(MakeShared(*Connection.Get())); } } else { UE_LOG(LogStorageServerPlatformFile, Fatal, TEXT("Failed to get file list from Zen at '%s'"), *Connection->GetHostAddr()); } } else { if (!FApp::IsUnattended()) { FText FailedConnectionTitle = NSLOCTEXT("StorageServer", "StorageServer_ConnectFailedTitle", "Failed to connect"); FText FailedConnectionText = FText::Format(NSLOCTEXT("StorageServer", "StorageServer_ConnectFailedText", "Network data streaming failed to connect to any of the following data sources:\n\n{0}\n\n" "This can be due to the sources being offline, the Unreal Zen Storage process not currently running, " "invalid addresses, firewall blocking, or the sources being on a different network from this device. " "Please verify that your Unreal Zen Storage process is running using the ZenDashboard utility. " "If these issues can't be addressed, you can use an installed build without network data streaming by " "building with the '-pak' argument. This process will now exit."), FText::FromString(FString::Join(HostAddrs, TEXT("\n")))); FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *FailedConnectionText.ToString(), *FailedConnectionTitle.ToString()); } UE_LOG(LogStorageServerPlatformFile, Error, TEXT("Failed to initialize connection to %s"), *FString::Join(HostAddrs, TEXT("\n"))); FPlatformMisc::RequestExit(true); } } bool FStorageServerPlatformFile::FileExists(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return true; } return LowerLevel->FileExists(Filename); } FDateTime FStorageServerPlatformFile::GetTimeStamp(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilename)) { const FFileStatData FileStatData = SendGetStatDataMessage(*FileChunkId); check(FileStatData.bIsValid); return FileStatData.ModificationTime; } } return LowerLevel->GetTimeStamp(Filename); } FDateTime FStorageServerPlatformFile::GetAccessTimeStamp(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilename)) { const FFileStatData FileStatData = SendGetStatDataMessage(*FileChunkId); check(FileStatData.bIsValid); return FileStatData.AccessTime; } } return LowerLevel->GetAccessTimeStamp(Filename); } int64 FStorageServerPlatformFile::FileSize(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilename)) { const FFileStatData FileStatData = SendGetStatDataMessage(*FileChunkId); check(FileStatData.bIsValid); return FileStatData.FileSize; } } return LowerLevel->FileSize(Filename); } int64 FStorageServerPlatformFile::FileSize(const TCHAR* Filename, EPlatformFileFlags PlatformFlags) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilename)) { const FFileStatData FileStatData = SendGetStatDataMessage(*FileChunkId); check(FileStatData.bIsValid); return FileStatData.FileSize; } } if (PlatformFlags == EPlatformFileFlags::StorageServerOnly) { return -1; } return LowerLevel->FileSize(Filename); } bool FStorageServerPlatformFile::IsReadOnly(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return true; } return LowerLevel->IsReadOnly(Filename); } FFileStatData FStorageServerPlatformFile::GetStatData(const TCHAR* FilenameOrDirectory) { TStringBuilder<1024> StorageServerFilenameOrDirectory; if (MakeStorageServerPath(FilenameOrDirectory, StorageServerFilenameOrDirectory)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilenameOrDirectory)) { return SendGetStatDataMessage(*FileChunkId); } else if (ServerToc.DirectoryExists(*StorageServerFilenameOrDirectory)) { return FFileStatData( FDateTime::MinValue(), FDateTime::MinValue(), FDateTime::MinValue(), 0, true, true); } } return LowerLevel->GetStatData(FilenameOrDirectory); } IFileHandle* FStorageServerPlatformFile::InternalOpenFile(const FIoChunkId& FileChunkId, const TCHAR* LocalFilename) { return new FStorageServerFileHandle(*this, FileChunkId, LocalFilename); } IFileHandle* FStorageServerPlatformFile::OpenRead(const TCHAR* Filename, bool bAllowWrite) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename)) { if (const FIoChunkId* FileChunkId = ServerToc.GetFileChunkId(*StorageServerFilename)) { return InternalOpenFile(*FileChunkId, Filename); } } return LowerLevel->OpenRead(Filename, bAllowWrite); } bool FStorageServerPlatformFile::IterateDirectory(const TCHAR* Directory, IPlatformFile::FDirectoryVisitor& Visitor) { TStringBuilder<1024> StorageServerDirectory; bool bResult = false; if (MakeStorageServerPath(Directory, StorageServerDirectory) && ServerToc.DirectoryExists(*StorageServerDirectory)) { bResult |= ServerToc.IterateDirectory(*StorageServerDirectory, [this, &Visitor](const FIoChunkId& FileChunkId, const TCHAR* FilenameOrDirectory) { TStringBuilder<1024> LocalPath; bool bConverted = MakeLocalPath(FilenameOrDirectory, LocalPath); check(bConverted); const bool bDirectory = !FileChunkId.IsValid(); return Visitor.CallShouldVisitAndVisit(*LocalPath, bDirectory); }); } else { bResult |= LowerLevel->IterateDirectory(Directory, Visitor); } return bResult; } bool FStorageServerPlatformFile::IterateDirectoryStat(const TCHAR* Directory, FDirectoryStatVisitor& Visitor) { TStringBuilder<1024> StorageServerDirectory; bool bResult = false; if (MakeStorageServerPath(Directory, StorageServerDirectory) && ServerToc.DirectoryExists(*StorageServerDirectory)) { bResult |= ServerToc.IterateDirectory(*StorageServerDirectory, [this, &Visitor](const FIoChunkId& FileChunkId, const TCHAR* ServerFilenameOrDirectory) { TStringBuilder<1024> LocalPath; bool bConverted = MakeLocalPath(ServerFilenameOrDirectory, LocalPath); check(bConverted); FFileStatData FileStatData; if (FileChunkId.IsValid()) { FileStatData = SendGetStatDataMessage(FileChunkId); check(FileStatData.bIsValid); } else { FileStatData = FFileStatData( FDateTime::MinValue(), FDateTime::MinValue(), FDateTime::MinValue(), 0, true, true); } return Visitor.CallShouldVisitAndVisit(*LocalPath, FileStatData); }); } else { bResult |= LowerLevel->IterateDirectoryStat(Directory, Visitor); } return bResult; } IMappedFileHandle* FStorageServerPlatformFile::OpenMapped(const TCHAR* Filename) { return LowerLevel->OpenMapped(Filename); } bool FStorageServerPlatformFile::DirectoryExists(const TCHAR* Directory) { TStringBuilder<1024> StorageServerDirectory; if (MakeStorageServerPath(Directory, StorageServerDirectory) && ServerToc.DirectoryExists(*StorageServerDirectory)) { return true; } return LowerLevel->DirectoryExists(Directory); } FString FStorageServerPlatformFile::GetFilenameOnDisk(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { UE_LOG(LogStorageServerPlatformFile, Warning, TEXT("Attempting to get disk filename of remote file '%s'"), Filename); return Filename; } return LowerLevel->GetFilenameOnDisk(Filename); } bool FStorageServerPlatformFile::DeleteFile(const TCHAR* Filename) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return false; } return LowerLevel->DeleteFile(Filename); } bool FStorageServerPlatformFile::MoveFile(const TCHAR* To, const TCHAR* From) { TStringBuilder<1024> StorageServerTo; if (MakeStorageServerPath(To, StorageServerTo) && ServerToc.FileExists(*StorageServerTo)) { return false; } TStringBuilder<1024> StorageServerFrom; if (MakeStorageServerPath(From, StorageServerFrom)) { if (const FIoChunkId* FromFileChunkId = ServerToc.GetFileChunkId(*StorageServerFrom)) { TUniquePtr ToFile(LowerLevel->OpenWrite(To, false, false)); if (!ToFile) { return false; } TUniquePtr FromFile(InternalOpenFile(*FromFileChunkId, *StorageServerFrom)); if (!FromFile) { return false; } const int64 BufferSize = 64 << 10; TArray Buffer; Buffer.SetNum(BufferSize); int64 BytesLeft = FromFile->Size(); while (BytesLeft) { int64 BytesToWrite = FMath::Min(BufferSize, BytesLeft); if (!FromFile->Read(Buffer.GetData(), BytesToWrite)) { return false; } if (!ToFile->Write(Buffer.GetData(), BytesToWrite)) { return false; } BytesLeft -= BytesToWrite; } return true; } } return LowerLevel->MoveFile(To, From); } bool FStorageServerPlatformFile::SetReadOnly(const TCHAR* Filename, bool bNewReadOnlyValue) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return bNewReadOnlyValue; } return LowerLevel->SetReadOnly(Filename, bNewReadOnlyValue); } void FStorageServerPlatformFile::SetTimeStamp(const TCHAR* Filename, FDateTime DateTime) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return; } LowerLevel->SetTimeStamp(Filename, DateTime); } IFileHandle* FStorageServerPlatformFile::OpenWrite(const TCHAR* Filename, bool bAppend, bool bAllowRead) { TStringBuilder<1024> StorageServerFilename; if (MakeStorageServerPath(Filename, StorageServerFilename) && ServerToc.FileExists(*StorageServerFilename)) { return nullptr; } return LowerLevel->OpenWrite(Filename, bAppend, bAllowRead); } bool FStorageServerPlatformFile::CreateDirectory(const TCHAR* Directory) { TStringBuilder<1024> StorageServerDirectory; if (MakeStorageServerPath(Directory, StorageServerDirectory) && ServerToc.DirectoryExists(*StorageServerDirectory)) { return true; } return LowerLevel->CreateDirectory(Directory); } bool FStorageServerPlatformFile::DeleteDirectory(const TCHAR* Directory) { TStringBuilder<1024> StorageServerDirectory; if (MakeStorageServerPath(Directory, StorageServerDirectory) && ServerToc.DirectoryExists(*StorageServerDirectory)) { return false; } return LowerLevel->DeleteDirectory(Directory); } FString FStorageServerPlatformFile::ConvertToAbsolutePathForExternalAppForRead(const TCHAR* Filename) { #if PLATFORM_DESKTOP && (UE_GAME || UE_SERVER) TStringBuilder<1024> Result; // New code should not end up in here and should instead be written in such a // way that data can be served from a (remote) server. // Some data must exist in files on disk such that it can be accessed by external // APIs. Any such data required by a title should have been written to Saved/Cooked // at cook time. If a file prefix with UE's canonical ../../../ is requested we // look inside Saved/Cooked. A read-only filesystem overlay if you will. static FString* CookedDir = nullptr; if (CookedDir == nullptr) { static FString Inner; CookedDir = &Inner; Result << *FPaths::ProjectDir(); Result << TEXT("Saved/Cooked/"); Result << FPlatformProperties::PlatformName(); Result << TEXT("/"); Inner = Result.ToString(); } else { Result << *(*CookedDir); } const TCHAR* DotSlashSkip = Filename; for (; *DotSlashSkip == '.' || *DotSlashSkip == '/'; ++DotSlashSkip); if (PTRINT(DotSlashSkip - Filename) == 9) // 9 == ../../../ { Result << DotSlashSkip; if (LowerLevel->FileExists(Result.ToString())) { return FString(Result.GetData(), Result.Len()); } } #endif return LowerLevel->ConvertToAbsolutePathForExternalAppForRead(Filename); } bool FStorageServerPlatformFile::MakeStorageServerPath(const TCHAR* LocalFilenameOrDirectory, FStringBuilderBase& OutPath) const { FStringView LocalEngineDirView(FPlatformMisc::EngineDir()); FStringView LocalProjectDirView(FPlatformMisc::ProjectDir()); FStringView LocalFilenameOrDirectoryView(LocalFilenameOrDirectory); bool bValid = false; if (LocalFilenameOrDirectoryView.StartsWith(LocalEngineDirView, ESearchCase::IgnoreCase)) { OutPath.Append(ServerEngineDirView); OutPath.Append(LocalFilenameOrDirectoryView.RightChop(LocalEngineDirView.Len())); bValid = true; } else if (LocalFilenameOrDirectoryView.StartsWith(LocalProjectDirView, ESearchCase::IgnoreCase)) { OutPath.Append(ServerProjectDirView); OutPath.Append(LocalFilenameOrDirectoryView.RightChop(LocalProjectDirView.Len())); bValid = true; } if (bValid) { Algo::Replace(MakeArrayView(OutPath), '\\', '/'); OutPath.RemoveSuffix(LocalFilenameOrDirectoryView.EndsWith('/') ? 1 : 0); } return bValid; } bool FStorageServerPlatformFile::MakeLocalPath(const TCHAR* ServerFilenameOrDirectory, FStringBuilderBase& OutPath) const { FStringView ServerFilenameOrDirectoryView(ServerFilenameOrDirectory); if (ServerFilenameOrDirectoryView.StartsWith(ServerEngineDirView, ESearchCase::IgnoreCase)) { OutPath.Append(FPlatformMisc::EngineDir()); OutPath.Append(ServerFilenameOrDirectoryView.RightChop(ServerEngineDirView.Len())); return true; } else if (ServerFilenameOrDirectoryView.StartsWith(ServerProjectDirView, ESearchCase::IgnoreCase)) { OutPath.Append(FPlatformMisc::ProjectDir()); OutPath.Append(ServerFilenameOrDirectoryView.RightChop(ServerProjectDirView.Len())); return true; } return false; } bool FStorageServerPlatformFile::SendGetFileListMessage() { TRACE_CPUPROFILER_EVENT_SCOPE(StorageServerPlatformFileGetFileList); Connection->FileManifestRequest([&](FIoChunkId Id, FStringView Path) { ServerToc.AddFile(Id, Path); }); return true; } FFileStatData FStorageServerPlatformFile::SendGetStatDataMessage(const FIoChunkId& FileChunkId) { TRACE_CPUPROFILER_EVENT_SCOPE(StorageServerPlatformFileGetStatData); const int64 FileSize = Connection->ChunkSizeRequest(FileChunkId); if (FileSize < 0) { return FFileStatData(); } FDateTime CreationTime = FDateTime::Now(); FDateTime AccessTime = FDateTime::Now(); FDateTime ModificationTime = FDateTime::Now(); return FFileStatData(CreationTime, AccessTime, ModificationTime, FileSize, false, true); } int64 FStorageServerPlatformFile::SendReadMessage(uint8* Destination, const FIoChunkId& FileChunkId, int64 Offset, int64 BytesToRead) { TRACE_CPUPROFILER_EVENT_SCOPE(StorageServerPlatformFileRead); int64 BytesRead = 0; Connection->ReadChunkRequest(FileChunkId, Offset, BytesToRead, [Destination, Offset, BytesToRead, &BytesRead](FStorageServerResponse& Response) { BytesRead = Response.SerializeChunkTo(MakeMemoryView(Destination, BytesToRead), Offset); }); return BytesRead; } bool FStorageServerPlatformFile::SendMessageToServer(const TCHAR* Message, IPlatformFile::IFileServerMessageHandler* Handler) { #if WITH_COTF if (!CookOnTheFlyServerConnection->IsConnected()) { return false; } if (FCString::Stricmp(Message, TEXT("RecompileShaders")) == 0) { UE::Cook::FCookOnTheFlyRequest Request(UE::Cook::ECookOnTheFlyMessage::RecompileShaders); { TUniquePtr Ar = Request.WriteBody(); Handler->FillPayload(*Ar); } UE::Cook::FCookOnTheFlyResponse Response = CookOnTheFlyServerConnection->SendRequest(Request).Get(); if (Response.IsOk()) { TUniquePtr Ar = Response.ReadBody(); Handler->ProcessResponse(*Ar); } return Response.IsOk(); } #endif return false; } #if WITH_COTF void FStorageServerPlatformFile::OnCookOnTheFlyMessage(const UE::Cook::FCookOnTheFlyMessage& Message) { switch (Message.GetHeader().MessageType) { case UE::Cook::ECookOnTheFlyMessage::FilesAdded: { UE_LOG(LogCookOnTheFly, Verbose, TEXT("Received '%s' message"), LexToString(Message.GetHeader().MessageType)); TArray Filenames; TArray ChunkIds; { TUniquePtr Ar = Message.ReadBody(); *Ar << Filenames; *Ar << ChunkIds; } check(Filenames.Num() == ChunkIds.Num()); for (int32 Idx = 0, Num = Filenames.Num(); Idx < Num; ++Idx) { UE_LOG(LogCookOnTheFly, Verbose, TEXT("Adding file '%s'"), *Filenames[Idx]); ServerToc.AddFile(ChunkIds[Idx], Filenames[Idx]); } break; } } } #endif class FStorageServerClientFileModule : public IPlatformFileModule { public: virtual IPlatformFile* GetPlatformFile() override { static TUniquePtr AutoDestroySingleton = MakeUnique(); return AutoDestroySingleton.Get(); } }; IMPLEMENT_MODULE(FStorageServerClientFileModule, StorageServerClient); #endif