diff options
author | Spartan322 <Megacake1234@gmail.com> | 2023-10-04 10:55:42 +0200 |
---|---|---|
committer | Spartan322 <Megacake1234@gmail.com> | 2023-10-12 16:18:37 +0200 |
commit | 5c7af98e3a8a3e7f1462e389c273566d7cdaa5d4 (patch) | |
tree | efeebd80a707a07eda296883772140e9ce32a9ee /src/openvic-simulation/dataloader/Dataloader.cpp | |
parent | 1d113b46161f27551bc3a6a857b8727cfb657b81 (diff) |
Add static `Dataloader::search_for_game_path(fs::path)`
Searches for Victoria 2 according to the supplied path
If supplied path is empty, presumes Steam install according to platform environment variables
If invalid supplied path, falls back to empty path behavior
Supports Steam install on Windows, Mac, Linux, and FreeBSD
Supports Windows registry
Update .clang-format categories to include lexy-vdf
Add Utility/ConstexprIntToStr.hpp
Diffstat (limited to 'src/openvic-simulation/dataloader/Dataloader.cpp')
-rw-r--r-- | src/openvic-simulation/dataloader/Dataloader.cpp | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index 6469b63..70164c3 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -1,16 +1,319 @@ #include "Dataloader.hpp" +#include <array> +#include <cstdlib> +#include <filesystem> +#include <optional> +#include <string_view> +#include <system_error> +#include <type_traits> +#include <variant> + #include <openvic-dataloader/csv/Parser.hpp> #include <openvic-dataloader/detail/CallbackOStream.hpp> #include <openvic-dataloader/v2script/Parser.hpp> +#include <lexy-vdf/KeyValues.hpp> +#include <lexy-vdf/Parser.hpp> + #include "openvic-simulation/GameManager.hpp" +#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; +// Windows and Mac by default act like case insensitive filesystems +constexpr bool path_equals(std::string_view lhs, std::string_view rhs) { +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) + constexpr auto ichar_equals = [](unsigned char l, unsigned char r) { + return std::tolower(l) == std::tolower(r); + }; + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), ichar_equals); +#else + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +#endif +} + +template<typename LT, typename RT> +constexpr bool filename_equals(const LT& lhs, const RT& rhs) { + std::string_view left, right; + if constexpr (std::same_as<LT, std::filesystem::path>) + left = lhs.filename().string(); + else left = lhs; + + if constexpr (std::same_as<RT, std::filesystem::path>) + right = rhs.filename().string(); + else right = rhs; + + return path_equals(left, right); +} + +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 + constexpr int max_amount_of_steam_libraries = 8; + constexpr std::string_view Victoria_2_folder = "Victoria 2"; + constexpr std::string_view v2_game_exe = "v2game.exe"; + constexpr std::string_view steamapps = "steamapps"; + constexpr std::string_view libraryfolders = "libraryfolders.vdf"; + constexpr std::string_view vic2_appmanifest = "appmanifest_42960.acf"; + 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 new_roots) { if (!roots.empty()) { Logger::error("Overriding existing dataloader roots!"); |