OpenLoco/src/openloco/Environment.cpp

271 lines
8.0 KiB
C++

#include "Environment.h"
#include "Config.h"
#include "Ui.h"
#include "interop/interop.hpp"
#include "platform/platform.h"
#include "utility/collection.hpp"
#include "utility/string.hpp"
#include <cstring>
#include <fstream>
#include <iostream>
using namespace openloco::interop;
namespace openloco::environment
{
loco_global<char[260], 0x009D118E> _path_buffer;
loco_global<char[257], 0x0050B0CE> _path_install;
loco_global<char[257], 0x0050B1CF> _path_saves_single_player;
loco_global<char[257], 0x0050B2EC> _path_saves_two_player;
loco_global<char[257], 0x0050B406> _path_scenarios;
loco_global<char[257], 0x0050B518> _path_landscapes;
loco_global<char[257], 0x0050B635> _path_objects;
static fs::path getBasePath(path_id id);
static fs::path getSubPath(path_id id);
#ifndef _WIN32
static fs::path findSimilarFile(const fs::path& path);
#endif
static bool validateLocoInstallPath(const fs::path& path)
{
if (path.empty())
{
return false;
}
else
{
auto g1Path = path / getSubPath(path_id::g1);
bool g1Exists = fs::exists(g1Path);
#ifndef _WIN32
if (!g1Exists)
{
g1Path = findSimilarFile(g1Path);
g1Exists = !g1Path.empty();
}
#endif
return g1Exists;
}
}
static fs::path autoDetectLocoInstallPath()
{
static constexpr const char* searchPaths[] = {
"C:/Program Files (x86)/Atari/Locomotion",
"C:/GOG Games/Chris Sawyer's Locomotion",
};
std::cout << "Searching for Locomotion install path..." << std::endl;
for (auto path : searchPaths)
{
if (validateLocoInstallPath(path))
{
std::cout << " found: " << path << std::endl;
return path;
}
}
return fs::path();
}
static fs::path resolveLocoInstallPath()
{
auto& cfg = config::getNew();
auto path = fs::path(cfg.loco_install_path);
if (!path.empty())
{
if (validateLocoInstallPath(path))
{
config::writeNewConfig();
return path;
}
std::cerr << "Configured install path for Locomotion is missing Data/g1.DAT." << std::endl;
}
path = autoDetectLocoInstallPath();
if (!path.empty())
{
cfg.loco_install_path = path.make_preferred().u8string();
config::writeNewConfig();
return path;
}
else
{
std::cerr << "Unable to find install path for Locomotion." << std::endl
<< "You will need to manually provide it." << std::endl;
ui::showMessageBox("OpenLoco", "Select your Locomotion install path.");
path = platform::promptDirectory("Select your Locomotion install path.");
if (validateLocoInstallPath(path))
{
cfg.loco_install_path = path.make_preferred().u8string();
config::writeNewConfig();
return path;
}
std::cerr << "Path is missing g1.dat." << std::endl;
ui::showMessageBox("OpenLoco", "Path is missing Data/g1.DAT.");
std::exit(-1);
}
}
static fs::path getLocoInstallPath()
{
return _path_install.get();
}
#ifndef _WIN32
/**
* Performs a case-insensitive search on the containing directory of
* the given file and returns the first match.
*/
static fs::path findSimilarFile(const fs::path& path)
{
try
{
auto expectedFilename = path.filename().generic_string();
auto directory = path.parent_path();
for (auto& item : fs::directory_iterator(directory))
{
auto& p = item.path();
if (utility::iequals(p.filename().generic_string(), expectedFilename))
{
return p;
}
}
}
catch (const std::exception&)
{
// Ignore errors when searching, most common will be that the
// parent directory does not exist
}
return fs::path();
}
#endif // _WIN32
// 0x004416B5
fs::path getPath(path_id id)
{
auto basePath = getBasePath(id);
auto subPath = getSubPath(id);
auto result = basePath / subPath;
if (!fs::exists(result))
{
#ifndef _WIN32
auto similarResult = findSimilarFile(result);
if (similarResult.empty())
{
std::cerr << "Warning: file " << result << " could not be not found" << std::endl;
}
else
{
result = similarResult;
}
#else
std::cerr << "Warning: file " << result << " could not be not found" << std::endl;
#endif
}
return result;
}
template<typename T>
static void setDirectory(T& buffer, fs::path path)
{
utility::strcpy_safe(buffer, path.make_preferred().u8string().c_str());
}
// 0x004412CE
void resolvePaths()
{
auto basePath = resolveLocoInstallPath();
setDirectory(_path_install, basePath);
setDirectory(_path_saves_single_player, basePath / "Single Player Saved Games/");
setDirectory(_path_saves_two_player, basePath / "Two Player Saved Games/");
setDirectory(_path_scenarios, basePath / "Scenarios/*.SC5");
setDirectory(_path_landscapes, basePath / "Scenarios/Landscapes/*.SC5");
setDirectory(_path_objects, basePath / "ObjData/*.DAT");
}
static fs::path getBasePath(path_id id)
{
switch (id)
{
case path_id::plugin1:
case path_id::plugin2:
case path_id::gamecfg:
case path_id::scores:
case path_id::openloco_yml:
return platform::getUserDirectory();
case path_id::language_files:
#if defined(__APPLE__) && defined(__MACH__)
return platform::GetBundlePath();
#else
return platform::GetCurrentExecutablePath().parent_path() / "data";
#endif
default:
return getLocoInstallPath();
}
}
static fs::path getSubPath(path_id id)
{
static constexpr const char* paths[] = {
"Data/g1.DAT",
"plugin.dat",
"plugin2.dat",
"Data/CSS1.DAT",
"Data/CSS2.DAT",
"Data/CSS3.DAT",
"Data/CSS4.DAT",
"Data/CSS5.DAT",
"game.cfg",
"Data/KANJI.DAT",
"Data/20s1.DAT",
"Data/20s2.DAT",
"Data/20s4.DAT",
"Data/50s1.DAT",
"Data/50s2.DAT",
"Data/70s1.DAT",
"Data/70s2.DAT",
"Data/70s3.DAT",
"Data/80s1.DAT",
"Data/90s1.DAT",
"Data/90s2.DAT",
"Data/rag3.DAT",
"Data/Chrysanthemum.DAT",
"Data/Eugenia.DAT",
"Data/Rag2.DAT",
"Data/Rag1.DAT",
"Data/20s3.DAT",
"Data/40s1.DAT",
"Data/40s2.DAT",
"Data/50s3.DAT",
"Data/40s3.DAT",
"Data/80s2.DAT",
"Data/60s1.DAT",
"Data/80s3.DAT",
"Data/60s2.DAT",
"Data/60s3.DAT",
"Data/80s4.DAT",
"Data/20s5.DAT",
"Data/20s6.DAT",
"Data/title.dat",
"scores.dat",
"Scenarios/Boulder Breakers.SC5",
"Data/TUT1024_1.DAT",
"Data/TUT1024_2.DAT",
"Data/TUT1024_3.DAT",
"Data/TUT800_1.DAT",
"Data/TUT800_2.DAT",
"Data/TUT800_3.DAT",
"openloco.yml",
"language",
};
size_t index = (size_t)id;
if (index >= utility::length(paths))
{
throw std::runtime_error("Invalid path_id: " + std::to_string((int32_t)id));
}
return paths[(size_t)id];
}
}