From 890ffa923e511c5cb30f40541341a29924e6dc50 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:35:07 +0200 Subject: [PATCH] Launcher: light overhaul and use KeyValues class Reworked some of the code to make globals static and used KeyValues class to parse VDF files instead. --- license/thirdpartylegalnotices.txt | 76 +- r5dev/public/utility/vdf_parser.h | 711 ------------------ r5dev/sdklauncher/CMakeLists.txt | 3 + r5dev/sdklauncher/basepanel.cpp | 271 +++---- r5dev/sdklauncher/basepanel.h | 6 +- r5dev/sdklauncher/sdklauncher.cpp | 36 +- r5dev/sdklauncher/sdklauncher.h | 20 +- r5dev/sdklauncher/sdklauncher_const.h | 2 +- .../thirdparty/cppnet/cppkore/Application.cpp | 4 +- r5dev/thirdparty/cppnet/cppkore/Application.h | 2 +- 10 files changed, 191 insertions(+), 940 deletions(-) delete mode 100644 r5dev/public/utility/vdf_parser.h diff --git a/license/thirdpartylegalnotices.txt b/license/thirdpartylegalnotices.txt index 3f00d2c7..f4cd422e 100644 --- a/license/thirdpartylegalnotices.txt +++ b/license/thirdpartylegalnotices.txt @@ -70,29 +70,6 @@ NVIDIA NvAPI // the above Disclaimer (as applicable) and U.S. Government End Users Notice. //////////////////////////////////////////////////////////////////////////// -************************************************************************************ -Recast & Detour -************************************************************************************ - - // Copyright (c) 2009 Mikko Mononen memon@inside.org - // - // This software is provided 'as-is', without any express or implied - // warranty. In no event will the authors be held liable for any damages - // arising from the use of this software. - // - // Permission is granted to anyone to use this software for any purpose, - // including commercial applications, and to alter it and redistribute it - // freely, subject to the following restrictions: - // - // 1. The origin of this software must not be misrepresented; you must not - // claim that you wrote the original software. If you use this software - // in a product, an acknowledgment in the product documentation would be - // appreciated but is not required. - // 2. Altered source versions must be plainly marked as such, and must not be - // misrepresented as being the original software. - // 3. This notice may not be removed or altered from any source distribution. - //////////////////////////////////////////////////////////////////////////// - ************************************************************************************ Google protocol buffers ************************************************************************************ @@ -131,6 +108,29 @@ Google protocol buffers // support library is itself covered by the above license. //////////////////////////////////////////////////////////////////////////// +************************************************************************************ +Recast & Detour +************************************************************************************ + + // Copyright (c) 2009 Mikko Mononen memon@inside.org + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + //////////////////////////////////////////////////////////////////////////// + ************************************************************************************ Dear ImGui ************************************************************************************ @@ -299,33 +299,6 @@ Format {fmt} // without including the above copyright and permission notices. //////////////////////////////////////////////////////////////////////////// -************************************************************************************ -VDF Parser -************************************************************************************ - - // The MIT License (MIT) - // - // Copyright (c) Matthias Moeller 2016 m_moeller@live.de - // - // 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. - //////////////////////////////////////////////////////////////////////////// - ************************************************************************************ RapidJSON ************************************************************************************ @@ -642,7 +615,7 @@ EAThread (EA WebKit) // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //-------------------------------------------------------------------------- - // + // // Additional licenses also apply to this software package as detailed below. // //-------------------------------------------------------------------------- @@ -788,6 +761,7 @@ LZHAM // THE SOFTWARE. //////////////////////////////////////////////////////////////////////////// + ************************************************************************************ CRC32 ************************************************************************************ diff --git a/r5dev/public/utility/vdf_parser.h b/r5dev/public/utility/vdf_parser.h deleted file mode 100644 index 13d6f7ad..00000000 --- a/r5dev/public/utility/vdf_parser.h +++ /dev/null @@ -1,711 +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__ -#pragma warning( disable : 4996 ) - -#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 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 - 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 malformed, parser will read the file until no longer possible. - @param first begin iterator - @param end end iterator - @param exclude_files list of files which cant be included anymore. - prevents circular includes - - can throw: - - "std::runtime_error" if a parsing error occured - - "std::bad_alloc" if not enough memory could 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: itterate position past a '/' character - 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 never 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 wasn't 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_key = conditional_fullfilled(curIter, last); - if (!conditional_key) - continue; - - while (*curIter == TYTI_L(charT, '/')) - { - - curIter = skip_comments(curIter, last); - if (curIter == last || *curIter == '}') - throw std::runtime_error{ "Key declared without value" }; - curIter = skip_whitespaces(curIter, last); - if (curIter == last || *curIter == '}') - throw std::runtime_error{ "Key declared without 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_value = conditional_fullfilled(curIter, last); - if (!conditional_value) - continue; - - // process value - if (curObj && key != TYTI_L(charT, "#include") && key != TYTI_L(charT, "#base")) - { - curObj->add_attribute(std::move(key), std::move(value)); - } - 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 (*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; - } - } - return roots; - } - - } // namespace detail - - /** \brief Read VDF formatted sequences defined by the range [first, last). - If the file is malformed, parser will read the file until no longer possible. - @param first begin iterator - @param end end iterator - - can thow: - - "std::runtime_error" if a parsing error occurred - - "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::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 malformed, parser will read the file until no longer possible. - @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 malformed - 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 malformed, parser will read the file until no longer possible. - @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 - -#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__ diff --git a/r5dev/sdklauncher/CMakeLists.txt b/r5dev/sdklauncher/CMakeLists.txt index f5701928..be513530 100644 --- a/r5dev/sdklauncher/CMakeLists.txt +++ b/r5dev/sdklauncher/CMakeLists.txt @@ -38,6 +38,9 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE "tier0" "tier1" + "filesystem_std" + "vstdlib" + "libdetours" "libcppkore" "libspdlog" diff --git a/r5dev/sdklauncher/basepanel.cpp b/r5dev/sdklauncher/basepanel.cpp index 4c3ae735..e99abdcd 100644 --- a/r5dev/sdklauncher/basepanel.cpp +++ b/r5dev/sdklauncher/basepanel.cpp @@ -6,7 +6,10 @@ #include "basepanel.h" #include "sdklauncher.h" #include "mathlib/bits.h" -#include "utility/vdf_parser.h" +#include "vstdlib/keyvaluessystem.h" +#include "filesystem/filesystem_std.h" + +extern CFileSystem_Stdio* FileSystem(); //----------------------------------------------------------------------------- // Purpose: creates the surface layout @@ -494,6 +497,8 @@ void CSurface::Init() this->PerformLayout(); // END DESIGNER CODE + + this->Setup(); } //----------------------------------------------------------------------------- @@ -522,97 +527,60 @@ void CSurface::Setup() //----------------------------------------------------------------------------- void CSurface::LoadSettings() { - const fs::path settingsPath(Format("platform/%s/%s", SDK_SYSTEM_CFG_PATH, LAUNCHER_SETTING_FILE)); - if (!fs::exists(settingsPath)) + CUtlString settingsPath; + settingsPath.Format("platform/" SDK_SYSTEM_CFG_PATH "%s", LAUNCHER_SETTING_FILE); + + const char* pSettingsPath = settingsPath.String(); + + if (!FileSystem()->FileExists(pSettingsPath)) + return; + + KeyValues kv("LauncherSettings"); + + if (!kv.LoadFromFile(FileSystem(), pSettingsPath, nullptr)) { + printf("%s: Failed to parse VDF file: '%s'\n", __FUNCTION__, pSettingsPath); return; } - bool success{ }; - std::ifstream fileStream(settingsPath, fstream::in); - vdf::object vRoot = vdf::read(fileStream, &success); + const int settingsVersion = kv.GetInt("version", -1); - if (!success) - { - printf("%s: Failed to parse VDF file: '%s'\n", __FUNCTION__, - settingsPath.u8string().c_str()); + if (settingsVersion != SDK_LAUNCHER_VERSION) return; - } - try + KeyValues* sv = kv.FindKey("vars"); + + if (!sv) { - string& attributeView = vRoot.attribs["version"]; - - int settingsVersion = atoi(attributeView.c_str()); - if (settingsVersion != SDK_LAUNCHER_VERSION) - return; - - vdf::object* pSubKey = vRoot.childs["vars"].get(); - if (!pSubKey) - return; - - // Game. - attributeView = pSubKey->attribs["playlistsFile"]; - this->m_PlaylistFileTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["enableCheats"]; - this->m_CheatsToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["enableDeveloper"]; - this->m_DeveloperToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["enableConsole"]; - this->m_ConsoleToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["colorConsole"]; - this->m_ColorConsoleToggle->SetChecked(attributeView != "0"); - - // Engine. - attributeView = pSubKey->attribs["reservedCoreCount"]; - this->m_ReservedCoresTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["workerThreadCount"]; - this->m_WorkerThreadsTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["processorAffinity"]; - this->m_ProcessorAffinityTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["noAsync"]; // No-async - this->m_NoAsyncJobsToggle->SetChecked(attributeView != "0"); - - // Network. - attributeView = pSubKey->attribs["encryptPackets"]; - this->m_NetEncryptionToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["randomNetKey"]; - this->m_NetRandomKeyToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["queuedPackets"]; - this->m_QueuedPacketThread->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["noTimeOut"]; - this->m_NoTimeOutToggle->SetChecked(attributeView != "0"); - - // Video. - attributeView = pSubKey->attribs["windowed"]; - this->m_WindowedToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["borderless"]; - this->m_NoBorderToggle->SetChecked(attributeView != "0"); - - attributeView = pSubKey->attribs["maxFPS"]; - this->m_FpsTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["width"]; - this->m_WidthTextBox->SetText(attributeView.data()); - - attributeView = pSubKey->attribs["height"]; - this->m_HeightTextBox->SetText(attributeView.data()); - } - catch (const std::exception& e) - { - printf("%s: Exception while parsing VDF file: %s\n", __FUNCTION__, e.what()); + printf("%s: VDF file '%s' lacks subkey: '%s'\n", __FUNCTION__, pSettingsPath, "vars"); + return; // No settings to apply } + + // Game. + this->m_PlaylistFileTextBox->SetText(sv->GetString("playlistsFile")); + this->m_CheatsToggle->SetChecked(sv->GetBool("enableCheats")); + this->m_DeveloperToggle->SetChecked(sv->GetBool("enableDeveloper")); + this->m_ConsoleToggle->SetChecked(sv->GetBool("enableConsole")); + this->m_ColorConsoleToggle->SetChecked(sv->GetBool("colorConsole")); + + // Engine. + this->m_ReservedCoresTextBox->SetText(sv->GetString("reservedCoreCount", "-1")); + this->m_WorkerThreadsTextBox->SetText(sv->GetString("workerThreadCount", "-1")); + this->m_ProcessorAffinityTextBox->SetText(sv->GetString("processorAffinity", "0")); + this->m_NoAsyncJobsToggle->SetChecked(sv->GetBool("noAsync")); + + // Network. + this->m_NetEncryptionToggle->SetChecked(sv->GetBool("encryptPackets", true)); + this->m_NetRandomKeyToggle->SetChecked(sv->GetBool("randomNetKey", true)); + this->m_QueuedPacketThread->SetChecked(sv->GetBool("queuedPackets", true)); + this->m_NoTimeOutToggle->SetChecked(sv->GetBool("noTimeOut")); + + // Video. + this->m_WindowedToggle->SetChecked(sv->GetBool("windowed")); + this->m_NoBorderToggle->SetChecked(sv->GetBool("borderless")); + this->m_FpsTextBox->SetText(sv->GetString("maxFPS", "-1")); + this->m_WidthTextBox->SetText(sv->GetString("width")); + this->m_HeightTextBox->SetText(sv->GetString("height")); } //----------------------------------------------------------------------------- @@ -620,60 +588,69 @@ void CSurface::LoadSettings() //----------------------------------------------------------------------------- void CSurface::SaveSettings() { - const fs::path settingsPath(Format("platform/%s/%s", SDK_SYSTEM_CFG_PATH, LAUNCHER_SETTING_FILE)); - const fs::path parentPath = settingsPath.parent_path(); + CUtlString settingsPath; + settingsPath.Format("platform/" SDK_SYSTEM_CFG_PATH "%s", LAUNCHER_SETTING_FILE); - if (!fs::exists(parentPath) && !fs::create_directories(parentPath)) + CUtlString settingsDir = settingsPath.DirName(); + + const char* pSettingsPath = settingsPath.String(); + const char* pSettingsDir = settingsDir.String(); + + FileSystem()->CreateDirHierarchy(pSettingsDir); + + if (!FileSystem()->IsDirectory(pSettingsDir)) { - printf("%s: Failed to create directory: '%s'\n", __FUNCTION__, - parentPath.relative_path().u8string().c_str()); + printf("%s: Failed to create directory: '%s'\n", __FUNCTION__, pSettingsPath); return; } - std::ofstream fileStream(settingsPath, fstream::out); - if (!fileStream) + KeyValues kv("LauncherSettings"); + kv.SetInt("version", SDK_LAUNCHER_VERSION); + + KeyValues* sv = new KeyValues("vars"); + + if (!sv) { - printf("%s: Failed to create VDF file: '%s'\n", __FUNCTION__, - settingsPath.u8string().c_str()); - return; + printf("%s: Failed to allocate subkey: '%s'\n", __FUNCTION__, "vars"); + return; // No settings to apply } - vdf::object vRoot; - vRoot.set_name("LauncherSettings"); - vRoot.add_attribute("version", std::to_string(SDK_LAUNCHER_VERSION)); - - vdf::object* vVars = new vdf::object(); - vVars->set_name("vars"); + kv.AddSubKey(sv); // Game. - vVars->add_attribute("playlistsFile", GetControlValue(this->m_PlaylistFileTextBox)); - vVars->add_attribute("enableCheats", GetControlValue(this->m_CheatsToggle)); - vVars->add_attribute("enableDeveloper", GetControlValue(this->m_DeveloperToggle)); - vVars->add_attribute("enableConsole", GetControlValue(this->m_ConsoleToggle)); - vVars->add_attribute("colorConsole", GetControlValue(this->m_ColorConsoleToggle)); + sv->SetString("playlistsFile", GetControlValue(this->m_PlaylistFileTextBox)); + sv->SetBool("enableCheats", this->m_CheatsToggle->Checked()); + sv->SetBool("enableDeveloper", this->m_DeveloperToggle->Checked()); + sv->SetBool("enableConsole", this->m_ConsoleToggle->Checked()); + sv->SetBool("colorConsole", this->m_ColorConsoleToggle->Checked()); // Engine. - vVars->add_attribute("reservedCoreCount", GetControlValue(this->m_ReservedCoresTextBox)); - vVars->add_attribute("workerThreadCount", GetControlValue(this->m_WorkerThreadsTextBox)); - vVars->add_attribute("processorAffinity", GetControlValue(this->m_ProcessorAffinityTextBox)); - vVars->add_attribute("noAsync", GetControlValue(this->m_NoAsyncJobsToggle)); + sv->SetString("reservedCoreCount", GetControlValue(this->m_ReservedCoresTextBox)); + sv->SetString("workerThreadCount", GetControlValue(this->m_WorkerThreadsTextBox)); + sv->SetString("processorAffinity", GetControlValue(this->m_ProcessorAffinityTextBox)); + sv->SetBool("noAsync", this->m_NoAsyncJobsToggle->Checked()); // Network. - vVars->add_attribute("encryptPackets", GetControlValue(this->m_NetEncryptionToggle)); - vVars->add_attribute("randomNetKey", GetControlValue(this->m_NetRandomKeyToggle)); - vVars->add_attribute("queuedPackets", GetControlValue(this->m_QueuedPacketThread)); - vVars->add_attribute("noTimeOut", GetControlValue(this->m_NoTimeOutToggle)); + sv->SetBool("encryptPackets", this->m_NetEncryptionToggle->Checked()); + sv->SetBool("randomNetKey", this->m_NetRandomKeyToggle->Checked()); + sv->SetBool("queuedPackets", this->m_QueuedPacketThread->Checked()); + sv->SetBool("noTimeOut", this->m_NoTimeOutToggle->Checked()); // Video. - vVars->add_attribute("windowed", GetControlValue(this->m_WindowedToggle)); - vVars->add_attribute("borderless", GetControlValue(this->m_NoBorderToggle)); - vVars->add_attribute("maxFPS", GetControlValue(this->m_FpsTextBox)); - vVars->add_attribute("width", GetControlValue(this->m_WidthTextBox)); - vVars->add_attribute("height", GetControlValue(this->m_HeightTextBox)); + sv->SetBool("windowed", this->m_WindowedToggle->Checked()); + sv->SetBool("borderless", this->m_NoBorderToggle->Checked()); + sv->SetString("maxFPS", GetControlValue(this->m_FpsTextBox)); + sv->SetString("width", GetControlValue(this->m_WidthTextBox)); + sv->SetString("height", GetControlValue(this->m_HeightTextBox)); - vRoot.add_child(std::unique_ptr(vVars)); + CUtlBuffer outBuf(ssize_t(0), 0, CUtlBuffer::TEXT_BUFFER); + kv.RecursiveSaveToFile(outBuf, 0); - vdf::write(fileStream, vRoot); + if (!FileSystem()->WriteFile(pSettingsPath, "PLATFORM", outBuf)) + { + printf("%s: Failed to create VDF file: '%s'\n", __FUNCTION__, pSettingsPath); + return; + } } //----------------------------------------------------------------------------- @@ -735,11 +712,11 @@ void CSurface::LaunchGame(Forms::Control* pSender) string svParameter; pSurface->AppendParameterInternal(svParameter, "-launcher"); - eLaunchMode launchMode = g_pLauncher->GetMainSurface()->BuildParameter(svParameter); + eLaunchMode launchMode = g_Launcher.BuildParameter(svParameter); uint64_t nProcessorAffinity = pSurface->GetProcessorAffinity(svParameter); - if (g_pLauncher->CreateLaunchContext(launchMode, nProcessorAffinity, svParameter.c_str(), "startup_launcher.cfg")) - g_pLauncher->LaunchProcess(); + if (g_Launcher.CreateLaunchContext(launchMode, nProcessorAffinity, svParameter.c_str(), "startup_launcher.cfg")) + g_Launcher.LaunchProcess(); } //----------------------------------------------------------------------------- @@ -790,41 +767,39 @@ void CSurface::ParseMaps() //----------------------------------------------------------------------------- void CSurface::ParsePlaylists() { - fs::path playlistPath(Format("platform\\%s", this->m_PlaylistFileTextBox->Text().ToCString())); - m_PlaylistCombo->Items.Add(""); + CUtlString playlistPath; + playlistPath.Format("platform\\%s", this->m_PlaylistFileTextBox->Text().ToCString()); - if (!fs::exists(playlistPath)) + const char* pPlaylistPath = playlistPath.String(); + + if (!m_PlaylistCombo->Items.Contains("")) + m_PlaylistCombo->Items.Add(""); + + if (!FileSystem()->FileExists(pPlaylistPath)) + return; + + KeyValues kv("playlists"); + + if (!kv.LoadFromFile(FileSystem(), pPlaylistPath, nullptr)) { + printf("%s: Failed to parse playlists file: '%s'\n", __FUNCTION__, pPlaylistPath); return; } - bool success{ }; - std::ifstream iFile(playlistPath); - vdf::object vRoot = vdf::read(iFile, &success); + KeyValues* playlists = kv.FindKey("Playlists"); - if (!success) - { - printf("%s: Failed to parse VDF file: '%s'\n", __FUNCTION__, - playlistPath.u8string().c_str()); - return; - } + if (!playlists) + return; // Empty playlists - try + for (KeyValues* pSubKey = playlists->GetFirstTrueSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextTrueSubKey()) { - const auto& vcPlaylists = vRoot.childs.at("Playlists"); - for (auto [id, it] = std::tuplechilds.begin())> - { 1, vcPlaylists->childs.begin() }; it != vcPlaylists->childs.end(); id++, it++) + const char* keyName = pSubKey->GetName(); + + if (!this->m_PlaylistCombo->Items.Contains(keyName)) { - if (!this->m_PlaylistCombo->Items.Contains(it->first.c_str())) - { - this->m_PlaylistCombo->Items.Add(it->first.c_str()); - } + this->m_PlaylistCombo->Items.Add(keyName); } } - catch (const std::exception& e) - { - printf("%s: Exception while parsing VDF file: %s\n", __FUNCTION__, e.what()); - } } //----------------------------------------------------------------------------- @@ -1300,8 +1275,4 @@ const char* CSurface::GetControlValue(Forms::Control* pControl) //----------------------------------------------------------------------------- CSurface::CSurface() : Forms::Form() { - this->Init(); - this->Setup(); } - -CSurface* g_pMainUI; \ No newline at end of file diff --git a/r5dev/sdklauncher/basepanel.h b/r5dev/sdklauncher/basepanel.h index f50eac05..d1705e33 100644 --- a/r5dev/sdklauncher/basepanel.h +++ b/r5dev/sdklauncher/basepanel.h @@ -22,8 +22,10 @@ public: UIX::UIXListView* ConsoleListView() const { return m_ConsoleListView; }; std::vector m_LogList; -private: void Init(); + eLaunchMode BuildParameter(string& svParameter); + +private: void Setup(); void LoadSettings(); void SaveSettings(); @@ -44,8 +46,6 @@ private: const char* GetControlValue(Forms::Control* pControl); uint64_t GetProcessorAffinity(string& szParameter); - eLaunchMode BuildParameter(string& svParameter); - void AppendParameterInternal(string& svParameterList, const char* szParameter, const char* szArgument = nullptr); void AppendProcessorParameters(string& svParameter); void AppendConsoleParameters(string& svParameter); diff --git a/r5dev/sdklauncher/sdklauncher.cpp b/r5dev/sdklauncher/sdklauncher.cpp index 4191e50b..23b09dc7 100644 --- a/r5dev/sdklauncher/sdklauncher.cpp +++ b/r5dev/sdklauncher/sdklauncher.cpp @@ -8,6 +8,28 @@ #include "basepanel.h" #include "sdklauncher.h" +#include "vstdlib/keyvaluessystem.h" +#include "filesystem/filesystem_std.h" + +static CKeyValuesSystem s_KeyValuesSystem; +static CFileSystem_Stdio g_FullFileSystem; + +/////////////////////////////////////////////////////////////////////////////// +// Purpose: keyvalues singleton accessor +/////////////////////////////////////////////////////////////////////////////// +IKeyValuesSystem* KeyValuesSystem() +{ + return &s_KeyValuesSystem; +} + +/////////////////////////////////////////////////////////////////////////////// +// Purpose: filesystem singleton accessor +/////////////////////////////////////////////////////////////////////////////// +CFileSystem_Stdio* FileSystem() +{ + return &g_FullFileSystem; +} + /////////////////////////////////////////////////////////////////////////////// // Purpose: initializes and runs the user interface /////////////////////////////////////////////////////////////////////////////// @@ -16,8 +38,8 @@ void CLauncher::RunSurface() Forms::Application::EnableVisualStyles(); UIX::UIXTheme::InitializeRenderer(new Themes::KoreTheme()); - m_pSurface = new CSurface(); - Forms::Application::Run(g_pLauncher->m_pSurface); + m_Surface.Init(); + Forms::Application::Run(&g_Launcher.m_Surface, false); UIX::UIXTheme::ShutdownRenderer(); } @@ -366,21 +388,21 @@ BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) /////////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]/*, char* envp[]*/) { - g_pLauncher->InitLogger(); + g_Launcher.InitLogger(); if (argc < 2) { #ifdef NDEBUG FreeConsole(); #endif // NDEBUG - g_pLauncher->RunSurface(); + g_Launcher.RunSurface(); } else { - int results = g_pLauncher->HandleCommandLine(argc, argv); + int results = g_Launcher.HandleCommandLine(argc, argv); if (results != -1) return results; - return g_pLauncher->HandleInput(); + return g_Launcher.HandleInput(); } return EXIT_SUCCESS; } @@ -388,4 +410,4 @@ int main(int argc, char* argv[]/*, char* envp[]*/) /////////////////////////////////////////////////////////////////////////////// // Singleton Launcher. /////////////////////////////////////////////////////////////////////////////// -CLauncher* g_pLauncher(new CLauncher("win_console")); +CLauncher g_Launcher("win_console"); diff --git a/r5dev/sdklauncher/sdklauncher.h b/r5dev/sdklauncher/sdklauncher.h index 627d4de5..b2533855 100644 --- a/r5dev/sdklauncher/sdklauncher.h +++ b/r5dev/sdklauncher/sdklauncher.h @@ -6,17 +6,12 @@ class CLauncher public: CLauncher(const char* pszLoggerName) { - m_pSurface = nullptr; m_pLogger = spdlog::stdout_color_mt(pszLoggerName); m_ProcessorAffinity = NULL; m_svCurrentDir = fs::current_path().u8string(); } ~CLauncher() { - if (m_pSurface) - { - delete m_pSurface; - } } void AddLog(spdlog::level::level_enum nLevel, const char* szFormat, ...) @@ -30,12 +25,9 @@ public: m_pLogger->log(nLevel, svBuffer); m_pLogger->flush(); - if (m_pSurface) - { - m_pSurface->m_LogList.push_back(LogList_t(nLevel, svBuffer)); - m_pSurface->ConsoleListView()->SetVirtualListSize(static_cast(m_pSurface->m_LogList.size())); - m_pSurface->ConsoleListView()->Refresh(); - } + m_Surface.m_LogList.push_back(LogList_t(nLevel, svBuffer)); + m_Surface.ConsoleListView()->SetVirtualListSize(static_cast(m_Surface.m_LogList.size())); + m_Surface.ConsoleListView()->Refresh(); } void RunSurface(); @@ -49,10 +41,10 @@ public: void SetupLaunchContext(const char* szConfig, const char* szGameDll, const char* szCommandLine); bool LaunchProcess() const; - CSurface* GetMainSurface() const { return m_pSurface; } + eLaunchMode BuildParameter(string& parameterList) { return m_Surface.BuildParameter(parameterList); } private: - CSurface* m_pSurface; + CSurface m_Surface; std::shared_ptr m_pLogger; uint64_t m_ProcessorAffinity; @@ -64,4 +56,4 @@ private: BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam); -extern CLauncher* g_pLauncher; +extern CLauncher g_Launcher; diff --git a/r5dev/sdklauncher/sdklauncher_const.h b/r5dev/sdklauncher/sdklauncher_const.h index 6be7d829..0df95c77 100644 --- a/r5dev/sdklauncher/sdklauncher_const.h +++ b/r5dev/sdklauncher/sdklauncher_const.h @@ -1,7 +1,7 @@ #pragma once // Change this each time the settings format has changed. -#define SDK_LAUNCHER_VERSION 1 +#define SDK_LAUNCHER_VERSION 2 // Uncomment this line to compile the launcher for dedicated server builds. //#define DEDI_LAUNCHER diff --git a/r5dev/thirdparty/cppnet/cppkore/Application.cpp b/r5dev/thirdparty/cppnet/cppkore/Application.cpp index b69886eb..d01b8801 100644 --- a/r5dev/thirdparty/cppnet/cppkore/Application.cpp +++ b/r5dev/thirdparty/cppnet/cppkore/Application.cpp @@ -11,7 +11,7 @@ namespace Forms std::atomic Application::IsGdipInitialized = false; ULONG_PTR Application::GdipToken = NULL; - void Application::Run(Form* MainWindow) + void Application::Run(Form* MainWindow, bool DeleteWindow) { if (MainWindow == nullptr) return; @@ -27,7 +27,7 @@ namespace Forms // Execute on the main loop Application::RunMainLoop(MainWindow); - if (MainWindow) + if (MainWindow && DeleteWindow) delete MainWindow; // Shutdown COM diff --git a/r5dev/thirdparty/cppnet/cppkore/Application.h b/r5dev/thirdparty/cppnet/cppkore/Application.h index 8ab073fb..df1e32d7 100644 --- a/r5dev/thirdparty/cppnet/cppkore/Application.h +++ b/r5dev/thirdparty/cppnet/cppkore/Application.h @@ -12,7 +12,7 @@ namespace Forms public: // Begins running a standard application message loop on the current // thread, and makes the specified form visible. - static void Run(Form* MainWindow); + static void Run(Form* MainWindow, bool DeleteWindow); // Begins running a dialog application loop on the // current thread, you MUST clean up the dialog after use.