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 |
+
+
+ - Get Source engine instance window title/position/size
+ - Run commands in a Source engine instance remotely
+
+ |
+ n/a |
+ n/a |
+ |
+
kvpp |
@@ -83,11 +95,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
| steampp |
- - Finding Steam install folder
- - Finding installed Steam games
+ - Find Steam install folder
+ - Find installed Steam games
|
- ✅ |
+ 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;