diff --git a/CMakeLists.txt b/CMakeLists.txt index 10af44aea..1c0de1d42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(SOURCEPP_USE_BSPPP "Build bsppp library" ON) option(SOURCEPP_USE_DMXPP "Build dmxpp library" ON) option(SOURCEPP_USE_FGDPP "Build fgdpp library" ON) +option(SOURCEPP_USE_GAMEPP "Build gamepp library" ON) option(SOURCEPP_USE_KVPP "Build kvpp library" ON) option(SOURCEPP_USE_MDLPP "Build mdlpp library" ON) option(SOURCEPP_USE_STEAMPP "Build steampp library" ON) @@ -88,6 +89,7 @@ endif() add_sourcepp_library(bsppp NO_TEST) add_sourcepp_library(dmxpp) add_sourcepp_library(fgdpp) +add_sourcepp_library(gamepp) add_sourcepp_library(kvpp) add_sourcepp_library(mdlpp) add_sourcepp_library(steampp C) diff --git a/FUTURE.md b/FUTURE.md index 9c529c7c4..f318da40d 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -14,6 +14,11 @@ - Add write support - `fgdpp` - Perhaps add the ability to parse TeamSpen's additions to the format? +- `gamepp` + - Add a function to create instances of a game rather than just finding existing ones + - When creating an instance of the game, attaching a console might be easier, or enabling -condebug, + which would then allow reading output from the console + - Add a method to take a screenshot of the game and move the file to a user-specified location - `kvpp` - Add write support - `mdlpp` diff --git a/README.md b/README.md index e2a5ef4a4..1f9164877 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,18 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ + + gamepp + + + + n/a + n/a + + kvpp @@ -83,11 +95,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one steampp - ✅ + n/a n/a C diff --git a/ext/bufferstream b/ext/bufferstream index 4db315795..b243467b1 160000 --- a/ext/bufferstream +++ b/ext/bufferstream @@ -1 +1 @@ -Subproject commit 4db3157952985ee4287e143895495d71d616082b +Subproject commit b243467b1a17afe0482451e251b99c392965425e diff --git a/include/gamepp/gamepp.h b/include/gamepp/gamepp.h new file mode 100644 index 000000000..b9775da5a --- /dev/null +++ b/include/gamepp/gamepp.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +#include + +namespace gamepp { + +class GameInstance { +public: + /** + * Find a running instance of a Source engine game. Note that this function will only ever + * succeed on Windows, because Valve never bothered to expose this functionality on Linux. + * @param windowNameOverride The classname of the window to search for, e.g. "Valve001". + * If unspecified it will check for "Valve001" and "Strata001". + * @return A GameInstance if a game is located. + */ + [[nodiscard]] static std::optional find(std::string_view windowNameOverride = ""); + + /** + * Get the human-readable window title. + * @return The window title. + */ + [[nodiscard]] std::string getWindowTitle() const; + + /** + * Get the window position on-screen. + * @return The window position. + */ + [[nodiscard]] sourcepp::math::Vec2i getWindowPos() const; + + /** + * Get the window size on-screen. + * @return The window size. + */ + [[nodiscard]] sourcepp::math::Vec2i getWindowSize() const; + + /** + * Send a command to the engine (ran as if it was entered into the console by the user). + * @param command The command to run. + * @return This GameInstance. + */ + const GameInstance& command(std::string_view command) const; + + /** + * Begin "pressing" an input such as forward or left. + * @param command The input to run (without the plus sign prefix). + * @return This GameInstance. + */ + const GameInstance& inputBegin(std::string_view input) const; + + /** + * End "pressing" an input such as forward or left. + * @param command The input to run (without the minus sign prefix). + * @return This GameInstance. + */ + const GameInstance& inputEnd(std::string_view input) const; + + /** + * Begin and end "pressing" an input in one tick, like tapping the use key. + * @param command The input to run (without any prefix). + * @return This GameInstance. + */ + const GameInstance& inputOnce(std::string_view input) const; + + /** + * Begin and end "pressing" an input in the given timespan, like holding the use key. + * @param input The input to run (without any prefix). + * @param sec The time to hold the input for. + * @return This GameInstance. + */ + const GameInstance& inputHold(std::string_view input, double sec) const; + + /** + * Sleep on the current thread for the given number of seconds. + * @param sec The number of seconds. + * @return This GameInstance. + */ + const GameInstance& wait(double sec) const; + +protected: + GameInstance() = default; + + void* hwnd; +}; + +} // namespace gamepp diff --git a/include/sourcepp/fs/FS.h b/include/sourcepp/FS.h similarity index 100% rename from include/sourcepp/fs/FS.h rename to include/sourcepp/FS.h diff --git a/include/sourcepp/string/String.h b/include/sourcepp/String.h similarity index 100% rename from include/sourcepp/string/String.h rename to include/sourcepp/String.h diff --git a/src/fgdpp/fgdpp.cpp b/src/fgdpp/fgdpp.cpp index 8560d234a..836b06b5a 100644 --- a/src/fgdpp/fgdpp.cpp +++ b/src/fgdpp/fgdpp.cpp @@ -6,9 +6,9 @@ #include -#include #include -#include +#include +#include using namespace fgdpp; using namespace sourcepp; diff --git a/src/gamepp/_gamepp.cmake b/src/gamepp/_gamepp.cmake new file mode 100644 index 000000000..86ee2af81 --- /dev/null +++ b/src/gamepp/_gamepp.cmake @@ -0,0 +1,4 @@ +add_pretty_parser(gamepp + SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/include/gamepp/gamepp.h" + "${CMAKE_CURRENT_LIST_DIR}/gamepp.cpp") diff --git a/src/gamepp/gamepp.cpp b/src/gamepp/gamepp.cpp new file mode 100644 index 000000000..b44892da9 --- /dev/null +++ b/src/gamepp/gamepp.cpp @@ -0,0 +1,112 @@ +#include + +#include +#include + +using namespace gamepp; +using namespace sourcepp; + +// All this stuff is very Windows-dependent, sorry! +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +std::optional GameInstance::find(std::string_view windowNameOverride) { + GameInstance instance; + + if (!windowNameOverride.empty()) { + instance.hwnd = FindWindowA(windowNameOverride.data(), nullptr); + } else { + instance.hwnd = FindWindowA("Valve001", nullptr); + if (!instance.hwnd) { + instance.hwnd = FindWindowA("Strata001", nullptr); + } + // are any other names used in other branches known? + } + + if (!instance.hwnd) { + return std::nullopt; + } + return instance; +} + +std::string GameInstance::getWindowTitle() const { + // This should be large enough + std::string title(512, '\0'); + if (auto size = GetWindowTextA(reinterpret_cast(this->hwnd), title.data(), title.length())) { + title.resize(size); + return title; + } + return ""; +} + +math::Vec2i GameInstance::getWindowPos() const { + RECT rect; + GetWindowRect(reinterpret_cast(this->hwnd), &rect); + return {rect.left, rect.top}; +} + +sourcepp::math::Vec2i GameInstance::getWindowSize() const { + RECT rect; + GetWindowRect(reinterpret_cast(this->hwnd), &rect); + return {rect.right - rect.left, rect.bottom - rect.top}; +} + +const GameInstance& GameInstance::command(std::string_view command) const { + COPYDATASTRUCT data; + data.dwData = 0; + data.cbData = command.length() + 1; + data.lpData = reinterpret_cast(const_cast(command.data())); + SendMessageTimeoutA(reinterpret_cast(this->hwnd), WM_COPYDATA, 0, reinterpret_cast(&data), SMTO_ABORTIFHUNG, 0, nullptr); + return *this; +} + +#else + +std::optional GameInstance::find(std::string_view) { + return std::nullopt; +} + +std::string GameInstance::getWindowTitle() const { + return ""; +} + +math::Vec2i GameInstance::getWindowPos() const { + return {}; +} + +math::Vec2i GameInstance::getWindowSize() const { + return {}; +} + +const GameInstance& GameInstance::command(std::string_view) const { + return *this; +} + +#endif + +const GameInstance& GameInstance::inputBegin(std::string_view input) const { + this->command("+" + std::string{input}); + return *this; +} + +const GameInstance& GameInstance::inputEnd(std::string_view input) const { + this->command("-" + std::string{input}); + return *this; +} + +const GameInstance& GameInstance::inputOnce(std::string_view input) const { + this->inputBegin(input).inputEnd(input); + return *this; +} + +const GameInstance& GameInstance::inputHold(std::string_view input, double sec) const { + this->inputBegin(input).wait(sec).inputEnd(input); + return *this; +} + +const GameInstance& GameInstance::wait(double sec) const { + std::this_thread::sleep_for(std::chrono::duration(sec)); + return *this; +} diff --git a/src/kvpp/kvpp.cpp b/src/kvpp/kvpp.cpp index cd5265226..a7aa35aa7 100644 --- a/src/kvpp/kvpp.cpp +++ b/src/kvpp/kvpp.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include using namespace kvpp; using namespace sourcepp; diff --git a/src/sourcepp/fs/FS.cpp b/src/sourcepp/FS.cpp similarity index 97% rename from src/sourcepp/fs/FS.cpp rename to src/sourcepp/FS.cpp index 949b32168..de7f91cac 100644 --- a/src/sourcepp/fs/FS.cpp +++ b/src/sourcepp/FS.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/src/sourcepp/string/String.cpp b/src/sourcepp/String.cpp similarity index 99% rename from src/sourcepp/string/String.cpp rename to src/sourcepp/String.cpp index b73a9ecca..31e98597d 100644 --- a/src/sourcepp/string/String.cpp +++ b/src/sourcepp/String.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/sourcepp/_sourcepp.cmake b/src/sourcepp/_sourcepp.cmake index fe9e93c03..30394fbeb 100644 --- a/src/sourcepp/_sourcepp.cmake +++ b/src/sourcepp/_sourcepp.cmake @@ -4,7 +4,6 @@ list(APPEND ${PROJECT_NAME}_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/MD5.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/RSA.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/crypto/String.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/fs/FS.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/math/Angles.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/math/Float.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/math/Integer.h" @@ -12,7 +11,8 @@ list(APPEND ${PROJECT_NAME}_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/math/Vector.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/parser/Binary.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/parser/Text.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/string/String.h") + "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/FS.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/sourcepp/String.h") add_library(${PROJECT_NAME} STATIC ${${PROJECT_NAME}_HEADERS} @@ -21,10 +21,10 @@ add_library(${PROJECT_NAME} STATIC "${CMAKE_CURRENT_LIST_DIR}/crypto/MD5.cpp" "${CMAKE_CURRENT_LIST_DIR}/crypto/RSA.cpp" "${CMAKE_CURRENT_LIST_DIR}/crypto/String.cpp" - "${CMAKE_CURRENT_LIST_DIR}/fs/FS.cpp" "${CMAKE_CURRENT_LIST_DIR}/parser/Binary.cpp" "${CMAKE_CURRENT_LIST_DIR}/parser/Text.cpp" - "${CMAKE_CURRENT_LIST_DIR}/string/String.cpp") + "${CMAKE_CURRENT_LIST_DIR}/FS.cpp" + "${CMAKE_CURRENT_LIST_DIR}/String.cpp") target_precompile_headers(${PROJECT_NAME} PUBLIC ${${PROJECT_NAME}_HEADERS}) diff --git a/src/steampp/steampp.cpp b/src/steampp/steampp.cpp index 73f8a721d..345b7d500 100644 --- a/src/steampp/steampp.cpp +++ b/src/steampp/steampp.cpp @@ -16,7 +16,7 @@ #endif #include -#include +#include using namespace kvpp; using namespace sourcepp; diff --git a/src/vpkpp/PackFile.cpp b/src/vpkpp/PackFile.cpp index 1f0af04bf..1db4de0a0 100644 --- a/src/vpkpp/PackFile.cpp +++ b/src/vpkpp/PackFile.cpp @@ -10,8 +10,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/vpkpp/format/BSP.cpp b/src/vpkpp/format/BSP.cpp index b42b51458..410a31695 100644 --- a/src/vpkpp/format/BSP.cpp +++ b/src/vpkpp/format/BSP.cpp @@ -9,8 +9,8 @@ #include #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/GCF.cpp b/src/vpkpp/format/GCF.cpp index 5a4b25a1f..34555dd1d 100644 --- a/src/vpkpp/format/GCF.cpp +++ b/src/vpkpp/format/GCF.cpp @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/GMA.cpp b/src/vpkpp/format/GMA.cpp index fb2652073..666193df7 100644 --- a/src/vpkpp/format/GMA.cpp +++ b/src/vpkpp/format/GMA.cpp @@ -5,8 +5,8 @@ #include #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/GRP.cpp b/src/vpkpp/format/GRP.cpp index 9119442f4..48ccc5c6b 100644 --- a/src/vpkpp/format/GRP.cpp +++ b/src/vpkpp/format/GRP.cpp @@ -4,8 +4,8 @@ #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp index 4fa637be9..fe2d7daac 100644 --- a/src/vpkpp/format/PAK.cpp +++ b/src/vpkpp/format/PAK.cpp @@ -4,8 +4,8 @@ #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/PCK.cpp b/src/vpkpp/format/PCK.cpp index 65e88cd39..376a4e14b 100644 --- a/src/vpkpp/format/PCK.cpp +++ b/src/vpkpp/format/PCK.cpp @@ -5,8 +5,8 @@ #include #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/src/vpkpp/format/VPK.cpp b/src/vpkpp/format/VPK.cpp index 65b23c8a6..f19b0003d 100644 --- a/src/vpkpp/format/VPK.cpp +++ b/src/vpkpp/format/VPK.cpp @@ -13,8 +13,8 @@ #include #include #include -#include -#include +#include +#include #include using namespace kvpp; diff --git a/src/vpkpp/format/ZIP.cpp b/src/vpkpp/format/ZIP.cpp index b72a91f7f..6e2134047 100644 --- a/src/vpkpp/format/ZIP.cpp +++ b/src/vpkpp/format/ZIP.cpp @@ -13,8 +13,8 @@ #include #include -#include -#include +#include +#include using namespace sourcepp; using namespace vpkpp; diff --git a/test/dmxpp.cpp b/test/dmxpp.cpp index 928769762..ee81f930b 100644 --- a/test/dmxpp.cpp +++ b/test/dmxpp.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include using namespace dmxpp; using namespace sourcepp; diff --git a/test/gamepp.cpp b/test/gamepp.cpp new file mode 100644 index 000000000..22fd90471 --- /dev/null +++ b/test/gamepp.cpp @@ -0,0 +1,45 @@ +#if 0 + +#include + +#include + +using namespace gamepp; + +TEST(gamepp, commandsBlocking) { + auto game = GameInstance::find(); + ASSERT_TRUE(game); + + // For Portal 2 - it can *almost* play through the whole map + (*game) + .command("developer 0").command("sv_cheats 1").command("cl_showpos 1").command("cl_yawspeed 180") + .command("map sp_a2_catapult_intro") + .wait(8) + .inputHold("forward", 15) + .inputOnce("use") + .inputHold("left", 0.99) + .wait(0.6) + .inputBegin("forward") + .wait(2.3) + .inputOnce("use") + .wait(1) + .inputEnd("forward") + .inputHold("left", 0.99) + .inputBegin("forward") + .wait(3) + .inputHold("right", 0.5) + .wait(0.86) + .inputHold("left", 0.5) + .wait(1.6) + .inputEnd("forward") + .wait(0.2) + .inputOnce("use") + .inputBegin("forward") + .inputHold("left", 0.3) + .wait(0.3) + .inputHold("right", 0.3) + .wait(8) + .inputEnd("forward"); +} + +#endif diff --git a/test/mdlpp.cpp b/test/mdlpp.cpp index 6b89e9ea3..06a4ff33c 100644 --- a/test/mdlpp.cpp +++ b/test/mdlpp.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include using namespace mdlpp; using namespace sourcepp; diff --git a/test/steampp.cpp b/test/steampp.cpp index 6254b6840..804a46651 100644 --- a/test/steampp.cpp +++ b/test/steampp.cpp @@ -1,3 +1,5 @@ +#if 0 + #include #include @@ -5,8 +7,6 @@ using namespace sourcepp; using namespace steampp; -#if 0 - TEST(steampp, list_installed_apps) { Steam steam; ASSERT_TRUE(steam); diff --git a/test/vicepp.cpp b/test/vicepp.cpp index 79049a5d1..286fb5503 100644 --- a/test/vicepp.cpp +++ b/test/vicepp.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include using namespace sourcepp; diff --git a/test/vtfpp.cpp b/test/vtfpp.cpp index c9ee1a028..135ca1011 100644 --- a/test/vtfpp.cpp +++ b/test/vtfpp.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include using namespace sourcepp;