|
|
|
|
@ -24,37 +24,23 @@
|
|
|
|
|
namespace devilution { |
|
|
|
|
|
|
|
|
|
std::vector<std::string> OverridePaths; |
|
|
|
|
|
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
std::optional<std::string> spawn_data_path; |
|
|
|
|
std::optional<std::string> diabdat_data_path; |
|
|
|
|
std::optional<std::string> hellfire_data_path; |
|
|
|
|
std::optional<std::string> font_data_path; |
|
|
|
|
std::optional<std::string> lang_data_path; |
|
|
|
|
#else |
|
|
|
|
std::map<int, std::unique_ptr<MpqArchive>> MpqArchives; |
|
|
|
|
std::map<int, MpqArchiveT> MpqArchives; |
|
|
|
|
bool HasHellfireMpq; |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
char *FindUnpackedMpqFile(char *relativePath) |
|
|
|
|
{ |
|
|
|
|
// Iterate over archives in reverse order to prefer files from high priority archives
|
|
|
|
|
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; |
|
|
|
|
for (auto rit = MpqArchives.rbegin(); rit != MpqArchives.rend(); ++rit) { |
|
|
|
|
const std::string_view unpackedDir = rit->second; |
|
|
|
|
path = relativePath - unpackedDir.size(); |
|
|
|
|
std::memcpy(path, unpackedDir.data(), unpackedDir.size()); |
|
|
|
|
if (FileExists(path)) break; |
|
|
|
|
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 |
|
|
|
|
@ -78,8 +64,8 @@ bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *file
|
|
|
|
|
|
|
|
|
|
// Iterate over archives in reverse order to prefer files from high priority archives
|
|
|
|
|
for (auto rit = MpqArchives.rbegin(); rit != MpqArchives.rend(); ++rit) { |
|
|
|
|
if (rit->second->GetFileNumber(fileHash, *fileNumber)) { |
|
|
|
|
*archive = rit->second.get(); |
|
|
|
|
if (rit->second.GetFileNumber(fileHash, *fileNumber)) { |
|
|
|
|
*archive = &rit->second; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -262,7 +248,7 @@ std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view
|
|
|
|
|
|
|
|
|
|
namespace { |
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
std::optional<std::string> FindUnpackedMpqData(const std::vector<std::string> &paths, std::string_view mpqName) |
|
|
|
|
std::optional<std::string> FindUnpackedMpqData(std::span<const std::string> paths, std::string_view mpqName) |
|
|
|
|
{ |
|
|
|
|
std::string targetPath; |
|
|
|
|
for (const std::string &path : paths) { |
|
|
|
|
@ -276,12 +262,28 @@ std::optional<std::string> FindUnpackedMpqData(const std::vector<std::string> &p
|
|
|
|
|
} |
|
|
|
|
return std::nullopt; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool FindMPQ(std::span<const std::string> paths, std::string_view mpqName) |
|
|
|
|
{ |
|
|
|
|
return FindUnpackedMpqData(paths, mpqName).has_value(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool LoadMPQ(std::span<const std::string> paths, std::string_view mpqName, int priority) |
|
|
|
|
{ |
|
|
|
|
std::optional<std::string> mpqPath = FindUnpackedMpqData(paths, mpqName); |
|
|
|
|
if (!mpqPath.has_value()) { |
|
|
|
|
LogVerbose("Missing: {}", mpqName); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
MpqArchives[priority] = *std::move(mpqPath); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
#else |
|
|
|
|
bool FindMPQ(const std::vector<std::string> &paths, std::string_view mpqName) |
|
|
|
|
bool FindMPQ(std::span<const std::string> paths, std::string_view mpqName) |
|
|
|
|
{ |
|
|
|
|
std::string mpqAbsPath; |
|
|
|
|
for (const auto &path : paths) { |
|
|
|
|
mpqAbsPath = path + mpqName.data(); |
|
|
|
|
mpqAbsPath = StrCat(path, mpqName, ".mpq"); |
|
|
|
|
if (FileExists(mpqAbsPath)) { |
|
|
|
|
LogVerbose(" Found: {} in {}", mpqName, path); |
|
|
|
|
return true; |
|
|
|
|
@ -290,16 +292,21 @@ bool FindMPQ(const std::vector<std::string> &paths, std::string_view mpqName)
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
bool LoadMPQ(const std::vector<std::string> &paths, std::string_view mpqName, int priority) |
|
|
|
|
|
|
|
|
|
bool LoadMPQ(std::span<const std::string> paths, std::string_view mpqName, int priority, std::string_view ext = ".mpq") |
|
|
|
|
{ |
|
|
|
|
std::optional<MpqArchive> archive; |
|
|
|
|
std::string mpqAbsPath; |
|
|
|
|
std::int32_t error = 0; |
|
|
|
|
for (const auto &path : paths) { |
|
|
|
|
mpqAbsPath = path + mpqName.data(); |
|
|
|
|
if ((archive = MpqArchive::Open(mpqAbsPath.c_str(), error))) { |
|
|
|
|
mpqAbsPath = StrCat(path, mpqName, ext); |
|
|
|
|
archive = MpqArchive::Open(mpqAbsPath.c_str(), error); |
|
|
|
|
if (archive.has_value()) { |
|
|
|
|
LogVerbose(" Found: {} in {}", mpqName, path); |
|
|
|
|
MpqArchives[priority] = std::make_unique<MpqArchive>(std::move(*archive)); |
|
|
|
|
auto [it, inserted] = MpqArchives.emplace(priority, *std::move(archive)); |
|
|
|
|
if (!inserted) { |
|
|
|
|
LogError("MPQ with priority {} is already registered, skipping {}", priority, mpqName); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if (error != 0) { |
|
|
|
|
@ -376,92 +383,78 @@ void LoadCoreArchives()
|
|
|
|
|
{ |
|
|
|
|
auto paths = GetMPQSearchPaths(); |
|
|
|
|
|
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
font_data_path = FindUnpackedMpqData(paths, "fonts"); |
|
|
|
|
#else // !UNPACKED_MPQS
|
|
|
|
|
#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__) |
|
|
|
|
// Load devilutionx.mpq first to get the font file for error messages
|
|
|
|
|
LoadMPQ(paths, "devilutionx.mpq", DevilutionXMpqPriority); |
|
|
|
|
#endif |
|
|
|
|
LoadMPQ(paths, "fonts.mpq", FontMpqPriority); // Extra fonts
|
|
|
|
|
HasHellfireMpq = FindMPQ(paths, "hellfire.mpq"); |
|
|
|
|
LoadMPQ(paths, "devilutionx", DevilutionXMpqPriority); |
|
|
|
|
#endif |
|
|
|
|
LoadMPQ(paths, "fonts", FontMpqPriority); // Extra fonts
|
|
|
|
|
HasHellfireMpq = FindMPQ(paths, "hellfire"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LoadLanguageArchive() |
|
|
|
|
{ |
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
lang_data_path = std::nullopt; |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
std::string_view code = GetLanguageCode(); |
|
|
|
|
const std::string_view code = GetLanguageCode(); |
|
|
|
|
if (code != "en") { |
|
|
|
|
std::string langMpqName { code }; |
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
lang_data_path = FindUnpackedMpqData(GetMPQSearchPaths(), langMpqName); |
|
|
|
|
#else |
|
|
|
|
langMpqName.append(".mpq"); |
|
|
|
|
LoadMPQ(GetMPQSearchPaths(), langMpqName, LangMpqPriority); |
|
|
|
|
#endif |
|
|
|
|
LoadMPQ(GetMPQSearchPaths(), code, LangMpqPriority); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LoadGameArchives() |
|
|
|
|
{ |
|
|
|
|
auto paths = GetMPQSearchPaths(); |
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
diabdat_data_path = FindUnpackedMpqData(paths, "diabdat"); |
|
|
|
|
if (!diabdat_data_path) { |
|
|
|
|
spawn_data_path = FindUnpackedMpqData(paths, "spawn"); |
|
|
|
|
if (spawn_data_path) |
|
|
|
|
gbIsSpawn = true; |
|
|
|
|
} |
|
|
|
|
if (!HeadlessMode) { |
|
|
|
|
AssetRef ref = FindAsset("ui_art\\title.clx"); |
|
|
|
|
if (!ref.ok()) { |
|
|
|
|
LogError("{}", SDL_GetError()); |
|
|
|
|
InsertCDDlg(_("diabdat.mpq or spawn.mpq")); |
|
|
|
|
const std::vector<std::string> paths = GetMPQSearchPaths(); |
|
|
|
|
bool haveDiabdat = false; |
|
|
|
|
bool haveSpawn = false; |
|
|
|
|
|
|
|
|
|
#ifndef UNPACKED_MPQS |
|
|
|
|
// DIABDAT.MPQ is uppercase on the original CD and the GOG version.
|
|
|
|
|
haveDiabdat = LoadMPQ(paths, "DIABDAT", MainMpqPriority, ".MPQ"); |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
if (!haveDiabdat) { |
|
|
|
|
haveDiabdat = LoadMPQ(paths, "diabdat", MainMpqPriority); |
|
|
|
|
if (!haveDiabdat) { |
|
|
|
|
gbIsSpawn = haveSpawn = LoadMPQ(paths, "spawn", MainMpqPriority); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
hellfire_data_path = FindUnpackedMpqData(paths, "hellfire"); |
|
|
|
|
if (forceHellfire && !hellfire_data_path) |
|
|
|
|
InsertCDDlg("hellfire"); |
|
|
|
|
#else // !UNPACKED_MPQS
|
|
|
|
|
if (!LoadMPQ(paths, "DIABDAT.MPQ", MainMpqPriority)) { |
|
|
|
|
// DIABDAT.MPQ is uppercase on the original CD and the GOG version.
|
|
|
|
|
if (!LoadMPQ(paths, "diabdat.mpq", MainMpqPriority)) |
|
|
|
|
gbIsSpawn = LoadMPQ(paths, "spawn.mpq", MainMpqPriority); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!HeadlessMode) { |
|
|
|
|
AssetRef ref = FindAsset("ui_art\\title.pcx"); |
|
|
|
|
if (!ref.ok()) { |
|
|
|
|
if (!haveDiabdat && !haveSpawn) { |
|
|
|
|
LogError("{}", SDL_GetError()); |
|
|
|
|
InsertCDDlg(_("diabdat.mpq or spawn.mpq")); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (forceHellfire && !HasHellfireMpq) |
|
|
|
|
if (forceHellfire && !HasHellfireMpq) { |
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
InsertCDDlg("hellfire"); |
|
|
|
|
#else |
|
|
|
|
InsertCDDlg("hellfire.mpq"); |
|
|
|
|
LoadMPQ(paths, "hfbard.mpq", 8110); |
|
|
|
|
LoadMPQ(paths, "hfbarb.mpq", 8120); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#ifndef UNPACKED_MPQS |
|
|
|
|
// In unpacked mode, all the hellfire data is in the hellfire directory.
|
|
|
|
|
LoadMPQ(paths, "hfbard", 8110); |
|
|
|
|
LoadMPQ(paths, "hfbarb", 8120); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LoadHellfireArchives() |
|
|
|
|
{ |
|
|
|
|
auto paths = GetMPQSearchPaths(); |
|
|
|
|
const std::vector<std::string> paths = GetMPQSearchPaths(); |
|
|
|
|
LoadMPQ(paths, "hellfire", 8000); |
|
|
|
|
|
|
|
|
|
#ifdef UNPACKED_MPQS |
|
|
|
|
const bool hasMonk = FileExists(*hellfire_data_path + "plrgfx/monk/mha/mhaas.clx"); |
|
|
|
|
const bool hasMusic = FileExists(*hellfire_data_path + "music/dlvlf.wav") |
|
|
|
|
|| FileExists(*hellfire_data_path + "music/dlvlf.mp3"); |
|
|
|
|
const bool hasVoice = FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.wav") |
|
|
|
|
|| FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.mp3"); |
|
|
|
|
#else // !UNPACKED_MPQS
|
|
|
|
|
LoadMPQ(paths, "hellfire.mpq", 8000); |
|
|
|
|
const bool hasMonk = LoadMPQ(paths, "hfmonk.mpq", 8100); |
|
|
|
|
const bool hasMusic = LoadMPQ(paths, "hfmusic.mpq", 8200); |
|
|
|
|
const bool hasVoice = LoadMPQ(paths, "hfvoice.mpq", 8500); |
|
|
|
|
const std::string &hellfireDataPath = MpqArchives.at(8000); |
|
|
|
|
const bool hasMonk = FileExists(hellfireDataPath + "plrgfx/monk/mha/mhaas.clx"); |
|
|
|
|
const bool hasMusic = FileExists(hellfireDataPath + "music/dlvlf.wav") |
|
|
|
|
|| FileExists(hellfireDataPath + "music/dlvlf.mp3"); |
|
|
|
|
const bool hasVoice = FileExists(hellfireDataPath + "sfx/hellfire/cowsut1.wav") |
|
|
|
|
|| FileExists(hellfireDataPath + "sfx/hellfire/cowsut1.mp3"); |
|
|
|
|
#else |
|
|
|
|
const bool hasMonk = LoadMPQ(paths, "hfmonk", 8100); |
|
|
|
|
const bool hasMusic = LoadMPQ(paths, "hfmusic", 8200); |
|
|
|
|
const bool hasVoice = LoadMPQ(paths, "hfvoice", 8500); |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
if (!hasMonk || !hasMusic || !hasVoice) |
|
|
|
|
@ -498,14 +491,12 @@ void LoadModArchives(std::span<const std::string_view> modnames)
|
|
|
|
|
} |
|
|
|
|
OverridePaths.emplace_back(paths::PrefPath()); |
|
|
|
|
|
|
|
|
|
#ifndef UNPACKED_MPQS |
|
|
|
|
int priority = 10000; |
|
|
|
|
auto paths = GetMPQSearchPaths(); |
|
|
|
|
for (std::string_view modname : modnames) { |
|
|
|
|
LoadMPQ(paths, StrCat("mods" DIRECTORY_SEPARATOR_STR, modname, ".mpq"), priority); |
|
|
|
|
LoadMPQ(paths, StrCat("mods" DIRECTORY_SEPARATOR_STR, modname), priority); |
|
|
|
|
priority++; |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} // namespace devilution
|
|
|
|
|
|