// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Engine/Platform/File.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" bool FileBase::ReadAllBytes(const StringView& path, byte* data, int32 length) { PROFILE_CPU_NAMED("File::ReadAllBytes"); ZoneText(*path, path.Length()); bool result = true; auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All); if (file) { const uint32 size = Math::Min(file->GetSize(), length); if (size > 0) { result = file->Read(data, size) != 0; } else { result = false; } Delete(file); } return result; } bool FileBase::ReadAllBytes(const StringView& path, Array& data) { PROFILE_CPU_NAMED("File::ReadAllBytes"); ZoneText(*path, path.Length()); bool result = true; auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All); if (file) { const uint32 size = file->GetSize(); if (size < MAX_int32) { data.Resize(size, false); if (size > 0) { result = file->Read(data.Get(), size) != 0; } else { data.Resize(0, false); result = false; } } else { LOG(Error, "Failed to load file {0}. It's too big. Size: {1} MB", path, size / (1024 * 1024)); } Delete(file); } return result; } bool FileBase::ReadAllBytes(const StringView& path, DataContainer& data) { PROFILE_CPU_NAMED("File::ReadAllBytes"); ZoneText(*path, path.Length()); bool result = true; auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All); if (file) { const uint32 size = file->GetSize(); if (size < MAX_int32) { data.Allocate(size); if (size > 0) { result = file->Read(data.Get(), size) != 0; } else { data.Release(); result = false; } } else { LOG(Error, "Failed to load file {0}. It's too big. Size: {1} MB", path, size / (1024 * 1024)); } Delete(file); } return result; } bool FileBase::ReadAllText(const StringView& path, String& data) { PROFILE_CPU_NAMED("File::ReadAllText"); ZoneText(*path, path.Length()); data.Clear(); // Read data Array bytes; if (ReadAllBytes(path, bytes)) return true; // Check for empty data if (bytes.IsEmpty()) return false; // Add null terminator char int32 count = bytes.Count(); bytes.Add('\0'); // Check BOM char // TODO: maybe let's check whole BOM???? switch (bytes[0]) { case 0xEF: // UTF-8 { // BOM: EF BB BF // Eat BOM count -= 3; if (count < 0) { // Invalid data return true; } // Convert to UTF-16 int32 utf16Length; Char* utf16Data = StringUtils::ConvertUTF82UTF16(reinterpret_cast(bytes.Get()), count, utf16Length); data.Set(utf16Data, utf16Length); Allocator::Free(utf16Data); } break; case 0xFE: // UTF-16 (BE) { // BOM: FE FF // Eat BOM count -= 2; if (count < 0) { // Invalid data return true; } data = (Char*)bytes.Get(); } break; case 0xFF: // UTF-16 (LE) { // BOM: FF FE // Eat BOM count -= 2; if (count < 0) { // Invalid data return true; } data = (Char*)bytes.Get(); } break; default: // ANSI { // Convert to UTF-16 data = (char*)bytes.Get(); } break; } return false; } bool FileBase::ReadAllText(const StringView& path, StringAnsi& data) { PROFILE_CPU_NAMED("File::ReadAllText"); ZoneText(*path, path.Length()); data.Clear(); // Read data Array bytes; if (ReadAllBytes(path, bytes)) return true; // Check for empty data if (bytes.IsEmpty()) return false; // Check BOM char // TODO: maybe let's check whole BOM???? switch (bytes[0]) { case 0xEF: // UTF-8 { // BOM: EF BB BF if (bytes.Count() < 3) return true; data.Set((char*)(bytes.Get() + 3), bytes.Count() - 3); } break; case 0xFE: // UTF-16 (BE) { // BOM: FE FF if (bytes.Count() < 2) return true; const int32 length = bytes.Count() / 2 - 1; data.ReserveSpace(length); StringUtils::ConvertUTF162ANSI((Char*)(bytes.Get() + 2), data.Get(), length); } break; case 0xFF: // UTF-16 (LE) { // BOM: FF FE if (bytes.Count() < 2) return true; const int32 length = bytes.Count() / 2 - 1; data.ReserveSpace(length); StringUtils::ConvertUTF162ANSI((Char*)(bytes.Get() + 2), data.Get(), length); } break; default: // ANSI { data.Set((char*)bytes.Get(), bytes.Count()); } break; } return false; } bool FileBase::WriteAllBytes(const StringView& path, const byte* data, int32 length) { PROFILE_CPU_NAMED("File::WriteAllBytes"); ZoneText(*path, path.Length()); bool result = true; auto file = File::Open(path, FileMode::CreateAlways, FileAccess::Write, FileShare::All); if (file) { if (length > 0) result = file->Write(data, length) != 0; else result = false; Delete(file); } return result; } bool FileBase::WriteAllBytes(const StringView& path, const Array& data) { return WriteAllBytes(path, data.Get(), data.Count()); } bool FileBase::WriteAllText(const StringView& path, const String& data, Encoding encoding) { return WriteAllText(path, *data, data.Length(), encoding); } bool FileBase::WriteAllText(const StringView& path, const StringBuilder& data, Encoding encoding) { return WriteAllText(path, *data, data.Length(), encoding); } bool FileBase::WriteAllText(const StringView& path, const Char* data, int32 length, Encoding encoding) { PROFILE_CPU_NAMED("File::WriteAllText"); ZoneText(*path, path.Length()); switch (encoding) { case Encoding::ANSI: { Array ansiBytes(length); StringUtils::ConvertUTF162ANSI(data, ansiBytes.Get(), length); return WriteAllBytes(path, (byte*)ansiBytes.Get(), length * sizeof(char)); } case Encoding::Unicode: { // UTF-16 (LE) // BOM: FF FE // TODO: make it faster without additional array int32 size = length * sizeof(Char); Array tmp; tmp.Resize(size + 2); tmp[0] = 0xFF; tmp[1] = 0xFE; Platform::MemoryCopy(tmp.Get() + 2, data, size); return WriteAllBytes(path, tmp.Get(), tmp.Count()); } case Encoding::UnicodeBigEndian: { // UTF-16 (BE) // BOM: FE FF // TODO: make it faster without additional array int32 size = length * sizeof(Char); Array tmp; tmp.Resize(size + 2); tmp[0] = 0xFE; tmp[1] = 0xFF; Platform::MemoryCopy(tmp.Get() + 2, data, size); return WriteAllBytes(path, tmp.Get(), tmp.Count()); } case Encoding::UTF8: { Array tmp; tmp.SetCapacity(length); for (int32 i = 0; i < length; i++) { Char c = data[i]; if (c < 0x0080) { tmp.Add((byte)c); } else if (c < 0x0800) { tmp.Add(0xC0 | (c >> 6)); tmp.Add(0x80 | (c & 0x3F)); } else if (c < 0xD800 || c >= 0xE000) { tmp.Add(0xE0 | (c >> 12)); tmp.Add(0x80 | ((c >> 6) & 0x3F)); tmp.Add(0x80 | (c & 0x3F)); } else // Surrogate pair { ++i; if (i >= length) return true; uint32 p = 0x10000 + (((c & 0x3FF) << 10) | (data[i] & 0x3FF)); tmp.Add(0xF0 | (p >> 18)); tmp.Add(0x80 | ((p >> 12) & 0x3F)); tmp.Add(0x80 | ((p >> 6) & 0x3F)); tmp.Add(0x80 | (p & 0x3F)); } } return WriteAllBytes(path, tmp.Get(), tmp.Count()); } default: return true; } }