mirror of
https://github.com/diasurgical/DevilutionX.git
synced 2026-10-12 02:13:06 +00:00
Open Track integrity of assets that modify game engine logic
This commit is contained in:
@@ -23,7 +23,7 @@ tl::expected<DataFile, DataFile::Error> DataFile::load(std::string_view path)
|
||||
// TODO: It should be possible to stream the data file contents instead of copying the whole thing into memory
|
||||
std::unique_ptr<char[]> data { new char[size] };
|
||||
{
|
||||
AssetHandle handle = OpenAsset(std::move(ref));
|
||||
AssetHandle handle = OpenIntegralAsset(std::move(ref));
|
||||
if (!handle.ok())
|
||||
return tl::unexpected { Error::OpenFailed };
|
||||
if (size > 0 && !handle.read(data.get(), size))
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace devilution {
|
||||
std::vector<std::string> OverridePaths;
|
||||
std::map<int, MpqArchiveT, std::greater<>> MpqArchives;
|
||||
bool HasHellfireMpq;
|
||||
bool IsAssetIntegrityViolated = false;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -149,6 +150,7 @@ AssetRef FindAsset(std::string_view filename)
|
||||
result.directHandle = OpenOptionalRWops(path);
|
||||
if (result.directHandle != nullptr) {
|
||||
LogVerbose("Loaded MPQ file override: {}", path);
|
||||
result.isOverridden = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +183,7 @@ AssetRef FindAsset(std::string_view filename)
|
||||
|
||||
AssetHandle OpenAsset(AssetRef &&ref, bool threadsafe)
|
||||
{
|
||||
#if UNPACKED_MPQS
|
||||
#ifdef UNPACKED_MPQS
|
||||
return AssetHandle { OpenFile(ref.path, "rb") };
|
||||
#else
|
||||
if (ref.archive != nullptr)
|
||||
@@ -213,6 +215,32 @@ AssetHandle OpenAsset(std::string_view filename, size_t &fileSize, bool threadsa
|
||||
return OpenAsset(std::move(ref), threadsafe);
|
||||
}
|
||||
|
||||
AssetHandle OpenIntegralAsset(AssetRef &&ref, bool threadsafe)
|
||||
{
|
||||
#ifndef UNPACKED_MPQS
|
||||
if (ref.isOverridden)
|
||||
IsAssetIntegrityViolated = true;
|
||||
#endif
|
||||
return OpenAsset(std::move(ref), threadsafe);
|
||||
}
|
||||
|
||||
AssetHandle OpenIntegralAsset(std::string_view filename, bool threadsafe)
|
||||
{
|
||||
AssetRef ref = FindAsset(filename);
|
||||
if (!ref.ok())
|
||||
return AssetHandle {};
|
||||
return OpenIntegralAsset(std::move(ref), threadsafe);
|
||||
}
|
||||
|
||||
AssetHandle OpenIntegralAsset(std::string_view filename, size_t &fileSize, bool threadsafe)
|
||||
{
|
||||
AssetRef ref = FindAsset(filename);
|
||||
if (!ref.ok())
|
||||
return AssetHandle {};
|
||||
fileSize = ref.size();
|
||||
return OpenIntegralAsset(std::move(ref), threadsafe);
|
||||
}
|
||||
|
||||
SDL_IOStream *OpenAssetAsSdlRwOps(std::string_view filename, bool threadsafe)
|
||||
{
|
||||
#ifdef UNPACKED_MPQS
|
||||
@@ -247,6 +275,28 @@ tl::expected<AssetData, std::string> LoadAsset(std::string_view path)
|
||||
return AssetData { std::move(data), size };
|
||||
}
|
||||
|
||||
tl::expected<AssetData, std::string> LoadIntegralAsset(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 = OpenIntegralAsset(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 };
|
||||
}
|
||||
|
||||
std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view error)
|
||||
{
|
||||
return fmt::format(fmt::runtime(_("Failed to open file:\n{:s}\n\n{:s}\n\nThe MPQ file(s) might be damaged. Please check the file integrity.")), path, error);
|
||||
|
||||
@@ -120,6 +120,7 @@ struct AssetRef {
|
||||
MpqArchive *archive = nullptr;
|
||||
uint32_t hashIndex = UINT32_MAX;
|
||||
std::string_view filename;
|
||||
bool isOverridden = false;
|
||||
|
||||
// Alternatively, a direct SDL_IOStream handle:
|
||||
SDL_IOStream *directHandle = nullptr;
|
||||
@@ -279,6 +280,9 @@ 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);
|
||||
AssetHandle OpenIntegralAsset(AssetRef &&ref, bool threadsafe = false);
|
||||
AssetHandle OpenIntegralAsset(std::string_view filename, bool threadsafe = false);
|
||||
AssetHandle OpenIntegralAsset(std::string_view filename, size_t &fileSize, bool threadsafe = false);
|
||||
|
||||
SDL_IOStream *OpenAssetAsSdlRwOps(std::string_view filename, bool threadsafe = false);
|
||||
|
||||
@@ -293,6 +297,7 @@ struct AssetData {
|
||||
};
|
||||
|
||||
tl::expected<AssetData, std::string> LoadAsset(std::string_view path);
|
||||
tl::expected<AssetData, std::string> LoadIntegralAsset(std::string_view path);
|
||||
|
||||
#ifdef UNPACKED_MPQS
|
||||
using MpqArchiveT = std::string;
|
||||
@@ -306,6 +311,7 @@ constexpr int DevilutionXMpqPriority = 9000;
|
||||
constexpr int LangMpqPriority = 9100;
|
||||
constexpr int FontMpqPriority = 9200;
|
||||
extern bool HasHellfireMpq;
|
||||
extern bool IsAssetIntegrityViolated;
|
||||
|
||||
void LoadCoreArchives();
|
||||
void LoadLanguageArchive();
|
||||
|
||||
@@ -35,6 +35,24 @@ tl::expected<void, std::string> LoadFileInMemWithStatus(const char *path, T *dat
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
tl::expected<void, std::string> LoadIntegralFileInMemWithStatus(const char *path, T *data)
|
||||
{
|
||||
size_t size;
|
||||
AssetHandle handle = OpenIntegralAsset(path, size);
|
||||
if (!handle.ok()) {
|
||||
if (HeadlessMode) return {};
|
||||
return tl::make_unexpected(FailedToOpenFileErrorMessage(path, handle.error()));
|
||||
}
|
||||
if ((size % sizeof(T)) != 0) {
|
||||
return tl::make_unexpected(StrCat("File size does not align with type\n", path));
|
||||
}
|
||||
if (!handle.read(data, size)) {
|
||||
return tl::make_unexpected("handle.read failed");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LoadFileInMem(const char *path, T *data)
|
||||
{
|
||||
|
||||
@@ -443,12 +443,12 @@ tl::expected<void, std::string> LoadLevelSOLData()
|
||||
{
|
||||
switch (leveltype) {
|
||||
case DTYPE_TOWN:
|
||||
if (!LoadFileInMemWithStatus("nlevels\\towndata\\town.sol", SOLData).has_value()) {
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\towndata\\town.sol", SOLData));
|
||||
if (!LoadIntegralFileInMemWithStatus("nlevels\\towndata\\town.sol", SOLData).has_value()) {
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("levels\\towndata\\town.sol", SOLData));
|
||||
}
|
||||
break;
|
||||
case DTYPE_CATHEDRAL:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l1data\\l1.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("levels\\l1data\\l1.sol", SOLData));
|
||||
// Fix incorrectly marked arched tiles
|
||||
SOLData[9] |= TileProperties::BlockLight | TileProperties::BlockMissile;
|
||||
SOLData[15] |= TileProperties::BlockLight | TileProperties::BlockMissile;
|
||||
@@ -476,10 +476,10 @@ tl::expected<void, std::string> LoadLevelSOLData()
|
||||
SOLData[450] |= TileProperties::BlockLight | TileProperties::BlockMissile;
|
||||
break;
|
||||
case DTYPE_CATACOMBS:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l2data\\l2.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("levels\\l2data\\l2.sol", SOLData));
|
||||
break;
|
||||
case DTYPE_CAVES:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l3data\\l3.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("levels\\l3data\\l3.sol", SOLData));
|
||||
// The graphics for tile 48 sub-tile 171 frame 461 are partly incorrect, as they
|
||||
// have a few pixels that should belong to the solid tile 49 instead.
|
||||
// Marks the sub-tile as "BlockMissile" to avoid treating it as a floor during rendering.
|
||||
@@ -490,14 +490,14 @@ tl::expected<void, std::string> LoadLevelSOLData()
|
||||
SOLData[487] |= TileProperties::Solid;
|
||||
break;
|
||||
case DTYPE_HELL:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l4data\\l4.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("levels\\l4data\\l4.sol", SOLData));
|
||||
SOLData[210] = TileProperties::None; // Tile is incorrectly marked as being solid
|
||||
break;
|
||||
case DTYPE_NEST:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("nlevels\\l6data\\l6.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("nlevels\\l6data\\l6.sol", SOLData));
|
||||
break;
|
||||
case DTYPE_CRYPT:
|
||||
RETURN_IF_ERROR(LoadFileInMemWithStatus("nlevels\\l5data\\l5.sol", SOLData));
|
||||
RETURN_IF_ERROR(LoadIntegralFileInMemWithStatus("nlevels\\l5data\\l5.sol", SOLData));
|
||||
SOLData[142] = TileProperties::None; // Tile is incorrectly marked as being solid
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -113,7 +113,7 @@ sol::object LuaLoadScriptFromAssets(std::string_view packageName)
|
||||
return luaState.sol.load(iter->second.as_string_view(), path, sol::load_mode::binary);
|
||||
}
|
||||
|
||||
tl::expected<AssetData, std::string> assetData = LoadAsset(path);
|
||||
tl::expected<AssetData, std::string> assetData = LoadIntegralAsset(path);
|
||||
if (!assetData.has_value()) {
|
||||
sol::stack::push(luaState.sol.lua_state(), assetData.error());
|
||||
return sol::stack_object(luaState.sol.lua_state(), -1);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#endif
|
||||
|
||||
#include "DiabloUI/diabloui.h"
|
||||
#include "DiabloUI/selok.h"
|
||||
#include "DiabloUI/settingsmenu.h"
|
||||
#include "engine/assets.hpp"
|
||||
#include "engine/demomode.h"
|
||||
@@ -80,6 +81,12 @@ bool InitSinglePlayerMenu()
|
||||
|
||||
bool InitMultiPlayerMenu()
|
||||
{
|
||||
#ifndef _DEBUG
|
||||
if (IsAssetIntegrityViolated) {
|
||||
UiSelOkDialog(_("Multi Player Game").data(), _("Cannot play Multiplayer with overridden *.lua, *.tsv, or *.sol assets.").data(), false);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
gbIsMultiplayer = true;
|
||||
return InitMenu(SELHERO_CONNECT);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user