v2
Peter Repukat 6 years ago
parent acd2bffcb3
commit c467e5aa9b

@ -1,543 +0,0 @@
/*
Copyright 2018 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "SteamTargetRenderer.h"
std::atomic<bool> SteamTargetRenderer::overlayOpen = false;
HHOOK SteamTargetRenderer::hook = nullptr;
std::atomic<bool> SteamTargetRenderer::bHookSteam = false;
SteamTargetRenderer::SteamTargetRenderer(int& argc, char** argv) : QApplication(argc, argv)
{
getSteamOverlay();
loadLogo();
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(ConsoleCtrlCallback), true);
if (this->arguments().size() == 1)
{
std::cerr << "Target configuration file must be specified!" << std::endl;
MessageBoxW(NULL, L"Target configuration file must be specified!", L"GloSC-SteamTarget", MB_OK);
QTimer::singleShot(0, this, []()
{
QApplication::exit(1);
}); //call after ctor
} else {
QSettings settings(this->arguments().at(1), QSettings::IniFormat);
settings.beginGroup("BaseConf");
const QStringList childKeys = settings.childKeys();
for (auto &childkey : childKeys)
{
if (childkey == "bDrawDebugEdges")
{
bDrawDebugEdges = settings.value(childkey).toBool();
}
else if (childkey == "bEnableOverlay") {
bDrawOverlay = settings.value(childkey).toBool();
}
else if (childkey == "bEnableControllers") {
bEnableControllers = settings.value(childkey).toBool();
}
else if (childkey == "bHookSteam") {
bHookSteam = settings.value(childkey).toBool();
}
else if (childkey == "bUseDesktopConfig") {
bUseDesktopConfig = settings.value(childkey).toBool();
}
}
settings.endGroup();
#ifndef NDEBUG
bDrawDebugEdges = true;
#endif // NDEBUG
sfCshape = sf::CircleShape(100.f);
sfCshape.setFillColor(sf::Color(128, 128, 128, 128));
sfCshape.setOrigin(sf::Vector2f(100, 100));
sf::VideoMode mode = sf::VideoMode::getDesktopMode();
sfWindow.create(sf::VideoMode(mode.width - 16, mode.height - 32), "GloSC_OverlayWindow"); //Window is too large ; always 16 and 32 pixels? - sf::Style::None breaks transparency!
sfWindow.setFramerateLimit(iRefreshRate);
sfWindow.setPosition(sf::Vector2i(0, 0));
makeSfWindowTransparent(sfWindow);
sfWindow.setActive(false);
consoleHwnd = GetConsoleWindow(); //We need a console for a dirty hack to make sure we stay in game bindings
//QT Windows cause trouble with the overlay, so we cannot use them
#ifdef NDEBUG
ShowWindow(consoleHwnd, SW_HIDE);
#endif // NDEBUG
if (bEnableControllers)
controllerThread.run();
QTimer::singleShot(2000, this, &SteamTargetRenderer::launchApp); // lets steam do its thing
if (hmodGameOverlayRenderer != nullptr)
{
//Hook MessageQueue to detect if overlay gets opened / closed
//Steam Posts a Message with 0x14FA / 0x14F7 when the overlay gets opened / closed
hook = SetWindowsHookEx(WH_GETMESSAGE, HookCallback, nullptr, GetCurrentThreadId());
}
if (bUseDesktopConfig)
{
bHookSteam = false;
QTimer::singleShot(1000, this, []()
{
HWND taskbar = FindWindow(L"Shell_TrayWnd", nullptr);
SetFocus(taskbar);
SetForegroundWindow(taskbar);
});
}
}
}
SteamTargetRenderer::~SteamTargetRenderer()
{
if (hmodGameOverlayRenderer != nullptr)
{
UnhookWindowsHookEx(hook);
}
renderThread.join();
if (controllerThread.isRunning())
controllerThread.stop();
}
void SteamTargetRenderer::run()
{
renderThread = std::thread(&SteamTargetRenderer::RunSfWindowLoop, this);
}
void SteamTargetRenderer::stop()
{
bRunLoop = false;
unhookBindings();
QApplication::exit(0);
}
void SteamTargetRenderer::RunSfWindowLoop()
{
if (!bRunLoop)
return;
sfWindow.setActive(true);
bool hasJustLaunched = true;
if (bDrawOverlay)
SetWindowPos(sfWindow.getSystemHandle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS);
else
{
ShowWindow(sfWindow.getSystemHandle(), SW_HIDE);
sfWindow.setFramerateLimit(1); //Window is not shown anyway,
ShowWindow(consoleHwnd, SW_SHOW); //Show the console window so the user sees SOMETHING
}
while (sfWindow.isOpen() && bRunLoop)
{
sf::Event event;
while (sfWindow.pollEvent(event))
{
if (event.type == sf::Event::Closed)
sfWindow.close();
}
sfWindow.clear(sf::Color::Transparent);
if (bDrawDebugEdges)
drawDebugEdges();
//we inject and hook here to spare IPC and let the dll grab the steam appID of the launched process when the config switches (config switches w/ focus)
if (hasJustLaunched)
{
if (bHookSteam)
hookBindings(); //cleanup - unhooking / unloading of dll is managed by the GloSC gamelauncher rather than here
hasJustLaunched = false;
}
//Window focus trickery
if (hmodGameOverlayRenderer != nullptr)
{
if (overlayOpen)
{
if (!bNeedFocusSwitch)
{
bNeedFocusSwitch = true;
hwForeGroundWindow = GetForegroundWindow();
std::cout << "Saving current ForegorundWindow HWND: " << hwForeGroundWindow << std::endl;
std::cout << "Activating OverlayWindow" << std::endl;
SetWindowLong(sfWindow.getSystemHandle(), GWL_EXSTYLE, WS_EX_LAYERED); //make overlay window clickable
//Actually activate the overlaywindow
stealFocus(sfWindow.getSystemHandle());
//Move the mouse cursor inside the overlaywindow
//this is neccessary because steam doesn't want to switch to big picture bindings if mouse isn't inside
moveMouseIntoOverlay();
}
sfWindow.draw(backgroundSprite);
} else {
if (bNeedFocusSwitch)
{
std::cout << "Deactivating OverlayWindow" << std::endl;
//make overlaywindow clickthrough - WS_EX_TRANSPARENT - again
SetWindowLong(sfWindow.getSystemHandle(), GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT);
std::cout << "Switching to previously focused window" << std::endl;
//switch back the the previosly focused window
stealFocus(hwForeGroundWindow);
bNeedFocusSwitch = false;
}
}
}
sfWindow.display();
}
stop();
}
void SteamTargetRenderer::getSteamOverlay()
{
hmodGameOverlayRenderer = GetModuleHandle(overlayModuleName);
if (hmodGameOverlayRenderer != nullptr)
{
std::cout << overlayModuleName << " found; Module at: 0x" << hmodGameOverlayRenderer << std::endl;
}
}
void SteamTargetRenderer::makeSfWindowTransparent(sf::RenderWindow & window)
{
HWND hwnd = window.getSystemHandle();
SetWindowLong(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP &~WS_CAPTION);
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST);
MARGINS margins;
margins.cxLeftWidth = -1;
DwmExtendFrameIntoClientArea(hwnd, &margins);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
window.clear(sf::Color::Transparent);
window.display();
}
void SteamTargetRenderer::drawDebugEdges()
{
sfCshape.setPosition(sf::Vector2f(-25, -25));
sfWindow.draw(sfCshape);
sfCshape.setPosition(sf::Vector2f(sfWindow.getSize().x + 25, -25));
sfWindow.draw(sfCshape);
sfCshape.setPosition(sf::Vector2f(-25, sfWindow.getSize().y));
sfWindow.draw(sfCshape);
sfCshape.setPosition(sf::Vector2f(sfWindow.getSize().x, sfWindow.getSize().y));
sfWindow.draw(sfCshape);
}
void SteamTargetRenderer::hookBindings() const
{
std::cout << "Hooking Steam..." << std::endl;
QString dir = QDir::toNativeSeparators(QCoreApplication::applicationDirPath());
dir = dir.mid(0, dir.lastIndexOf("\\"));
QProcess proc;
proc.setNativeArguments(" --inject ");
proc.setWorkingDirectory(dir);
proc.start("./Injector.exe", QIODevice::ReadOnly);
proc.waitForStarted();
proc.waitForFinished();
if (QString::fromStdString(proc.readAll().toStdString()).contains("Inject success!")) //if we have injected (and patched the function)
{
std::cout << "Successfully hooked Steam!" << std::endl;
//tell the GloSC_GameLauncher that we have hooked steam
//it will deal with checking if the target is still alive and unload the dll / unhook then
// - ensures unloading / unhooking even if this process crashes or gets unexpectedly killed
QSharedMemory sharedMemInstance("GloSC_GameLauncher");
if (!sharedMemInstance.create(1024) && sharedMemInstance.error() == QSharedMemory::AlreadyExists)
{
QBuffer buffer;
QDataStream dataStream(&buffer);
QStringList stringList;
sharedMemInstance.attach();
sharedMemInstance.lock();
buffer.setData(static_cast<const char*>(sharedMemInstance.constData()), sharedMemInstance.size());
buffer.open(QBuffer::ReadOnly);
dataStream >> stringList;
buffer.close();
int i = stringList.indexOf(IsSteamHooked) + 1;
stringList.replace(i, "1");
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
out << stringList;
int size = buffer.size();
char *to = static_cast<char*>(sharedMemInstance.data());
const char *from = buffer.data().data();
memcpy(to, from, qMin(sharedMemInstance.size(), size));
buffer.close();
sharedMemInstance.unlock();
sharedMemInstance.detach();
}
} else {
std::cout << "Hooking Steam failed!" << std::endl;
MessageBoxW(NULL, L"Hooking Steam failed!", L"GloSC-SteamTarget", MB_OK);
}
}
void SteamTargetRenderer::loadLogo()
{
HRSRC rsrcData = FindResource(NULL, L"ICOPNG", RT_RCDATA);
DWORD rsrcDataSize = SizeofResource(NULL, rsrcData);
HGLOBAL grsrcData = LoadResource(NULL, rsrcData);
LPVOID firstByte = LockResource(grsrcData);
spriteTexture = std::make_unique<sf::Texture>();
spriteTexture->loadFromMemory(firstByte, rsrcDataSize);
backgroundSprite.setTexture(*spriteTexture);
backgroundSprite.setOrigin(sf::Vector2f(spriteTexture->getSize().x / 2.f, spriteTexture->getSize().y / 2));
sf::VideoMode winSize = sf::VideoMode::getDesktopMode();
backgroundSprite.setPosition(sf::Vector2f(winSize.width / 2.f, winSize.height / 2.f));
}
void SteamTargetRenderer::moveMouseIntoOverlay()
{
RECT rect = { 0 };
if (GetWindowRect(sfWindow.getSystemHandle(), &rect))
{
POINT cursorPos = { 0 };
GetCursorPos(&cursorPos);
if (PtInRect(&rect, cursorPos))
{
SetCursorPos(cursorPos.x+1, cursorPos.y);
}
else
{
SetCursorPos(rect.left + 16, rect.top + 16);
}
}
}
//WinHook Callback to check if the overlay is opened/closed
LRESULT WINAPI SteamTargetRenderer::HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
PMSG msg = reinterpret_cast<PMSG>(lParam);
std::cout << "DEBUG: " << "message: " << msg->message << std::endl;
if (msg->message == 0x14FA || msg->message == 0x14FF) //Posted when the overlay gets opened
{
overlayOpen = true;
std::cout << "Overlay Opened!\n";
}
else if (msg->message == 0x14F7 || msg->message == 0x14FD || msg->message == 512 || msg->message == 0x2a3)
{
overlayOpen = false;
std::cout << "Overlay closed!\n";
}
}
return CallNextHookEx(hook, nCode, wParam, lParam);
}
void SteamTargetRenderer::unhookBindings()
{
if (bHookSteam)
{
QString dir = QDir::toNativeSeparators(QCoreApplication::applicationDirPath());
dir = dir.mid(0, dir.lastIndexOf("\\"));
QProcess proc;
proc.setNativeArguments(" --eject ");
proc.setWorkingDirectory(dir);
proc.start("./Injector.exe", QIODevice::ReadOnly);
proc.waitForStarted();
proc.waitForFinished();
}
}
BOOL SteamTargetRenderer::ConsoleCtrlCallback(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_CLOSE_EVENT || dwCtrlType == CTRL_BREAK_EVENT || dwCtrlType == CTRL_C_EVENT)
{
unhookBindings();
return true;
}
return false;
}
void SteamTargetRenderer::stealFocus(HWND hwnd)
{
DWORD dwCurrentThread = GetCurrentThreadId();
DWORD dwFGThread = GetWindowThreadProcessId(GetForegroundWindow(), nullptr);
AttachThreadInput(dwCurrentThread, dwFGThread, TRUE);
// Possible actions you may wan to bring the window into focus.
SetForegroundWindow(hwnd);
SetCapture(hwnd);
SetFocus(hwnd);
SetActiveWindow(hwnd);
EnableWindow(hwnd, TRUE);
AttachThreadInput(dwCurrentThread, dwFGThread, FALSE);
sf::Clock clock;
while (!SetForegroundWindow(hwnd) && clock.getElapsedTime().asMilliseconds() < 1000) //try to forcefully set foreground window
{
Sleep(1);
}
}
void SteamTargetRenderer::launchApp()
{
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
bool launchGame = false;
bool closeWhenDone = false;
QString type = "Win32";
QString path = "";
QString args;
QSettings settings(this->arguments().at(1), QSettings::IniFormat);
settings.beginGroup("LaunchGame");
const QStringList childKeys = settings.childKeys();
for (auto &childkey : childKeys)
{
if (childkey == "bLaunchGame")
launchGame = settings.value(childkey).toBool();
else if (childkey == "Type")
type = settings.value(childkey).toString();
else if (childkey == "Path")
path = settings.value(childkey).toString();
else if (childkey == "Args")
args = settings.value(childkey).toString();
else if (childkey == "bCloseWhenDone")
closeWhenDone = settings.value("bCloseWhenDone").toBool();
}
settings.endGroup();
if (launchGame)
{
QSharedMemory sharedMemInstance("GloSC_GameLauncher");
if (!sharedMemInstance.create(1024) && sharedMemInstance.error() == QSharedMemory::AlreadyExists)
{
if (type == "UWP")
{
HWND hwnd = sfWindow.getSystemHandle();
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS);
QTimer::singleShot(5000, [hwnd]()
{
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS);
});
}
QBuffer buffer;
QDataStream dataStream(&buffer);
QStringList stringList;
sharedMemInstance.attach();
sharedMemInstance.lock();
buffer.setData(static_cast<const char*>(sharedMemInstance.constData()), sharedMemInstance.size());
buffer.open(QBuffer::ReadOnly);
dataStream >> stringList;
buffer.close();
int lgt_index = stringList.indexOf(LaunchGame);
stringList.replace(lgt_index + 1, type);
stringList.replace(lgt_index + 2, path);
stringList.replace(lgt_index + 3, args);
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
out << stringList;
int size = buffer.size();
char *to = static_cast<char*>(sharedMemInstance.data());
const char *from = buffer.data().data();
memcpy(to, from, qMin(sharedMemInstance.size(), size));
buffer.close();
sharedMemInstance.unlock();
sharedMemInstance.detach();
if (closeWhenDone)
{
updateTimer.setInterval(1111);
connect(&updateTimer, SIGNAL(timeout()), this, SLOT(checkSharedMem()));
updateTimer.start();
}
}
}
}
void SteamTargetRenderer::checkSharedMem()
{
QSharedMemory sharedMemInstance("GloSC_GameLauncher");
if (!sharedMemInstance.create(1024) && sharedMemInstance.error() == QSharedMemory::AlreadyExists)
{
QBuffer buffer;
QDataStream in(&buffer);
QStringList stringList;
sharedMemInstance.attach();
sharedMemInstance.lock();
buffer.setData(static_cast<const char*>(sharedMemInstance.constData()), sharedMemInstance.size());
buffer.open(QBuffer::ReadOnly);
in >> stringList;
buffer.close();
sharedMemInstance.unlock();
sharedMemInstance.detach();
int close_index = stringList.indexOf(LaunchedProcessFinished) + 1;
if (close_index > 0 && stringList.at(close_index).toInt() == 1)
bRunLoop = false;
}
}

@ -1,128 +0,0 @@
/*
Copyright 2018 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <Windows.h>
#include <dwmapi.h>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <iostream>
#include <thread>
#include <QApplication>
#include <QTimer>
#include <QProcess>
#include <QBuffer>
#include <QDatastream>
#include <QSharedmemory>
#include <QSettings>
#include <QCoreApplication>
#include <QDir>
#include <psapi.h>
#include "VirtualControllerThread.h"
#include <atomic>
class SteamTargetRenderer : public QApplication
{
Q_OBJECT
public:
SteamTargetRenderer(int& argc, char** argv);
~SteamTargetRenderer();
void run();
private:
void stop();
void getSteamOverlay();
void RunSfWindowLoop();
static void makeSfWindowTransparent(sf::RenderWindow& window);
void drawDebugEdges();
void hookBindings() const;
void loadLogo();
void moveMouseIntoOverlay();
static LRESULT WINAPI HookCallback(int nCode, WPARAM wParam, LPARAM lParam);
static void unhookBindings();
static BOOL WINAPI ConsoleCtrlCallback(_In_ DWORD dwCtrlType);
static void stealFocus(HWND hwnd);
std::atomic<bool> bRunLoop = true;
bool bUseDesktopConfig = false;
bool bDrawDebugEdges = false;
bool bDrawOverlay = true;
bool bVsync = false;
int iRefreshRate = 30;
sf::CircleShape sfCshape;
sf::RenderWindow sfWindow;
std::thread renderThread;
HWND consoleHwnd;
HMODULE hmodGameOverlayRenderer = nullptr;
#ifdef _AMD64_
WCHAR* overlayModuleName = L"GameOverlayRenderer64.dll";
#else
WCHAR* overlayModuleName = L"GameOverlayRenderer.dll";
#endif
static std::atomic<bool> overlayOpen;
static HHOOK hook;
HWND hwForeGroundWindow = nullptr;
bool bNeedFocusSwitch = false;
VirtualControllerThread controllerThread;
bool bEnableControllers = true;
static std::atomic<bool> bHookSteam;
QTimer updateTimer;
std::unique_ptr<sf::Texture> spriteTexture;
sf::Sprite backgroundSprite;
const QString LaunchGame = "LaunchGame";
const QString LaunchedProcessFinished = "LaunchedProcessFinished";
const QString IsSteamHooked = "IsSteamHooked";
const QStringList defaultSharedMemData = QStringList()
<< LaunchGame
<< ""
<< ""
<< LaunchedProcessFinished
<< "0"
<< IsSteamHooked
<< "-1";
private slots:
void launchApp();
void checkSharedMem();
};
Loading…
Cancel
Save