mirror of
https://github.com/libretro/devilutionX.git
synced 2026-11-11 02:41:35 -10:00
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.
380 lines
10 KiB
C++
380 lines
10 KiB
C++
/**
|
|
* @file init.cpp
|
|
*
|
|
* Implementation of routines for initializing the environment, disable screen saver, load MPQ.
|
|
*/
|
|
#include <SDL.h>
|
|
#include <config.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#if (defined(_WIN64) || defined(_WIN32)) && !defined(__UWP__) && !defined(NXDK)
|
|
#include <find_steam_game.h>
|
|
#endif
|
|
|
|
#include "DiabloUI/diabloui.h"
|
|
#include "engine/assets.hpp"
|
|
#include "engine/backbuffer_state.hpp"
|
|
#include "engine/dx.h"
|
|
#include "hwcursor.hpp"
|
|
#include "miniwin/misc_msg.h"
|
|
#include "mpq/mpq_reader.hpp"
|
|
#include "options.h"
|
|
#include "pfile.h"
|
|
#include "utils/file_util.h"
|
|
#include "utils/language.h"
|
|
#include "utils/log.hpp"
|
|
#include "utils/paths.h"
|
|
#include "utils/str_split.hpp"
|
|
#include "utils/ui_fwd.h"
|
|
#include "utils/utf8.hpp"
|
|
|
|
#ifdef __vita__
|
|
// increase default allowed heap size on Vita
|
|
int _newlib_heap_size_user = 100 * 1024 * 1024;
|
|
#endif
|
|
|
|
namespace devilution {
|
|
|
|
/** True if the game is the current active window */
|
|
bool gbActive;
|
|
/** The current input handler function */
|
|
EventHandler CurrentEventHandler;
|
|
/** Indicate if we only have access to demo data */
|
|
bool gbIsSpawn;
|
|
/** Indicate if we have loaded the Hellfire expansion data */
|
|
bool gbIsHellfire;
|
|
/** Indicate if we want vanilla savefiles */
|
|
bool gbVanilla;
|
|
/** Whether the Hellfire mode is required (forced). */
|
|
bool forceHellfire;
|
|
|
|
#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::optional<MpqArchive> spawn_mpq;
|
|
std::optional<MpqArchive> diabdat_mpq;
|
|
std::optional<MpqArchive> hellfire_mpq;
|
|
std::optional<MpqArchive> hfmonk_mpq;
|
|
std::optional<MpqArchive> hfbard_mpq;
|
|
std::optional<MpqArchive> hfbarb_mpq;
|
|
std::optional<MpqArchive> hfmusic_mpq;
|
|
std::optional<MpqArchive> hfvoice_mpq;
|
|
std::optional<MpqArchive> devilutionx_mpq;
|
|
std::optional<MpqArchive> lang_mpq;
|
|
std::optional<MpqArchive> font_mpq;
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
#ifdef UNPACKED_MPQS
|
|
std::optional<std::string> FindUnpackedMpqData(const std::vector<std::string> &paths, string_view mpqName)
|
|
{
|
|
std::string targetPath;
|
|
for (const std::string &path : paths) {
|
|
targetPath.clear();
|
|
targetPath.reserve(path.size() + mpqName.size() + 1);
|
|
targetPath.append(path).append(mpqName) += DirectorySeparator;
|
|
if (FileExists(targetPath)) {
|
|
LogVerbose(" Found unpacked MPQ directory: {}", targetPath);
|
|
return targetPath;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
#else
|
|
std::optional<MpqArchive> LoadMPQ(const std::vector<std::string> &paths, string_view mpqName)
|
|
{
|
|
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))) {
|
|
LogVerbose(" Found: {} in {}", mpqName, path);
|
|
return archive;
|
|
}
|
|
if (error != 0) {
|
|
LogError("Error {}: {}", MpqArchive::ErrorMessage(error), mpqAbsPath);
|
|
}
|
|
}
|
|
if (error == 0) {
|
|
LogVerbose("Missing: {}", mpqName);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
#endif
|
|
|
|
std::vector<std::string> GetMPQSearchPaths()
|
|
{
|
|
std::vector<std::string> paths;
|
|
paths.push_back(paths::BasePath());
|
|
paths.push_back(paths::PrefPath());
|
|
if (paths[0] == paths[1])
|
|
paths.pop_back();
|
|
|
|
#if defined(__unix__) && !defined(__ANDROID__)
|
|
// `XDG_DATA_HOME` is usually the root path of `paths::PrefPath()`, so we only
|
|
// add `XDG_DATA_DIRS`.
|
|
const char *xdgDataDirs = std::getenv("XDG_DATA_DIRS");
|
|
if (xdgDataDirs != nullptr) {
|
|
for (const string_view path : SplitByChar(xdgDataDirs, ':')) {
|
|
std::string fullPath(path);
|
|
if (!path.empty() && path.back() != '/')
|
|
fullPath += '/';
|
|
fullPath.append("diasurgical/devilutionx/");
|
|
paths.push_back(std::move(fullPath));
|
|
}
|
|
} else {
|
|
paths.emplace_back("/usr/local/share/diasurgical/devilutionx/");
|
|
paths.emplace_back("/usr/share/diasurgical/devilutionx/");
|
|
}
|
|
#elif defined(NXDK)
|
|
paths.emplace_back("D:\\");
|
|
#elif (defined(_WIN64) || defined(_WIN32)) && !defined(__UWP__) && !defined(NXDK)
|
|
char gogpath[_FSG_PATH_MAX];
|
|
fsg_get_gog_game_path(gogpath, "1412601690");
|
|
if (strlen(gogpath) > 0) {
|
|
paths.emplace_back(std::string(gogpath) + "/");
|
|
paths.emplace_back(std::string(gogpath) + "/hellfire/");
|
|
}
|
|
#endif
|
|
|
|
paths.emplace_back(""); // PWD
|
|
|
|
if (SDL_LOG_PRIORITY_VERBOSE >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION)) {
|
|
LogVerbose("Paths:\n base: {}\n pref: {}\n config: {}\n assets: {}",
|
|
paths::BasePath(), paths::PrefPath(), paths::ConfigPath(), paths::AssetsPath());
|
|
|
|
std::string message;
|
|
for (std::size_t i = 0; i < paths.size(); ++i) {
|
|
message.append(fmt::format("\n{:6d}. '{}'", i + 1, paths[i]));
|
|
}
|
|
LogVerbose("MPQ search paths:{}", message);
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void init_cleanup()
|
|
{
|
|
if (gbIsMultiplayer && gbRunGame) {
|
|
pfile_write_hero(/*writeGameData=*/false);
|
|
sfile_write_stash();
|
|
}
|
|
|
|
#ifdef UNPACKED_MPQS
|
|
lang_data_path = std::nullopt;
|
|
font_data_path = std::nullopt;
|
|
hellfire_data_path = std::nullopt;
|
|
diabdat_data_path = std::nullopt;
|
|
spawn_data_path = std::nullopt;
|
|
#else
|
|
spawn_mpq = std::nullopt;
|
|
diabdat_mpq = std::nullopt;
|
|
hellfire_mpq = std::nullopt;
|
|
hfmonk_mpq = std::nullopt;
|
|
hfbard_mpq = std::nullopt;
|
|
hfbarb_mpq = std::nullopt;
|
|
hfmusic_mpq = std::nullopt;
|
|
hfvoice_mpq = std::nullopt;
|
|
lang_mpq = std::nullopt;
|
|
font_mpq = std::nullopt;
|
|
devilutionx_mpq = std::nullopt;
|
|
#endif
|
|
|
|
NetClose();
|
|
}
|
|
|
|
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
|
|
devilutionx_mpq = LoadMPQ(paths, "devilutionx.mpq");
|
|
#endif
|
|
font_mpq = LoadMPQ(paths, "fonts.mpq"); // Extra fonts
|
|
#endif
|
|
}
|
|
|
|
void LoadLanguageArchive()
|
|
{
|
|
#ifdef UNPACKED_MPQS
|
|
lang_data_path = std::nullopt;
|
|
#else
|
|
lang_mpq = std::nullopt;
|
|
#endif
|
|
|
|
string_view code = *sgOptions.Language.code;
|
|
if (code != "en") {
|
|
std::string langMpqName { code };
|
|
#ifdef UNPACKED_MPQS
|
|
lang_data_path = FindUnpackedMpqData(GetMPQSearchPaths(), langMpqName);
|
|
#else
|
|
langMpqName.append(".mpq");
|
|
lang_mpq = LoadMPQ(GetMPQSearchPaths(), langMpqName);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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"));
|
|
}
|
|
}
|
|
hellfire_data_path = FindUnpackedMpqData(paths, "hellfire");
|
|
if (hellfire_data_path)
|
|
gbIsHellfire = true;
|
|
if (forceHellfire && !hellfire_data_path)
|
|
InsertCDDlg("hellfire");
|
|
|
|
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");
|
|
|
|
// Bard and barbarian are not currently supported in unpacked mode
|
|
// because they use the same paths as rogue and warrior.
|
|
gbBard = false;
|
|
gbBarbarian = false;
|
|
|
|
if (gbIsHellfire && (!hasMonk || !hasMusic || !hasVoice)) {
|
|
UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files."));
|
|
diablo_quit(1);
|
|
}
|
|
#else // !UNPACKED_MPQS
|
|
diabdat_mpq = LoadMPQ(paths, "DIABDAT.MPQ");
|
|
if (!diabdat_mpq) {
|
|
// DIABDAT.MPQ is uppercase on the original CD and the GOG version.
|
|
diabdat_mpq = LoadMPQ(paths, "diabdat.mpq");
|
|
}
|
|
|
|
if (!diabdat_mpq) {
|
|
spawn_mpq = LoadMPQ(paths, "spawn.mpq");
|
|
if (spawn_mpq)
|
|
gbIsSpawn = true;
|
|
}
|
|
if (!HeadlessMode) {
|
|
AssetRef ref = FindAsset("ui_art\\title.pcx");
|
|
if (!ref.ok()) {
|
|
LogError("{}", SDL_GetError());
|
|
InsertCDDlg(_("diabdat.mpq or spawn.mpq"));
|
|
}
|
|
}
|
|
|
|
hellfire_mpq = LoadMPQ(paths, "hellfire.mpq");
|
|
if (hellfire_mpq)
|
|
gbIsHellfire = true;
|
|
if (forceHellfire && !hellfire_mpq)
|
|
InsertCDDlg("hellfire.mpq");
|
|
|
|
hfmonk_mpq = LoadMPQ(paths, "hfmonk.mpq");
|
|
hfbard_mpq = LoadMPQ(paths, "hfbard.mpq");
|
|
if (hfbard_mpq)
|
|
gbBard = true;
|
|
hfbarb_mpq = LoadMPQ(paths, "hfbarb.mpq");
|
|
if (hfbarb_mpq)
|
|
gbBarbarian = true;
|
|
hfmusic_mpq = LoadMPQ(paths, "hfmusic.mpq");
|
|
hfvoice_mpq = LoadMPQ(paths, "hfvoice.mpq");
|
|
|
|
if (gbIsHellfire && (!hfmonk_mpq || !hfmusic_mpq || !hfvoice_mpq)) {
|
|
UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files."));
|
|
diablo_quit(1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void init_create_window()
|
|
{
|
|
if (!SpawnWindow(PROJECT_NAME))
|
|
app_fatal(_("Unable to create main window"));
|
|
dx_init();
|
|
gbActive = true;
|
|
#ifndef USE_SDL1
|
|
SDL_DisableScreenSaver();
|
|
#endif
|
|
}
|
|
|
|
void MainWndProc(const SDL_Event &event)
|
|
{
|
|
#ifndef USE_SDL1
|
|
if (event.type != SDL_WINDOWEVENT)
|
|
return;
|
|
switch (event.window.event) {
|
|
case SDL_WINDOWEVENT_HIDDEN:
|
|
gbActive = false;
|
|
break;
|
|
case SDL_WINDOWEVENT_SHOWN:
|
|
gbActive = false;
|
|
RedrawEverything();
|
|
break;
|
|
case SDL_WINDOWEVENT_EXPOSED:
|
|
RedrawEverything();
|
|
break;
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
ReinitializeHardwareCursor();
|
|
break;
|
|
case SDL_WINDOWEVENT_LEAVE:
|
|
sgbMouseDown = CLICK_NONE;
|
|
LastMouseButtonAction = MouseActionType::None;
|
|
RedrawEverything();
|
|
break;
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
diablo_quit(0);
|
|
break;
|
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
diablo_focus_pause();
|
|
break;
|
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
|
diablo_focus_unpause();
|
|
break;
|
|
default:
|
|
LogVerbose("Unhandled SDL_WINDOWEVENT event: ", event.window.event);
|
|
break;
|
|
}
|
|
#else
|
|
if (event.type != SDL_ACTIVEEVENT)
|
|
return;
|
|
if ((event.active.state & SDL_APPINPUTFOCUS) != 0) {
|
|
if (event.active.gain == 0)
|
|
diablo_focus_pause();
|
|
else
|
|
diablo_focus_unpause();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EventHandler SetEventHandler(EventHandler eventHandler)
|
|
{
|
|
EventHandler previousHandler = CurrentEventHandler;
|
|
CurrentEventHandler = eventHandler;
|
|
return previousHandler;
|
|
}
|
|
|
|
} // namespace devilution
|