/* * The Mana Client * Copyright (C) 2024 The Mana Developers * * This file is part of The Mana Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once // Suppress deprecation warnings for PHYSFS_getUserDir #define PHYSFS_DEPRECATED #include "utils/physfsrwops.h" #include #include /** * These functions wrap PHYSFS functions to provide a more user-friendly * interface and to limit the direct use of the PHYSFS API to a single file. */ namespace FS { inline bool init(const char *argv0) { return PHYSFS_init(argv0) != 0; } inline void deinit() { PHYSFS_deinit(); } inline const char *getDirSeparator() { return PHYSFS_getDirSeparator(); } inline const char *getBaseDir() { return PHYSFS_getBaseDir(); } inline const char *getUserDir() { return PHYSFS_getUserDir(); } inline const char *getPrefDir(const char *org, const char *app) { return PHYSFS_getPrefDir(org, app); } /** * Sets the write directory. * * @param path The path of the directory to be added. * @return true on success, false otherwise. */ inline bool setWriteDir(const std::string &path) { return PHYSFS_setWriteDir(path.c_str()) != 0; } /** * Adds a directory or archive to the search path. If append is true * then the directory is added to the end of the search path, otherwise * it is added at the front. * * @return true on success, false otherwise. */ inline bool addToSearchPath(const std::string &path, bool append) { return PHYSFS_mount(path.c_str(), "/", append ? 1 : 0) != 0; } /** * Checks whether the given file or directory exists in the search path. */ inline bool exists(const std::string &path) { return PHYSFS_exists(path.c_str()) != 0; } inline std::optional getRealDir(const std::string &path) { auto dir = PHYSFS_getRealDir(path.c_str()); return dir ? std::optional(dir) : std::nullopt; } /** * Checks whether the given path is a directory. */ inline bool isDirectory(const std::string &path) { PHYSFS_Stat stat; if (PHYSFS_stat(path.c_str(), &stat) != 0) { return stat.filetype == PHYSFS_FILETYPE_DIRECTORY; } return false; } /** * Creates a directory in the write path. */ inline bool mkdir(const std::string &path) { return PHYSFS_mkdir(path.c_str()) != 0; } /** * Helper class to iterate over the files in a directory. * Based on https://stackoverflow.com/a/79051293/355419. */ class Files { public: struct End {}; friend bool operator!=(const char *const *files, End) { return *files != nullptr; } explicit Files(char **files) : mFiles(files) {} ~Files() { PHYSFS_freeList(mFiles); } Files(const Files &) = delete; Files &operator=(const Files &) = delete; // Relies on C++17 support for begin/end to not have the same return type const char* const *begin() const { return mFiles; } End end() const { return End(); } private: char **mFiles; }; /** * Returns a list of files in the given directory. */ inline Files enumerateFiles(const std::string &dir) { return Files(PHYSFS_enumerateFiles(dir.c_str())); } /** * File wrapper class to provide a more convenient API and automatic closing. */ class File { public: explicit File(PHYSFS_file *file) : file(file) {} ~File() { if (isOpen()) close(); } bool isOpen() const { return file != nullptr; } operator bool() const { return isOpen(); } bool close() { if (PHYSFS_close(file) != 0) { file = nullptr; return true; } return false; } std::optional read(void *data, size_t size) { auto len = PHYSFS_readBytes(file, data, size); return len >= 0 ? std::optional(len) : std::nullopt; } std::optional write(const void *data, size_t size) { auto len = PHYSFS_writeBytes(file, data, size); return len >= 0 ? std::optional(len) : std::nullopt; } bool flush() { return PHYSFS_flush(file) != 0; } bool seek(size_t pos) { return PHYSFS_seek(file, pos) != 0; } std::optional fileLength() const { auto len = PHYSFS_fileLength(file); return len >= 0 ? std::optional(len) : std::nullopt; } std::optional tell() const { auto pos = PHYSFS_tell(file); return pos >= 0 ? std::optional(pos) : std::nullopt; } bool eof() const { return PHYSFS_eof(file) != 0; } private: PHYSFS_file *file; }; inline File openWrite(const std::string &path) { return File(PHYSFS_openWrite(path.c_str())); } inline File openAppend(const std::string &path) { return File(PHYSFS_openAppend(path.c_str())); } inline File openRead(const std::string &path) { return File(PHYSFS_openRead(path.c_str())); } inline const char *getLastError() { return PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()); } // // Helper functions for loading files through SDL_RWops // inline SDL_RWops *openRWops(const std::string &path) { return PHYSFSRWOPS_openRead(path.c_str()); } /** * Creates a buffered SDL_RWops. * * Used to workaround a performance issue when SDL_mixer is using stb_vorbis, * in which case the file is read one byte at a time. * * See https://github.com/libsdl-org/SDL_mixer/issues/670 */ inline SDL_RWops *openBufferedRWops(const std::string &path, PHYSFS_uint64 bufferSize = 2048) { if (auto file = PHYSFS_openRead(path.c_str())) { PHYSFS_setBuffer(file, bufferSize); if (auto rw = PHYSFSRWOPS_makeRWops(file)) return rw; else PHYSFS_close(file); } return nullptr; } inline void *loadFile(const std::string &path, size_t &datasize) { auto file = openRWops(path); if (!file) return nullptr; return SDL_LoadFile_RW(file, &datasize, 1); } } // namespace FS