Skip to content

Commit

Permalink
Merge pull request #84 from OpenVicProject/move/vic2-path-search
Browse files Browse the repository at this point in the history
  • Loading branch information
Spartan322 authored Dec 5, 2023
2 parents 471d0c8 + 7019dcc commit db00dcf
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 316 deletions.
305 changes: 0 additions & 305 deletions src/openvic-simulation/dataloader/Dataloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@
#include "openvic-simulation/utility/ConstexprIntToStr.hpp"
#include "openvic-simulation/utility/Logger.hpp"

#ifdef _WIN32
#include <Windows.h>

#include "Dataloader_Windows.hpp"
#endif

#if defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#endif

using namespace OpenVic;
using namespace OpenVic::NodeTools;
using namespace ovdl;
Expand All @@ -42,301 +32,6 @@ static constexpr bool path_equals_case_insensitive(std::string_view lhs, std::st
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), ichar_equals);
}

// Windows and Mac by default act like case insensitive filesystems
static constexpr bool path_equals(std::string_view lhs, std::string_view rhs) {
#if defined(FILESYSTEM_CASE_INSENSITIVE)
return path_equals_case_insensitive(lhs, rhs);
#else
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
#endif
}

template<typename T>
concept is_filename = std::same_as<T, std::filesystem::path> || std::convertible_to<T, std::string_view>;

static bool filename_equals(const is_filename auto& lhs, const is_filename auto& rhs) {
auto left = [&lhs] {
if constexpr (std::same_as<std::decay_t<decltype(lhs)>, std::filesystem::path>) {
return lhs.filename().string();
} else {
return lhs;
}
}();
auto right = [&rhs] {
if constexpr (std::same_as<std::decay_t<decltype(rhs)>, std::filesystem::path>) {
return rhs.filename().string();
} else {
return rhs;
}
}();
return path_equals(left, right);
}

static fs::path _search_for_game_path(fs::path hint_path = {}) {
// Apparently max amount of steam libraries is 8, if incorrect please correct it to the correct max amount
static constexpr int max_amount_of_steam_libraries = 8;
static constexpr std::string_view Victoria_2_folder = "Victoria 2";
static constexpr std::string_view v2_game_exe = "v2game.exe";
static constexpr std::string_view steamapps = "steamapps";
static constexpr std::string_view libraryfolders = "libraryfolders.vdf";
static constexpr std::string_view vic2_appmanifest = "appmanifest_42960.acf";
static constexpr std::string_view common_folder = "common";

std::error_code error_code;

// Don't waste time trying to search for Victoria 2 when supplied a valid looking Victoria 2 game directory
if (filename_equals(Victoria_2_folder, hint_path)) {
if (fs::is_regular_file(hint_path / v2_game_exe, error_code)) {
return hint_path;
}
}

const bool hint_path_was_empty = hint_path.empty();
if (hint_path_was_empty) {
#if defined(_WIN32)
static const fs::path registry_path =
Windows::ReadRegValue<char>(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Paradox Interactive\\Victoria 2", "path");

if (!registry_path.empty()) {
return registry_path;
}

#pragma warning(push)
#pragma warning(disable : 4996)
static const fs::path prog_files = std::string(std::getenv("ProgramFiles"));
hint_path = prog_files / "Steam";
if (!fs::is_directory(hint_path, error_code)) {
static const fs::path prog_files_x86 = std::string(std::getenv("ProgramFiles(x86)"));
hint_path = prog_files_x86 / "Steam";
if (!fs::is_directory(hint_path, error_code)) {
Logger::warning("Could not find path for Steam installation on Windows.");
return "";
}
}
#pragma warning(pop)
// Cannot support Android
// Only FreeBSD currently unofficially supports emulating Linux
#elif (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__)
static const fs::path home = std::getenv("HOME");
hint_path = home / ".steam" / "steam";
if (fs::is_symlink(hint_path, error_code)) {
hint_path = fs::read_symlink(hint_path, error_code);
} else if (!fs::is_directory(hint_path, error_code)) {
hint_path = home / ".local" / "share" / "Steam";
if (!fs::is_directory(hint_path, error_code)) {
#ifdef __FreeBSD__
Logger::warning("Could not find path for Steam installation on FreeBSD.");
#else
Logger::warning("Could not find path for Steam installation on Linux.");
#endif
return "";
}
}
// Support only Mac, cannot support iOS
#elif (defined(__APPLE__) && defined(__MACH__)) && TARGET_OS_MAC == 1
static const fs::path home = std::getenv("HOME");
hint_path = home / "Library" / "Application Support" / "Steam";
if (!fs::is_directory(hint_path, error_code)) {
Logger::warning("Could not find path for Steam installation on Mac.");
return "";
}
// All platforms that reach this point do not seem to even have unofficial Steam support
#else
Logger::warning("Could not find path for Steam installation on unsupported platform.");
#endif
}

// Could not determine Steam install on platform
if (hint_path.empty()) {
return "";
}

// Supplied path was useless, ignore hint_path
if (!hint_path_was_empty && !fs::exists(hint_path, error_code)) {
return _search_for_game_path();
}

// Steam Library's directory that will contain Victoria 2
fs::path vic2_steam_lib_directory;
fs::path current_path = hint_path;

// If hinted path is directory that contains steamapps
bool is_steamapps = false;
if (fs::is_directory(current_path / steamapps, error_code)) {
current_path /= steamapps;
is_steamapps = true;
}

// If hinted path is steamapps directory
bool is_libraryfolders_vdf = false;
if (is_steamapps || (filename_equals(steamapps, current_path) && fs::is_directory(current_path, error_code))) {
current_path /= libraryfolders;
is_libraryfolders_vdf = true;
}

bool vic2_install_confirmed = false;
// if current_path is not a regular file, this is a non-default Steam Library, skip this parser evaluation
if (fs::is_regular_file(current_path, error_code) &&
(is_libraryfolders_vdf || filename_equals(libraryfolders, current_path))) {
lexy_vdf::Parser parser;

std::string buffer;
auto error_log_stream = detail::CallbackStream {
[](void const* s, std::streamsize n, void* user_data) -> std::streamsize {
if (s != nullptr && n > 0 && user_data != nullptr) {
static_cast<std::string*>(user_data)->append(static_cast<char const*>(s), n);
return n;
} else {
Logger::warning("Invalid input to parser error log callback: ", s, " / ", n, " / ", user_data);
return 0;
}
},
&buffer
};
parser.set_error_log_to(error_log_stream);

parser.load_from_file(current_path);
if (!parser.parse()) {
// Could not find or load libraryfolders.vdf, report error as warning
if (!buffer.empty()) {
Logger::warning _(buffer);
}
return "";
}
std::optional current_node = *(parser.get_key_values());

// check "libraryfolders" list
auto it = current_node.value().find("libraryfolders");
if (it == current_node.value().end()) {
Logger::warning("Expected libraryfolders.vdf to contain a libraryfolders key.");
return "";
}

static constexpr auto visit_node = [](auto&& arg) -> std::optional<lexy_vdf::KeyValues> {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, lexy_vdf::KeyValues>) {
return arg;
} else {
return std::nullopt;
}
};

current_node = std::visit(visit_node, it->second);

if (!current_node.has_value()) {
Logger::warning("Expected libraryfolders.vdf's libraryfolders key to be a KeyValue dictionary.");
return "";
}

// Array of strings contain "0" to std::to_string(max_amount_of_steam_libraries - 1)
static constexpr auto library_indexes = OpenVic::ConstexprIntToStr::make_itosv_array<max_amount_of_steam_libraries>();

for (const auto& index : library_indexes) {
decltype(current_node) node = std::nullopt;

auto it = current_node.value().find(index);
if (it != current_node.value().end()) {
node = std::visit(visit_node, it->second);
}

// check "apps" list
decltype(node) apps_node = std::nullopt;
if (node.has_value()) {
it = node.value().find("apps");
if (it != node.value().end()) {
apps_node = std::visit(visit_node, it->second);
}
}

bool lib_contains_victoria_2 = false;
if (apps_node.has_value()) {
lib_contains_victoria_2 = apps_node.value().find("42960") != node.value().end();
}

if (lib_contains_victoria_2) {
it = node.value().find("path");
if (it != node.value().end()) {
vic2_steam_lib_directory = std::visit(
[](auto&& arg) -> std::string_view {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::string>) {
return arg;
} else {
return "";
}
},
it->second
);
vic2_install_confirmed = true;
break;
}
}
}

if (vic2_steam_lib_directory.empty()) {
Logger::info("Steam installation appears not to contain Victoria 2.");
return "";
}
}

// If current_path points to steamapps/libraryfolders.vdf
if (vic2_steam_lib_directory.empty()) {
if (is_libraryfolders_vdf || filename_equals(libraryfolders, current_path)) {
vic2_steam_lib_directory = current_path.parent_path() / vic2_appmanifest;
} else if (filename_equals(vic2_appmanifest, current_path)) {
vic2_steam_lib_directory = current_path;
}
}

// If we could not confirm Victoria 2 was installed via the default Steam installation
bool is_common_folder = false;
if (!vic2_install_confirmed) {
auto parser = lexy_vdf::Parser::from_file(vic2_steam_lib_directory);
if (!parser.parse()) {
// Could not find or load appmanifest_42960.acf, report error as warning
for (auto& error : parser.get_errors()) {
Logger::warning(error.message);
}
return "";
}

// we can pretty much assume the Victoria 2 directory on Steam is valid from here
vic2_steam_lib_directory /= common_folder;
is_common_folder = true;
} else if (fs::is_directory(vic2_steam_lib_directory / steamapps, error_code)) {
vic2_steam_lib_directory /= fs::path(steamapps) / common_folder;
is_common_folder = true;
}

bool is_Victoria_2_folder = false;
if ((is_common_folder || filename_equals(common_folder, vic2_steam_lib_directory)) &&
fs::is_directory(vic2_steam_lib_directory, error_code)) {
vic2_steam_lib_directory /= Victoria_2_folder;
is_Victoria_2_folder = true;
}
if ((is_Victoria_2_folder || filename_equals(Victoria_2_folder, vic2_steam_lib_directory)) &&
fs::is_regular_file(vic2_steam_lib_directory / v2_game_exe, error_code)) {
return vic2_steam_lib_directory;
}

// Hail Mary check ignoring the hint_path
if (!hint_path_was_empty) {
return _search_for_game_path();
}

Logger::warning("Could not find Victoria 2 game path, this requires manually supplying one.");
return ""; // The supplied path fits literally none of the criteria
}

fs::path Dataloader::search_for_game_path(fs::path hint_path) {
auto it = _cached_paths.find(hint_path);
if (it != _cached_paths.end()) {
return it->second;
}

return _cached_paths[hint_path] = _search_for_game_path(hint_path);
}

bool Dataloader::set_roots(path_vector_t const& new_roots) {
if (!roots.empty()) {
Logger::error("Overriding existing dataloader roots!");
Expand Down
11 changes: 0 additions & 11 deletions src/openvic-simulation/dataloader/Dataloader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,5 @@ namespace OpenVic {
bool load_localisation_files(
localisation_callback_t callback, std::string_view localisation_dir = "localisation"
) const;

private:
struct fshash {
size_t operator()(const std::filesystem::path& p) const noexcept {
return std::filesystem::hash_value(p);
}
};

using hint_path_t = fs::path;
using game_path_t = fs::path;
static inline std::unordered_map<hint_path_t, game_path_t, fshash> _cached_paths;
};
}
Loading

0 comments on commit db00dcf

Please sign in to comment.