Browse Source

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.
pull/5522/head
Gleb Mazovetskiy 3 years ago committed by Anders Jenbo
parent
commit
9db80ef5d7
  1. 1
      Source/CMakeLists.txt
  2. 158
      Source/engine/assets.cpp
  3. 204
      Source/engine/assets.hpp
  4. 14
      Source/engine/load_cl2.hpp
  5. 13
      Source/engine/load_clx.cpp
  6. 85
      Source/engine/load_file.hpp
  7. 7
      Source/engine/load_pcx.cpp
  8. 19
      Source/engine/render/text_render.cpp
  9. 17
      Source/engine/sound.cpp
  10. 12
      Source/init.cpp
  11. 2
      Source/storm/storm_svid.cpp
  12. 45
      Source/utils/language.cpp
  13. 30
      Source/utils/pcx.cpp
  14. 4
      Source/utils/pcx.hpp
  15. 32
      Source/utils/pcx_to_clx.cpp
  16. 5
      Source/utils/pcx_to_clx.hpp
  17. 2
      Source/utils/png.h
  18. 4
      Source/utils/soundsample.cpp
  19. 19
      Source/utils/static_vector.hpp

1
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

158
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<std::string> &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<std::string> &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<MpqArchive> &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

204
Source/engine/assets.hpp

@ -1,14 +1,206 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <SDL.h>
#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<std::fstream> handle;
[[nodiscard]] bool ok() const
{
return handle && !handle->fail();
}
bool read(void *buffer, size_t len)
{
handle->read(static_cast<char *>(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

14
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 <size_t MaxCount>
OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)> filenames, size_t count, uint16_t width)
{
StaticVector<SFile, MaxCount> files;
StaticVector<AssetHandle, MaxCount> files;
StaticVector<size_t, MaxCount> 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<const char *(size_t)>
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);

13
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<uint8_t[]> 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);
}

85
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 <typename T>
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<byte *>(data), fileLen);
handle.read(data, size);
}
template <typename T>
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<byte *>(data), count * sizeof(T));
handle.read(data, count * sizeof(T));
}
template <typename T>
bool LoadOptionalFileInMem(const char *path, T *data, std::size_t count)
{
SFile file { path, true };
if (!file.Ok())
return false;
file.Read(reinterpret_cast<byte *>(data), count * sizeof(T));
return true;
AssetHandle handle = OpenAsset(path);
return handle.ok() && handle.read(data, count * sizeof(T));
}
template <typename T, std::size_t N>
@ -99,18 +59,18 @@ void LoadFileInMem(const char *path, std::array<T, N> &data)
template <typename T = byte>
std::unique_ptr<T[]> 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<T[]> buf { new T[fileLen / sizeof(T)] };
file.Read(reinterpret_cast<byte *>(buf.get()), fileLen);
std::unique_ptr<T[]> buf { new T[size / sizeof(T)] };
handle.read(buf.get(), size);
return buf;
}
@ -139,13 +99,18 @@ struct MultiFileLoader {
[[nodiscard]] std::unique_ptr<byte[]> operator()(size_t numFiles, PathFn &&pathFn, uint32_t *outOffsets,
FilterFn filterFn = DefaultFilterFn {})
{
StaticVector<SFile, MaxFiles> files;
StaticVector<AssetHandle, MaxFiles> files;
StaticVector<uint32_t, MaxFiles> 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<uint32_t>(size));
outOffsets[j] = static_cast<uint32_t>(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;

7
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;

19
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<uint8_t, 256> *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<uint8_t, 256> *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<uint8_t, 256> *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]) {

17
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<std::uint8_t>(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();

12
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");

2
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;

45
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<uint32_t>(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<const char *, 2> 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<MoEntry[]> 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<uint32_t>(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<MoEntry[]> 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<uint32_t>(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<char[]> { 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);
}

30
Source/utils/pcx.cpp

@ -1,30 +0,0 @@
#include "utils/pcx.hpp"
#include <SDL_endian.h>
#include <cstring>
#include <memory>
#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

4
Source/utils/pcx.hpp

@ -3,8 +3,6 @@
#include <cstddef>
#include <cstdint>
#include <SDL.h>
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

32
Source/utils/pcx_to_clx.cpp

@ -7,6 +7,8 @@
#include <memory>
#include <vector>
#include <SDL_endian.h>
#include "appfat.h"
#include "utils/clx_write.hpp"
#include "utils/endian.hpp"
@ -18,10 +20,6 @@
#include <iostream>
#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<uint8_t> transparentColor, SDL_Color *outPalette)
OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight, std::optional<uint8_t> 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<uint8_t[]> 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;

5
Source/utils/pcx_to_clx.hpp

@ -2,8 +2,7 @@
#include <cstdint>
#include <SDL.h>
#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<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
OptionalOwnedClxSpriteList PcxToClx(AssetHandle &handle, size_t fileSize, int numFramesOrFrameHeight = 1, std::optional<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
} // namespace devilution

2
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;

4
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)

19
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 <typename... Args>
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<T *>(&data_[pos]));
#else
return *reinterpret_cast<T *>(&data_[pos]);
#endif
}
const T &operator[](std::size_t pos) const
{
#if __cplusplus >= 201703L

Loading…
Cancel
Save