#include #include "Numerics/Bit.h" #include "Numerics/Math.h" #include "Templates/ScopeHelper.h" #include "Containers/StaticArray.h" #include #if PLATFORM_WINDOWS # undef TEXT # include # undef CreateDirectory #elif PLATFORM_LINUX # include # include # include #endif #pragma warning(push) #pragma warning(disable: 4996) NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) NAMESPACE_BEGIN(FileSystem) bool LoadFileToArray(TArray& Result, FStringView Path) { if (!FileSystem::Exists(Path)) return false; FILE* File = std::fopen(*Path, "rb"); if (File == nullptr) return false; auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); if (std::fseek(File, 0, SEEK_END) != 0) return false; const long Length = std::ftell(File); if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return false; if (std::fseek(File, 0, SEEK_SET) != 0) return false; Result.SetNum(Length); if (std::fread(Result.GetData(), sizeof(uint8), Length, File) != static_cast(Length)) return false; FileGuard.Release(); if (std::fclose(File) != 0) return false; return true; } bool SaveArrayToFile(TArrayView Data, FStringView Path) { FILE* File = std::fopen(*Path, "wb"); if (File == nullptr) return false; auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); if (std::fwrite(Data.GetData(), sizeof(uint8), Data.Num(), File) != Data.Num()) return false; FileGuard.Release(); if (std::fclose(File) != 0) return false; return true; } template bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bVerify /* = false */) { if (!FileSystem::Exists(Path)) return false; FILE* File = std::fopen(*Path, "rb"); if (File == nullptr) return false; auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); if (std::fseek(File, 0, SEEK_END) != 0) return false; long Length = std::ftell(File); if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return false; if (std::fseek(File, 0, SEEK_SET) != 0) return false; TStaticArray Buffer = { 0xAA, 0xAA, 0xAA, 0xAA }; Ignore = std::fread(Buffer.GetData(), sizeof(uint8), Buffer.Num(), File); // Auto-detect the encoding if it is not specified. if (Encoding == FileSystem::EEncoding::Default) { // Check if the file is a UTF-32 encoded file. if (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) Encoding = FileSystem::EEncoding::UTF32BE; else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::UTF32LE; // Check if the file is a UTF-16 encoded file. else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE) Encoding = FileSystem::EEncoding::UTF16LE; else if (Buffer[0] == 0xFE && Buffer[1] == 0xFF) Encoding = FileSystem::EEncoding::UTF16BE; // Check if the file is a UTF-8 encoded file. else if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) Encoding = FileSystem::EEncoding::UTF8; // Check if the file is a wide character encoded file. else if (Buffer[0] == 0x00 || Buffer[1] == 0x00 || Buffer[2] == 0x00 || Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::Wide; // Check if the file is a narrow character encoded file. else Encoding = FileSystem::EEncoding::Narrow; } // Jump to the BOM character if the file is a UTF-8, UTF-16 or UTF-32 encoded file. switch (Encoding) { case FileSystem::EEncoding::Narrow: case FileSystem::EEncoding::Wide: { Length -= 0; if (std::fseek(File, 0, SEEK_SET) != 0) return false; } break; case FileSystem::EEncoding::UTF8: if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) { Length -= 3; if (std::fseek(File, 3, SEEK_SET) != 0) return false; } break; case FileSystem::EEncoding::UTF16BE: if (Buffer[0] == 0xFE && Buffer[1] == 0xFF) { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; case FileSystem::EEncoding::UTF16LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE) { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; case FileSystem::EEncoding::UTF32BE: if (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; case FileSystem::EEncoding::UTF32LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; default: check_no_entry(); } check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); const bool bByteSwap = Math::EEndian::Native == Math::EEndian::Big ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; const auto LoadImpl = [File, Length, bByteSwap](TString& String) -> bool { if (Length % sizeof(U) != 0) return false; String.Reserve(Length / sizeof(U)); while (true) { U Char; const size_t ReadNum = std::fread(&Char, 1, sizeof(U), File); if (ReadNum == 0) break; if (ReadNum != sizeof(U)) return false; if (bByteSwap) Char = Math::ByteSwap(static_cast>(Char)); # if PLATFORM_WINDOWS { if (!String.IsEmpty() && String.Back() == LITERAL(U, '\r') && Char == LITERAL(U, '\n')) { String.PopBack(); } } # endif String.PushBack(Char); } return true; }; bool bCompatible = false; if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Narrow; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Wide; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF8; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; else static_assert(sizeof(T) == -1, "Unsupported character type"); if (!bCompatible || bVerify) { switch (Encoding) { case FileSystem::EEncoding::Narrow: { FString Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } case FileSystem::EEncoding::Wide: { FWString Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } case FileSystem::EEncoding::UTF8: { FU8String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } case FileSystem::EEncoding::UTF16BE: case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } case FileSystem::EEncoding::UTF32BE: case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } default: check_no_entry(); } } else if (!LoadImpl(Result)) return false; FileGuard.Release(); if (std::fclose(File) != 0) return false; return true; } template REDCRAFTUTILITY_API bool LoadFileToString (FString&, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool LoadFileToString (FWString&, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool LoadFileToString (FU8String&, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool LoadFileToString(FU16String&, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool LoadFileToString(FU32String&, FStringView, FileSystem::EEncoding, bool); template bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bWithBOM /* = true */) { bool bCompatible = Encoding == FileSystem::EEncoding::Default; if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Narrow; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Wide; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF8; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; else static_assert(sizeof(T) == -1, "Unsupported character type"); if (bCompatible) { FILE* File = std::fopen(*Path, "wb"); if (File == nullptr) return false; auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); if (bWithBOM) { if constexpr (CSameAs) { if (std::fwrite(U8TEXT("\uFEFF"), 1, 3, File) != 3) return false; } else if constexpr (CSameAs) { constexpr TStaticArray BufferBE = { 0xFE, 0xFF }; constexpr TStaticArray BufferLE = { 0xFF, 0xFE }; if (Encoding == FileSystem::EEncoding::UTF16BE) { if (std::fwrite(BufferBE.GetData(), 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } else if (Encoding == FileSystem::EEncoding::UTF16LE) { if (std::fwrite(BufferLE.GetData(), 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } else if (std::fwrite(U16TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; } else if constexpr (CSameAs) { constexpr TStaticArray BufferBE = { 0x00, 0x00, 0xFE, 0xFF }; constexpr TStaticArray BufferLE = { 0xFF, 0xFE, 0x00, 0x00 }; if (Encoding == FileSystem::EEncoding::UTF32BE) { if (std::fwrite(BufferBE.GetData() , 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } else if (Encoding == FileSystem::EEncoding::UTF32LE) { if (std::fwrite(BufferLE.GetData() , 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } else if (std::fwrite(U32TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; } } check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); const bool bByteSwap = Math::EEndian::Native == Math::EEndian::Big ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; for (T Char : String) { # if PLATFORM_WINDOWS { if (Char == LITERAL(T, '\n')) { T Return = LITERAL(T, '\r'); if (bByteSwap) Return = Math::ByteSwap(static_cast>(Return)); if (std::fwrite(&Return, 1, sizeof(T), File) != sizeof(T)) return false; } } # endif if (bByteSwap) Char = Math::ByteSwap(static_cast>(Char)); if (std::fwrite(&Char, 1, sizeof(T), File) != sizeof(T)) return false; } FileGuard.Release(); if (std::fclose(File) != 0) return false; return true; } FString PathWithNull; PathWithNull.Reserve(Path.Num() + 1); PathWithNull += Path; PathWithNull += '\0'; switch (Encoding) { case FileSystem::EEncoding::Narrow: { FString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Narrow, bWithBOM)) return false; break; } case FileSystem::EEncoding::Wide: { FWString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Wide, bWithBOM)) return false; break; } case FileSystem::EEncoding::UTF8: { FU8String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF8, bWithBOM)) return false; break; } case FileSystem::EEncoding::UTF16BE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16BE, bWithBOM)) return false; break; } case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16LE, bWithBOM)) return false; break; } case FileSystem::EEncoding::UTF32BE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32BE, bWithBOM)) return false; break; } case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32LE, bWithBOM)) return false; break; } default: check_no_entry(); return false; } return true; } template REDCRAFTUTILITY_API bool SaveStringToFile (FStringView, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool SaveStringToFile (FWStringView, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool SaveStringToFile (FU8StringView, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool SaveStringToFile(FU16StringView, FStringView, FileSystem::EEncoding, bool); template REDCRAFTUTILITY_API bool SaveStringToFile(FU32StringView, FStringView, FileSystem::EEncoding, bool); size_t FileSize(FStringView Path) { if (!FileSystem::Exists(Path)) return static_cast(-1); FILE* File = std::fopen(*Path, "rb"); if (File == nullptr) return static_cast(-1); auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); if (std::fseek(File, 0, SEEK_END) != 0) return static_cast(-1); const long Length = std::ftell(File); if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return static_cast(-1); FileGuard.Release(); if (std::fclose(File) != 0) return static_cast(-1); return Length; } bool Delete(FStringView Path) { return std::remove(*Path) == 0; } bool Exists(FStringView Path) { # if PLATFORM_WINDOWS { DWORD Attributes = GetFileAttributesA(*Path); if (Attributes == INVALID_FILE_ATTRIBUTES) return false; return !(Attributes & FILE_ATTRIBUTE_DIRECTORY); } # elif PLATFORM_LINUX { struct stat FileInfo; FileInfo.st_size = -1; if (stat(*Path, &FileInfo) != 0) return false; if (!S_ISREG(FileInfo.st_mode)) return false; return true; } # endif return false; } bool Copy(FStringView Destination, FStringView Source) { if (!FileSystem::Exists(Source)) return false; FILE* FileA = std::fopen(*Source, "rb"); if (FileA == nullptr) return false; auto FileGuardA = TScopeCallback([=] { Ignore = std::fclose(FileA); }); FILE* FileB = std::fopen(*Destination, "wb"); if (FileB == nullptr) return false; auto FileGuardB = TScopeCallback([=] { Ignore = std::fclose(FileB); }); size_t ReadSize; constexpr size_t BufferSize = 4096; TStaticArray Buffer; do { ReadSize = std::fread(Buffer.GetData(), 1, BufferSize, FileA); if (std::fwrite(Buffer.GetData(), 1, ReadSize, FileB) != ReadSize) return false; } while (ReadSize == BufferSize); FileGuardA.Release(); if (std::fclose(FileA) != 0) return false; FileGuardB.Release(); if (std::fclose(FileB) != 0) return false; return true; } bool Rename(FStringView Destination, FStringView Source) { return std::rename(*Source, *Destination) == 0; } bool CreateDirectory(FStringView Path, bool bRecursive /* = false */) { if (Path.Num() == 0) return false; if (bRecursive) { if (Path.Back() == '/' || Path.Back() == '\\') Path = Path.First(Path.Num() - 1); FStringView Parent = Path.First(Path.FindLastOf("/\\")); if (!FileSystem::ExistsDirectory(Parent) && !FileSystem::CreateDirectory(Parent, true)) return false; } # if PLATFORM_WINDOWS { return CreateDirectoryA(*Path, nullptr) != 0; } # elif PLATFORM_LINUX { return mkdir(*Path, 0755) == 0; } # endif return false; } bool DeleteDirectory(FStringView Path, bool bRecursive /* = false */) { if (bRecursive) { FString Temp; bool bSuccessfully = FileSystem::IterateDirectory(Path, [&](FStringView File, bool bIsDirectory) -> bool { Temp.Reset(false); Temp += Path; Temp += '/'; Temp += File; Temp += '\0'; if (bIsDirectory) { if (!FileSystem::DeleteDirectory(Temp, true)) return false; } else { if (!FileSystem::Delete(Temp)) return false; } return true; }); if (!bSuccessfully) return false; } # if PLATFORM_WINDOWS { return RemoveDirectoryA(*Path) != 0; } # elif PLATFORM_LINUX { return rmdir(*Path) == 0; } # endif return false; } bool ExistsDirectory(FStringView Path) { # if PLATFORM_WINDOWS { DWORD Attributes = GetFileAttributesA(*Path); if (Attributes == INVALID_FILE_ATTRIBUTES) return false; return Attributes & FILE_ATTRIBUTE_DIRECTORY; } # elif PLATFORM_LINUX { DIR* Directory = opendir(*Path); if (Directory == nullptr) return false; Ignore = closedir(Directory); return true; } # endif return false; } bool IterateDirectory(FStringView Path, TFunctionRef Visitor) { # if PLATFORM_WINDOWS { FString FindPath; FindPath.Reserve(Path.Num() + 3); FindPath += Path; FindPath += '\\'; FindPath += '*'; FindPath += '\0'; WIN32_FIND_DATA FindData; HANDLE FindHandle = FindFirstFileA(*FindPath, &FindData); auto FindGuard = TScopeCallback([=] { Ignore = FindClose(FindHandle); }); if (FindHandle == INVALID_HANDLE_VALUE) return false; do { const FStringView FilePath = FindData.cFileName; if (FilePath == "." || FilePath == "..") continue; const bool bIsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; if (!Visitor(FilePath, bIsDirectory)) return false; } while (FindNextFileA(FindHandle, &FindData) != 0); FindGuard.Release(); if (!FindClose(FindHandle)) return false; return true; } # elif PLATFORM_LINUX { DIR* Directory = opendir(*Path); if (Directory == nullptr) return false; auto DirectoryGuard = TScopeCallback([=] { Ignore = closedir(Directory); }); dirent* Entry; while ((Entry = readdir(Directory)) != nullptr) { const FStringView FilePath = Entry->d_name; if (FilePath == "." || FilePath == "..") continue; const bool bIsDirectory = Entry->d_type == DT_DIR; if (!Visitor(FilePath, bIsDirectory)) return false; } DirectoryGuard.Release(); if (closedir(Directory) != 0) return false; return true; } # endif return false; } NAMESPACE_END(FileSystem) NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END #pragma warning(pop)