From 7bbcde7e31fa24fc05617a8d14caf279708f3b3f Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 28 Sep 2023 11:24:34 -0500 Subject: [PATCH 1/2] Consolidate game search code in Gamebryo --- src/gamestarfield.cpp | 30 +- src/vdf_parser.h | 739 ------------------------------------------ 2 files changed, 1 insertion(+), 768 deletions(-) delete mode 100644 src/vdf_parser.h diff --git a/src/gamestarfield.cpp b/src/gamestarfield.cpp index f124b5f..8b72331 100644 --- a/src/gamestarfield.cpp +++ b/src/gamestarfield.cpp @@ -26,7 +26,6 @@ #include #include "scopeguard.h" -#include "vdf_parser.h" using namespace MOBase; @@ -67,34 +66,7 @@ void GameStarfield::detectGame() QString GameStarfield::identifyGamePath() const { - QString path = "Software\\Valve\\Steam"; - QString steamLocation = - findInRegistry(HKEY_CURRENT_USER, path.toStdWString().c_str(), L"SteamPath"); - if (!steamLocation.isEmpty()) { - QString steamLibraryLocation; - QString steamLibraries(steamLocation + "\\" + "config" + "\\" + - "libraryfolders.vdf"); - if (QFile(steamLibraries).exists()) { - std::ifstream file(steamLibraries.toStdString()); - auto root = tyti::vdf::read(file); - for (auto child : root.childs) { - tyti::vdf::object* library = child.second.get(); - auto apps = library->childs["apps"]; - if (apps->attribs.contains(steamAPPId().toStdString())) { - steamLibraryLocation = QString::fromStdString(library->attribs["path"]); - break; - } - } - } - if (!steamLibraryLocation.isEmpty()) { - QString gameLocation = steamLibraryLocation + "\\" + "steamapps" + "\\" + - "common" + "\\" + "Starfield"; - if (QDir(gameLocation).exists() && - QFile(gameLocation + "\\" + "Starfield.exe").exists()) - return gameLocation; - } - } - return ""; + return parseSteamLocation(steamAPPId()); } QDir GameStarfield::dataDirectory() const diff --git a/src/vdf_parser.h b/src/vdf_parser.h deleted file mode 100644 index 32d4d27..0000000 --- a/src/vdf_parser.h +++ /dev/null @@ -1,739 +0,0 @@ -// MIT License -// -// Copyright(c) 2016 Matthias Moeller -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef __TYTI_STEAM_VDF_PARSER_H__ -#define __TYTI_STEAM_VDF_PARSER_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// for wstring support -#include -#include - -// internal -#include - -// VS < 2015 has only partial C++11 support -#if defined(_MSC_VER) && _MSC_VER < 1900 -#ifndef CONSTEXPR -#define CONSTEXPR -#endif - -#ifndef NOEXCEPT -#define NOEXCEPT -#endif -#else -#ifndef CONSTEXPR -#define CONSTEXPR constexpr -#define TYTI_UNDEF_CONSTEXPR -#endif - -#ifndef NOEXCEPT -#define NOEXCEPT noexcept -#define TYTI_UNDEF_NOEXCEPT -#endif - -#endif - -namespace tyti -{ -namespace vdf -{ - namespace detail - { - /////////////////////////////////////////////////////////////////////////// - // Helper functions selecting the right encoding (char/wchar_T) - /////////////////////////////////////////////////////////////////////////// - - template - struct literal_macro_help - { - static CONSTEXPR const char* result(const char* c, const wchar_t*) NOEXCEPT - { - return c; - } - static CONSTEXPR const char result(const char c, const wchar_t) NOEXCEPT - { - return c; - } - }; - - template <> - struct literal_macro_help - { - static CONSTEXPR const wchar_t* result(const char*, const wchar_t* wc) NOEXCEPT - { - return wc; - } - static CONSTEXPR const wchar_t result(const char, const wchar_t wc) NOEXCEPT - { - return wc; - } - }; -#define TYTI_L(type, text) vdf::detail::literal_macro_help::result(text, L##text) - - inline std::string string_converter(const std::string& w) NOEXCEPT - { - return w; - } - - // utility wrapper to adapt locale-bound facets for wstring/wbuffer convert - // from cppreference - template - struct deletable_facet : Facet - { - template - deletable_facet(Args&&... args) : Facet(std::forward(args)...) - {} - ~deletable_facet() {} - }; - - inline std::string string_converter(const std::wstring& w) // todo: use us-locale - { - std::wstring_convert>> - conv1; - return conv1.to_bytes(w); - } - - /////////////////////////////////////////////////////////////////////////// - // Writer helper functions - /////////////////////////////////////////////////////////////////////////// - - template - class tabs - { - const size_t t; - - public: - explicit CONSTEXPR tabs(size_t i) NOEXCEPT : t(i) {} - std::basic_string print() const - { - return std::basic_string(t, TYTI_L(charT, '\t')); - } - inline CONSTEXPR tabs operator+(size_t i) const NOEXCEPT { return tabs(t + i); } - }; - - template - oStreamT& operator<<(oStreamT& s, const tabs t) - { - s << t.print(); - return s; - } - } // end namespace detail - - /////////////////////////////////////////////////////////////////////////// - // Interface - /////////////////////////////////////////////////////////////////////////// - - /// custom objects and their corresponding write functions - - /// basic object node. Every object has a name and can contains attributes saved as - /// key_value pairs or childrens - template - struct basic_object - { - typedef CharT char_type; - std::basic_string name; - std::unordered_map, std::basic_string> - attribs; - std::unordered_map, - std::shared_ptr>> - childs; - - void add_attribute(std::basic_string key, - std::basic_string value) - { - attribs.emplace(std::move(key), std::move(value)); - } - void add_child(std::unique_ptr> child) - { - std::shared_ptr> obj{child.release()}; - childs.emplace(obj->name, obj); - } - void set_name(std::basic_string n) { name = std::move(n); } - }; - - template - struct basic_multikey_object - { - typedef CharT char_type; - std::basic_string name; - std::unordered_multimap, std::basic_string> - attribs; - std::unordered_multimap, - std::shared_ptr>> - childs; - - void add_attribute(std::basic_string key, - std::basic_string value) - { - attribs.emplace(std::move(key), std::move(value)); - } - void add_child(std::unique_ptr> child) - { - std::shared_ptr> obj{child.release()}; - childs.emplace(obj->name, obj); - } - void set_name(std::basic_string n) { name = std::move(n); } - }; - - typedef basic_object object; - typedef basic_object wobject; - typedef basic_multikey_object multikey_object; - typedef basic_multikey_object wmultikey_object; - - struct Options - { - bool strip_escape_symbols; - bool ignore_all_platform_conditionals; - bool ignore_includes; - - Options() - : strip_escape_symbols(true), ignore_all_platform_conditionals(false), - ignore_includes(false) - {} - }; - - // forward decls - // forward decl - template - OutputT read(iStreamT& inStream, const Options& opt = Options{}); - - /** \brief writes given object tree in vdf format to given stream. - Output is prettyfied, using tabs - */ - template - void write(oStreamT& s, const T& r, - const detail::tabs tab = - detail::tabs(0)) - { - typedef typename oStreamT::char_type charT; - using namespace detail; - s << tab << TYTI_L(charT, '"') << r.name << TYTI_L(charT, "\"\n") << tab - << TYTI_L(charT, "{\n"); - for (const auto& i : r.attribs) - s << tab + 1 << TYTI_L(charT, '"') << i.first << TYTI_L(charT, "\"\t\t\"") - << i.second << TYTI_L(charT, "\"\n"); - for (const auto& i : r.childs) - if (i.second) - write(s, *i.second, tab + 1); - s << tab << TYTI_L(charT, "}\n"); - } - - namespace detail - { - template - std::basic_string read_file(iStreamT& inStream) - { - // cache the file - typedef typename iStreamT::char_type charT; - std::basic_string str; - inStream.seekg(0, std::ios::end); - str.resize(static_cast(inStream.tellg())); - if (str.empty()) - return str; - - inStream.seekg(0, std::ios::beg); - inStream.read(&str[0], str.size()); - return str; - } - - /** \brief Read VDF formatted sequences defined by the range [first, last). - If the file is mailformatted, parser will try to read it until it can. - @param first begin iterator - @param end end iterator - @param exclude_files list of files which cant be included anymore. - prevents circular includes - - can thow: - - "std::runtime_error" if a parsing error occured - - "std::bad_alloc" if not enough memory coup be allocated - */ - template - std::vector> - read_internal(IterT first, const IterT last, - std::unordered_set::value_type>>& exclude_files, - const Options& opt) - { - static_assert(std::is_default_constructible::value, - "Output Type must be default constructible (provide constructor " - "without arguments)"); - static_assert(std::is_move_constructible::value, - "Output Type must be move constructible"); - - typedef typename std::iterator_traits::value_type charT; - - const std::basic_string comment_end_str = TYTI_L(charT, "*/"); - const std::basic_string whitespaces = TYTI_L(charT, " \n\v\f\r\t"); - -#ifdef WIN32 - std::function&)> is_platform_str = - [](const std::basic_string& in) { - return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$WINDOWS"); - }; -#elif __APPLE__ - // WIN32 stands for pc in general - std::function&)> is_platform_str = - [](const std::basic_string& in) { - return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || - in == TYTI_L(charT, "$OSX"); - }; - -#elif __linux__ - // WIN32 stands for pc in general - std::function&)> is_platform_str = - [](const std::basic_string& in) { - return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || - in == TYTI_L(charT, "$LINUX"); - }; -#else - std::function&)> is_platform_str = - [](const std::basic_string& in) { - return false; - }; -#endif - - if (opt.ignore_all_platform_conditionals) - is_platform_str = [](const std::basic_string&) { - return false; - }; - - // function for skipping a comment block - // iter: iterator poition to the position after a '/' - auto skip_comments = [&comment_end_str](IterT iter, const IterT& last) -> IterT { - ++iter; - if (iter != last) { - if (*iter == TYTI_L(charT, '/')) { - // line comment, skip whole line - iter = std::find(iter + 1, last, TYTI_L(charT, '\n')); - } - - if (*iter == '*') { - // block comment, skip until next occurance of "*\" - iter = std::search(iter + 1, last, std::begin(comment_end_str), - std::end(comment_end_str)); - iter += 2; - } - } - return iter; - }; - - auto end_quote = [](IterT iter, const IterT& last) -> IterT { - const auto begin = iter; - auto last_esc = iter; - do { - ++iter; - iter = std::find(iter, last, TYTI_L(charT, '\"')); - if (iter == last) - break; - - last_esc = std::prev(iter); - while (last_esc != begin && *last_esc == '\\') - --last_esc; - } while (!(std::distance(last_esc, iter) % 2)); - if (iter == last) - throw std::runtime_error{"quote was opened but not closed."}; - return iter; - }; - - auto end_word = [&whitespaces](IterT iter, const IterT& last) -> IterT { - const auto begin = iter; - auto last_esc = iter; - do { - ++iter; - iter = std::find_first_of(iter, last, std::begin(whitespaces), - std::end(whitespaces)); - if (iter == last) - break; - - last_esc = std::prev(iter); - while (last_esc != begin && *last_esc == '\\') - --last_esc; - } while (!(std::distance(last_esc, iter) % 2)); - // if (iter == last) - // throw std::runtime_error{ "word wasnt properly ended" }; - return iter; - }; - - auto skip_whitespaces = [&whitespaces](IterT iter, const IterT& last) -> IterT { - iter = std::find_if_not(iter, last, [&whitespaces](charT c) { - // return true if whitespace - return std::any_of(std::begin(whitespaces), std::end(whitespaces), - [c](charT pc) { - return pc == c; - }); - }); - return iter; - }; - - std::function&)> strip_escape_symbols = - [](std::basic_string& s) { - auto quote_searcher = [&s](size_t pos) { - return s.find(TYTI_L(charT, "\\\""), pos); - }; - auto p = quote_searcher(0); - while (p != s.npos) { - s.replace(p, 2, TYTI_L(charT, "\"")); - p = quote_searcher(p); - } - auto searcher = [&s](size_t pos) { - return s.find(TYTI_L(charT, "\\\\"), pos); - }; - p = searcher(0); - while (p != s.npos) { - s.replace(p, 2, TYTI_L(charT, "\\")); - p = searcher(p); - } - }; - - if (!opt.strip_escape_symbols) - strip_escape_symbols = [](std::basic_string&) {}; - - auto conditional_fullfilled = [&skip_whitespaces, - &is_platform_str](IterT& iter, const IterT& last) { - iter = skip_whitespaces(iter, last); - if (*iter == '[') { - ++iter; - const auto end = std::find(iter, last, ']'); - const bool negate = *iter == '!'; - if (negate) - ++iter; - auto conditional = std::basic_string(iter, end); - - const bool is_platform = is_platform_str(conditional); - iter = end + 1; - - return static_cast(is_platform ^ negate); - } - return true; - }; - - // read header - // first, quoted name - std::unique_ptr curObj = nullptr; - std::vector> roots; - std::stack> lvls; - auto curIter = first; - - while (curIter != last && *curIter != '\0') { - // find first starting attrib/child, or ending - curIter = skip_whitespaces(curIter, last); - if (curIter == last || *curIter == '\0') - break; - if (*curIter == TYTI_L(charT, '/')) { - curIter = skip_comments(curIter, last); - } else if (*curIter != TYTI_L(charT, '}')) { - - // get key - const auto keyEnd = (*curIter == TYTI_L(charT, '\"')) - ? end_quote(curIter, last) - : end_word(curIter, last); - if (*curIter == TYTI_L(charT, '\"')) - ++curIter; - std::basic_string key(curIter, keyEnd); - strip_escape_symbols(key); - curIter = keyEnd + ((*keyEnd == TYTI_L(charT, '\"')) ? 1 : 0); - - curIter = skip_whitespaces(curIter, last); - - auto conditional = conditional_fullfilled(curIter, last); - if (!conditional) - continue; - - while (*curIter == TYTI_L(charT, '/')) { - - curIter = skip_comments(curIter, last); - if (curIter == last || *curIter == '}') - throw std::runtime_error{"key declared, but no value"}; - curIter = skip_whitespaces(curIter, last); - if (curIter == last || *curIter == '}') - throw std::runtime_error{"key declared, but no value"}; - } - // get value - if (*curIter != '{') { - const auto valueEnd = (*curIter == TYTI_L(charT, '\"')) - ? end_quote(curIter, last) - : end_word(curIter, last); - if (*curIter == TYTI_L(charT, '\"')) - ++curIter; - - auto value = std::basic_string(curIter, valueEnd); - strip_escape_symbols(value); - curIter = valueEnd + ((*valueEnd == TYTI_L(charT, '\"')) ? 1 : 0); - - auto conditional = conditional_fullfilled(curIter, last); - if (!conditional) - continue; - - // process value - if (key != TYTI_L(charT, "#include") && key != TYTI_L(charT, "#base")) { - if (curObj) { - curObj->add_attribute(std::move(key), std::move(value)); - } else { - throw std::runtime_error{"unexpected key without object"}; - } - } else { - if (!opt.ignore_includes && - exclude_files.find(value) == exclude_files.end()) { - exclude_files.insert(value); - std::basic_ifstream i(detail::string_converter(value)); - auto str = read_file(i); - auto file_objs = - read_internal(str.begin(), str.end(), exclude_files, opt); - for (auto& n : file_objs) { - if (curObj) - curObj->add_child(std::move(n)); - else - roots.push_back(std::move(n)); - } - exclude_files.erase(value); - } - } - } else if (*curIter == '{') { - if (curObj) - lvls.push(std::move(curObj)); - curObj = std::make_unique(); - curObj->set_name(std::move(key)); - ++curIter; - } - } - // end of new object - else if (curObj && *curIter == TYTI_L(charT, '}')) { - if (!lvls.empty()) { - // get object before - std::unique_ptr prev{std::move(lvls.top())}; - lvls.pop(); - - // add finished obj to obj before and release it from processing - prev->add_child(std::move(curObj)); - curObj = std::move(prev); - } else { - roots.push_back(std::move(curObj)); - curObj.reset(); - } - ++curIter; - } else { - throw std::runtime_error{"unexpected '}'"}; - } - } - if (curObj != nullptr || !lvls.empty()) { - throw std::runtime_error{"object is not closed with '}'"}; - } - - return roots; - } - - } // namespace detail - - /** \brief Read VDF formatted sequences defined by the range [first, last). - If the file is mailformatted, parser will try to read it until it can. - @param first begin iterator - @param end end iterator - - can thow: - - "std::runtime_error" if a parsing error occured - - "std::bad_alloc" if not enough memory coup be allocated - */ - template - OutputT read(IterT first, const IterT last, const Options& opt = Options{}) - { - auto exclude_files = std::unordered_set< - std::basic_string::value_type>>{}; - auto roots = detail::read_internal(first, last, exclude_files, opt); - - OutputT result; - if (roots.size() > 1) { - for (auto& i : roots) - result.add_child(std::move(i)); - } else if (roots.size() == 1) - result = std::move(*roots[0]); - - return result; - } - - /** \brief Read VDF formatted sequences defined by the range [first, last). - If the file is mailformatted, parser will try to read it until it can. - @param first begin iterator - @param end end iterator - @param ec output bool. 0 if ok, otherwise, holds an system error code - - Possible error codes: - std::errc::protocol_error: file is mailformatted - std::errc::not_enough_memory: not enough space - std::errc::invalid_argument: iterators throws e.g. out of range - */ - template - OutputT read(IterT first, IterT last, std::error_code& ec, - const Options& opt = Options{}) NOEXCEPT - - { - ec.clear(); - OutputT r{}; - try { - r = read(first, last, opt); - } catch (std::runtime_error&) { - ec = std::make_error_code(std::errc::protocol_error); - } catch (std::bad_alloc&) { - ec = std::make_error_code(std::errc::not_enough_memory); - } catch (...) { - ec = std::make_error_code(std::errc::invalid_argument); - } - return r; - } - - /** \brief Read VDF formatted sequences defined by the range [first, last). - If the file is mailformatted, parser will try to read it until it can. - @param first begin iterator - @param end end iterator - @param ok output bool. true, if parser successed, false, if parser failed - */ - template - OutputT read(IterT first, const IterT last, bool* ok, - const Options& opt = Options{}) NOEXCEPT - { - std::error_code ec; - auto r = read(first, last, ec, opt); - if (ok) - *ok = !ec; - return r; - } - - template - inline auto read(IterT first, const IterT last, bool* ok, - const Options& opt = Options{}) NOEXCEPT - ->basic_object::value_type> - { - return read::value_type>>( - first, last, ok, opt); - } - - template - inline auto read(IterT first, IterT last, std::error_code& ec, - const Options& opt = Options{}) NOEXCEPT - ->basic_object::value_type> - { - return read::value_type>>( - first, last, ec, opt); - } - - template - inline auto read(IterT first, const IterT last, const Options& opt = Options{}) - -> basic_object::value_type> - { - return read::value_type>>( - first, last, opt); - } - - /** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf - formatted data. throws "std::bad_alloc" if file buffer could not be allocated - */ - template - OutputT read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{}) - { - // cache the file - typedef typename iStreamT::char_type charT; - std::basic_string str = detail::read_file(inStream); - - // parse it - return read(str.begin(), str.end(), ec, opt); - } - - template - inline basic_object - read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{}) - { - return read>(inStream, ec, opt); - } - - /** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf - formatted data. throws "std::bad_alloc" if file buffer could not be allocated ok == - false, if a parsing error occured - */ - template - OutputT read(iStreamT& inStream, bool* ok, const Options& opt = Options{}) - { - std::error_code ec; - const auto r = read(inStream, ec, opt); - if (ok) - *ok = !ec; - return r; - } - - template - inline basic_object read(iStreamT& inStream, bool* ok, - const Options& opt = Options{}) - { - return read>(inStream, ok, opt); - } - - /** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf - formatted data. throws "std::bad_alloc" if file buffer could not be allocated - throws "std::runtime_error" if a parsing error occured - */ - template - OutputT read(iStreamT& inStream, const Options& opt) - { - - // cache the file - typedef typename iStreamT::char_type charT; - std::basic_string str = detail::read_file(inStream); - // parse it - return read(str.begin(), str.end(), opt); - } - - template - inline basic_object read(iStreamT& inStream, - const Options& opt = Options{}) - { - return read>(inStream, opt); - } - -} // namespace vdf -} // namespace tyti -#ifndef TYTI_NO_L_UNDEF -#undef TYTI_L -#endif - -#ifdef TYTI_UNDEF_CONSTEXPR -#undef CONSTEXPR -#undef TYTI_NO_L_UNDEF -#endif - -#ifdef TYTI_UNDEF_NOTHROW -#undef NOTHROW -#undef TYTI_UNDEF_NOTHROW -#endif - -#endif //__TYTI_STEAM_VDF_PARSER_H__ From 13ecd582b0076dc52ed06d7da9ea457fa98620b5 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 28 Sep 2023 20:26:28 -0500 Subject: [PATCH 2/2] Updated parseSteamLocation --- src/gamestarfield.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gamestarfield.cpp b/src/gamestarfield.cpp index 8b72331..1f46df9 100644 --- a/src/gamestarfield.cpp +++ b/src/gamestarfield.cpp @@ -66,7 +66,7 @@ void GameStarfield::detectGame() QString GameStarfield::identifyGamePath() const { - return parseSteamLocation(steamAPPId()); + return parseSteamLocation(steamAPPId(), gameName()); } QDir GameStarfield::dataDirectory() const