diff --git a/GlosSITarget/GlosSITarget.vcxproj b/GlosSITarget/GlosSITarget.vcxproj index 0da4432..560eab5 100644 --- a/GlosSITarget/GlosSITarget.vcxproj +++ b/GlosSITarget/GlosSITarget.vcxproj @@ -80,14 +80,14 @@ true - ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;$(ExternalIncludePath) + ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;$(ExternalIncludePath) ..\deps\SFML\out\build\x64-Debug\lib;$(LibraryPath) false true false - ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\subhook;$(ExternalIncludePath) + ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;$(ExternalIncludePath) ..\deps\SFML\out\build\x64-Release\lib;$(LibraryPath) @@ -122,7 +122,7 @@ Level3 true - _DEBUG;_CONSOLE;SPDLOG_WCHAR_TO_UTF8_SUPPORT;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;%(PreprocessorDefinitions) + _DEBUG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SUBHOOK_STATIC;%(PreprocessorDefinitions) true stdcpp20 @@ -138,7 +138,7 @@ true true true - NDEBUG;_CONSOLE;SPDLOG_WCHAR_TO_UTF8_SUPPORT;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;%(PreprocessorDefinitions) + NDEBUG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SUBHOOK_STATIC;%(PreprocessorDefinitions) true stdcpp20 @@ -150,12 +150,14 @@ + + diff --git a/GlosSITarget/GlosSITarget.vcxproj.filters b/GlosSITarget/GlosSITarget.vcxproj.filters index 53f0b4b..299bf55 100644 --- a/GlosSITarget/GlosSITarget.vcxproj.filters +++ b/GlosSITarget/GlosSITarget.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -41,6 +44,9 @@ Header Files + + Header Files + diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index 6bcc1da..ab3215f 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -24,16 +24,33 @@ limitations under the License. #include #include +#include + +#ifdef _WIN32 +subhook::Hook getFgWinHook; +static HWND target_hwnd = nullptr; + +HWND keepForegroundWindow() +{ + return target_hwnd; +} + +#endif + SteamTarget::SteamTarget(int argc, char *argv[]) - : window_([this] { run_ = false; }), + : window_([this] { run_ = false; }, getScreenshotHotkey()), detector_([this](bool overlay_open) { onOverlayChanged(overlay_open); }), target_window_handle_(window_.getSystemHandle()) { +#ifdef _WIN32 + target_hwnd = target_window_handle_; +#endif } int SteamTarget::run() { run_ = true; window_.setFpsLimit(90); + keepControllerConfig(true); while (run_) { detector_.update(); window_.update(); @@ -57,11 +74,17 @@ void SteamTarget::focusWindow(WindowHandle hndl) { #ifdef _WIN32 - //MH_DisableHook(&GetForegroundWindow); // TODO: when GetForegroundWindow hooked, unhook! - // store last focused window for later restore + if (hndl == target_window_handle_) { + spdlog::info("Bring own window to foreground"); + } + else { + spdlog::info("Bring window \"{:#x}\" to foreground", reinterpret_cast(hndl)); + } + + keepControllerConfig(false); // unhook GetForegroundWindow last_foreground_window_ = GetForegroundWindow(); const DWORD fg_thread = GetWindowThreadProcessId(last_foreground_window_, nullptr); - //MH_EnableHook(&GetForegroundWindow); // TODO: when GetForegroundWindow hooked, re-hook! + keepControllerConfig(true); // re-hook GetForegroundWindow // lot's of ways actually bringing our window to foreground... const DWORD current_thread = GetCurrentThreadId(); @@ -91,7 +114,9 @@ std::wstring SteamTarget::getSteamPath() // TODO: check if keys/value exist // steam should always be open and have written reg values... winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam"}; - return key.GetStringValue(L"SteamPath"); + const auto res = key.GetStringValue(L"SteamPath"); + spdlog::info(L"Detected Steam Path: {}", res); + return res; #else return L""; // TODO #endif @@ -103,7 +128,9 @@ std::wstring SteamTarget::getSteamUserId() // TODO: check if keys/value exist // steam should always be open and have written reg values... winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam\\ActiveProcess"}; - return std::to_wstring(key.GetDwordValue(L"ActiveUser")); + const auto res = std::to_wstring(key.GetDwordValue(L"ActiveUser")); + spdlog::info(L"Detected Steam UserId: {}", res); + return res; #else return L""; // TODO #endif @@ -111,10 +138,7 @@ std::wstring SteamTarget::getSteamUserId() std::vector SteamTarget::getOverlayHotkey() { - const auto steam_path = getSteamPath(); - const auto user_id = getSteamUserId(); - - const auto config_path = steam_path + std::wstring(user_data_path_) + user_id + std::wstring(config_file_name_); + const auto config_path = steam_path_ + std::wstring(user_data_path_) + steam_user_id_ + std::wstring(config_file_name_); std::ifstream config_file(config_path); // TODO: check if file exists auto root = tyti::vdf::read(config_file); @@ -125,7 +149,42 @@ std::vector SteamTarget::getOverlayHotkey() // has anyone more than 4 keys to open overlay?! std::smatch m; if (!std::regex_match(hotkeys, m, std::regex(R"((\w*)\s*(\w*)\s*(\w*)\s*(\w*))"))) { - return {"Shift", "KEY_TAB"}; + spdlog::warn("Couldn't detect overlay hotkey, using default: Shift+Tab"); + return {"Shift", "KEY_TAB"}; // default + } + + std::vector res; + for (auto i = 1; i < m.size(); i++) { + const auto s = std::string(m[i]); + if (!s.empty()) { + res.push_back(s); + } + } + if (res.empty()) { + spdlog::warn("Couldn't detect overlay hotkey, using default: Shift+Tab"); + return {"Shift", "KEY_TAB"}; // default + } + spdlog::info("Detected Overlay hotkey(s): {}", std::accumulate( + res.begin() + 1, res.end(), res[0], + [](auto acc, const auto curr) { return acc += "+" + curr; })); + return res; +} + +std::vector SteamTarget::getScreenshotHotkey() +{ + const auto config_path = steam_path_ + std::wstring(user_data_path_) + steam_user_id_ + std::wstring(config_file_name_); + std::ifstream config_file(config_path); + // TODO: check if file exists + auto root = tyti::vdf::read(config_file); + + auto children = root.childs["system"]; + auto hotkeys = children->attribs.at("InGameOverlayScreenshotHotKey"); + + // has anyone more than 4 keys to screenshot?! + std::smatch m; + if (!std::regex_match(hotkeys, m, std::regex(R"((\w*)\s*(\w*)\s*(\w*)\s*(\w*))"))) { + spdlog::warn("Couldn't detect overlay hotkey, using default: F12"); + return {"KEY_F12"}; //default } std::vector res; @@ -135,39 +194,63 @@ std::vector SteamTarget::getOverlayHotkey() res.push_back(s); } } - spdlog::info("Detected Overlay hotkeys: {}", std::accumulate( - res.begin() + 1, res.end(), res[0], - [](auto acc, const auto curr) { return acc += "+" + curr; })); + if (res.empty()) { + spdlog::warn("Couldn't detect overlay hotkey, using default: F12"); + return {"KEY_F12"}; //default + } + spdlog::info("Detected screenshot hotkey(s): {}", std::accumulate( + res.begin() + 1, res.end(), res[0], + [](auto acc, const auto curr) { return acc += "+" + curr; })); return res; } +void SteamTarget::keepControllerConfig(bool keep) +{ +#ifdef _WIN32 + if (keep && !getFgWinHook.IsInstalled()) { + spdlog::debug("Hooking GetForegroudnWindow (in own process)"); + getFgWinHook.Install(&GetForegroundWindow, &keepForegroundWindow, subhook::HookFlags::HookFlag64BitOffset); + if (!getFgWinHook.IsInstalled()) { + spdlog::error("Couldn't install GetForegroundWindow hook!"); + } + } + else if (!keep && getFgWinHook.IsInstalled()) { + spdlog::debug("Un-Hooking GetForegroudnWindow (in own process)"); + getFgWinHook.Remove(); + if (getFgWinHook.IsInstalled()) { + spdlog::error("Couldn't un-install GetForegroundWindow hook!"); + } + } +#endif +} + void SteamTarget::overlayHotkeyWorkaround() { static bool pressed = false; - if (std::all_of(overlay_hotkey_.begin(), overlay_hotkey_.end(), - [](const auto &key) { - return sf::Keyboard::isKeyPressed(keymap::sfkey[key]); - })) { + if (std::ranges::all_of(overlay_hotkey_, + [](const auto &key) { + return sf::Keyboard::isKeyPressed(keymap::sfkey[key]); + })) { + spdlog::debug("Detected overlay hotkey(s)"); pressed = true; - std::for_each( - overlay_hotkey_.begin(), overlay_hotkey_.end(), [this](const auto &key) { + std::ranges::for_each(overlay_hotkey_, [this](const auto &key) { #ifdef _WIN32 - PostMessage(target_window_handle_, WM_KEYDOWN, keymap::winkey[key], 0); + PostMessage(target_window_handle_, WM_KEYDOWN, keymap::winkey[key], 0); #else #endif - }); + }); spdlog::debug("Sending Overlay KeyDown events..."); - } else if (pressed) { + } + else if (pressed) { pressed = false; - std::for_each( - overlay_hotkey_.begin(), overlay_hotkey_.end(), [this](const auto &key) { + std::ranges::for_each(overlay_hotkey_, [this](const auto &key) { #ifdef _WIN32 - PostMessage(target_window_handle_, WM_KEYUP, keymap::winkey[key], 0); + PostMessage(target_window_handle_, WM_KEYUP, keymap::winkey[key], 0); #else #endif - }); + }); spdlog::debug("Sending Overlay KeyUp events..."); } } diff --git a/GlosSITarget/SteamTarget.h b/GlosSITarget/SteamTarget.h index d46ef0f..1124ceb 100644 --- a/GlosSITarget/SteamTarget.h +++ b/GlosSITarget/SteamTarget.h @@ -16,8 +16,8 @@ limitations under the License. #pragma once #include "OverlayDetector.h" -#include "TargetWindow.h" +#include "TargetWindow.h" class SteamTarget { public: @@ -29,8 +29,16 @@ class SteamTarget { void focusWindow(WindowHandle hndl); std::wstring getSteamPath(); std::wstring getSteamUserId(); + + std::wstring steam_path_ = getSteamPath(); + std::wstring steam_user_id_ = getSteamUserId(); + std::vector getOverlayHotkey(); + std::vector getScreenshotHotkey(); + // Keep controllerConfig even is window is switched. + // On Windoze hooking "GetForeGroundWindow" is enough; + void keepControllerConfig(bool keep); /* * Run once per frame * detects steam configured overlay hotkey, and simulates key presses to window @@ -49,5 +57,6 @@ class SteamTarget { static constexpr std::wstring_view user_data_path_ = L"/userdata/"; static constexpr std::wstring_view config_file_name_ = L"/config/localconfig.vdf"; - static constexpr std::string_view hotkey_name_ = "InGameOverlayShortcutKey "; + static constexpr std::string_view overlay_hotkey_name_ = "InGameOverlayShortcutKey "; + static constexpr std::string_view screenshot_hotkey_name_ = "InGameOverlayScreenshotHotKey "; }; diff --git a/GlosSITarget/TargetWindow.cpp b/GlosSITarget/TargetWindow.cpp index ec12c51..448ef3b 100644 --- a/GlosSITarget/TargetWindow.cpp +++ b/GlosSITarget/TargetWindow.cpp @@ -15,20 +15,24 @@ limitations under the License. */ #include "TargetWindow.h" +#include "steam_sf_keymap.h" + #include #include #include +#include #ifdef _WIN32 +#include #include #include #endif static const bool DEV_MODE = false; -TargetWindow::TargetWindow(std::function on_close) - : on_close_(std::move(on_close)) +TargetWindow::TargetWindow(std::function on_close, std::vector screenshot_hotkey) + : on_close_(std::move(on_close)), screenshot_keys_(std::move(screenshot_hotkey)) { if (DEV_MODE) { window_.create(sf::VideoMode{1920, 1080}, "GlosSITarget", sf::Style::Default); @@ -82,6 +86,7 @@ void TargetWindow::update() on_close_(); } } + if (DEV_MODE) { window_.clear(sf::Color(0, 0, 0, 128)); } @@ -89,6 +94,7 @@ void TargetWindow::update() window_.clear(sf::Color::Transparent); } + screenShotWorkaround(); window_.display(); } @@ -98,6 +104,79 @@ void TargetWindow::close() on_close_(); } +void TargetWindow::screenShotWorkaround() +{ +#ifdef _WIN32 + if (std::ranges::all_of(screenshot_keys_, + [](const auto &key) { + return sf::Keyboard::isKeyPressed(keymap::sfkey[key]); + })) { + spdlog::debug("Detected screenshot hotkey(s); Taking screenshot"); + + // stolen from: https://en.sfml-dev.org/forums/index.php?topic=14323.15 + // no time to do this shit. + HDC hScreenDC = GetDC(nullptr); + HDC hMemoryDC = CreateCompatibleDC(hScreenDC); + int width = GetDeviceCaps(hScreenDC, HORZRES); + int height = GetDeviceCaps(hScreenDC, VERTRES); + HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height); + auto hOldBitmap = static_cast(SelectObject(hMemoryDC, hBitmap)); + BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY); + hBitmap = static_cast(SelectObject(hMemoryDC, hOldBitmap)); + BITMAP bm; + GetObject(hBitmap, sizeof(bm), &bm); + BITMAPINFO bmpInfo; + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = bm.bmWidth; + bmpInfo.bmiHeader.biHeight = -bm.bmHeight; + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32; + bmpInfo.bmiHeader.biCompression = BI_RGB; + bmpInfo.bmiHeader.biSizeImage = 0; + bmpInfo.bmiHeader.biClrImportant = 0; + std::vector pixel; + pixel.resize(bm.bmWidth * bm.bmHeight); + sf::Image captureImage; + captureImage.create(bm.bmWidth, bm.bmHeight, sf::Color::Black); + GetDIBits(hMemoryDC, hBitmap, 0, bm.bmHeight, pixel.data(), &bmpInfo, DIB_RGB_COLORS); + unsigned int j = 0; + for (unsigned int y = 0; y < bm.bmHeight; ++y) { + for (unsigned int x = 0; x < bm.bmWidth; ++x) { + const COLORREF this_color = pixel[j++]; + captureImage.setPixel(x, y, sf::Color(GetBValue(this_color), GetGValue(this_color), GetRValue(this_color))); + } + } + ReleaseDC(NULL, hScreenDC); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + DeleteDC(hScreenDC); + + sf::Sprite sprite; + sf::Texture texture; + texture.loadFromImage(captureImage); + sprite.setTexture(texture); + + spdlog::debug("Sending screenshot key events and rendering screen..."); + std::ranges::for_each(screenshot_keys_, [this](const auto &key) { + PostMessage(window_.getSystemHandle(), WM_KEYDOWN, keymap::winkey[key], 0); + }); + std::ranges::for_each(screenshot_keys_, [this](const auto &key) { + PostMessage(window_.getSystemHandle(), WM_KEYUP, keymap::winkey[key], 0); + }); + //actually run event loop, so steam gets notified about keys. + sf::Event event{}; + while (window_.pollEvent(event)) { + } + // steam takes screenshot on next frame, so render our screenshot and dipslay... + window_.clear(sf::Color::Black); + window_.draw(sprite); + window_.display(); + // finally, draw another transparent frame. + window_.clear(sf::Color::Transparent); + } +#endif +} + WindowHandle TargetWindow::getSystemHandle() const { return window_.getSystemHandle(); diff --git a/GlosSITarget/TargetWindow.h b/GlosSITarget/TargetWindow.h index 620badb..0057b07 100644 --- a/GlosSITarget/TargetWindow.h +++ b/GlosSITarget/TargetWindow.h @@ -29,17 +29,32 @@ using WindowHandle = int; // ??? class TargetWindow { public: - explicit TargetWindow(std::function on_close = []() {}); + explicit TargetWindow( + std::function on_close = []() {}, std::vector screenshot_hotkey = {"KEY_F12"}); void setFpsLimit(unsigned int fps_limit); void setClickThrough(bool click_through); void update(); void close(); + /* + * Run once per frame + * - detects steam configured screenshot hotkey + * - takes actual screenshot + * - renders it to window + * - simulates screenshot keys + * - Wait a few millis... + * (- steam takes screenshot) + * - return to normal + * + */ + void screenShotWorkaround(); + WindowHandle getSystemHandle() const; private: const std::function on_close_; sf::RenderWindow window_; + std::vector screenshot_keys_; }; diff --git a/GlosSITarget/main.cpp b/GlosSITarget/main.cpp index 4f03975..99bd64b 100644 --- a/GlosSITarget/main.cpp +++ b/GlosSITarget/main.cpp @@ -24,19 +24,21 @@ limitations under the License. #include #include -//int CALLBACK WinMain( -// _In_ HINSTANCE hInstance, -// _In_ HINSTANCE hPrevInstance, -// _In_ LPSTR lpCmdLine, -// _In_ int nCmdShow -//) -//{ -// SteamTarget target(__argc, __argv); -// target.init(); -// return SteamTarget::exec(); -//} - +#define CONSOLE +#ifdef _WIN32 +#ifdef CONSOLE +int main(int argc, char *argv[]) +#else +int CALLBACK WinMain( + _In_ HINSTANCE hInstance, + _In_ HINSTANCE hPrevInstance, + _In_ LPSTR lpCmdLine, + _In_ int nCmdShow +) +#endif +#else int main(int argc, char *argv[]) +#endif { const auto console_sink = std::make_shared(); console_sink->set_level(spdlog::level::trace); @@ -51,8 +53,11 @@ int main(int argc, char *argv[]) logger->set_level(spdlog::level::trace); logger->flush_on(spdlog::level::info); spdlog::set_default_logger(logger); - +#ifdef _WIN32 + SteamTarget target(__argc, __argv); +#else SteamTarget target(argc, argv); +#endif const auto exit = target.run(); spdlog::shutdown(); return exit; diff --git a/GlosSITarget/steam_sf_keymap.h b/GlosSITarget/steam_sf_keymap.h index 8f6728d..4984037 100644 --- a/GlosSITarget/steam_sf_keymap.h +++ b/GlosSITarget/steam_sf_keymap.h @@ -5,11 +5,13 @@ #define QQ(x) #x #define QUOTE(x) QQ(x) -#define KEYCONVSF(KEY) \ - { QUOTE(KEY), sf::Keyboard::Key::KEY } +#define KEYCONVSF(KEY) \ + { \ + QUOTE(KEY), sf::Keyboard::Key::KEY \ + } namespace keymap { -std::unordered_map sfkey = { +static std::unordered_map sfkey = { {"Shift", sf::Keyboard::Key::LShift}, {"Alt", sf::Keyboard::Key::LAlt}, {"Ctrl", sf::Keyboard::Key::LControl}, @@ -56,20 +58,31 @@ std::unordered_map sfkey = { KEYCONVSF(W), KEYCONVSF(X), KEYCONVSF(Y), - KEYCONVSF(Z) -}; + KEYCONVSF(Z), + {"KEY_F1", sf::Keyboard::Key::F1}, + {"KEY_F2", sf::Keyboard::Key::F2}, + {"KEY_F3", sf::Keyboard::Key::F3}, + {"KEY_F4", sf::Keyboard::Key::F4}, + {"KEY_F5", sf::Keyboard::Key::F5}, + {"KEY_F6", sf::Keyboard::Key::F6}, + {"KEY_F7", sf::Keyboard::Key::F7}, + {"KEY_F8", sf::Keyboard::Key::F8}, + {"KEY_F9", sf::Keyboard::Key::F9}, + {"KEY_F10", sf::Keyboard::Key::F10}, + {"KEY_F11", sf::Keyboard::Key::F11}, + {"KEY_F12", sf::Keyboard::Key::F12}}; #ifdef _WIN32 #define NOMINMAX #include //yep.. there are smarter ways to tho this... -std::unordered_map winkey = { +static std::unordered_map winkey = { {"Shift", VK_SHIFT}, {"Alt", VK_MENU}, {"Ctrl", VK_CONTROL}, {"Del", VK_DELETE}, - {"Ins",VK_INSERT}, - {"Home",VK_HOME}, + {"Ins", VK_INSERT}, + {"Home", VK_HOME}, {"Space", VK_SPACE}, {"Backspace", VK_BACK}, {"Enter", VK_RETURN}, @@ -110,8 +123,18 @@ std::unordered_map winkey = { {"KEY_W", 0x57}, {"KEY_X", 0x58}, {"KEY_Y", 0x59}, - {"KEY_Z", 0x5A} - -}; + {"KEY_Z", 0x5A}, + {"KEY_F1", VK_F1}, + {"KEY_F2", VK_F2}, + {"KEY_F3", VK_F3}, + {"KEY_F4", VK_F4}, + {"KEY_F5", VK_F5}, + {"KEY_F6", VK_F6}, + {"KEY_F7", VK_F7}, + {"KEY_F8", VK_F8}, + {"KEY_F9", VK_F9}, + {"KEY_F10", VK_F10}, + {"KEY_F11", VK_F11}, + {"KEY_F12", VK_F12}}; #endif -} +} // namespace keymap