You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
7.0 KiB

#pragma once
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <map>
#include <span>
#include <string>
#include <string_view>
#ifdef USE_SDL3
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_iostream.h>
#else
#include <SDL.h>
#endif
#include <expected.hpp>
#include <fmt/format.h>
#include "appfat.h"
#include "game_mode.hpp"
#include "headless_mode.hpp"
#include "utils/file_util.h"
#include "utils/language.h"
#include "utils/sdl_compat.h"
#include "utils/str_cat.hpp"
#include "utils/string_or_view.hpp"
#ifndef UNPACKED_MPQS
#include "mpq/mpq_reader.hpp"
#endif
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#endif
namespace devilution {
#ifdef UNPACKED_MPQS
struct AssetRef {
static constexpr size_t PathBufSize = 4088;
char path[PathBufSize];
[[nodiscard]] bool ok() const
{
return path[0] != '\0';
}
// 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, &fileSize))
return 0;
return fileSize;
}
};
struct AssetHandle {
FILE *handle = nullptr;
AssetHandle() = default;
AssetHandle(FILE *handle)
: handle(handle)
{
}
AssetHandle(AssetHandle &&other) noexcept
: handle(other.handle)
{
other.handle = nullptr;
}
AssetHandle &operator=(AssetHandle &&other) noexcept
{
handle = other.handle;
other.handle = nullptr;
return *this;
}
~AssetHandle()
{
if (handle != nullptr)
std::fclose(handle);
}
[[nodiscard]] bool ok() const
{
return handle != nullptr && std::ferror(handle) == 0;
}
bool read(void *buffer, size_t len)
{
return std::fread(buffer, len, 1, handle) == 1;
}
bool seek(long pos)
{
return std::fseek(handle, pos, SEEK_SET) == 0;
}
[[nodiscard]] const char *error() const
{
return std::strerror(errno);
}
};
#else
struct AssetRef {
// An MPQ file reference:
MpqArchive *archive = nullptr;
uint32_t fileNumber;
std::string_view filename;
// Alternatively, a direct SDL_IOStream handle:
SDL_IOStream *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
{
closeDirectHandle();
archive = other.archive;
fileNumber = other.fileNumber;
filename = other.filename;
directHandle = other.directHandle;
other.directHandle = nullptr;
return *this;
}
~AssetRef()
{
closeDirectHandle();
}
[[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 static_cast<size_t>(SDL_GetIOSize(directHandle));
}
private:
void closeDirectHandle()
{
if (directHandle != nullptr) {
SDL_CloseIO(directHandle);
}
}
};
struct AssetHandle {
SDL_IOStream *handle = nullptr;
AssetHandle() = default;
explicit AssetHandle(SDL_IOStream *handle)
: handle(handle)
{
}
AssetHandle(AssetHandle &&other) noexcept
: handle(other.handle)
{
other.handle = nullptr;
}
AssetHandle &operator=(AssetHandle &&other) noexcept
{
closeHandle();
handle = other.handle;
other.handle = nullptr;
return *this;
}
~AssetHandle()
{
closeHandle();
}
[[nodiscard]] bool ok() const
{
return handle != nullptr;
}
bool read(void *buffer, size_t len)
{
return SDL_ReadIO(handle, buffer, len) == len;
}
bool seek(long pos)
{
return SDL_SeekIO(handle, pos, SDL_IO_SEEK_SET) != -1;
}
[[nodiscard]] const char *error() const
{
return SDL_GetError();
}
SDL_IOStream *release() &&
{
SDL_IOStream *result = handle;
handle = nullptr;
return result;
}
private:
void closeHandle()
{
if (handle != nullptr) {
SDL_CloseIO(handle);
}
}
};
#endif
std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view error);
[[noreturn]] inline void FailedToOpenFileError(std::string_view path, std::string_view error)
{
app_fatal(FailedToOpenFileErrorMessage(path, error));
}
inline bool ValidatAssetRef(std::string_view path, const AssetRef &ref)
{
if (ref.ok())
return true;
if (!HeadlessMode) {
FailedToOpenFileError(path, ref.error());
}
return false;
}
inline bool ValidateHandle(std::string_view path, const AssetHandle &handle)
{
if (handle.ok())
return true;
if (!HeadlessMode) {
FailedToOpenFileError(path, handle.error());
}
return false;
}
AssetRef FindAsset(std::string_view filename);
AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe = false);
AssetHandle OpenAsset(std::string_view filename, bool threadsafe = false);
AssetHandle OpenAsset(std::string_view filename, size_t &fileSize, bool threadsafe = false);
SDL_IOStream *OpenAssetAsSdlRwOps(std::string_view filename, bool threadsafe = false);
struct AssetData {
std::unique_ptr<char[]> data;
size_t size;
explicit operator std::string_view() const
{
return std::string_view(data.get(), size);
}
};
tl::expected<AssetData, std::string> LoadAsset(std::string_view path);
#ifdef UNPACKED_MPQS
using MpqArchiveT = std::string;
#else
using MpqArchiveT = MpqArchive;
#endif
extern DVL_API_FOR_TEST std::map<int, MpqArchiveT, std::greater<>> MpqArchives;
constexpr int MainMpqPriority = 1000;
constexpr int DevilutionXMpqPriority = 9000;
constexpr int LangMpqPriority = 9100;
constexpr int FontMpqPriority = 9200;
extern bool HasHellfireMpq;
void LoadCoreArchives();
void LoadLanguageArchive();
void LoadGameArchives();
void LoadHellfireArchives();
void UnloadModArchives();
void LoadModArchives(std::span<const std::string_view> modnames);
#ifdef BUILD_TESTING
[[nodiscard]] inline bool HaveMainData() { return MpqArchives.find(MainMpqPriority) != MpqArchives.end(); }
#endif
[[nodiscard]] inline bool HaveExtraFonts() { return MpqArchives.find(FontMpqPriority) != MpqArchives.end(); }
[[nodiscard]] inline bool HaveHellfire() { return HasHellfireMpq; }
[[nodiscard]] inline bool HaveIntro() { return FindAsset("gendata\\diablo1.smk").ok(); }
[[nodiscard]] inline bool HaveFullMusic() { return FindAsset("music\\dintro.wav").ok() || FindAsset("music\\dintro.mp3").ok(); }
[[nodiscard]] inline bool HaveBardAssets() { return FindAsset("plrgfx\\bard\\bha\\bhaas.clx").ok(); }
[[nodiscard]] inline bool HaveBarbarianAssets() { return FindAsset("plrgfx\\barbarian\\cha\\chaas.clx").ok(); }
} // namespace devilution