// Copyright 2025 Xenon Emulator Project. All rights reserved. #pragma once #include #include #include "Enum.h" #include "PathUtil.h" namespace Base::FS { enum class FileAccessMode { /** * If the file at path exists, it opens the file for reading. * If the file at path does not exist, it fails to open the file. */ Read = 1 << 0, /** * If the file at path exists, the existing contents of the file are erased. * The empty file is then opened for writing. * If the file at path does not exist, it creates and opens a new empty file for writing. */ Write = 1 << 1, /** * If the file at path exists, it opens the file for reading and writing. * If the file at path does not exist, it fails to open the file. */ ReadWrite = Read | Write, /** * If the file at path exists, it opens the file for appending. * If the file at path does not exist, it creates and opens a new empty file for appending. */ Append = 1 << 2, /** * If the file at path exists, it opens the file for both reading and appending. * If the file at path does not exist, it creates and opens a new empty file for both * reading and appending. */ ReadAppend = Read | Append }; DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode) enum class FileMode { BinaryMode, TextMode }; enum class FileShareFlag { ShareNone, // Provides exclusive access to the file. ShareReadOnly, // Provides read only shared access to the file. ShareWriteOnly, // Provides write only shared access to the file. ShareReadWrite // Provides read and write shared access to the file. }; enum class SeekOrigin : u32 { SetOrigin, // Seeks from the start of the file. CurrentPosition, // Seeks from the current file pointer position. End // Seeks from the end of the file. }; class IOFile final { public: IOFile(); explicit IOFile(const std::string& path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, FileShareFlag flag = FileShareFlag::ShareReadOnly); explicit IOFile(std::string_view path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, FileShareFlag flag = FileShareFlag::ShareReadOnly); explicit IOFile(const fs::path &path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, FileShareFlag flag = FileShareFlag::ShareReadOnly); ~IOFile(); IOFile(const IOFile&) = delete; IOFile& operator=(const IOFile&) = delete; IOFile(IOFile&& other) noexcept; IOFile& operator=(IOFile&& other) noexcept; fs::path GetPath() const { return filePath; } FileAccessMode GetAccessMode() const { return fileAccessMode; } FileMode GetType() const { return fileType; } bool IsOpen() const { return file != nullptr; } uptr GetFileMapping(); int Open(const fs::path& path, FileAccessMode mode, FileMode type = FileMode::BinaryMode, FileShareFlag flag = FileShareFlag::ShareReadOnly); void Close(); void Unlink(); bool Flush() const; bool Commit() const; bool SetSize(u64 size) const; u64 GetSize() const; bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; s64 Tell() const; template size_t Read(T& data) const { if constexpr (std::contiguous_iterator) { using ContiguousType = typename T::value_type; static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); return ReadSpan(data); } else { return ReadObject(data) ? 1 : 0; } } template size_t Write(const T& data) const { if constexpr (std::contiguous_iterator) { using ContiguousType = typename T::value_type; static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); return WriteSpan(data); } else { static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); return WriteObject(data) ? 1 : 0; } } template size_t ReadSpan(std::span data) const { static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); if (!IsOpen()) { return 0; } return ReadRaw(data.data(), data.size()); } template size_t ReadRaw(void* data, size_t size) const { return std::fread(data, sizeof(T), size, file); } template size_t WriteSpan(std::span data) const { static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); if (!IsOpen()) { return 0; } return std::fwrite(data.data(), sizeof(T), data.size(), file); } template bool ReadObject(T& object) const { static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); if (!IsOpen()) { return false; } return std::fread(&object, sizeof(T), 1, file) == 1; } template size_t WriteRaw(const void* data, size_t size) const { return std::fwrite(data, sizeof(T), size, file); } template bool WriteObject(const T& object) const { static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); if (!IsOpen()) { return false; } return std::fwrite(&object, sizeof(T), 1, file) == 1; } std::string ReadString(size_t length) const; size_t WriteString(std::span string) const { return WriteSpan(string); } static size_t WriteBytes(const fs::path path, const auto& data) { IOFile out(path, FileAccessMode::Write); return out.Write(data); } private: fs::path filePath{}; FileAccessMode fileAccessMode{}; FileMode fileType{}; std::FILE *file = nullptr; uptr fileMapping = 0; }; u64 GetDirectorySize(const fs::path &path); } // namespace Base::FS