// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "EditorUtilities.h" #include "Engine/Engine/Globals.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CreateProcessSettings.h" #include "Engine/Core/Log.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/Assets/Texture.h" #include "Engine/Utilities/StringConverter.h" #if PLATFORM_MAC #include "Engine/Platform/Apple/ApplePlatformSettings.h" #endif #include #define MSDOS_SIGNATURE 0x5A4D #define PE_SIGNATURE 0x00004550 #define PE_32BIT_SIGNATURE 0x10B #define PE_64BIT_SIGNATURE 0x20B #define PE_NUM_DIRECTORY_ENTRIES 16 #define PE_SECTION_UNINITIALIZED_DATA 0x00000080 #define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 #define PE_IMAGE_RT_ICON 3 /// /// MS-DOS header found at the beginning in a PE format file. /// struct MSDOSHeader { uint16 signature; uint16 lastSize; uint16 numBlocks; uint16 numReloc; uint16 hdrSize; uint16 minAlloc; uint16 maxAlloc; uint16 ss; uint16 sp; uint16 checksum; uint16 ip; uint16 cs; uint16 relocPos; uint16 numOverlay; uint16 reserved1[4]; uint16 oemId; uint16 oemInfo; uint16 reserved2[10]; uint32 lfanew; }; /// /// COFF header found in a PE format file. /// struct COFFHeader { uint16 machine; uint16 numSections; uint32 timeDateStamp; uint32 ptrSymbolTable; uint32 numSymbols; uint16 sizeOptHeader; uint16 characteristics; }; /// /// Contains address and size of data areas in a PE image. /// struct PEDataDirectory { uint32 virtualAddress; uint32 size; }; /// /// Optional header in a 32-bit PE format file. /// struct PEOptionalHeader32 { uint16 signature; uint8 majorLinkerVersion; uint8 minorLinkerVersion; uint32 sizeCode; uint32 sizeInitializedData; uint32 sizeUninitializedData; uint32 addressEntryPoint; uint32 baseCode; uint32 baseData; uint32 baseImage; uint32 alignmentSection; uint32 alignmentFile; uint16 majorOSVersion; uint16 minorOSVersion; uint16 majorImageVersion; uint16 minorImageVersion; uint16 majorSubsystemVersion; uint16 minorSubsystemVersion; uint32 reserved; uint32 sizeImage; uint32 sizeHeaders; uint32 checksum; uint16 subsystem; uint16 characteristics; uint32 sizeStackReserve; uint32 sizeStackCommit; uint32 sizeHeapReserve; uint32 sizeHeapCommit; uint32 loaderFlags; uint32 NumRvaAndSizes; PEDataDirectory dataDirectory[16]; }; /// /// Optional header in a 64-bit PE format file. /// struct PEOptionalHeader64 { uint16 signature; uint8 majorLinkerVersion; uint8 minorLinkerVersion; uint32 sizeCode; uint32 sizeInitializedData; uint32 sizeUninitializedData; uint32 addressEntryPoint; uint32 baseCode; uint64 baseImage; uint32 alignmentSection; uint32 alignmentFile; uint16 majorOSVersion; uint16 minorOSVersion; uint16 majorImageVersion; uint16 minorImageVersion; uint16 majorSubsystemVersion; uint16 minorSubsystemVersion; uint32 reserved; uint32 sizeImage; uint32 sizeHeaders; uint32 checksum; uint16 subsystem; uint16 characteristics; uint64 sizeStackReserve; uint64 sizeStackCommit; uint64 sizeHeapReserve; uint64 sizeHeapCommit; uint32 loaderFlags; uint32 NumRvaAndSizes; PEDataDirectory dataDirectory[16]; }; /// /// A section header in a PE format file. /// struct PESectionHeader { char name[8]; uint32 virtualSize; uint32 relativeVirtualAddress; uint32 physicalSize; uint32 physicalAddress; uint8 deprecated[12]; uint32 flags; }; /// /// A resource table header within a .rsrc section in a PE format file. /// struct PEImageResourceDirectory { uint32 flags; uint32 timeDateStamp; uint16 majorVersion; uint16 minorVersion; uint16 numNamedEntries; uint16 numIdEntries; }; /// /// A single entry in a resource table within a .rsrc section in a PE format file. /// struct PEImageResourceEntry { uint32 type; uint32 offsetDirectory : 31; uint32 isDirectory : 1; }; /// /// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. /// struct PEImageResourceEntryData { uint32 offsetData; uint32 size; uint32 codePage; uint32 resourceHandle; }; /// /// Header used in icon file format. /// struct IconHeader { uint32 size; int32 width; int32 height; uint16 planes; uint16 bitCount; uint32 compression; uint32 sizeImage; int32 xPelsPerMeter; int32 yPelsPerMeter; uint32 clrUsed; uint32 clrImportant; }; void UpdateIconData(uint8* iconData, const TextureData* icon) { IconHeader* iconHeader = (IconHeader*)iconData; if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) { // Unsupported format return; } uint8* iconPixels = iconData + sizeof(IconHeader); uint32 width = iconHeader->width; uint32 height = iconHeader->height / 2; // Check if can use mip from texture data or sample different mip uint32 iconTexSize; if (width != height) { // Only square icons are supported return; } if (Math::IsPowerOfTwo(width)) { // Use mip iconTexSize = width; } else { // Use resized mip iconTexSize = Math::RoundUpToPowerOf2(width); } // Try to pick a proper mip (require the same size) const TextureMipData* srcPixels = nullptr; int32 mipLevels = icon->GetMipLevels(); for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); if (iconTexSize == iconWidth && iconTexSize == iconHeight) { srcPixels = icon->GetData(0, mipIndex); break; } } if (srcPixels == nullptr) { // No icon of this size provided return; } // Write colors uint32* colorData = (uint32*)iconPixels; uint32 idx = 0; for (int32 y = (int32)height - 1; y >= 0; y--) { float v = (float)y / height; for (uint32 x = 0; x < width; x++) { float u = (float)x / width; int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); colorData[idx++] = ((Color32*)&srcPixels->Data.Get()[i])->GetAsBGRA(); } } // Write AND mask uint32 colorDataSize = width * height * sizeof(uint32); uint8* maskData = iconPixels + colorDataSize; // One per bit in byte uint32 numPackedPixels = width / 8; for (int32 y = (int32)height - 1; y >= 0; y--) { uint8 mask = 0; float v = (float)y / height; for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) { for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) { uint32 x = packedX * 8 + pixelIdx; float u = (float)x / width; int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); Color32 color = *((Color32*)&srcPixels->Data.Get()[i]); if (color.A < 64) mask |= 1 << (7 - pixelIdx); } *maskData = mask; maskData++; } } } void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) { uint32 numEntries = current->numIdEntries; // Not supporting name entries PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); for (uint32 i = 0; i < numEntries; i++) { // Only at root does the type identify resource type if (base == current && entries[i].type != PE_IMAGE_RT_ICON) continue; if (entries[i].isDirectory) { PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); SetIconData(base, child, imageData, sectionAddress, iconRGBA8); } else { PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); uint8* iconData = imageData + (data->offsetData - sectionAddress); UpdateIconData(iconData, iconRGBA8); } } } bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon) { // Validate input if (!FileSystem::FileExists(path)) { LOG(Warning, "Missing file"); return true; } if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) { LOG(Warning, "Inalid icon data"); return true; } // Convert to RGBA8 format if need to const TextureData* iconRGBA8 = &icon; TextureData tmpData1; if (icon.Format != PixelFormat::R8G8B8A8_UNorm) { if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) { LOG(Warning, "Failed convert icon data."); return true; } iconRGBA8 = &tmpData1; } // Resize if need to TextureData tmpData2; if (iconRGBA8->Width > 256 || iconRGBA8->Height > 256) { if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) { LOG(Warning, "Failed resize icon data."); return true; } iconRGBA8 = &tmpData2; } // A PE file is structured as such: // - MSDOS Header // - PE Signature // - COFF Header // - PE Optional Header // - One or multiple sections // - .code // - .data // - ... // - .rsrc // - icon/cursor/etc data std::fstream stream; StringAsANSI<> pathAnsi(path.Get()); stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); if (!stream.is_open()) { LOG(Warning, "Cannot open file"); return true; } // First check magic number to ensure file is even an executable uint16 magicNum; stream.read((char*)&magicNum, sizeof(magicNum)); if (magicNum != MSDOS_SIGNATURE) { LOG(Warning, "Provided file is not a valid executable."); return true; } // Read the MSDOS header and skip over it stream.seekg(0); MSDOSHeader msdosHeader; stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); // Read PE signature stream.seekg(msdosHeader.lfanew); uint32 peSignature; stream.read((char*)&peSignature, sizeof(peSignature)); if (peSignature != PE_SIGNATURE) { LOG(Warning, "Provided file is not in PE format."); return true; } // Read COFF header COFFHeader coffHeader; stream.read((char*)&coffHeader, sizeof(COFFHeader)); // .exe files always have an optional header if (coffHeader.sizeOptHeader == 0) { LOG(Warning, "Provided file is not a valid executable."); return true; } uint32 numSectionHeaders = coffHeader.numSections; // Read optional header auto optionalHeaderPos = stream.tellg(); uint16 optionalHeaderSignature; stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); PEDataDirectory* dataDirectory = nullptr; stream.seekg(optionalHeaderPos); if (optionalHeaderSignature == PE_32BIT_SIGNATURE) { PEOptionalHeader32 optionalHeader; stream.read((char*)&optionalHeader, sizeof(optionalHeader)); dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; } else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) { PEOptionalHeader64 optionalHeader; stream.read((char*)&optionalHeader, sizeof(optionalHeader)); dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; } else { LOG(Warning, "Unrecognized PE format."); return true; } // Read section headers auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; stream.seekg(sectionHeaderPos); Array sectionHeaders; sectionHeaders.Resize(numSectionHeaders); stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * numSectionHeaders); // Look for .rsrc section header for (uint32 i = 0; i < numSectionHeaders; i++) { if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA) continue; if (strcmp(sectionHeaders[i].name, ".rsrc") == 0) { uint32 imageSize = sectionHeaders[i].physicalSize; Array imageData; imageData.Resize(imageSize); stream.seekg(sectionHeaders[i].physicalAddress); stream.read((char*)imageData.Get(), imageSize); uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress; PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeaders[i].relativeVirtualAddress, iconRGBA8); stream.seekp(sectionHeaders[i].physicalAddress); stream.write((char*)imageData.Get(), imageSize); } } stream.close(); return false; } bool EditorUtilities::FormatAppPackageName(String& packageName) { const auto gameSettings = GameSettings::Get(); String productName = gameSettings->ProductName; productName.Replace(TEXT(" "), TEXT("")); productName.Replace(TEXT("."), TEXT("")); productName.Replace(TEXT("-"), TEXT("")); String companyName = gameSettings->CompanyName; companyName.Replace(TEXT(" "), TEXT("")); companyName.Replace(TEXT("."), TEXT("")); companyName.Replace(TEXT("-"), TEXT("")); packageName.Replace(TEXT("${PROJECT_NAME}"), *productName, StringSearchCase::IgnoreCase); packageName.Replace(TEXT("${COMPANY_NAME}"), *companyName, StringSearchCase::IgnoreCase); packageName = packageName.ToLower(); for (int32 i = 0; i < packageName.Length(); i++) { const auto c = packageName[i]; if (c != '_' && c != '.' && !StringUtils::IsAlnum(c)) { LOG(Error, "App identifier \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", packageName); return true; } } if (packageName.IsEmpty()) { LOG(Error, "App identifier is empty.", packageName); return true; } return false; } bool EditorUtilities::GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type) { AssetReference icon = Content::LoadAsync(imageId); if (icon == nullptr) { const auto gameSettings = GameSettings::Get(); if (gameSettings) { icon = Content::LoadAsync(gameSettings->Icon); } } if (icon == nullptr) { if (type == ApplicationImageType::Icon) { icon = Content::LoadAsyncInternal(TEXT("Engine/Textures/FlaxIconBlue")); } else if (type == ApplicationImageType::SplashScreen) { icon = Content::LoadAsyncInternal(TEXT("Engine/Textures/SplashScreenLogo")); } } if (icon) { return GetTexture(icon.GetID(), imageData); } return true; } bool EditorUtilities::GetTexture(const Guid& textureId, TextureData& textureData) { AssetReference texture = Content::LoadAsync(textureId); if (texture) { if (texture->WaitForLoaded()) { LOG(Warning, "Waiting for the texture to be loaded failed."); } else { const bool useGPU = texture->IsVirtual(); if (useGPU) { int32 waits = 1000; const auto targetResidency = texture->StreamingTexture()->GetMaxResidency(); ASSERT(targetResidency > 0); while (targetResidency != texture->StreamingTexture()->GetCurrentResidency() && waits-- > 0) { Platform::Sleep(10); } // Get texture data from GPU if (!texture->GetTexture()->DownloadData(textureData)) return false; } // Get texture data from asset if (!texture->GetTextureData(textureData)) return false; LOG(Warning, "Loading texture data failed."); } } return true; } bool EditorUtilities::ExportApplicationImage(const Guid& iconId, int32 width, int32 height, PixelFormat format, const String& path, ApplicationImageType type) { TextureData icon; if (GetApplicationImage(iconId, icon, type)) return true; return ExportApplicationImage(icon, width, height, format, path); } bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 width, int32 height, PixelFormat format, const String& path) { // Change format if need to const TextureData* iconData = &icon; TextureData tmpData1, tmpData2; if (icon.Format != format) { // Pre-multiply alpha if has it to prevent strange colors appear if (PixelFormatExtensions::HasAlpha(iconData->Format) && !PixelFormatExtensions::HasAlpha(format)) { // Pre-multiply alpha if can auto sampler = TextureTool::GetSampler(iconData->Format); if (!sampler) { if (TextureTool::Convert(tmpData2, *iconData, PixelFormat::R16G16B16A16_Float)) { LOG(Error, "Failed convert texture data."); return true; } iconData = &tmpData2; sampler = TextureTool::GetSampler(iconData->Format); } if (sampler) { auto mipData = iconData->GetData(0, 0); for (int32 y = 0; y < iconData->Height; y++) { for (int32 x = 0; x < iconData->Width; x++) { Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch); color *= color.A; color.A = 1.0f; TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color); } } } } if (TextureTool::Convert(tmpData1, *iconData, format)) { LOG(Error, "Failed convert texture data."); return true; } iconData = &tmpData1; } // Resize if need to TextureData tmpData3; if (iconData->Width != width || iconData->Height != height) { if (PixelFormatExtensions::HasAlpha(icon.Format) && !PixelFormatExtensions::HasAlpha(format)) { // Pre-multiply alpha if can auto sampler = TextureTool::GetSampler(icon.Format); if (sampler) { auto mipData = iconData->GetData(0, 0); for (int32 y = 0; y < iconData->Height; y++) { for (int32 x = 0; x < iconData->Width; x++) { Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch); color *= color.A; color.A = 1.0f; TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color); } } } } if (TextureTool::Resize(tmpData3, *iconData, width, height)) { LOG(Error, "Failed resize texture data."); return true; } iconData = &tmpData3; } // Save to file if (TextureTool::ExportTexture(path, *iconData)) { LOG(Error, "Failed to save application icon."); return true; } return false; } bool EditorUtilities::FindWDKBin(String& outputWdkBinPath) { // Find Windows Driver Kit (WDK) install location const Char* wdkPaths[] = { TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.19041.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.18362.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.17763.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.17134.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.16299.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.15063.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\10\\10.0.14393.0\\bin"), TEXT("C:\\Program Files (x86)\\Windows Kits\\8.1\\bin"), TEXT("C:\\Program Files\\Windows Kits\\10\\bin"), TEXT("C:\\Program Files\\Windows Kits\\8.1\\bin"), }; for (int32 i = 0; i < ARRAY_COUNT(wdkPaths); i++) { if (FileSystem::DirectoryExists(wdkPaths[i])) { outputWdkBinPath = wdkPaths[i]; return false; } } return true; } bool EditorUtilities::GenerateCertificate(const String& name, const String& outputPfxFilePath) { // Generate temp files paths const auto id = Guid::New().ToString(Guid::FormatType::D); auto outputCerFilePath = Globals::TemporaryFolder / id; const auto outputPvkFilePath = outputCerFilePath + TEXT(".pvk"); outputCerFilePath += TEXT(".cer"); // Generate .pfx file const bool result = GenerateCertificate(name, outputPfxFilePath, outputCerFilePath, outputPvkFilePath); // Cleanup unused files if (FileSystem::FileExists(outputCerFilePath)) FileSystem::DeleteFile(outputCerFilePath); if (FileSystem::FileExists(outputPvkFilePath)) FileSystem::DeleteFile(outputPvkFilePath); return result; } bool EditorUtilities::GenerateCertificate(const String& name, const String& outputPfxFilePath, const String& outputCerFilePath, const String& outputPvkFilePath) { String wdkPath; if (FindWDKBin(wdkPath)) { LOG(Warning, "Cannot find WDK install location."); return true; } wdkPath /= TEXT("x86\\"); // MakeCert auto path = wdkPath / TEXT("makecert.exe"); CreateProcessSettings procSettings; procSettings.FileName = String::Format(TEXT("\"{0}\" /r /h 0 /eku \"1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13\" /m 12 /len 2048 /n \"CN={1}\" -sv \"{2}\" \"{3}\""), path, name, outputPvkFilePath, outputCerFilePath); int32 result = Platform::CreateProcess(procSettings); if (result != 0) { LOG(Warning, "MakeCert failed with result {0}.", result); return true; } // Pvk2Pfx path = wdkPath / TEXT("pvk2pfx.exe"); procSettings.FileName = String::Format(TEXT("\"{0}\" -pvk \"{1}\" -spc \"{2}\" -pfx \"{3}\""), path, outputPvkFilePath, outputCerFilePath, outputPfxFilePath); result = Platform::CreateProcess(procSettings); if (result != 0) { LOG(Warning, "MakeCert failed with result {0}.", result); return true; } return false; } bool EditorUtilities::IsInvalidPathChar(Char c) { char illegalChars[] = { '?', '\\', '/', '\"', '<', '>', '|', ':', '*', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F' }; for (auto i : illegalChars) { if (c == i) return true; } return false; } bool EditorUtilities::ReplaceInFiles(const String& folderPath, const Char* searchPattern, DirectorySearchOption searchOption, const String& findWhat, const String& replaceWith) { Array files; FileSystem::DirectoryGetFiles(files, folderPath, searchPattern, searchOption); for (auto& file : files) if (ReplaceInFile(file, findWhat, replaceWith)) return true; return false; } bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& findWhat, const StringView& replaceWith) { String text; if (File::ReadAllText(file, text)) return true; text.Replace(findWhat.Get(), findWhat.Length(), replaceWith.Get(), replaceWith.Length()); return File::WriteAllText(file, text, Encoding::ANSI); } bool EditorUtilities::ReplaceInFile(const StringView& file, const Dictionary& replaceMap) { String text; if (File::ReadAllText(file, text)) return true; for (const auto& e : replaceMap) text.Replace(e.Key.Get(), e.Key.Length(), e.Value.Get(), e.Value.Length()); return File::WriteAllText(file, text, Encoding::ANSI); } bool EditorUtilities::CopyFileIfNewer(const StringView& dst, const StringView& src) { if (FileSystem::FileExists(dst) && FileSystem::GetFileLastEditTime(src) <= FileSystem::GetFileLastEditTime(dst) && FileSystem::GetFileSize(dst) == FileSystem::GetFileSize(src)) return false; return FileSystem::CopyFile(dst, src); } bool EditorUtilities::CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories) { if (FileSystem::DirectoryExists(dst)) { // Copy all files Array cache(32); if (FileSystem::DirectoryGetFiles(cache, *src, TEXT("*"), DirectorySearchOption::TopDirectoryOnly)) return true; for (int32 i = 0; i < cache.Count(); i++) { String dstFile = String(dst) / StringUtils::GetFileName(cache[i]); if (CopyFileIfNewer(*dstFile, *cache[i])) return true; } // Copy all subdirectories (if need to) if (withSubDirectories) { cache.Clear(); if (FileSystem::GetChildDirectories(cache, src)) return true; for (int32 i = 0; i < cache.Count(); i++) { String dstDir = String(dst) / StringUtils::GetFileName(cache[i]); if (CopyDirectoryIfNewer(dstDir, cache[i], true)) return true; } } return false; } else { return FileSystem::CopyDirectory(dst, src, withSubDirectories); } }