From 9db80ef5d776010ce39068624c4bebddd9767b38 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 13 Nov 2022 15:11:58 +0000 Subject: [PATCH] Assets: Load files directly for UNPACKED_MPQS When using `UNPACKED_MPQS`, avoid all the SDL machinery for reading files. This is beneficial not only due to reduced indirection but also because we can test for the file's existence and get the file size without opening it, which is much faster. --- Source/CMakeLists.txt | 1 - Source/engine/assets.cpp | 158 +++++++++++++++------ Source/engine/assets.hpp | 204 ++++++++++++++++++++++++++- Source/engine/load_cl2.hpp | 14 +- Source/engine/load_clx.cpp | 13 +- Source/engine/load_file.hpp | 85 ++++------- Source/engine/load_pcx.cpp | 7 +- Source/engine/render/text_render.cpp | 19 ++- Source/engine/sound.cpp | 17 +-- Source/init.cpp | 12 +- Source/storm/storm_svid.cpp | 2 +- Source/utils/language.cpp | 45 ++---- Source/utils/pcx.cpp | 30 ---- Source/utils/pcx.hpp | 4 - Source/utils/pcx_to_clx.cpp | 32 +++-- Source/utils/pcx_to_clx.hpp | 5 +- Source/utils/png.h | 2 +- Source/utils/soundsample.cpp | 4 +- Source/utils/static_vector.hpp | 19 +++ 19 files changed, 441 insertions(+), 232 deletions(-) delete mode 100644 Source/utils/pcx.cpp diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index cc2b3c7bd..6f66f2c4a 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -160,7 +160,6 @@ set(libdevilutionx_SRCS utils/language.cpp utils/logged_fstream.cpp utils/paths.cpp - utils/pcx.cpp utils/pcx_to_clx.cpp utils/sdl_bilinear_scale.cpp utils/sdl_thread.cpp diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 690ff79a6..3639d49dc 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -14,6 +14,24 @@ namespace devilution { namespace { +#ifdef UNPACKED_MPQS +std::string FindUnpackedMpqFile(const char *relativePath) +{ + std::string path; + const auto at = [&](const std::optional &unpackedDir) -> bool { + if (!unpackedDir) + return false; + if (FileExists(path.append(*unpackedDir).append(relativePath).c_str())) + return true; + path.clear(); + return false; + }; + at(font_data_path) || at(lang_data_path) + || (gbIsHellfire && at(hellfire_data_path)) + || at(spawn_data_path) || at(diabdat_data_path); + return path; +} +#else bool IsDebugLogging() { return SDL_LOG_PRIORITY_DEBUG >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); @@ -28,26 +46,7 @@ SDL_RWops *OpenOptionalRWops(const std::string &path) return SDL_RWFromFile(path.c_str(), "rb"); }; -#ifdef UNPACKED_MPQS -SDL_RWops *OpenUnpackedMpqFile(const std::string &relativePath) -{ - SDL_RWops *result = nullptr; - std::string tmpPath; - const auto at = [&](const std::optional &unpackedDir) -> bool { - if (!unpackedDir) - return false; - tmpPath.clear(); - tmpPath.reserve(unpackedDir->size() + relativePath.size()); - result = OpenOptionalRWops(tmpPath.append(*unpackedDir).append(relativePath)); - return result != nullptr; - }; - at(font_data_path) || at(lang_data_path) - || (gbIsHellfire && at(hellfire_data_path)) - || at(spawn_data_path) || at(diabdat_data_path); - return result; -} -#else -bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) +bool FindMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) { const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); const auto at = [=](std::optional &src) -> bool { @@ -65,50 +64,127 @@ bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumbe } // namespace -SDL_RWops *OpenAsset(const char *filename, bool threadsafe) +#ifdef UNPACKED_MPQS +AssetRef FindAsset(const char *filename) { + AssetRef result; std::string relativePath = filename; #ifndef _WIN32 std::replace(relativePath.begin(), relativePath.end(), '\\', '/'); #endif + // Absolute path: + if (relativePath[0] == '/') { + if (FileExists(relativePath)) + result.path = std::move(relativePath); + return result; + } - if (relativePath[0] == '/') - return SDL_RWFromFile(relativePath.c_str(), "rb"); + // Unpacked MPQ file: + result.path = FindUnpackedMpqFile(relativePath.c_str()); + if (!result.path.empty()) + return result; - SDL_RWops *rwops; + // The `/assets` directory next to the devilutionx binary. + std::string path = paths::AssetsPath() + relativePath; + if (FileExists(path)) + result.path = std::move(path); + return result; +} +#else +AssetRef FindAsset(const char *filename) +{ + AssetRef result; + std::string relativePath = filename; +#ifndef _WIN32 + std::replace(relativePath.begin(), relativePath.end(), '\\', '/'); +#endif + + if (relativePath[0] == '/') { + result.directHandle = SDL_RWFromFile(relativePath.c_str(), "rb"); + if (result.directHandle != nullptr) { + return result; + } + } // Files in the `PrefPath()` directory can override MPQ contents. { const std::string path = paths::PrefPath() + relativePath; - if ((rwops = OpenOptionalRWops(path)) != nullptr) { + result.directHandle = OpenOptionalRWops(path); + if (result.directHandle != nullptr) { LogVerbose("Loaded MPQ file override: {}", path); - return rwops; + return result; } } - // Load from all the MPQ archives. -#ifdef UNPACKED_MPQS - if ((rwops = OpenUnpackedMpqFile(relativePath)) != nullptr) - return rwops; -#else - MpqArchive *archive; - uint32_t fileNumber; - if (OpenMpqFile(filename, &archive, &fileNumber)) - return SDL_RWops_FromMpqFile(*archive, fileNumber, filename, threadsafe); -#endif + // Look for the file in all the MPQ archives: + if (FindMpqFile(filename, &result.archive, &result.fileNumber)) { + result.filename = filename; + return result; + } // Load from the `/assets` directory next to the devilutionx binary. - if ((rwops = OpenOptionalRWops(paths::AssetsPath() + relativePath))) - return rwops; + result.directHandle = OpenOptionalRWops(paths::AssetsPath() + relativePath); + if (result.directHandle != nullptr) + return result; #if defined(__ANDROID__) || defined(__APPLE__) // Fall back to the bundled assets on supported systems. // This is handled by SDL when we pass a relative path. - if (!paths::AssetsPath().empty() && (rwops = SDL_RWFromFile(relativePath.c_str(), "rb"))) - return rwops; + if (!paths::AssetsPath().empty()) { + result.directHandle = SDL_RWFromFile(relativePath.c_str(), "rb"); + if (result.directHandle != nullptr) + return result; + } +#endif + + return result; +} +#endif + +AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe) +{ +#ifdef UNPACKED_MPQS + return AssetHandle { CreateFileStream(ref.path.c_str(), std::fstream::in | std::fstream::binary) }; +#else + if (ref.archive != nullptr) + return AssetHandle { SDL_RWops_FromMpqFile(*ref.archive, ref.fileNumber, ref.filename, threadsafe) }; + if (ref.directHandle != nullptr) { + // Transfer handle ownership: + SDL_RWops *handle = ref.directHandle; + ref.directHandle = nullptr; + return AssetHandle { handle }; + } + return AssetHandle { nullptr }; #endif +} + +AssetHandle OpenAsset(const char *filename, bool threadsafe) +{ + AssetRef ref = FindAsset(filename); + if (!ref.ok()) + return AssetHandle {}; + return OpenAsset(std::move(ref), threadsafe); +} - return nullptr; +AssetHandle OpenAsset(const char *filename, size_t &fileSize, bool threadsafe) +{ + AssetRef ref = FindAsset(filename); + if (!ref.ok()) + return AssetHandle {}; + fileSize = ref.size(); + return OpenAsset(std::move(ref), threadsafe); +} + +SDL_RWops *OpenAssetAsSdlRwOps(const char *filename, bool threadsafe) +{ +#ifdef UNPACKED_MPQS + AssetRef ref = FindAsset(filename); + if (!ref.ok()) + return nullptr; + return SDL_RWFromFile(ref.path.c_str(), "rb"); +#else + return OpenAsset(filename, threadsafe).release(); +#endif } } // namespace devilution diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index e6df62698..90246623e 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -1,14 +1,206 @@ #pragma once +#include +#include +#include + #include +#include "appfat.h" +#include "diablo.h" +#include "mpq/mpq_reader.hpp" +#include "utils/file_util.h" +#include "utils/str_cat.hpp" +#include "utils/string_or_view.hpp" + namespace devilution { -/** - * @brief Opens a Storm file and creates a read-only SDL_RWops from its handle. - * - * Closes the handle when it gets closed. - */ -SDL_RWops *OpenAsset(const char *filename, bool threadsafe = false); +#ifdef UNPACKED_MPQS +struct AssetRef { + std::string path; + + [[nodiscard]] bool ok() const + { + return !path.empty(); + } + + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + [[nodiscard]] const char *error() const + { + return "File not found"; + } + + [[nodiscard]] size_t size() const + { + uintmax_t fileSize; + if (!GetFileSize(path.c_str(), &fileSize)) + return 0; + return fileSize; + } +}; + +struct AssetHandle { + std::optional handle; + + [[nodiscard]] bool ok() const + { + return handle && !handle->fail(); + } + + bool read(void *buffer, size_t len) + { + handle->read(static_cast(buffer), len); + return !handle->fail(); + } + + bool seek(std::ios::pos_type pos) + { + handle->seekg(pos); + return !handle->fail(); + } + + [[nodiscard]] const char *error() const + { + return std::strerror(errno); + } +}; +#else +struct AssetRef { + // An MPQ file reference: + MpqArchive *archive = nullptr; + uint32_t fileNumber; + const char *filename; + + // Alternatively, a direct SDL_RWops handle: + SDL_RWops *directHandle = nullptr; + + AssetRef() = default; + + AssetRef(AssetRef &&other) noexcept + : archive(other.archive) + , fileNumber(other.fileNumber) + , filename(other.filename) + , directHandle(other.directHandle) + { + other.directHandle = nullptr; + } + + AssetRef &operator=(AssetRef &&other) noexcept + { + if (directHandle != nullptr) + SDL_RWclose(directHandle); + archive = other.archive; + fileNumber = other.fileNumber; + filename = other.filename; + directHandle = other.directHandle; + other.directHandle = nullptr; + return *this; + } + + ~AssetRef() + { + if (directHandle != nullptr) + SDL_RWclose(directHandle); + } + + [[nodiscard]] bool ok() const + { + return directHandle != nullptr || archive != nullptr; + } + + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + [[nodiscard]] const char *error() const + { + return SDL_GetError(); + } + + [[nodiscard]] size_t size() const + { + if (archive != nullptr) { + int32_t error; + return archive->GetUnpackedFileSize(fileNumber, error); + } + return SDL_RWsize(directHandle); + } +}; + +struct AssetHandle { + SDL_RWops *handle = nullptr; + + AssetHandle() = default; + + explicit AssetHandle(SDL_RWops *handle) + : handle(handle) + { + } + + AssetHandle(AssetHandle &&other) noexcept + : handle(other.handle) + { + other.handle = nullptr; + } + + AssetHandle &operator=(AssetHandle &&other) noexcept + { + if (handle != nullptr) { + SDL_RWclose(handle); + } + handle = other.handle; + other.handle = nullptr; + return *this; + } + + ~AssetHandle() + { + if (handle != nullptr) + SDL_RWclose(handle); + } + + [[nodiscard]] bool ok() const + { + return handle != nullptr; + } + + bool read(void *buffer, size_t len) + { + return handle->read(handle, buffer, len, 1) == 1; + } + + bool seek(std::ios::pos_type pos) + { + return handle->seek(handle, pos, RW_SEEK_SET) != -1; + } + + [[nodiscard]] const char *error() const + { + return SDL_GetError(); + } + + SDL_RWops *release() && + { + SDL_RWops *result = handle; + handle = nullptr; + return result; + } +}; +#endif + +inline bool ValidateHandle(const char *path, const AssetHandle &handle) +{ + if (handle.ok()) + return true; + if (!HeadlessMode) { + app_fatal(StrCat("Failed to open file:\n", path, "\n\n", handle.error())); + } + return false; +} + +AssetRef FindAsset(const char *filename); + +AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe = false); +AssetHandle OpenAsset(const char *filename, bool threadsafe = false); +AssetHandle OpenAsset(const char *filename, size_t &fileSize, bool threadsafe = false); + +SDL_RWops *OpenAssetAsSdlRwOps(const char *filename, bool threadsafe = false); } // namespace devilution diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index 104bb1832..9e0a836f6 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -11,6 +11,7 @@ #include "utils/endian.hpp" #include "utils/pointer_value_union.hpp" #include "utils/static_vector.hpp" +#include "utils/str_cat.hpp" #ifdef UNPACKED_MPQS #define DEVILUTIONX_CL2_EXT ".clx" @@ -25,14 +26,17 @@ OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue template OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref filenames, size_t count, uint16_t width) { - StaticVector files; + StaticVector files; StaticVector fileSizes; const size_t sheetHeaderSize = 4 * count; size_t totalSize = sheetHeaderSize; for (size_t i = 0; i < count; ++i) { const char *filename = filenames(i); - files.emplace_back(filename); - const size_t size = files[i].Size(); + size_t size; + files.emplace_back(OpenAsset(filename, size)); + if (!files.back().ok()) { + app_fatal(StrCat("Failed to open file:\n", filename, "\n\n", files.back().error())); + } fileSizes.emplace_back(size); totalSize += size; } @@ -43,8 +47,8 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref size_t accumulatedSize = sheetHeaderSize; for (size_t i = 0; i < count; ++i) { const size_t size = fileSizes[i]; - if (!files[i].Read(&data[accumulatedSize], size)) - app_fatal(StrCat("Read failed: ", SDL_GetError())); + if (!files[i].read(&data[accumulatedSize], size)) + app_fatal(StrCat("Read failed:\n", files[i].error())); WriteLE32(&data[i * 4], accumulatedSize); #ifndef UNPACKED_MPQS [[maybe_unused]] const uint16_t numLists = Cl2ToClx(&data[accumulatedSize], size, frameWidth); diff --git a/Source/engine/load_clx.cpp b/Source/engine/load_clx.cpp index 93fc2fb43..0383bb8aa 100644 --- a/Source/engine/load_clx.cpp +++ b/Source/engine/load_clx.cpp @@ -13,13 +13,16 @@ namespace devilution { OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) { - SDL_RWops *handle = OpenAsset(path); - if (handle == nullptr) + AssetRef ref = FindAsset(path); + if (!ref.ok()) return std::nullopt; - const size_t size = SDL_RWsize(handle); + const size_t size = ref.size(); std::unique_ptr data { new uint8_t[size] }; - SDL_RWread(handle, data.get(), size, 1); - SDL_RWclose(handle); + { + AssetHandle handle = OpenAsset(std::move(ref)); + if (!handle.ok() || !handle.read(data.get(), size)) + return std::nullopt; + } return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 5b8dc6e05..dcc7b54ca 100644 --- a/Source/engine/load_file.hpp +++ b/Source/engine/load_file.hpp @@ -16,72 +16,32 @@ namespace devilution { -class SFile { -public: - explicit SFile(const char *path, bool isOptional = false) - { - handle_ = OpenAsset(path); - if (handle_ == nullptr) { - if (!HeadlessMode && !isOptional) { - app_fatal(StrCat("Failed to open file:\n", path, "\n\n", SDL_GetError())); - } - } - } - - ~SFile() - { - if (handle_ != nullptr) - SDL_RWclose(handle_); - } - - [[nodiscard]] bool Ok() const - { - return handle_ != nullptr; - } - - [[nodiscard]] std::size_t Size() const - { - return SDL_RWsize(handle_); - } - - bool Read(void *buffer, std::size_t len) const - { - return SDL_RWread(handle_, buffer, len, 1); - } - -private: - SDL_RWops *handle_; -}; - template void LoadFileInMem(const char *path, T *data) { - SFile file { path }; - if (!file.Ok()) + size_t size; + AssetHandle handle = OpenAsset(path, size); + if (!ValidateHandle(path, handle)) return; - const std::size_t fileLen = file.Size(); - if ((fileLen % sizeof(T)) != 0) + if ((size % sizeof(T)) != 0) app_fatal(StrCat("File size does not align with type\n", path)); - file.Read(reinterpret_cast(data), fileLen); + handle.read(data, size); } template void LoadFileInMem(const char *path, T *data, std::size_t count) { - SFile file { path }; - if (!file.Ok()) + AssetHandle handle = OpenAsset(path); + if (!ValidateHandle(path, handle)) return; - file.Read(reinterpret_cast(data), count * sizeof(T)); + handle.read(data, count * sizeof(T)); } template bool LoadOptionalFileInMem(const char *path, T *data, std::size_t count) { - SFile file { path, true }; - if (!file.Ok()) - return false; - file.Read(reinterpret_cast(data), count * sizeof(T)); - return true; + AssetHandle handle = OpenAsset(path); + return handle.ok() && handle.read(data, count * sizeof(T)); } template @@ -99,18 +59,18 @@ void LoadFileInMem(const char *path, std::array &data) template std::unique_ptr LoadFileInMem(const char *path, std::size_t *numRead = nullptr) { - SFile file { path }; - if (!file.Ok()) + size_t size; + AssetHandle handle = OpenAsset(path, size); + if (!ValidateHandle(path, handle)) return nullptr; - const std::size_t fileLen = file.Size(); - if ((fileLen % sizeof(T)) != 0) + if ((size % sizeof(T)) != 0) app_fatal(StrCat("File size does not align with type\n", path)); if (numRead != nullptr) - *numRead = fileLen / sizeof(T); + *numRead = size / sizeof(T); - std::unique_ptr buf { new T[fileLen / sizeof(T)] }; - file.Read(reinterpret_cast(buf.get()), fileLen); + std::unique_ptr buf { new T[size / sizeof(T)] }; + handle.read(buf.get(), size); return buf; } @@ -139,13 +99,18 @@ struct MultiFileLoader { [[nodiscard]] std::unique_ptr operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets, FilterFn filterFn = DefaultFilterFn {}) { - StaticVector files; + StaticVector files; StaticVector sizes; size_t totalSize = 0; for (size_t i = 0, j = 0; i < numFiles; ++i) { if (!filterFn(i)) continue; - const size_t size = files.emplace_back(pathFn(i)).Size(); + size_t size; + const char *path = pathFn(i); + files.emplace_back(OpenAsset(path, size)); + if (!ValidateHandle(path, files.back())) + return nullptr; + sizes.emplace_back(static_cast(size)); outOffsets[j] = static_cast(totalSize); totalSize += size; @@ -156,7 +121,7 @@ struct MultiFileLoader { for (size_t i = 0, j = 0; i < numFiles; ++i) { if (!filterFn(i)) continue; - files[j].Read(&buf[outOffsets[j]], sizes[j]); + files[j].read(&buf[outOffsets[j]], sizes[j]); ++j; } return buf; diff --git a/Source/engine/load_pcx.cpp b/Source/engine/load_pcx.cpp index f1782a92a..95ecd4024 100644 --- a/Source/engine/load_pcx.cpp +++ b/Source/engine/load_pcx.cpp @@ -57,8 +57,9 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames } return result; #else - SDL_RWops *handle = OpenAsset(path); - if (handle == nullptr) { + size_t fileSize; + AssetHandle handle = OpenAsset(path, fileSize); + if (!handle.ok()) { if (logError) LogError("Missing file: {}", path); return std::nullopt; @@ -66,7 +67,7 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames #ifdef DEBUG_PCX_TO_CL2_SIZE std::cout << filename; #endif - OptionalOwnedClxSpriteList result = PcxToClx(handle, numFramesOrFrameHeight, transparentColor, outPalette); + OptionalOwnedClxSpriteList result = PcxToClx(handle, fileSize, numFramesOrFrameHeight, transparentColor, outPalette); if (!result) return std::nullopt; return result; diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 8dba4abcb..991d493c9 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -143,9 +143,14 @@ void GetFontPath(GameFontTables size, uint16_t row, string_view ext, char *out) *fmt::format_to(out, FMT_COMPILE(R"(fonts\{}-{:02x}.{})"), FontSizes[size], row, ext) = '\0'; } +uint32_t GetFontId(GameFontTables size, uint16_t row) +{ + return (size << 16) | row; +} + std::array *LoadFontKerning(GameFontTables size, uint16_t row) { - uint32_t fontId = (size << 16) | row; + uint32_t fontId = GetFontId(size, row); auto hotKerning = FontKerns.find(fontId); if (hotKerning != FontKerns.end()) { @@ -162,11 +167,8 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) } else if (IsHangul(row)) { kerning->fill(HangulWidth[size]); } else { - SDL_RWops *handle = OpenAsset(path); - if (handle != nullptr) { - SDL_RWread(handle, kerning, 256, 1); - SDL_RWclose(handle); - } else { + AssetHandle handle = OpenAsset(path); + if (!handle.ok() || !handle.read(kerning, 256)) { LogError("Missing font kerning: {}", path); kerning->fill(CJKWidth[size]); } @@ -175,11 +177,6 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) return kerning; } -uint32_t GetFontId(GameFontTables size, uint16_t row) -{ - return (size << 16) | row; -} - const OwnedClxSpriteList *LoadFont(GameFontTables size, text_color color, uint16_t row) { if (ColorTranslations[color] != nullptr && !ColorTranslationsData[color]) { diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index 7c7542d45..7ad79d740 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -62,26 +62,27 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample #ifndef STREAM_ALL_AUDIO } else { bool isMp3 = true; - SDL_RWops *file = OpenAsset(GetMp3Path(path).c_str()); - if (file == nullptr) { + size_t dwBytes; + AssetHandle handle = OpenAsset(GetMp3Path(path).c_str(), dwBytes); + if (!handle.ok()) { +#ifndef UNPACKED_MPQS SDL_ClearError(); +#endif isMp3 = false; - file = OpenAsset(path); - if (file == nullptr) { + handle = OpenAsset(path, dwBytes); + if (!handle.ok()) { if (errorDialog) ErrDlg("OpenAsset failed", path, __FILE__, __LINE__); return false; } } - size_t dwBytes = SDL_RWsize(file); auto waveFile = MakeArraySharedPtr(dwBytes); - if (SDL_RWread(file, waveFile.get(), dwBytes, 1) == 0) { + if (!handle.read(waveFile.get(), dwBytes)) { if (errorDialog) ErrDlg("Failed to read file", StrCat(path, ": ", SDL_GetError()), __FILE__, __LINE__); return false; } - int error = result.SetChunk(waveFile, dwBytes, isMp3); - SDL_RWclose(file); + const int error = result.SetChunk(waveFile, dwBytes, isMp3); if (error != 0) { if (errorDialog) ErrSdl(); diff --git a/Source/init.cpp b/Source/init.cpp index eed425fb2..3d059de4f 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -231,7 +231,7 @@ void LoadLanguageArchive() void LoadGameArchives() { auto paths = GetMPQSearchPaths(); -#if UNPACKED_MPQS +#ifdef UNPACKED_MPQS diabdat_data_path = FindUnpackedMpqData(paths, "diabdat"); if (!diabdat_data_path) { spawn_data_path = FindUnpackedMpqData(paths, "spawn"); @@ -239,12 +239,11 @@ void LoadGameArchives() gbIsSpawn = true; } if (!HeadlessMode) { - SDL_RWops *handle = OpenAsset("ui_art\\title.clx"); - if (handle == nullptr) { + AssetRef ref = FindAsset("ui_art\\title.clx"); + if (!ref.ok()) { LogError("{}", SDL_GetError()); InsertCDDlg(_("diabdat.mpq or spawn.mpq")); } - SDL_RWclose(handle); } hellfire_data_path = FindUnpackedMpqData(paths, "hellfire"); if (hellfire_data_path) @@ -280,12 +279,11 @@ void LoadGameArchives() gbIsSpawn = true; } if (!HeadlessMode) { - SDL_RWops *handle = OpenAsset("ui_art\\title.pcx"); - if (handle == nullptr) { + AssetRef ref = FindAsset("ui_art\\title.pcx"); + if (!ref.ok()) { LogError("{}", SDL_GetError()); InsertCDDlg(_("diabdat.mpq or spawn.mpq")); } - SDL_RWclose(handle); } hellfire_mpq = LoadMPQ(paths, "hellfire.mpq"); diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index 8c4132654..541631c12 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -241,7 +241,7 @@ bool SVidPlayBegin(const char *filename, int flags) // 0x800000 // Edge detection // 0x200800 // Clear FB - SDL_RWops *videoStream = OpenAsset(filename); + SDL_RWops *videoStream = OpenAssetAsSdlRwOps(filename); SVidHandle = Smacker_Open(videoStream); if (!SVidHandle.isValid) { return false; diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index e5f5f8006..f18ce1fc9 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -245,12 +245,12 @@ void ParseMetadata(string_view metadata) } } -bool ReadEntry(SDL_RWops *rw, const MoEntry &e, char *result) +bool ReadEntry(AssetHandle &handle, const MoEntry &e, char *result) { - if (SDL_RWseek(rw, e.offset, RW_SEEK_SET) == -1) + if (!handle.seek(e.offset)) return false; result[e.length] = '\0'; - return static_cast(SDL_RWread(rw, result, sizeof(char), e.length)) == e.length; + return handle.read(result, e.length); } } // namespace @@ -306,12 +306,7 @@ bool HasTranslation(const std::string &locale) constexpr std::array Extensions { ".mo", ".gmo" }; return std::any_of(Extensions.cbegin(), Extensions.cend(), [locale](const std::string &extension) { - SDL_RWops *rw = OpenAsset((locale + extension).c_str()); - if (rw != nullptr) { - SDL_RWclose(rw); - return true; - } - return false; + return FindAsset((locale + extension).c_str()).ok(); }); } @@ -339,47 +334,43 @@ void LanguageInitialize() } const std::string lang(*sgOptions.Language.code); - SDL_RWops *rw; + + AssetHandle handle; // Translations normally come in ".gmo" files. // We also support ".mo" because that is what poedit generates // and what translators use to test their work. for (const char *ext : { ".mo", ".gmo" }) { - if ((rw = OpenAsset((lang + ext).c_str())) != nullptr) { + if ((handle = OpenAsset((lang + ext).c_str())).ok()) { break; } } - if (rw == nullptr) { + if (!handle.ok()) { SetPluralForm("plural=(n != 1);"); // Reset to English plural form return; } // Read header and do sanity checks MoHead head; - if (SDL_RWread(rw, &head, sizeof(MoHead), 1) != 1) { - SDL_RWclose(rw); + if (!handle.read(&head, sizeof(MoHead))) { return; } SwapLE(head); if (head.magic != MO_MAGIC) { - SDL_RWclose(rw); return; // not a MO file } if (head.revision.major > 1 || head.revision.minor > 1) { - SDL_RWclose(rw); return; // unsupported revision } // Read entries of source strings std::unique_ptr src { new MoEntry[head.nbMappings] }; - if (SDL_RWseek(rw, head.srcOffset, RW_SEEK_SET) == -1) { - SDL_RWclose(rw); + if (!handle.seek(head.srcOffset)) { return; } - if (static_cast(SDL_RWread(rw, src.get(), sizeof(MoEntry), head.nbMappings)) != head.nbMappings) { - SDL_RWclose(rw); + if (!handle.read(src.get(), head.nbMappings * sizeof(MoEntry))) { return; } for (size_t i = 0; i < head.nbMappings; ++i) { @@ -388,12 +379,10 @@ void LanguageInitialize() // Read entries of target strings std::unique_ptr dst { new MoEntry[head.nbMappings] }; - if (SDL_RWseek(rw, head.dstOffset, RW_SEEK_SET) == -1) { - SDL_RWclose(rw); + if (!handle.seek(head.dstOffset)) { return; } - if (static_cast(SDL_RWread(rw, dst.get(), sizeof(MoEntry), head.nbMappings)) != head.nbMappings) { - SDL_RWclose(rw); + if (!handle.read(dst.get(), head.nbMappings * sizeof(MoEntry))) { return; } for (size_t i = 0; i < head.nbMappings; ++i) { @@ -402,13 +391,11 @@ void LanguageInitialize() // MO header if (src[0].length != 0) { - SDL_RWclose(rw); return; } { auto headerValue = std::unique_ptr { new char[dst[0].length + 1] }; - if (!ReadEntry(rw, dst[0], &headerValue[0])) { - SDL_RWclose(rw); + if (!ReadEntry(handle, dst[0], &headerValue[0])) { return; } ParseMetadata(&headerValue[0]); @@ -431,7 +418,7 @@ void LanguageInitialize() char *keyPtr = &translationKeys[0]; char *valuePtr = &translationValues[0]; for (uint32_t i = 1; i < head.nbMappings; i++) { - if (ReadEntry(rw, src[i], keyPtr) && ReadEntry(rw, dst[i], valuePtr)) { + if (ReadEntry(handle, src[i], keyPtr) && ReadEntry(handle, dst[i], valuePtr)) { // Plural keys also have a plural form but it does not participate in lookup. // Plural values are \0-terminated. string_view value { valuePtr, dst[i].length + 1 }; @@ -445,6 +432,4 @@ void LanguageInitialize() valuePtr += dst[i].length + 1; } } - - SDL_RWclose(rw); } diff --git a/Source/utils/pcx.cpp b/Source/utils/pcx.cpp deleted file mode 100644 index b88b0f8f7..000000000 --- a/Source/utils/pcx.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "utils/pcx.hpp" - -#include -#include -#include - -#include "appfat.h" - -#ifdef USE_SDL1 -#include "utils/sdl2_to_1_2_backports.h" -#endif - -namespace devilution { -namespace { -constexpr unsigned NumPaletteColors = 256; -} // namespace - -bool LoadPcxMeta(SDL_RWops *handle, int &width, int &height, uint8_t &bpp) -{ - PCXHeader pcxhdr; - if (SDL_RWread(handle, &pcxhdr, PcxHeaderSize, 1) == 0) { - return false; - } - width = SDL_SwapLE16(pcxhdr.Xmax) - SDL_SwapLE16(pcxhdr.Xmin) + 1; - height = SDL_SwapLE16(pcxhdr.Ymax) - SDL_SwapLE16(pcxhdr.Ymin) + 1; - bpp = pcxhdr.BitsPerPixel; - return true; -} - -} // namespace devilution diff --git a/Source/utils/pcx.hpp b/Source/utils/pcx.hpp index 3e9157006..fa4b07404 100644 --- a/Source/utils/pcx.hpp +++ b/Source/utils/pcx.hpp @@ -3,8 +3,6 @@ #include #include -#include - namespace devilution { struct PCXHeader { @@ -30,6 +28,4 @@ struct PCXHeader { static constexpr size_t PcxHeaderSize = 128; -bool LoadPcxMeta(SDL_RWops *handle, int &width, int &height, uint8_t &bpp); - } // namespace devilution diff --git a/Source/utils/pcx_to_clx.cpp b/Source/utils/pcx_to_clx.cpp index a1130cc85..5d9ec9b6d 100644 --- a/Source/utils/pcx_to_clx.cpp +++ b/Source/utils/pcx_to_clx.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "appfat.h" #include "utils/clx_write.hpp" #include "utils/endian.hpp" @@ -18,10 +20,6 @@ #include #endif -#ifdef USE_SDL1 -#include "utils/sdl2_to_1_2_backports.h" -#endif - namespace devilution { namespace { @@ -41,15 +39,26 @@ size_t GetReservationSize(size_t pcxSize) } } +bool LoadPcxMeta(AssetHandle &handle, int &width, int &height, uint8_t &bpp) +{ + PCXHeader pcxhdr; + if (!handle.read(&pcxhdr, PcxHeaderSize)) { + return false; + } + width = SDL_SwapLE16(pcxhdr.Xmax) - SDL_SwapLE16(pcxhdr.Xmin) + 1; + height = SDL_SwapLE16(pcxhdr.Ymax) - SDL_SwapLE16(pcxhdr.Ymin) + 1; + bpp = pcxhdr.BitsPerPixel; + return true; +} + } // namespace -OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) { int width; int height; uint8_t bpp; if (!LoadPcxMeta(handle, width, height, bpp)) { - SDL_RWclose(handle); return std::nullopt; } assert(bpp == 8); @@ -64,17 +73,14 @@ OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeigh numFrames = height / frameHeight; } - ptrdiff_t pixelDataSize = SDL_RWsize(handle); - if (pixelDataSize < 0) { - SDL_RWclose(handle); + size_t pixelDataSize = fileSize; + if (pixelDataSize <= PcxHeaderSize) { return std::nullopt; } - pixelDataSize -= PcxHeaderSize; std::unique_ptr fileBuffer { new uint8_t[pixelDataSize] }; - if (SDL_RWread(handle, fileBuffer.get(), pixelDataSize, 1) == 0) { - SDL_RWclose(handle); + if (handle.read(fileBuffer.get(), pixelDataSize) == 0) { return std::nullopt; } @@ -174,8 +180,6 @@ OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeigh } } - SDL_RWclose(handle); - // Release buffers before allocating the result array to reduce peak memory use. frameBuffer = nullptr; fileBuffer = nullptr; diff --git a/Source/utils/pcx_to_clx.hpp b/Source/utils/pcx_to_clx.hpp index 50751edd4..71ffc586f 100644 --- a/Source/utils/pcx_to_clx.hpp +++ b/Source/utils/pcx_to_clx.hpp @@ -2,8 +2,7 @@ #include -#include - +#include "engine/assets.hpp" #include "engine/clx_sprite.hpp" #include "utils/stdcompat/optional.hpp" @@ -16,6 +15,6 @@ namespace devilution { * @param numFramesOrFrameHeight Pass a positive value with the number of frames, or the frame height as a negative value. * @param transparentColor The PCX palette index of the transparent color. */ -OptionalOwnedClxSpriteList PcxToClx(SDL_RWops *handle, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight = 1, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); } // namespace devilution diff --git a/Source/utils/png.h b/Source/utils/png.h index 6a74f8f86..d898c7f73 100644 --- a/Source/utils/png.h +++ b/Source/utils/png.h @@ -41,7 +41,7 @@ inline void QuitPNG() inline SDL_Surface *LoadPNG(const char *file) { - SDL_RWops *rwops = OpenAsset(file); + SDL_RWops *rwops = OpenAssetAsSdlRwOps(file); SDL_Surface *surface = IMG_LoadPNG_RW(rwops); SDL_RWclose(rwops); return surface; diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index 5cc7dad2b..77ee827a8 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -114,7 +114,7 @@ bool SoundSample::Play(int numIterations) int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) { - SDL_RWops *handle = OpenAsset(filePath.c_str(), /*threadsafe=*/true); + SDL_RWops *handle = OpenAssetAsSdlRwOps(filePath.c_str(), /*threadsafe=*/true); if (handle == nullptr) { if (logErrors) LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream): {}", SDL_GetError()); @@ -122,7 +122,7 @@ int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors } file_path_ = std::move(filePath); isMp3_ = isMp3; - stream_ = CreateStream(handle, isMp3_); + stream_ = CreateStream(handle, isMp3); if (!stream_->open()) { stream_ = nullptr; if (logErrors) diff --git a/Source/utils/static_vector.hpp b/Source/utils/static_vector.hpp index ad3a5ef75..636271628 100644 --- a/Source/utils/static_vector.hpp +++ b/Source/utils/static_vector.hpp @@ -44,6 +44,16 @@ public: return size_; } + [[nodiscard]] T &back() + { + return (*this)[size_ - 1]; + } + + [[nodiscard]] const T &back() const + { + return (*this)[size_ - 1]; + } + template T &emplace_back(Args &&...args) // NOLINT(readability-identifier-naming) { @@ -58,6 +68,15 @@ public: return result; } + T &operator[](std::size_t pos) + { +#if __cplusplus >= 201703L + return *std::launder(reinterpret_cast(&data_[pos])); +#else + return *reinterpret_cast(&data_[pos]); +#endif + } + const T &operator[](std::size_t pos) const { #if __cplusplus >= 201703L