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