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.

237 lines
6.6 KiB

#include "engine/assets.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string_view>
#include "init.h"
#include "utils/file_util.h"
#include "utils/log.hpp"
#include "utils/paths.h"
#include "utils/str_cat.hpp"
#ifndef UNPACKED_MPQS
#include "mpq/mpq_sdl_rwops.hpp"
#endif
namespace devilution {
namespace {
#ifdef UNPACKED_MPQS
char *FindUnpackedMpqFile(char *relativePath)
{
char *path = nullptr;
const auto at = [&](const std::optional<std::string> &unpackedDir) -> bool {
if (!unpackedDir)
return false;
path = relativePath - unpackedDir->size();
std::memcpy(path, unpackedDir->data(), unpackedDir->size());
if (FileExists(path))
return true;
path = nullptr;
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);
}
SDL_RWops *OpenOptionalRWops(const std::string &path)
{
// SDL always logs an error in Debug mode.
// We check the file presence in Debug mode to avoid this.
if (IsDebugLogging() && !FileExists(path.c_str()))
return nullptr;
return SDL_RWFromFile(path.c_str(), "rb");
};
bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *fileNumber)
{
const MpqFileHash fileHash = CalculateMpqFileHash(filename);
const auto at = [=](std::optional<MpqArchive> &src) -> bool {
if (src && src->GetFileNumber(fileHash, *fileNumber)) {
*archive = &(*src);
return true;
}
return false;
};
return at(font_mpq) || at(lang_mpq) || at(devilutionx_mpq)
|| (gbIsHellfire && (at(hfvoice_mpq) || at(hfmusic_mpq) || at(hfbarb_mpq) || at(hfbard_mpq) || at(hfmonk_mpq) || at(hellfire_mpq))) || at(spawn_mpq) || at(diabdat_mpq);
}
#endif
} // namespace
#ifdef UNPACKED_MPQS
AssetRef FindAsset(std::string_view filename)
{
AssetRef result;
if (filename.empty() || filename.back() == '\\')
return result;
result.path[0] = '\0';
char pathBuf[AssetRef::PathBufSize];
char *const pathEnd = pathBuf + AssetRef::PathBufSize;
char *const relativePath = &pathBuf[AssetRef::PathBufSize - filename.size() - 1];
*BufCopy(relativePath, filename) = '\0';
#ifndef _WIN32
std::replace(relativePath, pathEnd, '\\', '/');
#endif
// Absolute path:
if (relativePath[0] == '/') {
if (FileExists(relativePath)) {
*BufCopy(result.path, std::string_view(relativePath, filename.size())) = '\0';
}
return result;
}
// Unpacked MPQ file:
char *const unpackedMpqPath = FindUnpackedMpqFile(relativePath);
if (unpackedMpqPath != nullptr) {
*BufCopy(result.path, std::string_view(unpackedMpqPath, pathEnd - unpackedMpqPath)) = '\0';
return result;
}
// The `/assets` directory next to the devilutionx binary.
const std::string &assetsPathPrefix = paths::AssetsPath();
char *assetsPath = relativePath - assetsPathPrefix.size();
std::memcpy(assetsPath, assetsPathPrefix.data(), assetsPathPrefix.size());
if (FileExists(assetsPath)) {
*BufCopy(result.path, std::string_view(assetsPath, pathEnd - assetsPath)) = '\0';
}
return result;
}
#else
AssetRef FindAsset(std::string_view filename)
{
AssetRef result;
if (filename.empty() || filename.back() == '\\')
return 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;
result.directHandle = OpenOptionalRWops(path);
if (result.directHandle != nullptr) {
LogVerbose("Loaded MPQ file override: {}", path);
return result;
}
}
// 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.
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()) {
result.directHandle = SDL_RWFromFile(relativePath.c_str(), "rb");
if (result.directHandle != nullptr)
return result;
}
#endif
return result;
}
#endif
AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe)
{
#if UNPACKED_MPQS
return AssetHandle { OpenFile(ref.path, "rb") };
#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(std::string_view filename, bool threadsafe)
{
AssetRef ref = FindAsset(filename);
if (!ref.ok())
return AssetHandle {};
return OpenAsset(std::move(ref), threadsafe);
}
AssetHandle OpenAsset(std::string_view 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(std::string_view filename, bool threadsafe)
{
#ifdef UNPACKED_MPQS
AssetRef ref = FindAsset(filename);
if (!ref.ok())
return nullptr;
return SDL_RWFromFile(ref.path, "rb");
#else
return OpenAsset(filename, threadsafe).release();
#endif
}
tl::expected<AssetData, std::string> LoadAsset(std::string_view path)
{
AssetRef ref = FindAsset(path);
if (!ref.ok()) {
return tl::make_unexpected(StrCat("Asset not found: ", path));
}
const size_t size = ref.size();
std::unique_ptr<char[]> data { new char[size] };
AssetHandle handle = OpenAsset(std::move(ref));
if (!handle.ok()) {
return tl::make_unexpected(StrCat("Failed to open asset: ", path, "\n", handle.error()));
}
if (size > 0 && !handle.read(data.get(), size)) {
return tl::make_unexpected(StrCat("Read failed: ", path, "\n", handle.error()));
}
return AssetData { std::move(data), size };
}
} // namespace devilution