Compare commits

...

154 Commits

Author SHA1 Message Date
Peter Repukat 2b41208e6f
Merge pull request #239 from Alia5/develop
Develop
5 months ago
Peter Repukat 0ebfd1e973 Update release deps 5 months ago
Peter Repukat 73226d3c62 Add api to log from SteamTweaks 5 months ago
Peter Repukat f2f2144017 Remove GlosSITweaks uninstall from watchdog and let JS handle uninstall itself 5 months ago
Peter Repukat d303f2be79 Add /running endpoint 5 months ago
Peter Repukat 954d953f4b HttpEndpoints: Add endpoint to list avail endpoints + data hints 5 months ago
Peter Repukat b359053771 Add global mode initialization hint 5 months ago
Peter Repukat de2bd57979 Alter CEF remote debugging alert wording 5 months ago
Peter Repukat 56c4003810 Refactor: HttpServer endpoints 5 months ago
Peter Repukat 77c7b639ab Add setting for making window opaque when SteamOverlay is open 5 months ago
Peter Repukat 2ddb3f1bb0 SteamTweaks: add build:copy script 5 months ago
Peter Repukat 906e40b753 Add controller update rate setting 5 months ago
Peter Repukat 6bebae6c7b GlobalMode: inject tweaks early if globalmode && global mode BPM 5 months ago
Peter Repukat e9a02d046b Fix minimizing gamepadUI in global mode 5 months ago
Peter Repukat 545fc6af1c Settings: Fix minimizeGamepadUI Setting / Json.dump 5 months ago
Peter Repukat 8015853d1b SteamTarget: Start http server earlier 5 months ago
Peter Repukat fe91680fd1 CEFInject: Log found tweak files 5 months ago
Peter Repukat fe21af6787 Fix multiple WSASTartup/Cleanup calls (Improve Tweak inject perf) 5 months ago
Peter Repukat 88856be11a Add minimize SteamGamepadUI feature (untested) 5 months ago
Peter Repukat b9cb0414a7 CefInject: Steam table title matching by substring instead of startsWith 5 months ago
Peter Repukat ffdb227d2b GlosSITweaks: make linter happy 5 months ago
Peter Repukat 50a4ea814d Add auto controller count detection and make it default
Note: Existing users have to manually adjust this setting
5 months ago
Peter Repukat 0745e3684d AppLauncher: Fix potential crash 5 months ago
James Lamine 6c0a6145ec Allow launching win32 apps with admin permissions 5 months ago
Peter Repukat 9c98b28c72 GlosSITarget: add disabled CEF debugging alert 5 months ago
Peter Repukat 34b3f5322d GlosSIConfig: enable Steam CEF debugging by default 5 months ago
Peter Repukat 89c90a2ea0 update appveyor version helper 5 months ago
Peter Repukat bf8caca40e Update build and bundle scripts 5 months ago
Peter Repukat cb6cbdeac6 More work on CEFInject / SteamTweaks 5 months ago
Peter Repukat 4c847543fc CEFInject: SteamTweaks: uninstallTweaks: option to uninstall regardless of stored-state
TODO: use in watchDog
5 months ago
Peter Repukat 4d96ed94a1 Throw in some compiler optimizations (SSE2/fast floats) 5 months ago
Peter Repukat 08ffcad760 Fix GlosSIConfig crashing on launch 5 months ago
Peter Repukat e5368744f1 CEFInject: Add (WIP) GlosSITweaks injection system 5 months ago
Peter Repukat 0e356801e7 common: utils: add util to get executableDir 5 months ago
Peter Repukat efe12dc333 SteamTweaks: GlosSITweaks: Fix uninstall 5 months ago
Peter Repukat aa3f513e13 Add warning dialog about not properly setup Global mode 5 months ago
Peter Repukat 54fb29a79d Rename "standalone mode" -> "global mode" 5 months ago
Peter Repukat b86d548fc2 Support delayed target initialization and delay init if XBox controller rebinding is disabled in Steam 5 months ago
Peter Repukat b0b67f8c9d SteamTweaks: Move Overlay-Tweaks that run SteamSharedsContext to subfolder 5 months ago
Peter Repukat b35f422a54 SteamTweaks: update .gitignore 5 months ago
Peter Repukat 226b8368b1 Stop trying to build "common"-project files on their own 5 months ago
Peter Repukat 18241c1fc4 Update ViGEmCLient: DS4 dispose deadlock fix 5 months ago
Peter Repukat b26fd9faf5 Update SteamTweaks 5 months ago
Peter Repukat a07faa926c CEFInject: Kill manual scoping of injection and let rollup handle that crap 5 months ago
Peter Repukat d60def51d5 Add/Start SteamTweaks TS/JS package 5 months ago
Peter Repukat 766cac3969 CEFInjectLib: make wrapper function async 5 months ago
Peter Repukat 816b89cce6 SteamTarget: Support disabling standalone mode via cli-switch 5 months ago
Peter Repukat 00127dcd2b HttpLib/SteamUtils: Endpoint to get Steam User config as JSON 5 months ago
Peter Repukat e6db328273 Settings: Fix cli-switches not overriding .json config 5 months ago
Peter Repukat cf23edafdb CEFInject: Scope js injection 5 months ago
Peter Repukat f68e3070d0 Add (Partial) VSCode cpp config 5 months ago
Peter Repukat d89c84478f Add some fancy tail replacement scripts for viewing logs in craptastic visual studio 5 months ago
Peter Repukat 531857003e Settings: Fix crash if "launcherProcesses" is empty 🙄 5 months ago
Peter Repukat baa7aec0c8 Settings: don't log default value if setting is not in json 5 months ago
Peter Repukat 04653b2fb7 Cleanup 5 months ago
Peter Repukat 2a8df5fdc3 Update GlosSIWindow default refresh rate
Dont just use screen rerfresh-rate anymore (as it's a waste with high refresh monitors) and scale it down a bit
5 months ago
Peter Repukat da0b99d679 Increase time regular Steam overlay can be opened before opening GlosSIOverlay: 1s -> 2.5s 5 months ago
Peter Repukat f7df3deb7d Cleanup 5 months ago
Peter Repukat 738803fa2e Cleanup: consolidate utils and common code 5 months ago
Peter Repukat b33b2a0691 CEFInject: Return value of evaluated js 5 months ago
Peter Repukat b69bd4abdd GlosSIConfig: Add some UI for experimental standalone mode setup 5 months ago
Peter Repukat 70187090e7 GlosSITarget: Update experimental "stand-alone"-mode 5 months ago
Peter Repukat 10b65a1a9b WIP: Experimental standalone (not launched via Steam (steam needs to be open!) mode 5 months ago
Peter Repukat 2941b4548e Add CEFInject lib and add PoC 5 months ago
Peter Repukat 4b57c6cb94 GlosSIConfig: Default HideAltTab to true 5 months ago
Peter Repukat 7a01ceede1 GlosSITarget: default hideAltTab to true 5 months ago
Peter Repukat ba777193a5
Update Readme.md 9 months ago
Peter Repukat db403cd6e7
Update Readme.md 9 months ago
Peter Repukat 69f11f8e28
Update Readme.md 9 months ago
Peter Repukat beb6bbadc5
Update Readme.md 9 months ago
Peter Repukat 1571cc8abd
Merge pull request #232 from AdonisCodes/main
Added Steambase URL on line 127
10 months ago
Adonis 66f3c26ec7
Added Steambase URL on line 127 10 months ago
Peter Repukat a4d2bd81ad Merge branch 'develop' 1 year ago
Peter Repukat b76b6ee595 Option to disable GlosSIs additional overlay 1 year ago
Peter Repukat 2ac3325c5b Store SteamPath/SteamUserId in Settings and use as fallback 1 year ago
Peter Repukat 4e4c85885c Hack together shitty method to retrieve original unhooking bytes via GlosSIConfig
Here's hoping that this will prevent access-violation issues for some users
1 year ago
Peter Repukat 2824329687 Add Configurator option to exclude target-window from windowlist 1 year ago
Peter Repukat 924a7ecba7 Fix shortcut names not editable 1 year ago
Peter Repukat efdcc8f881 Uncamelcasify EGS game names 1 year ago
Peter Repukat 9b2c57b0db Clone submodules via https instead of ssh 1 year ago
Peter Repukat b4351d2743 It's that time of the year again 1 year ago
Marocco2 2594512c12 Added toggle inside ImGui 1 year ago
Marocco2 41c385e551 Added proper setting value (default: off) 1 year ago
Marocco2 be4c8fb209 added comment 1 year ago
Marocco2 4fc3ff8e93 typo 1 year ago
Marocco2 c0303c88d0 add `WS_EX_TOOLWINDOW` in extended windows styles for GlosSI-Target
https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles

Goal is to remove GlosSI-Target window in Alt-Tab listing as it will induce issues with Steam Link and not particularly intuitive when switching from and to a game.

Currently untested, PR needed only for building a PoC of it and checking for possible issues
1 year ago
Peter Repukat 85ab663804 GlosSITarget: Close existing target on launch 1 year ago
Peter Repukat be8e206683
Update bug_report.md 1 year ago
Peter Repukat 51180c6306
Update other.md 1 year ago
Peter Repukat 87330a521f
Update other.md 1 year ago
Peter Repukat 3c4584d0a8
Update bug_report.md 1 year ago
Peter Repukat ebd44ba53a
Update bug_report.md 1 year ago
Peter Repukat fb641048c9
Merge pull request #195 from Alia5/develop
Develop
2 years ago
Peter Repukat 128e7f4a62 GlosSITarget: add -window cli switch 2 years ago
Peter Repukat 9e170d24bd Fix typo 2 years ago
Peter Repukat 5176dab9be GlosSIConfig: Open Steam controller configurator from GlosSI 2 years ago
Peter Repukat a8235ca9c9 GlosSITarget: Fix UI Scaling 2 years ago
Peter Repukat 9232eead0a
Merge pull request #192 from Alia5/develop
Develop
2 years ago
Peter Repukat 6ee8317f77 GlosSIConfig: User configurable steamgridDB API key
Fuck this!
2 years ago
Peter Repukat 45bd68e595 GlosSITarget: Fix too low screen scale comparison 2 years ago
Peter Repukat 2fbf3b444d GlosSITarget: Fix not applying screen scale log level 2 years ago
Peter Repukat 78a9bbdc9d Merge branch 'develop' 2 years ago
Peter Repukat 5528dfb733 Add configurable Launcher options / Launcher ProcessList 2 years ago
Peter Repukat 63fdab1685 GlosSIConfig: Reload grid images after steamgrid is done 2 years ago
Peter Repukat 3df6cf510a AppLauncher: Fix Win32 launch args 2 years ago
Peter Repukat 20e86ce8e4 GlosSIConfig: Fix steam shortcuts updating when editing non added shortcuts 2 years ago
Peter Repukat 6e69b6ec1c GlosSIConfig: Integrate SteamGrid 2 years ago
Peter Repukat 93026dfb75 GlosSIConfig: Fix Win32 Apps Icons 2 years ago
Peter Repukat 616650c04c GlosSIConfig: Show grid images on SHortcutProps background 2 years ago
Peter Repukat e5d7dd4432 GlosSIConfig: Show Steam grid background images on shortcutCards 2 years ago
Peter Repukat 3458f907ca GlosSIConfig: Show non-Steam-Shortcut AppIDs in Debug builds 2 years ago
Peter Repukat c4f2382928 refactor/fix stupid qml shit 2 years ago
Peter Repukat be48ffee40 Update SteamShortcuts on target update 2 years ago
Peter Repukat 01efd8d02d GlosSIConfig: QML: change shortcut Icon 2 years ago
Peter Repukat b568418891 GlosSIConfig: Find and launch EGS games 2 years ago
Peter Repukat 929abfe5f2 GlosSITarget: Parse and impl "killEGS" 2 years ago
Peter Repukat e56a53a0b7 GlosSIConfig: auto-migrate (default) settings 2 years ago
Peter Repukat 1a22423f56 GlosSIConfig: Add "killEGS" settings 2 years ago
Peter Repukat cafb4e85a8 GlosSITarget: Add API to insert launched-pids 2 years ago
Peter Repukat 18a960abab GlosSITarget: parse and use "ignoreEGS" setting 2 years ago
Peter Repukat fc03e91c2b GlosSIConfig: setting to ignore egs-launcher procs 2 years ago
Peter Repukat 96d8f24c93 GlosSITarget: More EGS Hacks/workarounds
CloseOnExit: Ignore EpicGamesLauncher.exe, EpicWebHelper.exe in detection if GlosSITarget should quit
2 years ago
Peter Repukat 138dd98c59 GlosSITarget: Log process names in addition to PIDs 2 years ago
Peter Repukat 11e3029915 GlosSITarget: Also find nested child PIDs 2 years ago
Peter Repukat eee7390547 GlosSITarget: AppLauncher: Add processInfo/killer overlay element 2 years ago
Peter Repukat 51c2268dc0 GlosSIUtil: Add KIllProcess 2 years ago
Peter Repukat d22f44d37d GlosSITarget: Add somewhat of epic games launcher process detection workaround 🤨 2 years ago
Peter Repukat bf89cb8ddc GlosSITarget: Cleanup: util 2 years ago
Peter Repukat e3a55d9b1c Watchdog: Fix release mode include paths 2 years ago
Peter Repukat ed0b43a651 GlosSIConfig: Change/Update wording on "closeOnExit"/"waitForChildren"
Options now works different as watchdog also kills launched processes
2 years ago
Peter Repukat d9c45a4a7b Watchdog: retry fetching settings a few times 2 years ago
Peter Repukat 7b8eb7391b Watchdog: Log settings retrieval 2 years ago
Peter Repukat 496b8fc146 Watchdog: Include and retrieve settings from Target; only kill launched processes if "closeOnExit" 2 years ago
Peter Repukat 14d3e45379 GlosSSITarget: Settings: parse missing but unused common values (icon/name/version) 2 years ago
Peter Repukat 1b2b8b759a GlosSITarget: Settings: move extendedLogging and cli-opts to common-struct 2 years ago
Peter Repukat 31ce36095f HttpServer: endpoint to query shortcut settings 2 years ago
Peter Repukat a1c1a7d141 GlosSITarget: Refactor Settings.h
- Overload parse function to also accept nlohmann::json object
- Function to convert settings to nlohmann::json
2 years ago
Peter Repukat 73bbffd6cf Watchdog: Kill launched processes after GlosSITarget closed 2 years ago
Peter Repukat 655d3b0853 Watchdog: Fix unloading if no GlosSIWindow was found 2 years ago
Peter Repukat 3903f38c0e GlosSITarget: fix reading config-files with spaces 2 years ago
Peter Repukat febebafc25 GlosSITarget: Fix httpserver pids entries doubled 2 years ago
Peter Repukat e879d0ba5c GlosSITarget: Fix logging port 2 years ago
Peter Repukat c9c6a32432 GlosSITarget: Add HttpServer: Get launched pids 2 years ago
Peter Repukat ab575c1e29 Only allow single instance of GlosSITarget 2 years ago
Peter Repukat f9c993dc50 GlosSITarget: Add cli-switches:
-disableuwpoverlay
-disablewatchdog
2 years ago
Peter Repukat 9e08309a1a SteamTarget: always enable UWP overlay 2 years ago
Peter Repukat f6d959eab6
Update Readme.md 2 years ago
Peter Repukat d4e22c2a8a Update issue templates 2 years ago
Peter Repukat 1c18e84648 Merge branch 'develop' 2 years ago
Peter Repukat eacd05b4c6 Fix update notification not opening browser 2 years ago
Peter Repukat d2fd3a75da Update shortcuts.vdf parser 2 years ago
Peter Repukat ff014a935a GlosSITarget: Reduce CPU usage: InputRedirector: Reduce update rate from 1000Hz to 250Hz 2 years ago
Peter Repukat d4a3e22e9c Refactor: Build GlosSIWatchdog as .dll injecting into explorer.exe 2 years ago
Peter Repukat a24c16a44e Fix `std::filesystem::temp_directory_path()` misuse 2 years ago

@ -7,6 +7,9 @@ assignees: ''
---
- [ ] _**I have read and understand that tickets without Logs (or other info if Logs are 1000% not relevant) will be closed without comment.**_
## Expected Behavior
@ -20,12 +23,16 @@ assignees: ''
3.
...
## Specifications
## General Information
- Version:
- Version: (0.0.0.0-0-g1234567)
- Platform: (Win10/Win11 / Desktop/SteamDeck)
- Controller(s): (1 Steam Controller + 1 Switch Pro Controller)
- Application: (GlosSITarget/GlosSIConfig)
## Logs / Additional Files
(Log and config files are located in `%appdata%/GlosSI`)
Log and config files are located in `%appdata%/GlosSI`
The files are called: glossiconfig.log, glossitarget.log, GlosSIWatchdog.log and UWPOverlayEnabler.log
_(Windows-search may not pick up the files when searching for `log` **if** file endings are hidden (default setting))_

@ -0,0 +1,24 @@
---
name: Other
about: Create issue that doesn't fit other templates
title: ''
labels: ''
assignees: ''
---
- [ ] _**I have read and understand that tickets without Logs (or other info if Logs are 1000% not relevant) will be closed without comment.**_
## General Information
- Version: (0.0.0.0-0-g1234567)
- Platform: (Win10/Win11 / Desktop/SteamDeck)
- Controller(s): (1 Steam Controller + 1 Switch Pro Controller)
- Application: (GlosSITarget/GlosSIConfig)
## Logs / Additional Files
Log and config files are located in `%appdata%/GlosSI`
The files are called: glossiconfig.log, glossitarget.log, GlosSIWatchdog.log and UWPOverlayEnabler.log
_(Windows-search may not pick up the files when searching for `log` **if** file endings are hidden (default setting))_

2
.gitignore vendored

@ -370,3 +370,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
.visuallint
GlosSIConfig/steamgrid_api_keys.h
steamgrid.zip

12
.gitmodules vendored

@ -27,10 +27,16 @@
url = https://github.com/nlohmann/json
[submodule "deps/traypp"]
path = deps/traypp
url = https://github.com/Soundux/traypp.git
url = https://github.com/Soundux/traypp
[submodule "deps/fifo_map"]
path = deps/fifo_map
url = git@github.com:nlohmann/fifo_map.git
url = https://github.com/nlohmann/fifo_map
[submodule "deps/Shortcuts_VDF"]
path = deps/Shortcuts_VDF
url = git@github.com:Alia5/Shortcuts_VDF.git
url = https://github.com/Alia5/Shortcuts_VDF
[submodule "deps/cpp-httplib"]
path = deps/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "deps/easywsclient"]
path = deps/easywsclient
url = https://github.com/dhbaird/easywsclient

41
.vscode/tasks.json vendored

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build Solution (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:Build",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build Solution (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -0,0 +1,25 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../deps/json/include",
"${workspaceFolder}/../deps/cpp-httplib",
"${workspaceFolder}/../deps/easywsclient"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "msvc-x64"
}
],
"version": 4
}

@ -0,0 +1,43 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build CEFInjectLib (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:CEFInjectLib",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build CEFInjectLib (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:CEFInjectLib:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -0,0 +1,464 @@
/*
Copyright 2021-2023 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 "CEFInject.h"
#include <easywsclient.hpp>
#define _SSIZE_T_DEFINED
#include <easywsclient.cpp> // seems like a hack to me, but eh, what is in the doc will be done ¯\_(ツ)_/¯
#include "../common/nlohmann_json_wstring.h"
#include <fstream>
#include <streambuf>
#include <spdlog/spdlog.h>
#include "../common/Settings.h"
namespace CEFInject
{
namespace internal
{
httplib::Client GetHttpClient(uint16_t port)
{
httplib::Client cli("localhost", port);
cli.set_connection_timeout(0, 200000);
cli.set_read_timeout(0, 500000);
return cli;
}
static uint32_t msg_id = 0;
}
bool CEFDebugAvailable(uint16_t port)
{
auto cli = internal::GetHttpClient(port);
if (auto res = cli.Get("/json")) {
if (res->status == 200) {
return true;
}
}
return false;
}
std::vector<std::wstring> AvailableTabNames(uint16_t port)
{
std::vector<std::wstring> tabs;
const auto json = AvailableTabs(port);
for (const auto& j : json) {
tabs.push_back(j["title"].get<std::wstring>());
}
return tabs;
}
nlohmann::basic_json<> AvailableTabs(uint16_t port)
{
if (!CEFDebugAvailable()) {
return nlohmann::json::array();
}
//if (Settings::common.extendedLogging)
//{
spdlog::trace("Fetching available Steam CEF tabs");
//}
auto cli = internal::GetHttpClient(port);
if (auto res = cli.Get("/json")) {
if (res->status == 200) {
return nlohmann::json::parse(res->body);
}
}
return nlohmann::json::array();
}
nlohmann::basic_json<> InjectJs(std::string_view tab_name, std::string_view debug_url, std::wstring_view js, uint16_t port)
{
std::shared_ptr<easywsclient::WebSocket> ws{
easywsclient::WebSocket::from_url(debug_url.data())
};
if (ws)
{
nlohmann::json res = nullptr;
try
{
auto json_payload = nlohmann::json{
{"id", internal::msg_id++},
{"method", "Runtime.evaluate"},
{"params", {
{"userGesture", true},
{"expression", std::wstring{js.data()}}
//{"expression", js}
}}
};
auto payload_string = json_payload.dump();
spdlog::debug("Injecting JS into tab: {}, {}; JS: {}", tab_name, debug_url, payload_string);
ws->send(payload_string);
bool exit = false;
while (ws->getReadyState() != easywsclient::WebSocket::CLOSED) {
ws->poll();
ws->dispatch([&ws, &res, &exit](const std::string& message) {
const auto msg = nlohmann::json::parse(message);
try
{
if (msg.at("result").at("result").at("type").get<std::string>() != "undefined") {
res = msg.at("result").at("result").at("value");
}
}
catch (...) {
spdlog::error("CEFInject: Error parsing injection-result value: {}", message);
res = msg;
}
exit = true;
});
if (exit) {
ws->close();
return res;
}
}
}
catch (...) {
spdlog::error(
"CEFInject: Error injecting JS into tab: {}, {}",
std::string(tab_name.data()),
std::string(debug_url.data()));
}
return res;
}
return nullptr;
}
nlohmann::basic_json<> InjectJsByName(std::wstring_view tabname, std::wstring_view js, uint16_t port)
{
auto cli = internal::GetHttpClient(port);
if (auto res = cli.Get("/json")) {
if (res->status == 200) {
const auto json = nlohmann::json::parse(res->body);
for (const auto& tab : json) {
if (tab["title"].get<std::wstring>().find(tabname) != std::string::npos) {
return InjectJs(tab["title"].get<std::string>(), tab["webSocketDebuggerUrl"].get<std::string>(), js, port);
}
}
}
}
return nullptr;
}
WSAStartupWrap::WSAStartupWrap()
{
#ifdef _WIN32
WSADATA wsa_data;
if (const INT rc = WSAStartup(MAKEWORD(2, 2), &wsa_data)) {
spdlog::error("WSAStartup Failed.");
return;
}
wsa_startup_succeeded_ = true;
#endif
}
WSAStartupWrap::~WSAStartupWrap()
{
#ifdef _WIN32
if (wsa_startup_succeeded_)
{
WSACleanup();
}
#endif
}
bool SteamTweaks::injectGlosSITweaks(std::string_view tab_name, uint16_t port)
{
if (tab_name.empty()) {
for (auto ts = AvailableTabNames(); const auto & tn : ts)
{
// meh!
injectGlosSITweaks(util::string::to_string(tn), port);
}
return true;
}
return injectGlosSITweaks(Tab_Info{ std::string(tab_name) }, port);
}
bool SteamTweaks::injectGlosSITweaks(const Tab_Info& info, uint16_t port)
{
if (!CEFDebugAvailable()) {
return false;
}
if (glossi_tweaks_js_.empty())
{
if (!readGlosSITweaksJs()) {
return false;
}
}
const auto find_tab = (
[&info]() -> std::function<nlohmann::json(const nlohmann::json::array_t& tabList)>
{
if (!info.name.empty())
{
return [&info](const nlohmann::json::array_t& tabList)
{
for (const auto& tab : tabList) {
if (tab["title"].get<std::string>().find(info.name.data()) != std::string::npos) {
return tab;
}
}
return nlohmann::json{};
};
}
if (!info.id.empty())
{
return [&info](const nlohmann::json::array_t& tabList)
{
for (const auto& tab : tabList) {
if (tab["id"].get<std::string>() == info.id) {
return tab;
}
}
return nlohmann::json{};
};
}
if (!info.webSocketDebuggerUrl.empty())
{
return [&info](const nlohmann::json::array_t& tabList)
{
for (const auto& tab : tabList) {
if (tab["webSocketDebuggerUrl"].get<std::string>() == info.webSocketDebuggerUrl) {
return tab;
}
}
return nlohmann::json{};
};
}
return nullptr;
}
)();
if (find_tab == nullptr) {
return false;
}
const auto tabs = AvailableTabs(port);
const auto tab = find_tab(tabs);
if (tab.empty()) {
return false;
}
const auto res = InjectJs(tab["title"].get<std::string>(), tab["webSocketDebuggerUrl"].get<std::string>(), glossi_tweaks_js_, port);
if (res.is_boolean() && res.get<bool>()) {
glossi_tweaks_injected_map_[tab["id"].get<std::string>()] = true;
spdlog::trace("CEFInject: GlosSITweaks injected into tab: {}", tab["title"].get<std::string>());
return true;
}
return false;
}
bool SteamTweaks::uninstallTweaks(bool force)
{
if (!CEFDebugAvailable()) {
return false;
}
auto_inject_ = false;
if (auto_inject_future_.valid()) {
auto_inject_future_.wait();
}
if (glossi_tweaks_injected_map_.empty() && !force) {
return false;
}
std::vector<std::future<void>> futures;
for (auto ts = AvailableTabs(); auto & tab : ts) {
futures.push_back(std::async(std::launch::async, [this, &tab]()
{
InjectJs(tab["title"].get<std::string>(), tab["webSocketDebuggerUrl"].get<std::string>(), uninstall_glossi_tweaks_js_);
}));
}
for (auto& f : futures)
{
if (f.valid()) {
f.wait();
}
}
glossi_tweaks_injected_map_.clear();
return true;
}
void SteamTweaks::update(float elapsed_time)
{
if (!auto_inject_) {
return;
}
using namespace std::chrono_literals;
if (auto_inject_future_.valid() && auto_inject_future_.wait_for(0ms) != std::future_status::ready) {
time_since_last_update_ = 0.0f;
return;
}
time_since_last_update_ += elapsed_time;
if (time_since_last_update_ < update_interval_) {
return;
}
time_since_last_update_ = 0.0f;
spdlog::trace("CEFInject: Starting auto inject GlosSITweaks");
auto_inject_future_ = std::async(std::launch::async, [this]() {
if (js_tweaks_cache_.empty()) [[unlikely]] {
readAvailableTweaks();
}
if (glossi_tweaks_js_.empty()) [[unlikely]]
{
if (!readGlosSITweaksJs()) {
return;
}
}
auto futures = std::vector<std::future<void>>{};
auto tabs = AvailableTabs();
for (auto& tab : tabs) {
if (glossi_tweaks_injected_map_.contains(tab["id"].get<std::string>())) {
continue;
}
glossi_tweaks_injected_map_[tab["id"].get<std::string>()] = true;
futures.push_back(std::async([this, &tab]()
{
InjectJs(tab["title"].get<std::string>(), tab["webSocketDebuggerUrl"].get<std::string>(), glossi_tweaks_js_);
for (auto& [path, js] : js_tweaks_cache_) {
const auto dir_name = path.parent_path().filename();
if (path_tab_map_.contains(dir_name.wstring())) {
if (tab["title"].get<std::string>().find(path_tab_map_.at(dir_name.wstring())) != std::string::npos) {
InjectJs(tab["title"].get<std::string>(), tab["webSocketDebuggerUrl"].get<std::string>(), js);
}
}
}
}));
}
for (auto& f : futures)
{
if (f.valid()) {
f.wait();
}
}
spdlog::trace("CEFInject: Auto Inject thread done");
});
}
bool SteamTweaks::isAutoInject() const
{
return auto_inject_;
}
void SteamTweaks::setAutoInject(const bool auto_inject)
{
auto_inject_ = auto_inject;
}
bool SteamTweaks::readGlosSITweaksJs()
{
if (glossi_tweaks_js_.empty())
{
spdlog::trace("CEFInject: Loadings GlosSITweaks.js");
auto glossi_path = util::path::getGlosSIDir();
glossi_path /= steam_tweaks_path_;
glossi_path /= "GlosSITweaks.js";
if (!std::filesystem::exists(glossi_path))
{
spdlog::error("CEFInject: GlosSITweaks.js not found");
return false;
}
std::wifstream js_file(glossi_path);
glossi_tweaks_js_ = { (std::istreambuf_iterator<wchar_t>(js_file)),
std::istreambuf_iterator<wchar_t>() };
if (glossi_tweaks_js_.empty()) {
spdlog::error("CEFInject: GlosSITweaks.js empty?");
return false;
}
js_file.close();
}
return true;
}
void SteamTweaks::readAvailableTweaks(bool builtin)
{
auto tweaks_path = builtin ? util::path::getGlosSIDir() : util::path::getDataDirPath();
spdlog::log(
builtin ? spdlog::level::trace : spdlog::level::debug,
"CEFInject: Loading {} {} {}",
builtin ? "builtin" : "user",
"tweaks from",
tweaks_path.string()
);
tweaks_path /= steam_tweaks_path_;
if (!std::filesystem::exists(tweaks_path))
{
return;
}
auto find_tweak_files = [this](std::wstring_view path, auto&& recurse) -> void
{
for (const auto& dir_file : std::filesystem::directory_iterator(path))
{
if (std::filesystem::is_directory(dir_file))
{
recurse(dir_file.path().wstring(), recurse);
continue;
}
if (std::filesystem::is_regular_file(dir_file) && dir_file.path().extension() == ".js")
{
if (dir_file.path().filename() == "GlosSITweaks.js") {
continue;
}
std::wifstream js_file(dir_file.path());
std::wstring tweaks_js = { (std::istreambuf_iterator<wchar_t>(js_file)),
std::istreambuf_iterator<wchar_t>() };
if (tweaks_js.empty()) {
continue;
}
js_file.close();
js_tweaks_cache_[dir_file.path().wstring()] = tweaks_js;
}
}
};
find_tweak_files(tweaks_path.wstring(), find_tweak_files);
for (const auto& tweak : js_tweaks_cache_ | std::views::keys) {
spdlog::debug(
"CEFInject: Found {} tweak: {}",
builtin ? "builtin" : "user",
tweak.string()
);
}
}
}

@ -0,0 +1,104 @@
/*
Copyright 2021-2023 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 <future>
#include <httplib.h>
#include <nlohmann/json.hpp>
namespace CEFInject
{
namespace internal {
httplib::Client GetHttpClient(uint16_t port);
static inline uint16_t port_ = 8080;
}
inline void setPort(uint16_t port)
{
internal::port_ = port;
}
bool CEFDebugAvailable(uint16_t port = internal::port_);
std::vector<std::wstring> AvailableTabNames(uint16_t port = internal::port_);
nlohmann::basic_json<> AvailableTabs(uint16_t port = internal::port_);
nlohmann::basic_json<> InjectJs(std::string_view tab_name, std::string_view debug_url, std::wstring_view js, uint16_t port = internal::port_);
nlohmann::basic_json<> InjectJsByName(std::wstring_view tabname, std::wstring_view js, uint16_t port = internal::port_);
class WSAStartupWrap
{
public:
explicit WSAStartupWrap();
~WSAStartupWrap();
private:
bool wsa_startup_succeeded_ = false;
};
class SteamTweaks
{
public:
SteamTweaks() = default;
struct Tab_Info
{
std::string name;
std::string id;
std::string webSocketDebuggerUrl;
};
bool injectGlosSITweaks(std::string_view tab_name, uint16_t port = internal::port_);
bool injectGlosSITweaks(const Tab_Info& info, uint16_t port = internal::port_);
bool uninstallTweaks(bool force = false);
void update(float elapsed_time);
[[nodiscard]] bool isAutoInject() const;
void setAutoInject(const bool auto_inject);
private:
bool readGlosSITweaksJs();
void readAvailableTweaks(bool builtin = true);
bool auto_inject_ = false;
static constexpr float update_interval_ = 30.f;
float time_since_last_update_ = update_interval_;
using tab_id = std::string;
std::map<tab_id, bool> glossi_tweaks_injected_map_;
std::future<void> auto_inject_future_;
std::wstring glossi_tweaks_js_;
std::map<std::filesystem::path, std::wstring> js_tweaks_cache_;
using path_name = std::wstring;
using tab_name = std::string;
static inline const std::map<path_name, tab_name> path_tab_map_ = {
{L"SharedContext", "Steam Shared Context"},
{L"Overlay", "HOW TF GET OVERLAY TAB NAME?"}, // TODO: Figure out how to get the overlay tab name
{L"GamepadUI", "Steam Big Picture Mode"},
};
static constexpr std::string_view steam_shared_ctx_tab_name_ = "Steam Shared Context";
static constexpr std::string_view steam_tweaks_path_ = "SteamTweaks";
static constexpr std::wstring_view uninstall_glossi_tweaks_js_ = LR"(
(() => {
return window.GlosSITweaks?.GlosSI?.uninstall();
})();
)";
};
namespace internal {
static WSAStartupWrap wsa_startup_wrap{};
}
}

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CEFInject.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CEFInject.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{74fba967-ab7e-43ea-b561-3f4821954b3b}</ProjectGuid>
<RootNamespace>CEFInjectLib</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\deps\cpp-httplib;..\deps\json\include;..\deps\easywsclient;..\deps\spdlog\include;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>..\deps\cpp-httplib;..\deps\json\include;..\deps\easywsclient;..\deps\spdlog\include;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_CRT_SECURE_NO_WARNINGS;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_CRT_SECURE_NO_WARNINGS;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CEFInject.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CEFInject.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

@ -4,12 +4,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlosSITarget", "GlosSITarget\GlosSITarget.vcxproj", "{076E263E-0687-4435-836E-8F4EF6668843}"
ProjectSection(ProjectDependencies) = postProject
{74FBA967-AB7E-43EA-B561-3F4821954B3B} = {74FBA967-AB7E-43EA-B561-3F4821954B3B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlosSIConfig", "GlosSIConfig\GlosSIConfig.vcxproj", "{4B42920B-3CC6-475F-A5B3-441337968483}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UWPOverlayEnablerDLL", "UWPOverlayEnablerDLL\UWPOverlayEnablerDLL.vcxproj", "{50212575-87E2-40AB-87EE-EAED19C8EBB2}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GlosSIWatchdog", "GlosSIWatchdog\GlosSIWatchdog.vcxproj", "{BF273B90-CB69-43C8-9AF6-F3256DAFD41E}"
ProjectSection(ProjectDependencies) = postProject
{74FBA967-AB7E-43EA-B561-3F4821954B3B} = {74FBA967-AB7E-43EA-B561-3F4821954B3B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CEFInjectLib", "CEFInjectLib\CEFInjectLib.vcxproj", "{74FBA967-AB7E-43EA-B561-3F4821954B3B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common\common.vcxproj", "{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -49,6 +59,20 @@ Global
{BF273B90-CB69-43C8-9AF6-F3256DAFD41E}.Release|x64.Build.0 = Release|x64
{BF273B90-CB69-43C8-9AF6-F3256DAFD41E}.Release|x86.ActiveCfg = Release|Win32
{BF273B90-CB69-43C8-9AF6-F3256DAFD41E}.Release|x86.Build.0 = Release|Win32
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Debug|x64.ActiveCfg = Debug|x64
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Debug|x64.Build.0 = Debug|x64
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Debug|x86.ActiveCfg = Debug|Win32
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Debug|x86.Build.0 = Debug|Win32
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Release|x64.ActiveCfg = Release|x64
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Release|x64.Build.0 = Release|x64
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Release|x86.ActiveCfg = Release|Win32
{74FBA967-AB7E-43EA-B561-3F4821954B3B}.Release|x86.Build.0 = Release|Win32
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Debug|x64.ActiveCfg = Debug|x64
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Debug|x86.ActiveCfg = Debug|Win32
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Debug|x86.Build.0 = Debug|Win32
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Release|x64.ActiveCfg = Release|x64
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Release|x86.ActiveCfg = Release|Win32
{DFED4B7E-D04C-442B-BB48-5B6068A6B31B}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -0,0 +1,43 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build GlosSIConfig (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSIConfig",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build GlosSIConfig (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSIConfig:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -2,6 +2,7 @@
#include <QQuickImageProvider>
#include <Windows.h>
#include <QRegularExpression>
#include <shellapi.h>
class ExeImageProvider : public QQuickImageProvider {
public:
ExeImageProvider()

@ -69,8 +69,12 @@
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories>
<AdditionalOptions>/Zc:__cplusplus /Zc:twoPhase- %(AdditionalOptions)</AdditionalOptions>
<CompileAsWinRT>false</CompileAsWinRT>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NOMINMAX;CONFIGAPP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\deps\WinReg;..\deps\fifo_map\src;..\deps\Shortcuts_VDF\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Manifest>
<EnableDpiAwareness>true</EnableDpiAwareness>
@ -92,8 +96,12 @@
<AdditionalUsingDirectories>%(AdditionalUsingDirectories)</AdditionalUsingDirectories>
<AdditionalOptions>/Zc:__cplusplus /Zc:zwoPhase- /permissive- %(AdditionalOptions)</AdditionalOptions>
<CompileAsWinRT>false</CompileAsWinRT>
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NOMINMAX;CONFIGAPP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\deps\WinReg;..\deps\fifo_map\src;..\deps\Shortcuts_VDF\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Manifest>
<EnableDpiAwareness>true</EnableDpiAwareness>
@ -136,6 +144,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\common\UnhookUtil.cpp" />
<ClCompile Include="ExeImageProvider.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="UIModel.cpp" />
@ -143,7 +152,9 @@
<None Include="GetAUMIDs.ps1" />
<None Include="qml\AdvancedTargetSettings.qml" />
<None Include="qml\CollapsiblePane.qml" />
<None Include="qml\EGSSelectDialog.qml" />
<None Include="qml\GlobalConf.qml" />
<None Include="qml\SteamGridDialog.qml" />
<None Include="qml\SteamInputXboxDisabledDialog.qml" />
<None Include="qml\AddSelectTypeDialog.qml" />
<None Include="qml\FluentTextInput.qml" />

@ -37,6 +37,9 @@
<ClCompile Include="ExeImageProvider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\common\UnhookUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="qml\main.qml">
@ -80,6 +83,12 @@
<None Include="qml\GlobalConf.qml">
<Filter>qml</Filter>
</None>
<None Include="qml\EGSSelectDialog.qml">
<Filter>qml</Filter>
</None>
<None Include="qml\SteamGridDialog.qml">
<Filter>qml</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtMoc Include="UIModel.h">

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -16,10 +16,14 @@ limitations under the License.
#include "UIModel.h"
#include <QDir>
#include <QFont>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QMetaType>
#include <WinReg/WinReg.hpp>
@ -30,16 +34,16 @@ limitations under the License.
#include <Windows.h>
#endif
#include "ExeImageProvider.h"
#include "../version.hpp"
#include "../common/UnhookUtil.h"
#include "../common/util.h"
UIModel::UIModel() : QObject(nullptr)
{
auto path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path();
path /= "Roaming";
path /= "GlosSI";
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto path = util::path::getDataDirPath();
qDebug() << "Version: " << getVersionString();
@ -49,9 +53,22 @@ UIModel::UIModel() : QObject(nullptr)
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto defaultConf = getDefaultConf();
saveDefaultConf(defaultConf);
parseShortcutVDF();
readTargetConfigs();
updateCheck();
readUnhookBytes();
auto font = QGuiApplication::font();
font.setPointSize(11);
font.setFamily("Roboto");
QGuiApplication::setFont(font);
std::ofstream{getSteamPath() / ".cef-enable-remote-debugging"};
}
void UIModel::readTargetConfigs()
@ -94,22 +111,43 @@ void UIModel::addTarget(QVariant shortcut)
emit targetListChanged();
}
void UIModel::updateTarget(int index, QVariant shortcut)
bool UIModel::updateTarget(int index, QVariant shortcut)
{
const auto map = shortcut.toMap();
const auto json = QJsonObject::fromVariantMap(map);
const auto was_in_steam_ = isInSteam(shortcut);
auto oldSteamName = targets_[index].toMap()["name"].toString();
auto oldName =
targets_[index].toMap()["name"].toString().replace(QRegularExpression("[\\\\/:*?\"<>|]"), "") + ".json";
auto path = config_path_;
path /= config_dir_name_.toStdString();
path /= (oldName).toStdString();
std::filesystem::remove(path);
auto oldPath = config_path_;
oldPath /= config_dir_name_.toStdString();
oldPath /= (oldName).toStdString();
std::filesystem::remove(oldPath);
writeTarget(json, map["name"].toString());
targets_.replace(index, QJsonDocument(json).toVariant());
emit targetListChanged();
auto path = config_path_;
path /= config_dir_name_.toStdString();
path /= (map["name"].toString()).toStdString();
if (was_in_steam_) {
if (removeFromSteam(oldSteamName, QString::fromStdWString(path.wstring()))) {
if (!addToSteam(shortcut, QString::fromStdWString(path.wstring()))) {
qDebug() << "Couldn't add shortcut \"" << (map["name"].toString()) << "\" to Steam when updating";
return false;
}
return true;
}
qDebug() << "Couldn't remove shortcut \"" << oldName << "\" from Steam when updating";
return false;
} else {
return true;
}
}
void UIModel::deleteTarget(int index)
@ -124,11 +162,13 @@ void UIModel::deleteTarget(int index)
emit targetListChanged();
}
bool UIModel::isInSteam(QVariant shortcut)
bool UIModel::isInSteam(QVariant shortcut) const
{
const auto map = shortcut.toMap();
for (auto& steam_shortcut : shortcuts_vdf_) {
if (map["name"].toString() == QString::fromStdString(steam_shortcut.appname)) {
if (
map["name"].toString() == QString::fromStdString(steam_shortcut.appname) ||
map["oldName"].toString() == QString::fromStdString(steam_shortcut.appname)) {
if (QString::fromStdString(steam_shortcut.exe).toLower().contains("glossitarget.exe")) {
return true;
}
@ -138,6 +178,41 @@ bool UIModel::isInSteam(QVariant shortcut)
return false;
}
uint32_t UIModel::getAppId(QVariant shortcut)
{
if (!isInSteam(shortcut)) {
return 0;
}
const auto map = shortcut.toMap();
for (auto& steam_shortcut : shortcuts_vdf_) {
if (map["name"].toString() == QString::fromStdString(steam_shortcut.appname)) {
if (QString::fromStdString(steam_shortcut.exe).toLower().contains("glossitarget.exe")) {
if (steam_shortcut.appid == 0) {
parseShortcutVDF();
return getAppId(shortcut);
}
return steam_shortcut.appid;
}
}
}
return 0;
}
Q_INVOKABLE QString UIModel::getGameId(QVariant shortcut) {
/*
* enum SteamLaunchableType
{
App = 0,
GameMod = 1,
Shortcut = 2,
P2P = 3
}
*/
uint64_t gameId = (((uint64_t)getAppId(shortcut) << 32) | ((uint32_t)2 << 24) | 0);
return QVariant(gameId).toString();
}
bool UIModel::addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd)
{
QDir appDir = QGuiApplication::applicationDirPath();
@ -275,13 +350,18 @@ void UIModel::enableSteamInputXboxSupport()
}
}
bool UIModel::restartSteam()
bool UIModel::restartSteam(const QString& steamURL)
{
const auto path = getSteamPath();
if (QProcess::execute("taskkill.exe", {"/im", steam_executable_name_, "/f"}) != 0) {
return false;
}
QProcess::startDetached(QString::fromStdWString(path) + "/" + steam_executable_name_);
if (steamURL.isEmpty()) {
QProcess::startDetached(QString::fromStdWString(path) + "/" + steam_executable_name_);
}
else {
system((QString::fromLatin1("start ") + steamURL).toStdString().c_str());
}
return true;
}
@ -307,74 +387,92 @@ void UIModel::updateCheck()
QVariantMap UIModel::getDefaultConf() const
{
auto path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path();
auto path = util::path::getDataDirPath();
path /= "Roaming";
path /= "GlosSI";
path /= "default.json";
QJsonObject defaults = {
{"icon", QJsonValue::Null},
{"name", QJsonValue::Null},
{"version", 1},
{"extendedLogging", false},
{"snapshotNotify", false},
{"steamgridApiKey", QJsonValue::Null},
{"steamPath",
QJsonValue::fromVariant(QString::fromStdWString(getSteamPath(false).wstring()))},
{"steamUserId",
QJsonValue::fromVariant(QString::fromStdWString(getSteamUserId(false)))},
{"globalModeGameId", ""},
{"globalModeUseGamepadUI", true},
{"minimizeSteamGamepadUI", true},
{"controller",
QJsonObject{{"maxControllers", -1},
{"emulateDS4", false},
{"allowDesktopConfig", false},
{"updateRate", 144}
}},
{"devices",
QJsonObject{
{"hideDevices", true},
{"realDeviceIds", false},
}},
{"launch",
QJsonObject{
{"closeOnExit", true},
{"launch", false},
{"launchAppArgs", QJsonValue::Null},
{"launchPath", QJsonValue::Null},
{"waitForChildProcs", true},
{"launcherProcesses", QJsonArray{}},
{"ignoreLauncher", true},
{"killLauncher", false},
}},
{"window",
QJsonObject{
{"disableOverlay", false},
{"hideAltTab", true},
{"maxFps", QJsonValue::Null},
{"scale", QJsonValue::Null},
{"windowMode", false},
{"disableGlosSIOverlay", false},
{"opaqueSteamOverlay", false}
}},
};
if (std::filesystem::exists(path)) {
QFile file(QString::fromStdWString(path));
if (file.open(QIODevice::ReadOnly)) {
const auto data = file.readAll();
file.close();
return QJsonDocument::fromJson(data).object().toVariantMap();
auto json = QJsonDocument::fromJson(data).object();
const auto applyDefaults = [](QJsonObject obj, const QJsonObject& defaults,
auto applyDefaultsFn) -> QJsonObject {
for (const auto& key : defaults.keys()) {
qDebug() << key << ": " << obj[key];
if ((obj[key].isUndefined() || obj[key].isNull()) && !defaults[key].isNull()) {
obj[key] = defaults.value(key);
}
if (obj.value(key).isObject()) {
obj[key] =
applyDefaultsFn(obj[key].toObject(), defaults.value(key).toObject(), applyDefaultsFn);
}
}
return obj;
};
json = applyDefaults(json, defaults, applyDefaults);
return json.toVariantMap();
}
}
QJsonObject obj = {
{"icon", QJsonValue::Null},
{"name", QJsonValue::Null},
{"version", 1},
{"extendedLogging", false},
{"snapshotNotify", false},
{
"controller",
QJsonObject{
{"maxControllers", 1},
{"emulateDS4", false},
{"allowDesktopConfig", false}
}
},
{
"devices",
QJsonObject{
{"hideDevices", true},
{"realDeviceIds", false},
}
},
{
"launch",
QJsonObject{
{"closeOnExit", true},
{"launch", false},
{"launchAppArgs", QJsonValue::Null},
{"launchPath", QJsonValue::Null},
{"waitForChildProcs", true},
}
},
{
"window",
QJsonObject{
{"disableOverlay", false},
{"maxFps", QJsonValue::Null},
{"scale", QJsonValue::Null},
{"windowMode", false},
}
},
};
saveDefaultConf(obj.toVariantMap());
saveDefaultConf(defaults.toVariantMap());
return getDefaultConf();
}
void UIModel::saveDefaultConf(QVariantMap conf) const
{
auto path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path();
auto path = util::path::getDataDirPath();
path /= "Roaming";
path /= "GlosSI";
path /= "default.json";
QFile file(path);
@ -387,10 +485,108 @@ void UIModel::saveDefaultConf(QVariantMap conf) const
file.close();
}
Q_INVOKABLE QVariant UIModel::globalModeShortcutConf() {
for (auto& target : targets_) {
const auto map = target.toMap();
if (map["name"] == "GlosSI GlobalMode/Desktop") {
return target;
}
}
return QVariant();
}
Q_INVOKABLE bool UIModel::globalModeShortcutExists() {
const auto map = globalModeShortcutConf().toMap();
if (map["name"] == "GlosSI GlobalMode/Desktop") {
return true;
}
return false;
}
Q_INVOKABLE uint32_t UIModel::globalModeShortcutAppId() {
if (!globalModeShortcutExists()) {
return 0;
}
return getAppId(globalModeShortcutConf());
}
Q_INVOKABLE QString UIModel::globalModeShortcutGameId() {
if (!globalModeShortcutExists()) {
return "";
}
return getGameId(globalModeShortcutConf());
}
#ifdef _WIN32
QVariantList UIModel::uwpApps() { return UWPFetch::UWPAppList(); }
#endif
QVariantList UIModel::egsGamesList() const
{
wchar_t* program_data_path_str;
std::filesystem::path path;
if (SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE, NULL, &program_data_path_str) != S_OK) {
qDebug() << "Couldn't get ProgramDataPath";
return {{"InstallLocation", "Error"}};
}
path = std::filesystem::path(program_data_path_str);
path /= egs_games_json_path_;
QFile file(path);
if (file.open(QIODevice::ReadOnly)) {
const auto data = file.readAll();
file.close();
auto json = QJsonDocument::fromJson(data).object();
if (json["InstallationList"].isArray()) {
return json["InstallationList"].toVariant().toList();
}
qDebug() << "InstallationList does not exist!";
}
qDebug() << "Couldn't read EGS LauncherInstalled.dat " << path;
return {{"InstallLocation", "Error"}};
}
void UIModel::loadSteamGridImages()
{
std::filesystem::path path = QCoreApplication::applicationDirPath().toStdWString();
path /= "steamgrid.exe";
auto api_key = getDefaultConf().value("steamgridApiKey").toString();
steamgrid_output_.clear();
steamgrid_proc_.setProgram(path.string().c_str());
steamgrid_proc_.setArguments({"-nonsteamonly", "--onlymissingartwork", "--steamgriddb", api_key});
connect(&steamgrid_proc_, &QProcess::readyReadStandardOutput, this, &UIModel::onSteamGridReadReady);
steamgrid_proc_.start();
steamgrid_proc_.write("\n");
}
QString UIModel::getGridImagePath(QVariant shortcut)
{
if (!foundSteam()) {
return "";
}
const auto& app_id = getAppId(shortcut);
if (app_id == 0) {
return "";
}
const std::filesystem::path grid_dir =
std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + L"/config/grid";
if (!std::filesystem::exists(grid_dir)) {
return "";
}
const std::vector<std::string> extensions = {".png", ".jpg"};
for (const auto& entry : std::filesystem::directory_iterator(grid_dir)) {
if (entry.is_regular_file() &&
std::ranges::find(extensions, entry.path().extension().string()) != extensions.end() &&
entry.path().filename().string().find(std::to_string(app_id)) != std::string::npos) {
return QString::fromStdString(entry.path().string());
}
}
return "";
}
bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath,
bool is_admin_try) const
{
@ -452,6 +648,15 @@ bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& na
#endif
}
bool UIModel::getIsDebug() const
{
#ifdef _DEBUG
return true;
#else
return false;
#endif
}
bool UIModel::getIsWindows() const { return is_windows_; }
bool UIModel::hasAcrylicEffect() const { return has_acrylic_affect_; }
@ -462,6 +667,8 @@ void UIModel::setAcrylicEffect(bool has_acrylic_affect)
emit acrylicChanged();
}
QStringList UIModel::getSteamgridOutput() const { return steamgrid_output_; }
void UIModel::onAvailFilesResponse(QNetworkReply* reply)
{
@ -475,9 +682,7 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply)
const auto defaultConf = getDefaultConf();
bool snapshotNotify =
defaultConf.contains("snapshotNotify")
? defaultConf["snapshotNotify"].toJsonValue().toBool()
: false;
defaultConf.contains("snapshotNotify") ? defaultConf["snapshotNotify"].toJsonValue().toBool() : false;
struct VersionInfo {
int major;
@ -490,8 +695,9 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply)
std::vector<std::pair<QString, VersionInfo>> new_versions;
for (const auto& info :
json.keys() | std::ranges::views::filter([this, &json, snapshotNotify](const auto& key) {
return notify_on_snapshots_ ? true
: json[key].toObject().value("type") == (snapshotNotify ? "snapshot" : "release");
return notify_on_snapshots_
? true
: json[key].toObject().value("type") == (snapshotNotify ? "snapshot" : "release");
}) | std::ranges::views::transform([&json](const auto& key) -> std::pair<QString, VersionInfo> {
const auto versionString = json[key].toObject().value("version").toString();
const auto cleanVersion = versionString.split("-")[0];
@ -535,6 +741,16 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply)
}
}
void UIModel::onSteamGridReadReady()
{
const auto output = QString::fromLocal8Bit(steamgrid_proc_.readAllStandardOutput());
steamgrid_output_.push_back(output);
emit steamgridOutputChanged();
if (output.toLower().contains("token is missing or invalid")) {
steamgrid_proc_.kill();
}
}
void UIModel::writeTarget(const QJsonObject& json, const QString& name) const
{
auto path = config_path_;
@ -546,7 +762,10 @@ void UIModel::writeTarget(const QJsonObject& json, const QString& name) const
return;
}
file.write(QString(QJsonDocument(json).toJson(QJsonDocument::Indented)).toStdString().data());
auto json_ob = QJsonDocument(json).object();
json_ob.remove("steamgridApiKey");
file.write(QString(QJsonDocument(json_ob).toJson(QJsonDocument::Indented)).toStdString().data());
file.close();
}
@ -554,20 +773,33 @@ QString UIModel::getVersionString() const { return QString(version::VERSION_STR)
QString UIModel::getNewVersionName() const { return new_version_name_; }
std::filesystem::path UIModel::getSteamPath() const
std::filesystem::path UIModel::getSteamPath(bool tryConfig) const
{
QVariantMap defaultConf;
if (tryConfig) {
defaultConf = getDefaultConf();
}
try {
#ifdef _WIN32
// 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"};
if (!key.IsValid()) {
if (defaultConf.contains("steamPath") &&
QMetaType::canConvert(defaultConf["steamPath"].metaType(), QMetaType(QMetaType::QString))) {
return defaultConf["steamPath"].toString().toStdWString();
}
return "";
}
const auto res = key.GetStringValue(L"SteamPath");
return res;
}
catch (...) {
if (defaultConf.contains("steamPath") &&
QMetaType::canConvert(defaultConf["steamPath"].metaType(), QMetaType(QMetaType::QString))) {
return defaultConf["steamPath"].toString().toStdWString();
}
return "";
}
#else
@ -575,23 +807,39 @@ std::filesystem::path UIModel::getSteamPath() const
#endif
}
std::wstring UIModel::getSteamUserId() const
std::wstring UIModel::getSteamUserId(bool tryConfig) const
{
QVariantMap defaultConf;
if (tryConfig) {
defaultConf = getDefaultConf();
}
#ifdef _WIN32
try {
// 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"};
if (!key.IsValid()) {
if (defaultConf.contains("steamUserId") &&
QMetaType::canConvert(defaultConf["steamUserId"].metaType(), QMetaType(QMetaType::QString))) {
return defaultConf["steamUserId"].toString().toStdWString();
}
return L"0";
}
const auto res = std::to_wstring(key.GetDwordValue(L"ActiveUser"));
if (res == L"0") {
qDebug() << "Steam not open?";
if (defaultConf.contains("steamUserId") &&
QMetaType::canConvert(defaultConf["steamUserId"].metaType(), QMetaType(QMetaType::QString))) {
return defaultConf["steamUserId"].toString().toStdWString();
}
}
return res;
}
catch (...) {
if (defaultConf.contains("steamUserId") &&
QMetaType::canConvert(defaultConf["steamUserId"].metaType(), QMetaType(QMetaType::QString))) {
return defaultConf["steamUserId"].toString().toStdWString();
}
return L"0";
}
#else
@ -665,3 +913,35 @@ bool UIModel::isSteamInputXboxSupportEnabled() const
}
return true;
}
void UIModel::readUnhookBytes() const
{
std::map<std::string, std::string> unhook_bytes;
for (const auto& name : UnhookUtil::UNHOOK_BYTES_ORIGINAL_22000 | std::views::keys) {
auto bytes = UnhookUtil::ReadOriginalBytes(
name,
name.starts_with("Hid")
? L"hid.dll"
: name.starts_with("Setup")
? L"setupapi.dll"
: L"Kernel32.dll"
);
unhook_bytes[name] = bytes;
}
auto path = config_path_;
path /= "unhook_bytes";
QFile file(path);
if (!file.open(QIODevice::Truncate | QIODevice::ReadWrite)) {
qDebug() << "Couldn't open file for writing: " << path;
return;
}
for (const auto& [name, bytes] : unhook_bytes) {
file.write(
QString::fromStdString(name + ":").toStdString().data()
);
file.write(bytes.data(), bytes.size());
file.write("\n");
}
file.close();
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -17,6 +17,7 @@ limitations under the License.
#include <QJsonObject>
#include <QObject>
#include <QVariant>
#include <QProcess>
#include <filesystem>
#include <shortcuts_vdf.hpp>
@ -25,56 +26,76 @@ class QNetworkReply;
class UIModel : public QObject {
Q_OBJECT
Q_PROPERTY(bool isDebug READ getIsDebug CONSTANT)
Q_PROPERTY(bool isWindows READ getIsWindows CONSTANT)
Q_PROPERTY(bool hasAcrlyicEffect READ hasAcrylicEffect NOTIFY acrylicChanged)
Q_PROPERTY(QVariantList targetList READ getTargetList NOTIFY targetListChanged)
Q_PROPERTY(QVariantList uwpList READ uwpApps CONSTANT)
Q_PROPERTY(QVariantList egsList READ egsGamesList CONSTANT)
Q_PROPERTY(bool foundSteam READ foundSteam CONSTANT)
Q_PROPERTY(bool steamInputXboxSupportEnabled READ isSteamInputXboxSupportEnabled CONSTANT)
Q_PROPERTY(QString versionString READ getVersionString CONSTANT)
Q_PROPERTY(QString newVersionName READ getNewVersionName NOTIFY newVersionAvailable)
Q_PROPERTY(QStringList steamgridOutput READ getSteamgridOutput NOTIFY steamgridOutputChanged)
public:
UIModel();
Q_INVOKABLE void readTargetConfigs();
Q_INVOKABLE QVariantList getTargetList() const;
Q_INVOKABLE void addTarget(QVariant shortcut);
Q_INVOKABLE void updateTarget(int index, QVariant shortcut);
Q_INVOKABLE bool updateTarget(int index, QVariant shortcut);
Q_INVOKABLE void deleteTarget(int index);
Q_INVOKABLE bool isInSteam(QVariant shortcut);
Q_INVOKABLE bool isInSteam(QVariant shortcut) const;
Q_INVOKABLE uint32_t getAppId(QVariant shortcut);
Q_INVOKABLE QString getGameId(QVariant shortcut);
Q_INVOKABLE bool addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd = false);
bool addToSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false);
Q_INVOKABLE bool removeFromSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false);
Q_INVOKABLE QVariantMap manualProps(QVariant shortcut);
Q_INVOKABLE void enableSteamInputXboxSupport();
Q_INVOKABLE bool restartSteam();
Q_INVOKABLE bool restartSteam(const QString& steamURL = "");
Q_INVOKABLE void updateCheck();
Q_INVOKABLE QVariantMap getDefaultConf() const;
Q_INVOKABLE void saveDefaultConf(QVariantMap conf) const;
Q_INVOKABLE QVariant globalModeShortcutConf();
Q_INVOKABLE bool globalModeShortcutExists();
Q_INVOKABLE uint32_t globalModeShortcutAppId();
Q_INVOKABLE QString globalModeShortcutGameId();
#ifdef _WIN32
Q_INVOKABLE QVariantList uwpApps();
#endif
Q_INVOKABLE QVariantList egsGamesList() const;
Q_INVOKABLE void loadSteamGridImages();
Q_INVOKABLE QString getGridImagePath(QVariant shortcut);
[[nodiscard]] bool writeShortcutsVDF(const std::wstring& mode, const std::wstring& name,
const std::wstring& shortcutspath, bool is_admin_try = false) const;
bool getIsDebug() const;
bool getIsWindows() const;
[[nodiscard]] bool hasAcrylicEffect() const;
void setAcrylicEffect(bool has_acrylic_affect);
QStringList getSteamgridOutput() const;
signals:
void acrylicChanged();
void targetListChanged();
void newVersionAvailable();
void steamgridOutputChanged();
public slots:
void onAvailFilesResponse(QNetworkReply* reply);
void onSteamGridReadReady();
private:
#ifdef _WIN32
@ -92,11 +113,17 @@ class UIModel : public QObject {
QString user_data_path_ = "/userdata/";
QString steam_executable_name_ = "steam.exe";
const std::wstring_view egs_games_json_path_ =
L"Epic/UnrealEngineLauncher/LauncherInstalled.dat";
QVariantList targets_;
QString new_version_name_;
bool notify_on_snapshots_ = false;
QProcess steamgrid_proc_;
QStringList steamgrid_output_;
std::vector<VDFParser::Shortcut> shortcuts_vdf_;
void writeTarget(const QJsonObject& json, const QString& name) const;
@ -104,10 +131,12 @@ class UIModel : public QObject {
QString getVersionString() const;
QString getNewVersionName() const;
std::filesystem::path getSteamPath() const;
std::wstring getSteamUserId() const;
std::filesystem::path getSteamPath(bool tryConfig = true) const;
std::wstring getSteamUserId(bool tryConfig = true) const;
bool foundSteam() const;
void parseShortcutVDF();
bool isSteamInputXboxSupportEnabled() const;
void readUnhookBytes() const;
};

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -22,6 +22,8 @@ limitations under the License.
#include <QFile>
#include <QTextStream>
#include "../common/util.h"
#ifdef _WIN32
#include <Windows.h>
#include <VersionHelpers.h>
@ -30,6 +32,7 @@ limitations under the License.
#include "ExeImageProvider.h"
#endif
#include "UIModel.h"
#include "WinEventFilter.h"
@ -89,15 +92,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext&, const QString&
break;
}
auto path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
path /= "Roaming";
path /= "GlosSI";
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto path = util::path::getDataDirPath();
QFile outFile(QString::fromStdWString(path) + "/glossiconfig.log");
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
@ -115,15 +110,7 @@ int main(int argc, char* argv[])
#endif
if (argc < 3) {
auto path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
path /= "Roaming";
path /= "GlosSI";
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto path = util::path::getDataDirPath();
QFile outFile(QString::fromStdWString(path) + "/glossiconfig.log");
outFile.open(QIODevice::WriteOnly);

@ -22,5 +22,8 @@
<file>qml/AdvancedTargetSettings.qml</file>
<file>qml/GlobalConf.qml</file>
<file>svg/settings_fill_white_24dp.svg</file>
<file>qml/EGSSelectDialog.qml</file>
<file>svg/add_photo_alternate_white_24dp.svg</file>
<file>qml/SteamGridDialog.qml</file>
</qresource>
</RCC>

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -15,6 +15,9 @@ limitations under the License.
*/
import QtQuick 6.2
import QtQuick.Controls 6.2
import QtQuick.Controls.Material 6.2
import QtQuick.Dialogs 6.2
Dialog {
id: dlg
anchors.centerIn: parent
@ -89,7 +92,7 @@ Dialog {
}
}
Button {
visible: uiModel.isWindows
visible: uiModel.isWindows
text: qsTr("UWP App")
highlighted: true
onClicked: function(){
@ -97,6 +100,15 @@ Dialog {
confirmed("uwp")
}
}
Button {
visible: uiModel.isWindows
text: qsTr("EGS Game")
highlighted: true
onClicked: function(){
close()
confirmed("egs")
}
}
}
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -29,10 +29,13 @@ CollapsiblePane {
property var shortcutInfo: ({})
readonly property bool isAppSettings: subTitle != ""
content:
Column {
spacing: 16
spacing: 16
id: contentColumn
height: subTitleLabel.height + 16 + advancedLaunchPane.height + 16 + deviceWindowRow.height + 16 + commonPane.height
Label {
id: subTitleLabel
width: parent.width
@ -42,6 +45,7 @@ CollapsiblePane {
}
RPane {
id: advancedLaunchPane
width: parent.width
radius: 4
Material.elevation: 32
@ -62,7 +66,7 @@ CollapsiblePane {
spacing: 2
CheckBox {
id: closeOnExit
text: qsTr("Close when launched app quits")
text: qsTr("Close GlosSI when launched app quits and vice versa")
checked: shortcutInfo.launch.closeOnExit
onCheckedChanged: function() {
shortcutInfo.launch.closeOnExit = checked
@ -74,7 +78,7 @@ CollapsiblePane {
}
}
Label {
text: qsTr("Recommended to disable for launcher-games")
text: qsTr("(Might cause issues with launcher-games)")
wrapMode: Text.WordWrap
width: parent.width
leftPadding: 32
@ -82,12 +86,32 @@ CollapsiblePane {
}
CheckBox {
id: waitForChildren
text: qsTr("Wait for child processes")
text: qsTr("Include child processes")
checked: shortcutInfo.launch.waitForChildProcs
onCheckedChanged: function(){
shortcutInfo.launch.waitForChildProcs = checked
}
}
/*CheckBox {
height: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") ? 32 : 0
visible: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher")
id: ignoreEGS
text: qsTr("Ignore EpicGamesLauncher process")
checked: shortcutInfo.ignoreEGS
onCheckedChanged: function(){
shortcutInfo.ignoreEGS = checked
}
}
CheckBox {
height: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") ? 32 : 0
visible: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher")
id: killEGS
text: qsTr("Kill EpicGamesLauncher process on exit")
checked: shortcutInfo.killEGS
onCheckedChanged: function(){
shortcutInfo.killEGS = checked
}
}*/
}
Column {
spacing: 2
@ -112,10 +136,11 @@ CollapsiblePane {
Row {
spacing: 16
width: parent.width
id: deviceWindowRow
RPane {
width: parent.width / 2 - 8
height: 248
height: 324
radius: 4
Material.elevation: 32
bgOpacity: 0.97
@ -236,8 +261,14 @@ CollapsiblePane {
width: 128
editable: true
value: shortcutInfo.controller.maxControllers
from: 0
from: -1
to: 4
textFromValue: function(value) {
if (value == -1) {
return qsTr("auto");
}
return Number(value);
}
onValueChanged: shortcutInfo.controller.maxControllers = value
}
RoundButton {
@ -246,7 +277,12 @@ CollapsiblePane {
helpInfoDialog.text =
qsTr("GlosSI will only provide [NUMBER] of controllers")
+ "\n"
+ qsTr("Required to set to actually connected controller count when using \"real device IDs\" ")
+ qsTr("-1 ^= auto-detect")
+ "\n"
+ qsTr("auto detection only works upon launch")
+ "\n"
+ "\n"
+ qsTr("Required to manuelly set to actually connected controller count when using \"real device IDs\" ")
helpInfoDialog.open()
}
width: 48
@ -265,7 +301,7 @@ CollapsiblePane {
}
RPane {
width: parent.width / 2 - 8
height: 248
height: 324
radius: 4
Material.elevation: 32
bgOpacity: 0.97
@ -334,6 +370,76 @@ CollapsiblePane {
height: 24
}
}
}
Item {
width: 1
height: 4
}
Row {
CheckBox {
id: hideAltTabCheckbox
text: qsTr("Hide GlosSI from Windowlist (Alt+Tab)")
checked: shortcutInfo.window.hideAltTab
onCheckedChanged: shortcutInfo.window.hideAltTab = checked
}
RoundButton {
onClicked: () => {
helpInfoDialog.titleText = qsTr("Hide GlosSI from Windowlist (Alt+Tab)")
helpInfoDialog.text =
qsTr("Hides GlosSI from the Windowlist (Alt+Tab)")
+ "\n"
+ qsTr("You can close the GlosSI-Window via the system-tray")
+ "\n"
+ "\n"
+ qsTr("Might help with Steam remote play.")
helpInfoDialog.open()
}
width: 48
height: 48
Material.elevation: 0
anchors.topMargin: 16
Image {
anchors.centerIn: parent
source: "qrc:/svg/help_outline_white_24dp.svg"
width: 24
height: 24
}
}
}
Item {
width: 1
height: 4
}
Row {
CheckBox {
id: disableGlosSIOverlayCheckbox
text: qsTr("Disable GlosSI overlay")
checked: shortcutInfo.window.disableGlosSIOverlay
onCheckedChanged: shortcutInfo.window.disableGlosSIOverlay = checked
}
RoundButton {
onClicked: () => {
helpInfoDialog.titleText = qsTr("Disable GlosSI overlay")
helpInfoDialog.text =
qsTr("Disables the additional GlosSI overlay")
+ "\n"
+ qsTr("but keeps the Steam overlay");
helpInfoDialog.open()
}
width: 48
height: 48
Material.elevation: 0
anchors.topMargin: 16
Image {
anchors.centerIn: parent
source: "qrc:/svg/help_outline_white_24dp.svg"
width: 24
height: 24
}
}
}
Item {
width: 1
@ -457,9 +563,107 @@ CollapsiblePane {
radius: 4
Material.elevation: 32
bgOpacity: 0.97
id: commonPane
Column {
spacing: 4
width: parent.width
Column {
width: parent.width
Row {
width: parent.width
Label {
text: qsTr("Launcher processes")
anchors.verticalCenter: parent.verticalCenter
}
RoundButton {
onClicked: () => {
helpInfoDialog.titleText = qsTr("Launcher processes")
helpInfoDialog.text =
qsTr("Tells GlosSI what processes should be treated as launchers")
+ "\n"
qsTr("Only use executable name, not full path")
+ "\n"
qsTr("One process per line")
+ "\n"
+ qsTr("List must be filled for \"")
+ qsTr("Ignore launcher for close detection")
+ qsTr("\" and \"") + qsTr("Close launcher on game exit.")
+ qsTr("\" to work")
helpInfoDialog.open()
}
width: 48
height: 48
Material.elevation: 0
anchors.verticalCenter: parent.verticalCenter
Image {
anchors.centerIn: parent
source: "qrc:/svg/help_outline_white_24dp.svg"
width: 24
height: 24
}
}
}
RPane {
color: Qt.lighter(Material.background, 1.6)
bgOpacity: 0.3
radius: 8
width: parent.width
height: launcherProcessesTextArea.height + 16
Flickable {
width: parent.width
height: parent.height
clip: true
ScrollBar.vertical: ScrollBar {
}
contentWidth: parent.width
flickableDirection: Flickable.VerticalFlick
TextArea {
id: launcherProcessesTextArea
width: parent.width
TextArea.flickable: parent
text: ((shortcutInfo.launch.launcherProcesses || []).length == 0 && (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher"))
? "EpicGamesLauncher.exe\nEpicWebHelper.exe"
: (shortcutInfo.launch.launcherProcesses || [""]).reduce((acc, curr) => {
return acc + "\n" + curr;
})
onTextChanged: function() {
shortcutInfo.launch.launcherProcesses = text.split("\n")
.map((e) => {
e = e.endsWith(".exe") ? e : e + ".exe"
return e.trim()
})
.filter((e) => {
return e != "" && e != ".exe"
});
}
}
}
}
}
Row {
CheckBox {
id: ignoreLauncherCheckbox
text: qsTr("Ignore launcher for close detection")
checked: shortcutInfo.launch.ignoreLauncher
onCheckedChanged: function(){
shortcutInfo.launch.ignoreLauncher = checked
}
}
CheckBox {
id: killLauncherCheckbox
text: qsTr("Close launcher on game exit.")
enabled: ignoreLauncherCheckbox.checked
checked: shortcutInfo.launch.killLauncher
onCheckedChanged: function(){
shortcutInfo.launch.killLauncher = checked
}
}
}
Row {
anchors.topMargin: 24
Row {
CheckBox {
id: extendedLogging

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -0,0 +1,230 @@
/*
Copyright 2021-2023 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.
*/
import QtQuick 6.2
import QtQuick.Controls 6.2
import QtQuick.Layouts 6.2
import QtQuick.Controls.Material 6.2
Dialog {
id: dlg
anchors.centerIn: parent
signal confirmed(var param)
visible: false
modal: true
dim: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: Qt.rgba(0,0,0,0.4)
opacity: backdropOpacity
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
property real backdropOpacity: 1.0
property var unfilteredModel: null;
property var filteredModel: [];
onOpened: function() {
unfilteredModel = null;
unfilteredModel = uiModel.egsList;
listview.model = null;
filteredModel = [];
for(let i = 0; i < unfilteredModel.length; i++)
{
filteredModel.push(unfilteredModel[i])
}
listview.model = filteredModel
}
onClosed: function() {
listview.model = null;
unfilteredModel = null;
filteredModel = null;
}
enter: Transition {
NumberAnimation{target: content; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: background; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: dlg; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad }
}
exit: Transition {
NumberAnimation{target: content; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: background; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: dlg; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad }
}
background: RPane {
id: background
radius: 4
Material.elevation: 64
bgOpacity: 0.97
}
contentItem: Item {
id: content
implicitWidth: listview.width
implicitHeight: listview.height + titlelabel.height + 16 + 64
clip: true
Label {
id: titlelabel
text: qsTr("Select Epic Games Launcher Game...")
font.pixelSize: 24
font.bold: true
}
FluentTextInput {
width: listview.width - 2
x: 1
anchors.top: titlelabel.bottom
anchors.topMargin: 8
id: searchBar
enabled: true
placeholderText: qsTr("Search...")
text: ""
onTextChanged: function() {
listview.model = null;
filteredModel = [];
for(let i = 0; i < unfilteredModel.length; i++)
{
if(unfilteredModel[i].AppName.toLowerCase().includes(searchBar.text.toLowerCase())) {
filteredModel.push(unfilteredModel[i])
}
}
listview.model = filteredModel
}
}
BusyIndicator {
running: visible
anchors.centerIn: parent
opacity: (!unfilteredModel || unfilteredModel.length == 0) ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
}
visible: opacity == 0 ? false : true
}
Button {
anchors.right: parent.right
anchors.top: listview.bottom
anchors.topMargin: 16
anchors.rightMargin: 2
text: qsTr("Cancel")
onClicked: dlg.close()
}
ListView {
anchors.top: searchBar.bottom
anchors.topMargin: 16
id: listview
width: window.width * 0.45
height: window.height * 0.66
spacing: 0
clip: true
model: filteredModel
ScrollBar.vertical: ScrollBar {
}
opacity: (!unfilteredModel || unfilteredModel.length == 0) ? 0 : 1
Behavior on opacity {
ParallelAnimation {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
PropertyAnimation {
target: listview
property: "anchors.topMargin"
from: window.height * 0.75
to: 16
duration: 350
easing.type: Easing.InOutQuad
}
}
}
delegate: Item {
width: listview.width
height: textcolumn.implicitHeight > 72 ? 500 : 72
/*Image {
id: maybeIcon
width: textcolumn.implicitHeight > 72 ? 0 : 56
height: 56
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
source: "file:///" + modelData.IconPath
mipmap: true
smooth: true
}*/
Column {
id: textcolumn
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.verticalCenter: parent.verticalCenter
spacing: 2
Label {
text: modelData.InstallLocation.split('/').pop().split('\\').pop().replace(/([a-z])([A-Z])/g, '$1 $2')
font.pixelSize: 18
font.bold: true
}
Label {
id: appNameLabel
text: "AppName: " + modelData.AppName
font.pixelSize: 12
wrapMode: Text.WordWrap
width: parent.width
}
Label {
id: fullPathLabel
text: modelData.InstallLocation
font.pixelSize: 12
color: '#888888'
wrapMode: Text.WordWrap
width: parent.width
}
}
Rectangle {
anchors.bottom: parent.bottom
height: 1
width: parent.width
color: Qt.rgba(1,1,1,0.25)
}
MouseArea {
anchors.fill: parent
onClicked: function(){
confirmed(modelData)
dlg.close();
}
}
}
}
}
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -81,6 +81,25 @@ Item {
config.snapshotNotify = checked
}
}
}
Row {
leftPadding: 12
Row {
spacing: 16
Label {
topPadding: 8
id: apiKeyLabel
font.bold: true
text: qsTr("SteamGridDB-API-Key")
}
FluentTextInput {
width: 128
id: apiKeyInput
placeholderText: qsTr("...")
text: config.steamgridApiKey
onTextChanged: config.steamgridApiKey = text
}
}
}
}
}
@ -99,6 +118,85 @@ Item {
+ "as well as settings applied when launching GlosSITarget without config")
}
Item {
width: 1
height: 4
}
RPane {
width: parent.width
radius: 4
Material.elevation: 32
bgOpacity: 0.97
Column {
width: parent.width
height: parent.height
spacing: 4
Label {
font.bold: true
font.pixelSize: 24
text: qsTr("Experimental 🧪")
}
Row {
Row {
CheckBox {
id: globalModeUseGamepadUI
text: qsTr("Use BPM for global-/desktop-mode")
checked: config.globalModeUseGamepadUI
onCheckedChanged: config.globalModeUseGamepadUI = checked
}
}
}
Row {
leftPadding: 12
Row {
spacing: 16
Label {
topPadding: 8
id: globalModeGameIdLabel
text: qsTr("GlobalMode GameId")
}
FluentTextInput {
width: 128
id: globalModeGameId
enabled: false
text: config.globalModeGameId
onTextChanged: config.globalModeGameId = text
}
Button {
id: globalModeGameIdButton
text: qsTr("Create global-/desktop-mode shortcut")
onClicked: {
const globalModeConf = uiModel.getDefaultConf();
globalModeConf.name = "GlosSI GlobalMode/Desktop";
globalModeConf.launch.launch = false;
uiModel.addTarget(globalModeConf);
if (uiModel.addToSteam(globalModeConf, "")) {
steamChangedDialog.open();
}
const globalModeGID = uiModel.globalModeShortcutGameId();
globalModeGameId.text = globalModeGID;
setTimeout(() => {
uiModel.saveDefaultConf(config);
done();
}, 10);
}
highlighted: true
visible: !uiModel.globalModeShortcutExists()
}
Button {
id: globalModeGameIdConfigButton
text: qsTr("Open global-/desktop-mode controller config")
onClicked: {
Qt.openUrlExternally("steam://currentcontrollerconfig/" + uiModel.globalModeShortcutAppId() + "/");
}
visible: uiModel.globalModeShortcutExists()
}
}
}
}
}
Item {
width: 1
height: 32

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -15,6 +15,9 @@ limitations under the License.
*/
import QtQuick 6.2
import QtQuick.Controls 6.2
import QtQuick.Controls.Material 6.2
import QtQuick.Dialogs 6.2
Dialog {
id: dlg
anchors.centerIn: parent

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -23,6 +23,8 @@ Pane {
property int radius: 0
property color color: control.Material.backgroundColor
property real bgOpacity: 1
property string bgImgSource: null
property real bgImgOpacity: -1
background: Rectangle {
color: parent.color
opacity: parent.bgOpacity
@ -33,13 +35,14 @@ Pane {
elevation: control.Material.elevation
}
Image {
id: bgImage
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
source: "qrc:/noise.png"
fillMode: Image.Tile
opacity: 0.035
source: bgImgSource ? bgImgSource : "qrc:/noise.png"
fillMode: bgImgSource ? Image.PreserveAspectCrop : Image.Tile
opacity: bgImgOpacity < 0 ? 0.035 : bgImgOpacity
}
}
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -13,11 +13,11 @@ 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.
*/
import QtQuick 6.2
import QtQuick.Layouts 6.2
import QtQuick.Controls 6.2
import QtQuick.Controls.Material 6.2
import QtQuick.Dialogs 6.2
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
GridView {
@ -34,14 +34,12 @@ GridView {
property real margins: 16
cellWidth: 292 + 16
cellHeight: 190 + 16
cellHeight: 212 + 16
readonly property real displayedItems: Math.floor((parent.width - margins*2) / cellWidth)
width: displayedItems * cellWidth
model: uiModel.targetList;
GridView.delayRemove: true
property var manualInfo: null
// TODO: animations only properly work with abstractListModel... grrr...
addDisplaced: Transition {
NumberAnimation { properties: "x,y"; duration: 300 }
@ -67,134 +65,6 @@ GridView {
NumberAnimation { properties: "x,y"; duration: 300; easing.type: Easing.InQuad }
}
Dialog {
id: manualAddDialog
anchors.centerIn: parent
visible: false
modal: true
dim: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: Qt.rgba(0,0,0,0.4)
opacity: backdropOpacity
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
property real backdropOpacity: 1.0
enter: Transition {
NumberAnimation{target: madcontent; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: madbackground; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad }
}
exit: Transition {
NumberAnimation{target: madcontent; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: madbackground; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad }
}
background: RPane {
id: madbackground
radius: 4
Material.elevation: 64
bgOpacity: 0.97
}
contentItem: Item {
id: madcontent
implicitWidth: steamscreener.width
implicitHeight: madtext.height + 16 + steamscreener.height + 16 + madrow.height
Label {
id: madtext
text: qsTr("Add \"GlosSITarget\" as game to Steam and change it's properties (in Steam) to this:")
}
Image {
anchors.top: madtext.bottom
anchors.left: madtext.left
anchors.topMargin: 16
id: steamscreener
source: "qrc:/steamscreener.png"
}
FluentTextInput {
id: madnameinput
text: manualInfo ? manualInfo.name : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 72
anchors.leftMargin: 92
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: glossiPathInput
text: manualInfo ? manualInfo.launch : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 192
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: startDirInput
text: manualInfo ? manualInfo.launchDir : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 266
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: launchOptsInput
text: manualInfo ? manualInfo.config : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 432
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
Row {
id: madrow
anchors.top: steamscreener.bottom
anchors.topMargin: 16
spacing: 16
Button {
text: qsTr("OK")
onClicked: function(){
manualAddDialog.close()
}
}
anchors.right: parent.right
}
}
}
InfoDialog {
id: writeErrorDialog
titleText: qsTr("Error")
text: qsTr("Error writing shortcuts.vdf...\nPlease make sure Steam is running")
extraButton: true
extraButtonText: qsTr("Manual instructions")
onConfirmedExtra: function(data) {
manualAddDialog.open();
}
}
delegate: RPane {
id: delegateRoot
@ -202,10 +72,12 @@ GridView {
bgOpacity: 0.3
radius: 8
width: 292
height: 190
height: 212
Material.elevation: 4
clip: true
property bool isInSteam: uiModel.isInSteam(modelData);
bgImgSource: isInSteam ? "file:///" + uiModel.getGridImagePath(modelData) : null
bgImgOpacity: isInSteam ? 0.12 : -1
Image {
anchors.top: parent.top
@ -215,10 +87,10 @@ GridView {
? modelData.icon.endsWith(".exe")
? "image://exe/" + modelData.icon
: "file:///" + modelData.icon
: null
: 'qrc:/svg/add_photo_alternate_white_24dp.svg'
width: 48
height: 48
visible: !!modelData.icon
fillMode: Image.PreserveAspectFit
}
Label {
@ -261,89 +133,85 @@ GridView {
}
}
Row {
visible: false // TODO: dunno about this...
visible: uiModel.isDebug
spacing: 4
Label {
text: qsTr("Is in")
text: qsTr("AppID: ")
font.bold: true
}
Image {
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/svg/steam.svg"
width: 16
height: 16
smooth: true
mipmap: true
ColorOverlay {
anchors.fill: parent
source: parent
color: "white"
}
}
Item {
width: 4
height: 1
}
Label {
anchors.verticalCenter: parent.verticalCenter
text: delegateRoot.isInSteam ? qsTr("Yes") : qsTr("No")
width: 292 - typeLabel.width - 72
elide: Text.ElideRight
text: uiModel.getAppId(modelData)
}
}
}
Button {
id: steambutton
anchors.left: parent.left
Column {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: 72
onClicked: function(){
if (delegateRoot.isInSteam) {
if (!uiModel.removeFromSteam(modelData.name, "")) {
writeErrorDialog.open();
return;
}
} else {
if (!uiModel.addToSteam(modelData, "")) {
manualInfo = uiModel.manualProps(modelData);
writeErrorDialog.open();
return;
}
}
if (steamShortcutsChanged == false) {
steamChangedDialog.open();
spacing: 4
Button {
highlighted: true
visible: delegateRoot.isInSteam
text: qsTr("Steam controller config")
onClicked: function() {
controllerConfigDialog.confirmedExtraParam = uiModel.getAppId(modelData)
controllerConfigDialog.confirmedParam = uiModel.getAppId(modelData)
controllerConfigDialog.open();
}
delegateRoot.isInSteam = uiModel.isInSteam(modelData)
steamShortcutsChanged = true
}
highlighted: delegateRoot.isInSteam
Material.accent: Material.color(Material.Red, Material.Shade800)
Row {
anchors.centerIn: parent
spacing: 8
Label {
anchors.verticalCenter: parent.verticalCenter
text: delegateRoot.isInSteam ? "-" : "+"
font.bold: true
font.pixelSize: 24
Button {
id: steambutton
width: 72
onClicked: function(){
if (delegateRoot.isInSteam) {
if (!uiModel.removeFromSteam(modelData.name, "")) {
writeErrorDialog.open();
return;
}
} else {
if (!uiModel.addToSteam(modelData, "")) {
manualInfo = uiModel.manualProps(modelData);
writeErrorDialog.open();
return;
}
}
if (steamShortcutsChanged == false) {
steamChangedDialog.open();
}
delegateRoot.isInSteam = uiModel.isInSteam(modelData)
steamShortcutsChanged = true
}
Image {
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/svg/steam.svg"
width: 22
height: 22
smooth: true
mipmap: true
ColorOverlay {
anchors.fill: parent
source: parent
color: "white"
highlighted: delegateRoot.isInSteam
Material.accent: Material.color(Material.Red, Material.Shade800)
Row {
anchors.centerIn: parent
spacing: 8
Label {
anchors.verticalCenter: parent.verticalCenter
text: delegateRoot.isInSteam ? "-" : "+"
font.bold: true
font.pixelSize: 24
}
Image {
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/svg/steam.svg"
width: 22
height: 22
smooth: true
mipmap: true
ColorOverlay {
anchors.fill: parent
source: parent
color: "white"
}
}
}
}
}
Row {
id: buttonrow
anchors.right: parent.right

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -13,11 +13,12 @@ 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.
*/
import QtQuick 6.2
import QtQuick.Controls 6.2
import QtQuick.Layouts 6.2
import QtQuick.Controls.Material 6.2
import QtQuick.Dialogs 6.2
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
Item {
@ -26,6 +27,7 @@ Item {
property alias fileDialog: fileDialog
property alias uwpSelectDialog: uwpSelectDialog
property alias egsSelectDialog: egsSelectDialog
signal cancel()
signal done(var shortcut)
@ -52,6 +54,39 @@ Item {
if (advancedTargetSettings) { // advanced settings (collapsible container)
advancedTargetSettings.shortcutInfo = shortcutInfo;
}
if (maybeIcon) {
maybeIcon.source = shortcutInfo.icon
? shortcutInfo.icon.endsWith(".exe")
? "image://exe/" + shortcutInfo.icon
: "file:///" + shortcutInfo.icon
: 'qrc:/svg/add_photo_alternate_white_24dp.svg';
}
}
Image {
id: bgImage
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
fillMode: Image.PreserveAspectCrop
source: "file:///" + uiModel.getGridImagePath(shortcutInfo)
autoTransform: true
opacity: 0
}
LinearGradient {
id: mask
anchors.fill: bgImage
gradient: Gradient {
GradientStop { position: 0.0; color: "#afFFFFFF"}
GradientStop { position: 0.7; color: "transparent" }
GradientStop { position: 1; color: "transparent" }
}
}
OpacityMask {
source: bgImage
maskSource: mask
anchors.fill: bgImage
}
Flickable {
@ -101,7 +136,11 @@ Item {
id: nameInput
placeholderText: qsTr("...")
text: shortcutInfo.name
onTextChanged: shortcutInfo.name = text
onTextChanged: function() {
shortcutInfo.oldName = shortcutInfo.oldName || shortcutInfo.name
shortcutInfo.name = nameInput.text
shortcutInfo = shortcutInfo
}
validator: RegularExpressionValidator { regularExpression: /([0-z]|\s|.)+/gm }
}
}
@ -152,24 +191,28 @@ Item {
spacing: 4
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 32
anchors.rightMargin: 32
Image {
id: maybeIcon
source: shortcutInfo.icon
? shortcutInfo.icon.endsWith(".exe")
? "image://exe/" + shortcutInfo.icon
: "file:///" + shortcutInfo.icon
: ''
Layout.preferredWidth: 48
Layout.preferredHeight: 48
visible: shortcutInfo.icon
anchors.leftMargin: 8
anchors.rightMargin: 16
Button {
id: iconButton
Layout.preferredWidth: 56
Layout.preferredHeight: 64
Layout.alignment: Qt.AlignVCenter
flat: true
contentItem: Image {
id: maybeIcon
fillMode: Image.PreserveAspectFit
source: shortcutInfo.icon
? shortcutInfo.icon.endsWith(".exe")
? "image://exe/" + shortcutInfo.icon
: "file:///" + shortcutInfo.icon
: 'qrc:/svg/add_photo_alternate_white_24dp.svg'
}
onClicked: iconFileDialog.open()
}
Item {
Layout.preferredWidth: 8
Layout.preferredHeight: 8
visible: shortcutInfo.icon
}
Item {
Layout.preferredWidth: parent.width / 2
@ -190,7 +233,10 @@ Item {
placeholderText: qsTr("...")
enabled: launchApp.checked
text: shortcutInfo.launch.launchPath || ""
onTextChanged: shortcutInfo.launch.launchPath = text
onTextChanged: function() {
shortcutInfo.launch.launchPath = text
shortcutInfo = shortcutInfo
}
}
}
Button {
@ -206,6 +252,13 @@ Item {
visible: uiModel.isWindows
onClicked: uwpSelectDialog.open();
}
Button {
Layout.preferredWidth: 64
Layout.alignment: Qt.AlignBottom
text: qsTr("EGS")
visible: uiModel.isWindows
onClicked: egsSelectDialog.open();
}
Item {
height: 1
Layout.preferredWidth: 12
@ -240,7 +293,6 @@ Item {
AdvancedTargetSettings {
id: advancedTargetSettings
shortcutInfo: shortcutInfo
}
Item {
@ -283,9 +335,25 @@ Item {
pathInput.text = fileDialog.selectedFile.toString().replace("file:///", "")
if (nameInput.text == "") {
nameInput.text = pathInput.text.replace(/.*(\\|\/)/,"").replace(/\.[0-z]*$/, "")
shortcutInfo.icon = nameInput.text
}
shortcutInfo.icon = pathInput.text
launchApp.checked = true
}
shortcutInfo = shortcutInfo;
}
onRejected: {
}
}
FileDialog {
id: iconFileDialog
title: qsTr("Please choose an icon")
nameFilters: uiModel.isWindows ? ["Image/Executable (*.exe *.png *.ico *.jpg)"] : ["Image (*.png *.ico *.jpg)"]
onAccepted: {
if (iconFileDialog.selectedFile != null) {
shortcutInfo.icon = iconFileDialog.selectedFile.toString().replace("file:///", "")
shortcutInfo = shortcutInfo;
}
}
onRejected: {
@ -306,6 +374,21 @@ Item {
launchApp.checked = true
}
}
EGSSelectDialog {
id: egsSelectDialog
onConfirmed: function(modelData) {
if (nameInput.text == "") {
nameInput.text = modelData.InstallLocation.split('/').pop().split('\\').pop().replace(/([a-z])([A-Z])/g, '$1 $2')
}
pathInput.text = "com.epicgames.launcher://apps/"
+ modelData.NamespaceId
+ "%3A"
+ modelData.ItemId
+ "%3A"
+ modelData.ArtifactId + "?action=launch&silent=true"
launchApp.checked = true
}
}
InfoDialog {
id: helpInfoDialog

@ -0,0 +1,191 @@
/*
Copyright 2021-2023 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.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Controls.Material
Dialog {
id: gridDialog
anchors.centerIn: parent
signal confirmed(var param)
visible: false
modal: true
dim: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: Qt.rgba(0,0,0,0.4)
opacity: backdropOpacity
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
property real backdropOpacity: 1.0
property bool loading: true
property bool hasTokenError: false
onOpened: function() {
loading = true;
uiModel.loadSteamGridImages();
}
onClosed: function() {
}
enter: Transition {
NumberAnimation{target: content; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: background; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: gridDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad }
}
exit: Transition {
NumberAnimation{target: content; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: background; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: gridDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad }
}
background: RPane {
id: background
radius: 4
Material.elevation: 64
bgOpacity: 0.97
}
contentItem: Item {
id: content
implicitWidth: listview.width
implicitHeight: listview.height + titlelabel.height + 16 + 64
clip: true
Label {
id: titlelabel
text: qsTr("Loading Grid images...")
font.pixelSize: 24
font.bold: true
}
Item {
id: topContentContainer
anchors.top: titlelabel.bottom
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
anchors.left: parent.left
anchors.right: parent.right
height: loading || hasTokenError ? 72 : 0
BusyIndicator {
id: busyIndicator
running: visible
anchors.horizontalCenter: parent.horizontalCenter
opacity: loading ? 1 : 0
height: loading ? 72 : 0
Behavior on opacity {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
}
visible: loading
}
Label {
id: tokenErrorInfo
anchors.horizontalCenter: parent.horizontalCenter
opacity: hasTokenError ? 1 : 0
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.Wrap
font.bold: true
color: "yellow"
textFormat: TextEdit.RichText
onLinkActivated: Qt.openUrlExternally(link)
Behavior on opacity {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
}
visible: hasTokenError
text: qsTr("Please go to <a href=\"https://www.steamgriddb.com/profile/preferences/api\">SteamGridDB</a> and gerenate a new API token. Then paste it into the settings dialog.")
}
}
ListView {
anchors.top: topContentContainer.bottom
anchors.topMargin: 16
anchors.bottom: parent.bottom
anchors.bottomMargin: 16
id: listview
width: window.width * 0.45
height: window.height * 0.66
spacing: 0
clip: true
model: uiModel.steamgridOutput
ScrollBar.vertical: ScrollBar {
}
onCountChanged: function() {
listview.positionViewAtIndex(listview.count - 1, ListView.Visible);
hasTokenError = listview.model.some((l) => l.includes("missing or invalid"));
loading = !(listview.model.some((l) => l.includes("Press enter")) || hasTokenError);
}
Behavior on opacity {
ParallelAnimation {
NumberAnimation {
duration: 350
easing.type: Easing.InOutQuad
}
PropertyAnimation {
target: listview
property: "anchors.topMargin"
from: window.height * 0.75
to: 16
duration: 350
easing.type: Easing.InOutQuad
}
}
}
delegate: /* Item {
width: listview.width
height: outputLabel.implicitHeight */
Label {
id: outputLabel
text: modelData
}
// }
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 2
anchors.rightMargin: 2
text: qsTr("Ok")
onClicked: function() {
gridDialog.close();
confirmed(undefined);
}
}
}
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -13,10 +13,12 @@ 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.
*/
import QtQuick 6.2
import QtQuick.Controls 6.2
import QtQuick.Layouts 6.2
import QtQuick.Controls.Material 6.2
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
Dialog {
id: dlg
@ -80,10 +82,6 @@ Dialog {
}
Button {
anchors.right: parent.right
anchors.top: listview.bottom
anchors.topMargin: 16
anchors.rightMargin: 2
text: qsTr("Ok")
onClicked: dlg.close()
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -126,15 +126,6 @@ Dialog {
visible: opacity == 0 ? false : true
}
Button {
anchors.right: parent.right
anchors.top: listview.bottom
anchors.topMargin: 16
anchors.rightMargin: 2
text: qsTr("Cancel")
onClicked: dlg.close()
}
ListView {
anchors.top: searchBar.bottom
anchors.topMargin: 16
@ -218,5 +209,14 @@ Dialog {
}
}
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 2
anchors.rightMargin: 2
text: qsTr("Cancel")
onClicked: dlg.close()
}
}
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -13,9 +13,12 @@ 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.
*/
import QtQuick 6.2
import QtQuick.Layouts 6.2
import QtQuick.Controls.Material 6.2
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
Window {
id: window
@ -26,6 +29,7 @@ Window {
Material.accent: Material.color(Material.Blue, Material.Shade900)
property bool itemSelected: false;
property var manualInfo: null
title: qsTr("GlosSI - Config")
@ -44,6 +48,12 @@ Window {
}
property bool steamShortcutsChanged: false
onSteamShortcutsChanged: function() {
shouldShowLoadGridImagesButton = uiModel.targetList.some((shortcut) => uiModel.isInSteam(shortcut))
}
property bool shouldShowLoadGridImagesButton: false
Component.onCompleted: function() {
if (!uiModel.foundSteam) {
@ -53,6 +63,7 @@ Window {
if (!uiModel.steamInputXboxSupportEnabled) {
steamXboxDisabledDialog.open();
}
shouldShowLoadGridImagesButton = uiModel.targetList.some((shortcut) => uiModel.isInSteam(shortcut))
}
Image {
@ -117,14 +128,160 @@ Window {
titleText: qsTr("New version available!")
text: uiModel.newVersionName + "\n\n" + qsTr("Would you like to visit the download page now?")
onConfirmed: function (callback) {
Qt.openUrlExternally(`https://glossi.flatspot.pictures/#downloads${ uiModel.newVersionName.includes('snapshot') ? '-snapshots' : '' }-${uiModel.newVersionName}`);
callback();
Qt.openUrlExternally(`https://glossi.flatspot.pictures/#downloads-${uiModel.newVersionName}`);
}
buttonText: qsTr("Yes")
extraButton: true
extraButtonText: qsTr("Remind me later")
visible: !!uiModel.newVersionName
}
Dialog {
id: manualAddDialog
anchors.centerIn: parent
visible: false
modal: true
dim: true
parent: Overlay.overlay
Overlay.modal: Rectangle {
color: Qt.rgba(0,0,0,0.4)
opacity: backdropOpacity
Behavior on opacity {
NumberAnimation {
duration: 300
}
}
}
property real backdropOpacity: 1.0
enter: Transition {
NumberAnimation{target: madcontent; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: madbackground; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad }
NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad }
}
exit: Transition {
NumberAnimation{target: madcontent; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: madbackground; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad }
NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad }
}
background: RPane {
id: madbackground
radius: 4
Material.elevation: 64
bgOpacity: 0.97
}
contentItem: Item {
id: madcontent
implicitWidth: steamscreener.width
implicitHeight: madtext.height + 16 + steamscreener.height + 16 + madrow.height
Label {
id: madtext
text: qsTr("Add \"GlosSITarget\" as game to Steam and change it's properties (in Steam) to this:")
}
Image {
anchors.top: madtext.bottom
anchors.left: madtext.left
anchors.topMargin: 16
id: steamscreener
source: "qrc:/steamscreener.png"
}
FluentTextInput {
id: madnameinput
text: manualInfo ? manualInfo.name : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 72
anchors.leftMargin: 92
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: glossiPathInput
text: manualInfo ? manualInfo.launch : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 192
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: startDirInput
text: manualInfo ? manualInfo.launchDir : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 266
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
FluentTextInput {
id: launchOptsInput
text: manualInfo ? manualInfo.config : ""
anchors.top: steamscreener.top
anchors.left: steamscreener.left
anchors.topMargin: 432
anchors.leftMargin: 24
readOnly: true
background: Item {}
width: 550
}
Row {
id: madrow
anchors.top: steamscreener.bottom
anchors.topMargin: 16
spacing: 16
Button {
text: qsTr("OK")
onClicked: function(){
manualAddDialog.close()
}
}
anchors.right: parent.right
}
}
}
InfoDialog {
id: writeErrorDialog
titleText: qsTr("Error")
text: qsTr("Error writing shortcuts.vdf...\nPlease make sure Steam is running")
extraButton: true
extraButtonText: qsTr("Manual instructions")
onConfirmedExtra: function(data) {
manualAddDialog.open();
}
}
InfoDialog {
id: controllerConfigDialog
titleText: qsTr("Open Steam controller config")
text: qsTr("Steams controller config can only open if a controller is connected")
+ "\n"
+ qsTr("and the shortcut is visible in Steam (Steam restarted after adding).")
buttonText: qsTr("Open")
extraButton: true
extraButtonText: qsTr("Restart and open")
onConfirmedExtra: function(data) {
uiModel.restartSteam("steam://currentcontrollerconfig/" + data + "/")
}
onConfirmed: function(data) {
Qt.openUrlExternally("steam://currentcontrollerconfig/" + data + "/");
}
}
Rectangle {
id: titleBar
@ -237,47 +394,64 @@ Window {
windowContent.editedIndex = index;
}
}
Column {
Row {
spacing: 8
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 24
RoundButton {
id: optionsBtn
width: 64
height: 64
text: ""
contentItem: Item {
Image {
Column {
spacing: 8
RoundButton {
id: optionsBtn
anchors.right: parent.right
width: 64
height: 64
text: ""
contentItem: Item {
Image {
anchors.centerIn: parent
source: "qrc:/svg/settings_fill_white_24dp.svg"
width: 24
height: 24
}
}
highlighted: true
onClicked: function() {
globalConf.opacity = 1;
homeContent.opacity = 0;
}
}
RoundButton {
id: addBtn
anchors.right: parent.right
width: 64
height: 64
text: "+"
contentItem: Label {
anchors.centerIn: parent
source: "qrc:/svg/settings_fill_white_24dp.svg"
width: 24
height: 24
text: addBtn.text
font.pixelSize: 32
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
highlighted: true
onClicked: function() {
globalConf.opacity = 1;
homeContent.opacity = 0;
highlighted: true
onClicked: selectTypeDialog.open()
}
}
RoundButton {
id: addBtn
width: 64
height: 64
text: "+"
contentItem: Label {
anchors.centerIn: parent
text: addBtn.text
font.pixelSize: 32
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
Button {
visible: shouldShowLoadGridImagesButton || steamShortcutsChanged
id: loadGridImagesBtn
text: qsTr("🖼️ Load steam grid images")
highlighted: true
onClicked: function() {
steamGridDialog.open()
}
}
highlighted: true
onClicked: selectTypeDialog.open()
}
}
}
Item {
@ -318,7 +492,16 @@ Window {
if (windowContent.editedIndex < 0) {
uiModel.addTarget(shortcut)
} else {
uiModel.updateTarget(windowContent.editedIndex, shortcut)
if (uiModel.updateTarget(windowContent.editedIndex, shortcut)) {
if (uiModel.isInSteam(shortcut) && steamShortcutsChanged == false) {
steamChangedDialog.open();
}
} else {
if (uiModel.isInSteam(shortcut)) {
manualInfo = uiModel.manualProps(shortcut);
writeErrorDialog.open();
}
}
}
}
}
@ -387,7 +570,19 @@ Window {
if (param == "uwp") {
props.uwpSelectDialog.open();
}
if (param == "egs") {
props.egsSelectDialog.open();
}
}
}
SteamGridDialog {
id: steamGridDialog
onConfirmed: function() {
shortcutgrid.model = [];
shortcutgrid.model = uiModel.targetList;
}
}
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#FFFFFF"><path d="M29.45 6v3H9v30h30V18.6h3V39q0 1.2-.9 2.1-.9.9-2.1.9H9q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6ZM38 6v4.05h4.05v3H38v4.05h-3v-4.05h-4.05v-3H35V6ZM12 33.9h24l-7.2-9.6-6.35 8.35-4.7-6.2ZM9 9v30V9Z"/></svg>

After

Width:  |  Height:  |  Size: 299 B

@ -0,0 +1,37 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../deps/SFML/include",
"${workspaceFolder}/../deps/WinReg",
"${workspaceFolder}/../deps/spdlog/include",
"${workspaceFolder}/../deps/ValveFileVDF",
"${workspaceFolder}/../deps/subhook",
"${workspaceFolder}/../deps/ViGEmClient/include",
"${workspaceFolder}/../deps/imgui",
"${workspaceFolder}/../deps/imgui-sfml",
"${workspaceFolder}/../deps/json/include",
"${workspaceFolder}/../deps/traypp/tray/include",
"${workspaceFolder}/../deps/cpp-httplib",
"${workspaceFolder}/../CEFInjectLib"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"SPDLOG_WCHAR_TO_UTF8_SUPPORT",
"SPDLOG_WCHAR_FILENAMES",
"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING",
"SUBHOOK_STATIC"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "msvc-x64"
}
],
"version": 4
}

@ -5,24 +5,18 @@
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"name": "Launch GlosSITarget",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/GlosSITarget",
"preLaunchTask": "Build",
"args": [],
"program": "${workspaceFolder}/../x64/Debug/GlosSITarget.exe",
"preLaunchTask": "Build GlosSITarget (Debug)",
"args": [
"-window"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build",
"cwd": "${workspaceFolder}/../x64/Debug",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

@ -1,3 +0,0 @@
{
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
}

@ -1,15 +1,43 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cmake",
"command": "build",
"label": "Build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
}
]
}
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build GlosSITarget (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSITarget",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build GlosSITarget (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSITarget:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -23,14 +23,19 @@ limitations under the License.
#include <tlhelp32.h>
#include <Propsys.h>
#include <propkey.h>
#include <shellapi.h>
#include <winerror.h>
#pragma comment(lib, "Shell32.lib")
#endif
#include "Settings.h"
#include "../common/Settings.h"
#include <regex>
#include "UnhookUtil.h"
#include "HttpServer.h"
#include "Overlay.h"
#include "../common/UnhookUtil.h"
#include "../common/util.h"
AppLauncher::AppLauncher(
std::vector<HWND>& process_hwnds,
@ -43,11 +48,50 @@ AppLauncher::AppLauncher(
spdlog::debug("App launch requested");
}
#endif
HttpServer::AddEndpoint({
"/launched-pids",
HttpServer::Method::GET,
[this](const httplib::Request& req, httplib::Response& res) {
const nlohmann::json j = launchedPids();
res.set_content(j.dump(), "text/json");
},
{1, 2, 3},
});
HttpServer::AddEndpoint({
"/launched-pids",
HttpServer::Method::POST,
[this](const httplib::Request& req, httplib::Response& res) {
try {
const nlohmann::json postbody = nlohmann::json::parse(req.body);
addPids(postbody.get<std::vector<DWORD>>());
}
catch (std::exception& e) {
res.status = 401;
res.set_content(nlohmann::json{
{"code", 401},
{"name", "Bad Request"},
{"message", e.what()},
}
.dump(),
"text/json");
}
},
{1, 2, 3, 4},
{2, 3, 4},
});
};
void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args)
{
#ifdef _WIN32
if (!Settings::launch.launcherProcesses.empty()) {
has_extra_launchers_ = true;
spdlog::debug("Has extra launchers");
}
if (Settings::launch.isUWP) {
spdlog::info("LaunchApp is UWP, launching...");
launched_uwp_path_ = path;
@ -61,19 +105,38 @@ void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args)
spdlog::info("LaunchApp is Win32, launching...");
launchWin32App(path, args);
}
Overlay::AddOverlayElem([this](bool has_focus, ImGuiID dockspace_id) {
ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Launched Processes")) {
ImGui::BeginChild("Inner##LaunchedProcs", {0.f, ImGui::GetItemRectSize().y - 64}, true);
std::ranges::for_each(pids_, [](DWORD pid) {
ImGui::Text("%s | %d", util::string::to_string(util::win::process::GetProcName(pid)).c_str(), pid);
ImGui::SameLine();
if (ImGui::Button((" Kill ##" + std::to_string(pid)).c_str())) {
util::win::process::KillProcess(pid);
}
});
ImGui::EndChild();
}
ImGui::End();
});
#endif
}
void AppLauncher::update()
{
if (process_check_clock_.getElapsedTime().asMilliseconds() > 250) {
pid_mutex_.lock();
#ifdef _WIN32
if (has_extra_launchers_ && pids_.empty()) {
findLauncherPids();
}
if (!pids_.empty() && pids_[0] > 0) {
if (Settings::launch.waitForChildProcs) {
getChildPids(pids_[0]);
}
if (!IsProcessRunning(pids_[0])) {
spdlog::info("Launched App with PID \"{}\" died", pids_[0]);
spdlog::info(L"Launched App \"{}\" with PID \"{}\" died", util::win::process::GetProcName(pids_[0]), pids_[0]);
if (Settings::launch.closeOnExit && !Settings::launch.waitForChildProcs && Settings::launch.launch) {
spdlog::info("Configured to close on exit. Shutting down...");
shutdown_();
@ -82,22 +145,41 @@ void AppLauncher::update()
}
}
if (Settings::launch.waitForChildProcs) {
std::erase_if(pids_, [](auto pid) {
if (pid == 0) {
return true;
}
const auto running = IsProcessRunning(pid);
if (!running)
spdlog::trace("Child process with PID \"{}\" died", pid);
spdlog::trace(L"Child process \"{}\" with PID \"{}\" died", util::win::process::GetProcName(pid), pid);
return !running;
});
if (Settings::launch.closeOnExit && pids_.empty() && Settings::launch.launch) {
spdlog::info("Configured to close on all children exit. Shutting down...");
shutdown_();
auto filtered_pids = pids_ | std::ranges::views::filter([](DWORD pid) {
return std::ranges::find(Settings::launch.launcherProcesses, util::win::process::GetProcName(pid)) == Settings::launch.launcherProcesses.end();
});
if (has_extra_launchers_ && !filtered_pids.empty()) {
launcher_has_launched_game_ = true;
}
if (Settings::launch.closeOnExit && Settings::launch.launch) {
if (has_extra_launchers_ && (Settings::launch.ignoreLauncher || Settings::launch.killLauncher)) {
if (launcher_has_launched_game_ && filtered_pids.empty()) {
spdlog::info("Configured to close on all children exit. Shutting down after game launched via EGS quit...");
shutdown_();
}
}
else {
if (pids_.empty()) {
spdlog::info("Configured to close on all children exit. Shutting down...");
shutdown_();
}
}
}
}
getProcessHwnds();
#endif
pid_mutex_.unlock();
process_check_clock_.restart();
}
}
@ -112,28 +194,41 @@ void AppLauncher::close()
#endif
}
void AppLauncher::launchWatchdog()
std::vector<DWORD> AppLauncher::launchedPids()
{
// wchar_t buff[MAX_PATH];
// GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH);
// const std::wstring glossipath(buff);
// GetWindowsDirectory(buff, MAX_PATH);
// const std::wstring winpath(buff);
// launchWin32App(
// (glossipath.substr(0, 1 + glossipath.find_last_of(L'\\')) + L"GlosSIWatchdog.exe"),
// L"",
// true);
spdlog::debug("Launching GlosSIWatchdog");
char buff[MAX_PATH];
GetModuleFileNameA(GetModuleHandle(NULL), buff, MAX_PATH);
const std::string glossipath(buff);
// hack to start a TRULY detached process...
const auto launchString = ("start /b cmd.exe /c \"" + (glossipath.substr(0, 1 + glossipath.find_last_of(L'\\')) + "GlosSIWatchdog.exe" + "\""));
system(launchString.data());
pid_mutex_.lock();
std::vector<DWORD> res;
res.reserve(pids_.size());
if (!Settings::launch.killLauncher && Settings::launch.ignoreLauncher) {
for (const auto& pid : pids_ | std::ranges::views::filter(
[](DWORD pid) {
return std::ranges::find(
Settings::launch.launcherProcesses,
util::win::process::GetProcName(pid)) == Settings::launch.launcherProcesses.end();
})) {
res.push_back(pid);
}
}
else {
std::ranges::copy(pids_.begin(), pids_.end(),
std::back_inserter(res));
}
pid_mutex_.unlock();
return res;
}
void AppLauncher::addPids(const std::vector<DWORD>& pids)
{
pid_mutex_.lock();
for (const auto pid : pids) {
if (pid > 0 && std::ranges::find(pids_, pid) == pids_.end()) {
if (Settings::common.extendedLogging) {
spdlog::debug("Added PID {} via API", pid);
}
pids_.push_back(pid);
}
}
pid_mutex_.unlock();
}
#ifdef _WIN32
@ -156,10 +251,11 @@ void AppLauncher::getChildPids(DWORD parent_pid)
do {
if (pe.th32ParentProcessID == parent_pid) {
if (std::ranges::find(pids_, pe.th32ProcessID) == pids_.end()) {
if (Settings::extendedLogging) {
spdlog::info("Found new child process with PID \"{}\"", pe.th32ProcessID);
if (Settings::common.extendedLogging) {
spdlog::info(L"Found new child process \"{}\" with PID \"{}\"", util::win::process::GetProcName(pe.th32ProcessID), pe.th32ProcessID);
}
pids_.push_back(pe.th32ProcessID);
getChildPids(pe.th32ProcessID);
}
}
} while (Process32Next(hp, &pe));
@ -189,9 +285,11 @@ void AppLauncher::getProcessHwnds()
IPropertyStore* propStore;
SHGetPropertyStoreForWindow(curr_wnd, IID_IPropertyStore, reinterpret_cast<void**>(&propStore));
PROPVARIANT prop;
propStore->GetValue(PKEY_AppUserModel_ID, &prop);
if (prop.bstrVal != nullptr && std::wstring(prop.bstrVal) == launched_uwp_path_) {
process_hwnds_.push_back(curr_wnd);
if (propStore != nullptr) {
propStore->GetValue(PKEY_AppUserModel_ID, &prop);
if (prop.bstrVal != nullptr && std::wstring(prop.bstrVal) == launched_uwp_path_) {
process_hwnds_.push_back(curr_wnd);
}
}
} while (curr_wnd != nullptr);
}
@ -200,6 +298,16 @@ void AppLauncher::getProcessHwnds()
#endif
#ifdef _WIN32
bool AppLauncher::findLauncherPids()
{
if (const auto pid = util::win::process::PidByName(L"EpicGamesLauncher.exe")) {
spdlog::debug("Found EGS-Launcher running");
pids_.push_back(pid);
return true;
}
return false;
}
void AppLauncher::UnPatchValveHooks()
{
// need to load addresses that way.. Otherwise we may land before some jumps...
@ -222,10 +330,19 @@ void AppLauncher::launchWin32App(const std::wstring& path, const std::wstring& a
// } else {
// launch_dir = m[0];
// }
std::wstring args_cpy(args);
std::wstring args_cpy(
args.empty()
? L""
: ((native_seps_path.find(L" ") != std::wstring::npos
? L"\"" + native_seps_path + L"\""
: native_seps_path) +
L" " + args));
DWORD pid;
spdlog::debug(L"Launching Win32App app \"{}\"; args \"{}\"", native_seps_path, args_cpy);
if (CreateProcessW(native_seps_path.data(),
args_cpy.data(),
args_cpy.empty() ? nullptr : args_cpy.data(),
nullptr,
nullptr,
watchdog ? FALSE : TRUE,
@ -234,15 +351,50 @@ void AppLauncher::launchWin32App(const std::wstring& path, const std::wstring& a
nullptr, // launch_dir.empty() ? nullptr : launch_dir.data(),
&info,
&process_info)) {
// spdlog::info(L"Started Program: \"{}\" in directory: \"{}\"", native_seps_path, launch_dir);
spdlog::info(L"Started Program: \"{}\"; PID: {}", native_seps_path, process_info.dwProcessId);
if (!watchdog) {
pids_.push_back(process_info.dwProcessId);
}
pid = process_info.dwProcessId;
}
else {
// spdlog::error(L"Couldn't start program: \"{}\" in directory: \"{}\"", native_seps_path, launch_dir);
spdlog::error(L"Couldn't start program: \"{}\"", native_seps_path);
DWORD error_code = GetLastError();
if (error_code == ERROR_ELEVATION_REQUIRED) {
spdlog::info("Elevated permissions required. Trying again with elevated permissions");
SHELLEXECUTEINFOW shExecInfo = {0};
shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExecInfo.hwnd = NULL;
shExecInfo.lpVerb = L"runas";
shExecInfo.lpFile = native_seps_path.data();
shExecInfo.lpParameters = args_cpy.empty() ? nullptr : args_cpy.data();
shExecInfo.lpDirectory = nullptr; // launch_dir.empty() ? nullptr : launch_dir.data(),
shExecInfo.nShow = SW_SHOW;
shExecInfo.hInstApp = NULL;
if (ShellExecuteExW(&shExecInfo)) {
pid = GetProcessId(shExecInfo.hProcess);
if (pid == 0u) {
spdlog::error(L"Couldn't get process id after starting program: \"{}\"; Error code {}", native_seps_path, GetLastError());
}
CloseHandle(shExecInfo.hProcess);
}
else {
spdlog::error(L"Couldn't start program with elevated permissions: \"{}\"; Error code {}", native_seps_path, GetLastError());
return;
}
}
else {
spdlog::error(L"Could't start program: \"{}\"; Error code: {}", native_seps_path, error_code);
return;
}
}
spdlog::info(L"Started Program: \"{}\"; PID: {}", native_seps_path, pid);
if (!watchdog) {
pid_mutex_.lock();
pids_.push_back(pid);
pid_mutex_.unlock();
}
}
@ -267,7 +419,7 @@ void AppLauncher::launchUWPApp(const LPCWSTR package_full_name, const std::wstri
if (!SUCCEEDED(result)) {
spdlog::warn("CoAllowSetForegroundWindow failed. Code: {}", result);
}
pid_mutex_.lock();
pids_.push_back(0);
// Launch the app
result = sp_app_activation_manager->ActivateApplication(
@ -282,6 +434,7 @@ void AppLauncher::launchUWPApp(const LPCWSTR package_full_name, const std::wstri
else {
spdlog::info(L"Launched UWP Package \"{}\"; PID: {}", package_full_name, pids_[0]);
}
pid_mutex_.unlock();
}
else {
spdlog::error("CoCreateInstance failed: Code {}", result);
@ -320,13 +473,30 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c
}
CoUninitialize();
if (url.find(L"epicgames.launcher") != std::wstring::npos) {
has_extra_launchers_ = true;
}
if (execute_info.hProcess != nullptr) {
if (const auto pid = GetProcessId(execute_info.hProcess); pid > 0) {
pid_mutex_.lock();
pids_.push_back(pid);
spdlog::trace("Launched URL; PID: {}", pid);
pid_mutex_.unlock();
return;
}
}
if (has_extra_launchers_) {
spdlog::debug("Epic Games launch; Couldn't find egs launcher PID");
pid_mutex_.lock();
const auto pid = util::win::process::PidByName(L"EpicGamesLauncher.exe");
if (!findLauncherPids()) {
spdlog::debug("Did not find EGS-Launcher not running, retrying later...");
}
pid_mutex_.unlock();
return;
}
spdlog::warn("Couldn't get PID of launched URL process");
}
#endif

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -17,9 +17,12 @@ limitations under the License.
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#include <functional>
#include <mutex>
#include <array>
#include <string>
#include <unordered_set>
#include <SFML/System/Clock.hpp>
@ -34,11 +37,13 @@ class AppLauncher {
void update();
void close();
void launchWatchdog();
std::vector<DWORD> launchedPids();
void addPids(const std::vector<DWORD>& pids);
private:
std::function<void()> shutdown_;
sf::Clock process_check_clock_;
std::mutex pid_mutex_;
#ifdef _WIN32
static bool IsProcessRunning(DWORD pid);
@ -46,6 +51,11 @@ class AppLauncher {
void getProcessHwnds();
std::vector<HWND>& process_hwnds_;
bool has_extra_launchers_ = false;
bool launcher_has_launched_game_ = false;
bool findLauncherPids();
std::wstring launched_uwp_path_;
static void UnPatchValveHooks();

@ -0,0 +1,80 @@
#pragma once
#include "HttpServer.h"
#include "../common/Settings.h"
#include "../common/steam_util.h"
namespace CHTE {
inline void addEndpoints()
{
HttpServer::AddEndpoint(
{"/running",
HttpServer::Method::GET,
[](const httplib::Request& req, httplib::Response& res) {
// TODO: extend this when "passive" running of global mods is implemented
res.set_content(nlohmann::json{{"state", nlohmann::json{{"running", true}}}}.dump(), "text/json");
}});
HttpServer::AddEndpoint(
{"/settings",
HttpServer::Method::GET,
[](const httplib::Request& req, httplib::Response& res) {
res.set_content(Settings::toJson().dump(), "text/json");
},
"json"});
HttpServer::AddEndpoint(
{"/steam_settings",
HttpServer::Method::GET,
[](const httplib::Request& req, httplib::Response& res) {
res.set_content(util::steam::getSteamConfig().dump(4), "text/json");
},
"json"});
HttpServer::AddEndpoint({
"/log",
HttpServer::Method::POST,
[](const httplib::Request& req, httplib::Response& res) {
struct LogEntry {
std::string level;
std::string message;
};
auto entry = LogEntry{};
try {
const nlohmann::json postbody = nlohmann::json::parse(req.body);
entry.level = postbody.at("level");
entry.message = postbody.at("message");
}
catch (std::exception& e) {
res.status = 401;
res.set_content(nlohmann::json{
{"code", 401},
{"name", "Bad Request"},
{"message", e.what()},
}
.dump(),
"text/json");
}
if (entry.level == "info") {
spdlog::info("GlosSITweaks: {}", entry.message);
}
else if (entry.level == "warn") {
spdlog::warn("GlosSITweaks: {}", entry.message);
}
else if (entry.level == "error") {
spdlog::error("GlosSITweaks: {}", entry.message);
}
else if (entry.level == "debug") {
spdlog::debug("GlosSITweaks: {}", entry.message);
}
else {
spdlog::trace("GlosSITweaks: {}", entry.message);
}
},
});
};
} // namespace CHTE

@ -1,9 +1,9 @@
#pragma once
#include <windows.h>
#include <tlhelp32.h>
#include <spdlog/spdlog.h>
#include "../common/util.h"
namespace DllInjector {
inline bool TakeDebugPrivilege()
@ -107,4 +107,25 @@ inline bool findModule(DWORD pid, std::wstring& lib_path, HMODULE& hMod)
return false;
}
inline void injectDllInto(std::filesystem::path dllPath, const std::wstring& processName)
{
if (std::filesystem::exists(dllPath)) {
const auto explorer_pid = util::win::process::PidByName(processName);
if (explorer_pid != 0) {
if (DllInjector::TakeDebugPrivilege()) {
// No need to eject, as the dll is self-ejecting.
if (DllInjector::Inject(explorer_pid, dllPath.wstring())) {
spdlog::info(L"Successfully injected {} into {}", dllPath.filename().wstring(), processName);
}
}
}
else {
spdlog::error(L"{} not found", processName); // needs loglevel WTF
}
}
else {
spdlog::error(L"{} not found", dllPath.wstring());
}
}
}; // namespace DllInjector

@ -51,6 +51,7 @@
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation />
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
@ -80,7 +81,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<ExternalIncludePath>..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;..\deps\cpp-httplib;$(ExternalIncludePath)</ExternalIncludePath>
<LibraryPath>..\deps\SFML\out\Debug\lib\Debug;..\deps\ViGEmClient\lib\debug\x64;$(LibraryPath)</LibraryPath>
<CopyLocalProjectReference>false</CopyLocalProjectReference>
<CopyLocalDeploymentContent>true</CopyLocalDeploymentContent>
@ -88,7 +89,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<ExternalIncludePath>..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;..\deps\cpp-httplib;$(ExternalIncludePath)</ExternalIncludePath>
<LibraryPath>..\deps\SFML\out\Release\lib\RelWithDebInfo;..\deps\ViGEmClient\lib\release\x64;$(LibraryPath)</LibraryPath>
<CustomBuildBeforeTargets>ResourceCompile</CustomBuildBeforeTargets>
<CopyLocalDeploymentContent>true</CopyLocalDeploymentContent>
@ -128,6 +129,9 @@
<PreprocessorDefinitions>_DEBUG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SUBHOOK_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -151,6 +155,9 @@
<PreprocessorDefinitions>NDEBUG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;SUBHOOK_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -168,6 +175,8 @@
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\common\HidHide.cpp" />
<ClCompile Include="..\common\UnhookUtil.cpp" />
<ClCompile Include="..\deps\imgui-sfml\imgui-SFML.cpp" />
<ClCompile Include="..\deps\imgui\imgui.cpp" />
<ClCompile Include="..\deps\imgui\imgui_draw.cpp" />
@ -187,23 +196,23 @@
<ClCompile Include="..\deps\traypp\tray\src\core\windows\image.cpp" />
<ClCompile Include="..\deps\traypp\tray\src\core\windows\tray.cpp" />
<ClCompile Include="AppLauncher.cpp" />
<ClCompile Include="HidHide.cpp" />
<ClCompile Include="HttpServer.cpp" />
<ClCompile Include="InputRedirector.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="Overlay.cpp" />
<ClCompile Include="SteamOverlayDetector.cpp" />
<ClCompile Include="SteamTarget.cpp" />
<ClCompile Include="TargetWindow.cpp" />
<ClCompile Include="UnhookUtil.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\deps\imgui-sfml\imgui-SFML.h" />
<ClInclude Include="..\deps\imgui\imgui.h" />
<ClInclude Include="..\deps\subhook\subhook.h" />
<ClInclude Include="AppLauncher.h" />
<ClInclude Include="CommonHttpEndpoints.h" />
<ClInclude Include="DllInjector.h" />
<ClInclude Include="GlosSI_logo.h" />
<ClInclude Include="HidHide.h" />
<ClInclude Include="HttpServer.h" />
<ClInclude Include="imconfig.h" />
<ClInclude Include="InputRedirector.h" />
<ClInclude Include="Overlay.h" />
@ -211,12 +220,10 @@
<ClInclude Include="ProcessPriority.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Roboto.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="SteamOverlayDetector.h" />
<ClInclude Include="SteamTarget.h" />
<ClInclude Include="steam_sf_keymap.h" />
<ClInclude Include="TargetWindow.h" />
<ClInclude Include="UnhookUtil.h" />
<ClInclude Include="UWPOverlayEnabler.h" />
</ItemGroup>
<ItemGroup>

@ -51,9 +51,6 @@
<ClCompile Include="InputRedirector.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HidHide.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Overlay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -114,7 +111,13 @@
<ClCompile Include="..\deps\traypp\tray\src\components\toggle.cpp">
<Filter>Source Files\tray</Filter>
</ClCompile>
<ClCompile Include="UnhookUtil.cpp">
<ClCompile Include="HttpServer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\common\UnhookUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\common\HidHide.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
@ -134,9 +137,6 @@
<ClInclude Include="InputRedirector.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HidHide.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Overlay.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -158,9 +158,6 @@
<ClInclude Include="AppLauncher.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -179,7 +176,10 @@
<ClInclude Include="GlosSI_logo.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UnhookUtil.h">
<ClInclude Include="HttpServer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CommonHttpEndpoints.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>

@ -0,0 +1,144 @@
/*
Copyright 2021-2023 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 "HttpServer.h"
#include <spdlog/spdlog.h>
#include <utility>
#include <algorithm>
HttpServer::HttpServer(std::function<void()> close) : close_(std::move(close))
{
}
std::string HttpServer::ToString(Method m)
{
switch (m) {
case POST:
return "POST";
case PATCH:
return "PATCH";
case PUT:
return "PUT";
default:
return "GET";
}
}
void HttpServer::AddEndpoint(const Endpoint&& e)
{
endpoints_.push_back(e);
}
void HttpServer::run()
{
auto setCorsHeader = [](httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "*");
};
server_.Get("/", [this, &setCorsHeader](const httplib::Request& req, httplib::Response& res) {
setCorsHeader(res);
auto content_json = nlohmann::json{
{"endpoints", nlohmann::json::array()}};
for (const auto& e : endpoints_) {
content_json["endpoints"].push_back(
nlohmann::json{
{"path", e.path},
{"method", ToString(e.method)},
{"response", e.response_hint},
{"payload", e.payload_hint},
});
}
content_json["endpoints"].push_back(
nlohmann::json{
{"path", "/quit"},
{"method", "POST"}
});
res.set_content(content_json.dump(4),
"text/json");
});
for (const auto& e : endpoints_) {
const auto fn = ([this, &e]() -> httplib::Server& (httplib::Server::*)(const std::string&, httplib::Server::Handler) {
switch (e.method) {
case POST:
return &httplib::Server::Post;
case PUT:
return &httplib::Server::Put;
case PATCH:
return &httplib::Server::Patch;
default:
return &httplib::Server::Get;
}
})();
(server_.*fn)(e.path, [this, &e, &setCorsHeader](const httplib::Request& req, httplib::Response& res) {
setCorsHeader(res);
res.status = 0;
res.content_length_ = 0;
try {
e.handler(req, res);
}
catch (std::exception& err) {
spdlog::error("Exception in http handler: {}", err.what());
res.status = res.status == 0 ? 500 : res.status;
if (res.content_length_ == 0) {
res.set_content(nlohmann::json{
{"code", res.status},
{"name", "HandlerError"},
{"message", err.what()},
}
.dump(),
"text/json");
}
}
catch (...) {
res.status = 500;
res.set_content(nlohmann::json{
{"code", res.status},
{"name", "Internal Server Error"},
{"message", "Unknown Error"},
}
.dump(),
"text/json");
}
});
}
server_.Post("/quit", [this, &setCorsHeader](const httplib::Request& req, httplib::Response& res) {
setCorsHeader(res);
close_();
});
server_thread_ = std::thread([this]() {
if (!server_.listen("0.0.0.0", port_)) {
spdlog::error("Couldn't start http-server");
return;
}
spdlog::debug("Started http-server on port {}", static_cast<int>(port_));
});
}
void HttpServer::stop()
{
server_.stop();
if (server_thread_.joinable())
server_thread_.join();
}

@ -0,0 +1,62 @@
/*
Copyright 2021-2023 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 <thread>
#include <httplib.h>
#include <nlohmann/json.hpp>
class AppLauncher;
class HttpServer {
public:
explicit HttpServer(std::function<void()> close);
// C++ enums suck.
enum Method {
GET,
POST,
PUT,
PATCH,
};
// but im not in the mood of adding yet another dependency for just that shit here.
static std::string ToString(Method m);
struct Endpoint {
std::string path;
Method method;
std::function<void(const httplib::Request& req, httplib::Response& res)> handler;
nlohmann::json response_hint = nullptr;
nlohmann::json payload_hint = nullptr;
};
static void AddEndpoint(const Endpoint&& e);
void run();
void stop();
private:
httplib::Server server_;
std::thread server_thread_;
uint16_t port_ = 8756;
std::function<void()> close_;
static inline std::vector<Endpoint> endpoints_;
};

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -20,7 +20,7 @@ limitations under the License.
#include <spdlog/spdlog.h>
#include "Overlay.h"
#include "Settings.h"
#include "..\common\Settings.h"
InputRedirector::InputRedirector()
{
@ -49,6 +49,22 @@ InputRedirector::~InputRedirector()
void InputRedirector::run()
{
run_ = vigem_connected_;
max_controllers_ = Settings::controller.maxControllers;
if (max_controllers_ < 0) {
for (int i = 0; i < XUSER_MAX_COUNT; i++) {
XINPUT_STATE state{};
if (XInputGetState(i, &state) == ERROR_SUCCESS) {
max_controllers_ = i + 1;
}
}
if (max_controllers_ < 0) {
max_controllers_ = 1;
spdlog::error("Failed to auto detect controller count. Defaulting to 1");
}
else {
spdlog::info("Auto detected {} controllers", max_controllers_);
}
}
controller_thread_ = std::thread(&InputRedirector::runLoop, this);
#ifdef _WIN32
Overlay::AddOverlayElem([this](bool window_has_focus, ImGuiID dockspace_id) {
@ -58,13 +74,17 @@ void InputRedirector::run()
ImGui::Text("Max. controller count");
ImGui::SameLine();
ImGui::InputInt("##Max. controller count", &countcopy, 1, 1);
ImGui::Text("-1 = Auto-detection (auto-detection only works on launch");
if (countcopy > XUSER_MAX_COUNT) {
countcopy = XUSER_MAX_COUNT;
}
if (countcopy < 0) {
countcopy = 0;
if (countcopy < -1) {
countcopy = -1;
}
Settings::controller.maxControllers = countcopy;
if (Settings::controller.maxControllers > -1) {
max_controllers_ = countcopy;
}
if (ImGui::Checkbox("Emulate DS4 (instead of Xbox360 controller)", &Settings::controller.emulateDS4)) {
controller_settings_changed_ = true;
@ -131,12 +151,12 @@ void InputRedirector::runLoop()
unplugVigemPad(i);
}
}
if (Settings::controller.maxControllers < XUSER_MAX_COUNT) {
for (int i = Settings::controller.maxControllers; i < XUSER_MAX_COUNT; i++) {
if (max_controllers_ < XUSER_MAX_COUNT) {
for (int i = max_controllers_; i < XUSER_MAX_COUNT; i++) {
unplugVigemPad(i);
}
}
for (int i = 0; i < XUSER_MAX_COUNT && i < Settings::controller.maxControllers; i++) {
for (int i = 0; i < XUSER_MAX_COUNT && i < max_controllers_; i++) {
XINPUT_STATE state{};
if (XInputGetState(i, &state) == ERROR_SUCCESS) {
if (vt_pad_[i] != nullptr) {
@ -216,6 +236,10 @@ void InputRedirector::runLoop()
vigem_target_get_pid(vt_pad_[i]));
if (Settings::controller.emulateDS4) {
// TODO: make sense of DS4_OUTPUT_BUFFER
// there is no doc? Ask @Nef about this...
// ReSharper disable once CppDeprecatedEntity
#pragma warning(disable : 4996)
const auto callback_register_res = vigem_target_ds4_register_notification(
driver_,
vt_pad_[i],
@ -242,7 +266,7 @@ void InputRedirector::runLoop()
unplugVigemPad(i);
}
}
sf::sleep(sf::milliseconds(1));
Sleep(static_cast<int>(1000.f / Settings::controller.updateRate));
#endif
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -17,6 +17,7 @@ limitations under the License.
#include <thread>
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Xinput.h>
#include <ViGEm/Client.h>
@ -34,6 +35,7 @@ class InputRedirector {
private:
void runLoop();
int max_controllers_ = -1;
static constexpr int start_delay_ms_ = 2000;
bool run_ = false;
int overlay_elem_id_ = -1;

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -17,12 +17,11 @@ limitations under the License.
#include <filesystem>
#include <utility>
#include <locale>
#include <codecvt>
#include <regex>
#include <shlobj_core.h>
#include "Roboto.h"
#include "Settings.h"
#include "..\common\Settings.h"
#include "GlosSI_logo.h"
#include "../version.hpp"
@ -51,19 +50,11 @@ Overlay::Overlay(
ImGui::SFML::UpdateFontTexture();
#ifdef _WIN32
auto config_path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
config_path /= "Roaming";
config_path /= "GlosSI";
if (!std::filesystem::exists(config_path))
std::filesystem::create_directories(config_path);
auto config_path = util::path::getDataDirPath();
config_path /= "imgui.ini";
// This assumes that char is utf8 and wchar_t is utf16, which is guaranteed on Windows.
config_file_name_ = std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(config_path.wstring());
config_file_name_ = util::string::to_string(config_path.wstring());
io.IniFilename = config_file_name_.data();
#endif
@ -172,11 +163,20 @@ void Overlay::update()
const auto remain_millis = SPLASH_DURATION_S_ * 1000 - millis;
if (remain_millis <= fade_millis) {
showSplash(static_cast<float>(remain_millis) / static_cast<float>(fade_millis) * 128.f);
} else {
}
else {
showSplash(128);
}
}
if (Settings::window.disableGlosSIOverlay) {
std::ranges::for_each(FORCED_OVERLAY_ELEMS_, [this](const auto& elem) {
elem.second(window_.hasFocus(), 0);
});
ImGui::SFML::Render(window_);
return;
}
showLogs(0);
if (enabled_ || force_enable_) {
@ -194,7 +194,7 @@ void Overlay::update()
Settings::StoreSettings();
}
}
ImGui::Checkbox("Extended logging", &Settings::extendedLogging);
ImGui::Checkbox("Extended logging", &Settings::common.extendedLogging);
ImGuiID dockspace_id = ImGui::GetID("GlosSI-DockSpace");
ImGui::DockSpace(dockspace_id);
@ -215,6 +215,17 @@ void Overlay::update()
closeOverlayButton();
}
std::ranges::for_each(FORCED_OVERLAY_ELEMS_, [this](const auto& elem) {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.07f, 0.07f, 0.95f));
ImGui::SetNextWindowPos({
ImGui::GetMainViewport()->Size.x * 0.5f,
ImGui::GetMainViewport()->Size.y * 0.5f
}, ImGuiCond_Always,
{0.5f, 0.5f});
elem.second(window_.hasFocus(), 0);
ImGui::PopStyleColor();
});
ImGui::SFML::Render(window_);
}
@ -233,9 +244,14 @@ void Overlay::AddLog(const spdlog::details::log_msg& msg)
LOG_MSGS_.push_back({.time = msg.time, .level = msg.level, .payload = msg.payload.data()});
}
int Overlay::AddOverlayElem(const std::function<void(bool window_has_focus, ImGuiID dockspace_id)>& elem_fn)
int Overlay::AddOverlayElem(const std::function<void(bool window_has_focus, ImGuiID dockspace_id)>& elem_fn, bool force_show)
{
OVERLAY_ELEMS_.insert({overlay_element_id_, elem_fn});
if (force_show) {
FORCED_OVERLAY_ELEMS_.insert({overlay_element_id_, elem_fn});
}
else {
OVERLAY_ELEMS_.insert({overlay_element_id_, elem_fn});
}
// keep this non confusing, but longer...
const auto res = overlay_element_id_;
overlay_element_id_++;
@ -244,7 +260,10 @@ int Overlay::AddOverlayElem(const std::function<void(bool window_has_focus, ImGu
void Overlay::RemoveOverlayElem(int id)
{
OVERLAY_ELEMS_.erase(id);
if (OVERLAY_ELEMS_.contains(id))
OVERLAY_ELEMS_.erase(id);
if (FORCED_OVERLAY_ELEMS_.contains(id))
FORCED_OVERLAY_ELEMS_.erase(id);
}
void Overlay::showLogs(ImGuiID dockspace_id)

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -38,7 +38,7 @@ class Overlay {
static void Shutdown();
static void AddLog(const spdlog::details::log_msg& msg);
static int AddOverlayElem(const std::function<void(bool window_has_focus, ImGuiID dockspace_id)>& elem_fn);
static int AddOverlayElem(const std::function<void(bool window_has_focus, ImGuiID dockspace_id)>& elem_fn, bool force_show_ = false);
static void RemoveOverlayElem(int id);
private:
@ -71,6 +71,8 @@ class Overlay {
static inline int overlay_element_id_ = 0;
static inline std::map<int, std::function<void(bool window_has_focus, ImGuiID dockspace_id)>> OVERLAY_ELEMS_;
static inline std::map<int, std::function<void(bool window_has_focus, ImGuiID dockspace_id)>> FORCED_OVERLAY_ELEMS_;
#ifdef _WIN32
std::string config_file_name_;
#endif

@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,0,9,1040000008386
PRODUCTVERSION 0,0,9,1040000008386
FILEVERSION 0,1,2,0068000002000
PRODUCTVERSION 0,1,2,0068000002000
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Peter Repukat - FlatspotSoftware"
VALUE "FileDescription", "GlosSI - SteamTarget"
VALUE "FileVersion", "0.0.9.1-40-gdae8386"
VALUE "FileVersion", "0.1.2.0-68-g0f02eca"
VALUE "InternalName", "GlosSITarget"
VALUE "LegalCopyright", "Copyright (C) 2021-2022 Peter Repukat - FlatspotSoftware"
VALUE "OriginalFilename", "GlosSITarget.exe"
VALUE "ProductName", "GlosSI"
VALUE "ProductVersion", "0.0.9.1-40-gdae8386"
VALUE "ProductVersion", "0.1.2.0-68-g0f02eca"
END
END
BLOCK "VarFileInfo"
@ -160,6 +160,101 @@ IDI_ICON1 ICON "..\\GlosSI_Icon.ico"

@ -1,260 +0,0 @@
/*
Copyright 2021-2022 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 <fstream>
#include <regex>
#include <string>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <locale>
#include <codecvt>
#ifdef WIN32
#define NOMINMAX
#include <Windows.h>
#endif
namespace Settings {
inline struct Launch {
bool launch = false;
std::wstring launchPath;
std::wstring launchAppArgs;
bool closeOnExit = true;
bool waitForChildProcs = true;
bool isUWP = false;
} launch;
inline struct Devices {
bool hideDevices = true;
bool realDeviceIds = false;
} devices;
inline struct Window {
bool windowMode = false;
int maxFps = 0;
float scale = 0.f;
bool disableOverlay = false;
} window;
inline struct Controller {
int maxControllers = 1;
bool allowDesktopConfig = false;
bool emulateDS4 = false;
} controller;
inline bool extendedLogging = false;
inline std::filesystem::path settings_path_ = "";
inline bool checkIsUwp(const std::wstring& launch_path)
{
if (launch_path.find(L"://") != std::wstring::npos) {
return false;
}
std::wsmatch m;
if (!std::regex_search(launch_path, m, std::wregex(L"^.{1,5}:"))) {
return true;
}
return false;
}
#ifdef WIN32
inline bool isWin10 = false;
typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
inline RTL_OSVERSIONINFOW GetRealOSVersion()
{
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
if (hMod) {
RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
if (fxPtr != nullptr) {
RTL_OSVERSIONINFOW rovi = {0};
rovi.dwOSVersionInfoSize = sizeof(rovi);
if (STATUS_SUCCESS == fxPtr(&rovi)) {
return rovi;
}
}
}
RTL_OSVERSIONINFOW rovi = {0};
return rovi;
}
inline void checkWinVer()
{
auto VN = GetRealOSVersion();
isWin10 = VN.dwBuildNumber < 22000;
if (isWin10) {
spdlog::info("Running on Windows 10; Winver: {}.{}.{}", VN.dwMajorVersion, VN.dwMinorVersion, VN.dwBuildNumber);
}
else {
spdlog::info("Running on Windows 11; Winver: {}.{}.{}", VN.dwMajorVersion, VN.dwMinorVersion, VN.dwBuildNumber);
}
}
#endif
inline void Parse(std::wstring arg1)
{
const auto config_specified = !std::views::filter(arg1, [](const auto& ch) {
return ch != ' ';
}).empty();
if (config_specified) {
if (!arg1.ends_with(L".json")) {
arg1 += L".json";
}
}
std::filesystem::path path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
path /= "Roaming";
path /= "GlosSI";
if (config_specified) {
path /= "Targets";
path /= arg1;
}
else {
spdlog::info("No config file specified, using default");
path /= "default.json";
}
std::ifstream json_file;
json_file.open(path);
if (!json_file.is_open()) {
spdlog::error(L"Couldn't open settings file {}", path.wstring());
spdlog::debug(L"Using sane defaults...");
return;
}
settings_path_ = path;
auto safeParseValue = [](const auto& object, const auto& key, auto& value) {
try {
if (object.is_null() || object.empty() || object.at(key).empty() || object.at(key).is_null()) {
return;
}
value = object[key];
}
catch (const nlohmann::json::exception& e) {
e.id == 403
? spdlog::trace("Err parsing \"{}\"; {}", key, e.what())
: spdlog::warn("Err parsing \"{}\"; {}", key, e.what());
}
catch (const std::exception& e) {
spdlog::warn("Err parsing \"{}\"; {}", key, e.what());
}
};
auto safeWStringParse = [&safeParseValue](const auto& object, const auto& key, std::wstring& value) {
std::string meh;
safeParseValue(object, key, meh);
if (!meh.empty()) {
// This assumes that char is utf8 and wchar_t is utf16, which is guaranteed on Windows.
value = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(meh);
}
};
const auto json = nlohmann::json::parse(json_file);
int version;
safeParseValue(json, "version", version);
if (version != 1) { // TODO: versioning stuff
spdlog::warn("Config version doesn't match application version.");
}
// TODO: make this as much generic as fits in about the same amount of code if one would parse every value separately.
try {
if (auto launchconf = json["launch"]; !launchconf.is_null() && !launchconf.empty() && launchconf.is_object()) {
safeParseValue(launchconf, "launch", launch.launch);
safeWStringParse(launchconf, "launchPath", launch.launchPath);
safeWStringParse(launchconf, "launchAppArgs", launch.launchAppArgs);
safeParseValue(launchconf, "closeOnExit", launch.closeOnExit);
safeParseValue(launchconf, "waitForChildProcs", launch.waitForChildProcs);
}
if (auto devconf = json["devices"]; !devconf.is_null() && !devconf.empty() && devconf.is_object()) {
safeParseValue(devconf, "hideDevices", devices.hideDevices);
safeParseValue(devconf, "realDeviceIds", devices.realDeviceIds);
}
if (auto winconf = json["window"]; !winconf.is_null() && !winconf.empty() && winconf.is_object()) {
safeParseValue(winconf, "windowMode", window.windowMode);
safeParseValue(winconf, "maxFps", window.maxFps);
safeParseValue(winconf, "scale", window.scale);
safeParseValue(winconf, "disableOverlay", window.disableOverlay);
}
if (auto controllerConf = json["controller"]; !controllerConf.is_null() && !controllerConf.empty() && controllerConf.is_object()) {
safeParseValue(controllerConf, "maxControllers", controller.maxControllers);
safeParseValue(controllerConf, "allowDesktopConfig", controller.allowDesktopConfig);
safeParseValue(controllerConf, "emulateDS4", controller.emulateDS4);
}
}
catch (const nlohmann::json::exception& e) {
spdlog::warn("Err parsing config: {}", e.what());
}
catch (const std::exception& e) {
spdlog::warn("Err parsing config: {}", e.what());
}
safeParseValue(json, "extendedLogging", extendedLogging);
json_file.close();
spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump());
if (launch.launch) {
launch.isUWP = checkIsUwp(launch.launchPath);
}
}
inline void StoreSettings()
{
nlohmann::json json;
json["version"] = 1;
json["launch"]["launch"] = launch.launch;
json["launch"]["launchPath"] = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(launch.launchPath);
json["launch"]["launchAppArgs"] = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(launch.launchAppArgs);
json["launch"]["closeOnExit"] = launch.closeOnExit;
json["launch"]["waitForChildProcs"] = launch.waitForChildProcs;
json["devices"]["hideDevices"] = devices.hideDevices;
json["devices"]["realDeviceIds"] = devices.realDeviceIds;
json["window"]["windowMode"] = window.windowMode;
json["window"]["maxFps"] = window.maxFps;
json["window"]["scale"] = window.scale;
json["window"]["disableOverlay"] = window.disableOverlay;
json["controller"]["maxControllers"] = controller.maxControllers;
json["controller"]["allowDesktopConfig"] = controller.allowDesktopConfig;
json["controller"]["emulateDS4"] = controller.emulateDS4;
json["extendedLogging"] = extendedLogging;
std::ofstream json_file;
json_file.open(settings_path_);
if (!json_file.is_open()) {
spdlog::error(L"Couldn't open settings file {}", settings_path_.wstring());
return;
}
json_file << json.dump(4);
json_file.close();
}
} // namespace Settings

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -17,7 +17,7 @@ limitations under the License.
#include <spdlog/spdlog.h>
#include "Settings.h"
#include "..\common\Settings.h"
#ifdef _WIN32
#define NOMINMAX
@ -47,7 +47,7 @@ void SteamOverlayDetector::update()
if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
// filter out some messages as not all get altered by steam...
if (Settings::extendedLogging && msg.message != 512 && msg.message != 5374) {
if (Settings::common.extendedLogging && msg.message != 512 && msg.message != 5374) {
spdlog::trace("PeekMessage: Window msg: {}", msg.message);
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -15,26 +15,26 @@ limitations under the License.
*/
#include "SteamTarget.h"
#include "Settings.h"
#include "../common/Settings.h"
#include "steam_sf_keymap.h"
#include <SFML/Window/Keyboard.hpp>
#include <WinReg/WinReg.hpp>
#include <numeric>
#include <regex>
#include <spdlog/spdlog.h>
#include <vdf_parser.hpp>
#ifdef _WIN32
#include "UWPOverlayEnabler.h"
#include <tray.hpp>
#endif
#include <CEFInject.h>
#include "CommonHttpEndpoints.h"
SteamTarget::SteamTarget()
: window_(
[this] { run_ = false; },
[this] { toggleGlossiOverlay(); },
getScreenshotHotkey(),
util::steam::getScreenshotHotkey(steam_path_, steam_user_id_),
[this]() {
target_window_handle_ = window_.getSystemHandle();
overlay_ = window_.getOverlay();
@ -44,81 +44,161 @@ SteamTarget::SteamTarget()
launcher_(force_config_hwnds_, [this] {
delayed_shutdown_ = true;
delay_shutdown_clock_.restart();
})
}),
server_([this] { run_ = false; })
{
target_window_handle_ = window_.getSystemHandle();
#ifdef _WIN32
if (Settings::launch.isUWP) {
UWPOverlayEnabler::EnableUwpOverlay();
}
else {
UWPOverlayEnabler::AddUwpOverlayOvWidget();
}
#endif
}
int SteamTarget::run()
{
if (!SteamOverlayDetector::IsSteamInjected()) {
spdlog::warn("Steam-overlay not detected. Showing GlosSI-overlay!\n\
Application will not function!");
window_.setClickThrough(false);
if (!overlay_.expired())
overlay_.lock()->setEnabled(true);
steam_overlay_present_ = false;
}
else {
spdlog::info("Steam-overlay detected.");
spdlog::warn("Double press Steam- overlay key(s)/Controller button to show GlosSI-overlay"); // Just to color output and really get users attention
window_.setClickThrough(true);
if (!overlay_.expired())
overlay_.lock()->setEnabled(false);
steam_overlay_present_ = true;
launcher_.launchWatchdog();
}
getXBCRebindingEnabled();
run_ = true;
#ifdef _WIN32
hidhide_.hideDevices(steam_path_);
input_redirector_.run();
#endif
if (Settings::launch.launch) {
launcher_.launchApp(Settings::launch.launchPath, Settings::launch.launchAppArgs);
}
keepControllerConfig(true);
#ifdef _WIN32
HICON icon = 0;
TCHAR path[MAX_PATH];
GetModuleFileName(nullptr, path, MAX_PATH);
icon = (HICON)LoadImage(
0,
path,
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_LOADFROMFILE | LR_LOADMAP3DCOLORS);
if (!icon) {
ExtractIconEx(path, 0, &icon, nullptr, 1);
auto closeBPM = false;
auto closeBPMTimer = sf::Clock{};
if (!SteamOverlayDetector::IsSteamInjected()) {
if (Settings::common.allowStandAlone) {
spdlog::warn("GlosSI not launched via Steam.\nEnabling EXPERIMENTAL global controller and overlay...");
if (Settings::common.standaloneModeGameId == L"") {
spdlog::error("No game id set for standalone mode. Controller will use desktop-config!");
}
auto steam_tweaks = CEFInject::SteamTweaks();
steam_tweaks.setAutoInject(true);
CHTE::addEndpoints();
server_.run();
if (!overlay_.expired())
overlay_.lock()->setEnabled(false);
std::vector<std::function<void()>> end_frame_callbacks;
if (!CEFInject::CEFDebugAvailable()) {
auto overlay_id = std::make_shared<int>(-1);
*overlay_id = Overlay::AddOverlayElem(
[this, overlay_id, &end_frame_callbacks](bool window_has_focus, ImGuiID dockspace_id) {
can_fully_initialize_ = false;
ImGui::Begin("GlosSI - CEF remote debug not available");
ImGui::Text("GlosSI makes use of Steam CEF remote debugging for some functionality and plugins.");
ImGui::Text("GlosSI might not work fully without it.");
if (ImGui::Button("Ignore and continue")) {
can_fully_initialize_ = true;
cef_tweaks_enabled_ = false;
if (*overlay_id != -1) {
end_frame_callbacks.emplace_back([this, overlay_id] {
Overlay::RemoveOverlayElem(*overlay_id);
});
}
}
if (ImGui::Button("Enable and restart Steam")) {
std::ofstream{steam_path_ / ".cef-enable-remote-debugging"};
system("taskkill.exe /im steam.exe /f");
Sleep(200);
launcher_.launchApp((steam_path_ / "Steam.exe").wstring());
run_ = false;
}
ImGui::Text("GlosSI will close upon restarting Steam");
ImGui::End();
},
true);
can_fully_initialize_ = false;
cef_tweaks_enabled_ = false;
}
if (!SteamOverlayDetector::IsSteamInjected() && Settings::common.allowGlobalMode && Settings::common.globalModeGameId == L"") {
auto overlay_id = std::make_shared<int>(-1);
*overlay_id = Overlay::AddOverlayElem(
[this, overlay_id, &end_frame_callbacks](bool window_has_focus, ImGuiID dockspace_id) {
can_fully_initialize_ = false;
ImGui::Begin("Global mode", nullptr, ImGuiWindowFlags_NoSavedSettings);
ImGui::Text("You are running GlosSI in (experimental) global mode (=outside of Steam)");
ImGui::Text("but global mode doesn't appear to be setup properly.");
ImGui::Text("");
ImGui::Text("Please open GlosSI-Config first and setup global mode");
ImGui::Text("");
ImGui::Text("Application will exit on confirm");
if (ImGui::Button("OK")) {
can_fully_initialize_ = true;
if (*overlay_id != -1) {
end_frame_callbacks.emplace_back([this, overlay_id] {
Overlay::RemoveOverlayElem(*overlay_id);
run_ = false;
});
}
}
ImGui::End();
},
true);
can_fully_initialize_ = false;
}
if (!SteamOverlayDetector::IsSteamInjected() && Settings::common.allowGlobalMode) {
auto overlay_id = std::make_shared<int>(-1);
*overlay_id = Overlay::AddOverlayElem(
[this, overlay_id, &end_frame_callbacks](bool window_has_focus, ImGuiID dockspace_id) {
ImGui::Begin("Global mode", nullptr, ImGuiWindowFlags_NoSavedSettings);
ImGui::Text("Global mode is initializing, please stand by...");
ImGui::End();
if (fully_initialized_) {
end_frame_callbacks.emplace_back([this, overlay_id] {
Overlay::RemoveOverlayElem(*overlay_id);
});
}
},
true);
window_.update();
}
Tray::Tray tray{"GlosSITarget", icon};
#else
Tray::Tray tray{"GlosSITarget", "ico.png"};
#endif
tray.addEntry(Tray::Button{
"Quit", [this, &tray]() {
run_ = false;
}});
if (!util::steam::getXBCRebindingEnabled(steam_path_, steam_user_id_)) {
auto overlay_id = std::make_shared<int>(-1);
*overlay_id = Overlay::AddOverlayElem(
[this, overlay_id, &end_frame_callbacks](bool window_has_focus, ImGuiID dockspace_id) {
can_fully_initialize_ = false;
ImGui::Begin("XBox Controller configuration support Disabled", nullptr, ImGuiWindowFlags_NoSavedSettings);
ImGui::TextColored({1.f, 0.8f, 0.f, 1.f}, "XBox Controller configuration support is disabled in Steam. Please enable it in Steam Settings.");
if (ImGui::Button("OK")) {
can_fully_initialize_ = true;
if (*overlay_id != -1) {
end_frame_callbacks.emplace_back([this, overlay_id] {
Overlay::RemoveOverlayElem(*overlay_id);
});
}
}
ImGui::End();
},
true);
can_fully_initialize_ = false;
}
const auto tray = createTrayMenu();
bool delayed_full_init_1_frame = false;
sf::Clock frame_time_clock;
while (run_) {
if (!fully_initialized_ && can_fully_initialize_ && delayed_full_init_1_frame) {
init_FuckingRenameMe();
}
else if (!fully_initialized_ && can_fully_initialize_) {
delayed_full_init_1_frame = true;
}
else {
delayed_full_init_1_frame = false;
}
detector_.update();
overlayHotkeyWorkaround();
window_.update();
if (cef_tweaks_enabled_ && fully_initialized_) {
steam_tweaks_.update(frame_time_clock.getElapsedTime().asSeconds());
}
// Wait on shutdown; User might get confused if window closes to fast if anything with launchApp get's borked.
if (delayed_shutdown_) {
if (delay_shutdown_clock_.getElapsedTime().asSeconds() >= 3) {
@ -126,16 +206,30 @@ Application will not function!");
}
}
else {
launcher_.update();
if (fully_initialized_) {
launcher_.update();
}
}
for (auto& efc : end_frame_callbacks) {
efc();
}
end_frame_callbacks.clear();
frame_time_clock.restart();
}
tray.exit();
tray->exit();
server_.stop();
if (fully_initialized_) {
#ifdef _WIN32
input_redirector_.stop();
hidhide_.disableHidHide();
input_redirector_.stop();
hidhide_.disableHidHide();
#endif
launcher_.close();
launcher_.close();
if (cef_tweaks_enabled_) {
steam_tweaks_.uninstallTweaks();
}
}
return 0;
}
@ -144,11 +238,17 @@ void SteamTarget::onOverlayChanged(bool overlay_open)
if (overlay_open) {
focusWindow(target_window_handle_);
window_.setClickThrough(!overlay_open);
if (!Settings::window.windowMode && Settings::window.opaqueSteamOverlay) {
window_.setTransparent(false);
}
}
else {
if (!(overlay_.expired() ? false : overlay_.lock()->isEnabled())) {
window_.setClickThrough(!overlay_open);
focusWindow(last_foreground_window_);
if (!Settings::window.windowMode && Settings::window.opaqueSteamOverlay) {
window_.setTransparent(true);
}
}
}
if (!overlay_trigger_flag_) {
@ -165,6 +265,9 @@ void SteamTarget::onOverlayChanged(bool overlay_open)
void SteamTarget::toggleGlossiOverlay()
{
if (Settings::window.disableGlosSIOverlay) {
return;
}
if (overlay_.expired()) {
return;
}
@ -214,7 +317,7 @@ void SteamTarget::focusWindow(WindowHandle hndl)
AttachThreadInput(current_thread, fg_thread, FALSE);
//try to forcefully set foreground window at least a few times
// try to forcefully set foreground window at least a few times
sf::Clock clock;
while (!SetForegroundWindow(hndl) && clock.getElapsedTime().asMilliseconds() < 20) {
SetActiveWindow(hndl);
@ -223,155 +326,110 @@ void SteamTarget::focusWindow(WindowHandle hndl)
#endif
}
std::filesystem::path SteamTarget::getSteamPath() const
{
#ifdef _WIN32
try {
// 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"};
const auto res = key.GetStringValue(L"SteamPath");
spdlog::info(L"Detected Steam Path: {}", res);
return res;
}
catch (const winreg::RegException& e) {
spdlog::error("Couldn't get Steam path from Registry; {}", e.what());
}
return L"";
#else
return L""; // TODO
#endif
}
std::wstring SteamTarget::getSteamUserId() const
{
#ifdef _WIN32
try {
// 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"};
const auto res = std::to_wstring(key.GetDwordValue(L"ActiveUser"));
spdlog::info(L"Detected Steam UserId: {}", res);
return res;
}
catch (const winreg::RegException& e) {
spdlog::error("Couldn't get Steam path from Registry; {}", e.what());
}
return L"";
#else
return L""; // TODO
#endif
}
std::vector<std::string> SteamTarget::getOverlayHotkey()
void SteamTarget::init_FuckingRenameMe()
{
const auto config_path = std::wstring(steam_path_) + std::wstring(user_data_path_) + steam_user_id_ + std::wstring(config_file_name_);
if (!std::filesystem::exists(config_path)) {
spdlog::warn(L"Couldn't read Steam config file: \"{}\"", config_path);
return {"Shift", "KEY_TAB"}; // default
}
std::ifstream config_file(config_path);
auto root = tyti::vdf::read(config_file);
std::shared_ptr<tyti::vdf::basic_object<char>> children = root.childs["system"];
if (!children || children->attribs.empty() || !children->attribs.contains("InGameOverlayShortcutKey")) {
spdlog::warn("Couldn't detect overlay hotkey, using default: Shift+Tab");
return {"Shift", "KEY_TAB"}; // default
}
auto hotkeys = children->attribs.at("InGameOverlayShortcutKey");
if (!SteamOverlayDetector::IsSteamInjected()) {
if (Settings::common.allowGlobalMode) {
spdlog::warn("GlosSI not launched via Steam.\nEnabling EXPERIMENTAL global controller and overlay...");
if (Settings::common.globalModeGameId == L"") {
spdlog::error("No game id set for global mode. Controller will use desktop-config!");
}
// 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*))"))) {
spdlog::warn("Couldn't detect overlay hotkey, using default: Shift+Tab");
return {"Shift", "KEY_TAB"}; // default
}
SetEnvironmentVariable(L"SteamAppId", L"0");
SetEnvironmentVariable(L"SteamClientLaunch", L"0");
SetEnvironmentVariable(L"SteamEnv", L"1");
SetEnvironmentVariable(L"SteamPath", steam_path_.wstring().c_str());
SetEnvironmentVariable(L"SteamTenfoot", Settings::common.globalModeUseGamepadUI ? L"1" : L"0");
// SetEnvironmentVariable(L"SteamTenfootHybrid", L"1");
SetEnvironmentVariable(L"SteamGamepadUI", Settings::common.globalModeUseGamepadUI ? L"1" : L"0");
SetEnvironmentVariable(L"SteamGameId", Settings::common.globalModeGameId.c_str());
SetEnvironmentVariable(L"SteamOverlayGameId", Settings::common.globalModeGameId.c_str());
SetEnvironmentVariable(L"EnableConfiguratorSupport", L"15");
SetEnvironmentVariable(L"SteamStreamingForceWindowedD3D9", L"1");
if (Settings::common.globalModeUseGamepadUI) {
system("start steam://open/bigpicture");
auto steamwindow = FindWindow(L"Steam Big Picture Mode", nullptr);
auto timer = sf::Clock{};
while (!steamwindow && timer.getElapsedTime().asSeconds() < 2) {
steamwindow = FindWindow(L"Steam Big Picture Mode", nullptr);
Sleep(50);
}
if (cef_tweaks_enabled_) {
steam_tweaks_.setAutoInject(true);
steam_tweaks_.update(999);
}
Sleep(6000); // DIRTY HACK to wait until BPM (GamepadUI) is initialized
// TODO: find way to force BPM even if BPM is not active
LoadLibrary((steam_path_ / "GameOverlayRenderer64.dll").wstring().c_str());
// Overlay switches back to desktop one, once BPM is closed... Disable closing BPM for now.
// TODO: find way to force BPM even if BPM is not active
// closeBPM = true;
// closeBPMTimer.restart();
}
else {
LoadLibrary((steam_path_ / "GameOverlayRenderer64.dll").wstring().c_str());
}
std::vector<std::string> res;
for (auto i = 1; i < m.size(); i++) {
const auto s = std::string(m[i]);
if (!s.empty()) {
res.push_back(s);
window_.setClickThrough(true);
steam_overlay_present_ = true;
}
else {
spdlog::warn("Steam-overlay not detected and global mode disabled. Showing GlosSI-overlay!\n\
Application will not function!");
window_.setClickThrough(false);
if (!overlay_.expired())
overlay_.lock()->setEnabled(true);
steam_overlay_present_ = false;
}
}
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<std::string> SteamTarget::getScreenshotHotkey()
{
const auto config_path = std::wstring(steam_path_) + std::wstring(user_data_path_) + steam_user_id_ + std::wstring(config_file_name_);
if (!std::filesystem::exists(config_path)) {
spdlog::warn(L"Couldn't read Steam config file: \"{}\"", config_path);
return {"KEY_F12"}; //default
else {
spdlog::info("Steam-overlay detected.");
spdlog::warn("Double press Steam- overlay key(s)/Controller button to show GlosSI-overlay"); // Just to color output and really get users attention
window_.setClickThrough(true);
steam_overlay_present_ = true;
}
std::ifstream config_file(config_path);
auto root = tyti::vdf::read(config_file);
std::shared_ptr<tyti::vdf::basic_object<char>> children = root.childs["system"];
if (!children || children->attribs.empty() || !children->attribs.contains("InGameOverlayScreenshotHotKey")) {
spdlog::warn("Couldn't detect overlay hotkey, using default: F12");
return {"KEY_F12"}; //default
}
auto hotkeys = children->attribs.at("InGameOverlayScreenshotHotKey");
#ifdef WIN32
if (!Settings::common.disable_watchdog) {
wchar_t buff[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH);
std::wstring watchDogPath(buff);
watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll";
// 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
DllInjector::injectDllInto(watchDogPath, L"explorer.exe");
}
std::vector<std::string> res;
for (auto i = 1; i < m.size(); i++) {
const auto s = std::string(m[i]);
if (!s.empty()) {
res.push_back(s);
}
if (Settings::common.no_uwp_overlay) {
UWPOverlayEnabler::AddUwpOverlayOvWidget();
}
if (res.empty()) {
spdlog::warn("Couldn't detect overlay hotkey, using default: F12");
return {"KEY_F12"}; //default
else {
UWPOverlayEnabler::EnableUwpOverlay();
}
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;
}
bool SteamTarget::getXBCRebindingEnabled()
{
const auto config_path = std::wstring(steam_path_) + std::wstring(user_data_path_) + steam_user_id_ + std::wstring(config_file_name_);
if (!std::filesystem::exists(config_path)) {
spdlog::warn(L"Couldn't read Steam config file: \"{}\"", config_path);
return false;
hidhide_.hideDevices(steam_path_);
input_redirector_.run();
#endif
if (Settings::launch.launch) {
launcher_.launchApp(Settings::launch.launchPath, Settings::launch.launchAppArgs);
}
std::ifstream config_file(config_path);
auto root = tyti::vdf::read(config_file);
keepControllerConfig(true);
if (root.attribs.empty() || !root.attribs.contains("SteamController_XBoxSupport")) {
spdlog::warn("\"Xbox Configuration Support\" is disabled in Steam. This may cause doubled Inputs!");
return false;
if (cef_tweaks_enabled_) {
steam_tweaks_.setAutoInject(true);
}
auto xbsup = root.attribs.at("SteamController_XBoxSupport");
if (xbsup != "1") {
spdlog::warn("\"Xbox Configuration Support\" is disabled in Steam. This may cause doubled Inputs!");
}
return xbsup == "1";
fully_initialized_ = true;
}
/*
* The "magic" that keeps a controller-config forced (without hooking into Steam)
*
* Hook into own process and detour "GetForegroundWindow"
* Deatour function always returns HWND of own application window
* Detour function always returns HWND of own application window
* Steam now doesn't detect application changes and keeps the game-specific input config without reverting to desktop-conf
*/
void SteamTarget::keepControllerConfig(bool keep)
@ -391,6 +449,7 @@ void SteamTarget::keepControllerConfig(bool keep)
spdlog::error("Couldn't un-install GetForegroundWindow hook!");
}
}
#endif
}
@ -422,6 +481,34 @@ HWND SteamTarget::keepFgWindowHookFn()
}
#endif
std::unique_ptr<Tray::Tray> SteamTarget::createTrayMenu()
{
#ifdef _WIN32
HICON icon = 0;
TCHAR path[MAX_PATH];
GetModuleFileName(nullptr, path, MAX_PATH);
icon = (HICON)LoadImage(
0,
path,
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_LOADFROMFILE | LR_LOADMAP3DCOLORS);
if (!icon) {
ExtractIconEx(path, 0, &icon, nullptr, 1);
}
auto tray = std::make_unique<Tray::Tray>("GlosSITarget", icon);
#else
auto tray = std::make_unique<Tray::Tray>("GlosSITarget", "ico.png");
#endif
tray->addEntry(Tray::Button{
"Quit", [this, &tray]() {
run_ = false;
}});
return tray;
}
void SteamTarget::overlayHotkeyWorkaround()
{
static bool pressed = false;

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -14,22 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#define WIN32_LEAN_AND_MEAN
#include "SteamOverlayDetector.h"
#include "TargetWindow.h"
#ifdef _WIN32
#include "HidHide.h"
#include "../common/HidHide.h"
#include "InputRedirector.h"
#include <subhook.h>
#endif
#include <filesystem>
#include "AppLauncher.h"
#include "CEFInject.h"
#include "Overlay.h"
#include "HttpServer.h"
#include <filesystem>
#include "../common/steam_util.h"
namespace Tray {
class Tray;
}
class SteamTarget {
public:
explicit SteamTarget();
@ -39,17 +47,16 @@ class SteamTarget {
void onOverlayChanged(bool overlay_open);
void toggleGlossiOverlay();
void focusWindow(WindowHandle hndl);
std::filesystem::path getSteamPath() const;
std::wstring getSteamUserId() const;
std::filesystem::path steam_path_ = getSteamPath();
std::wstring steam_user_id_ = getSteamUserId();
std::vector<std::string> getOverlayHotkey();
std::vector<std::string> getScreenshotHotkey();
bool getXBCRebindingEnabled();
std::filesystem::path steam_path_ = util::steam::getSteamPath();
std::wstring steam_user_id_ = util::steam::getSteamUserId();
CEFInject::SteamTweaks steam_tweaks_;
bool cef_tweaks_enabled_ = true;
bool steam_overlay_present_ = false;
bool fully_initialized_ = false;
bool can_fully_initialize_ = true;
void init_FuckingRenameMe();
// Keep controllerConfig even is window is switched.
// On Windoze hooking "GetForeGroundWindow" is enough;
@ -62,6 +69,8 @@ class SteamTarget {
static inline HWND last_real_hwnd_ = nullptr;
#endif
std::unique_ptr<Tray::Tray> createTrayMenu();
/*
* Run once per frame
* detects steam configured overlay hotkey, and simulates key presses to window
@ -71,7 +80,7 @@ class SteamTarget {
void overlayHotkeyWorkaround();
bool run_ = false;
std::vector<std::string> overlay_hotkey_ = getOverlayHotkey();
std::vector<std::string> overlay_hotkey_ = util::steam::getOverlayHotkey(steam_path_, steam_user_id_);
#ifdef _WIN32
HidHide hidhide_;
@ -81,18 +90,14 @@ class SteamTarget {
std::weak_ptr<Overlay> overlay_;
SteamOverlayDetector detector_;
AppLauncher launcher_;
HttpServer server_;
WindowHandle last_foreground_window_ = nullptr;
static inline WindowHandle target_window_handle_ = nullptr;
sf::Clock overlay_trigger_clock_;
uint32_t overlay_trigger_max_seconds_ = 1;
float overlay_trigger_max_seconds_ = 2.5;
bool overlay_trigger_flag_ = false;
bool delayed_shutdown_ = false;
sf::Clock delay_shutdown_clock_;
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 overlay_hotkey_name_ = "InGameOverlayShortcutKey ";
static constexpr std::string_view screenshot_hotkey_name_ = "InGameOverlayScreenshotHotKey ";
};

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -30,7 +30,7 @@ limitations under the License.
#include "ProcessPriority.h"
#include "Settings.h"
#include "..\common\Settings.h"
#if !defined(WM_DPICHANGED)
#define WM_DPICHANGED 0x02E0
@ -56,6 +56,11 @@ TargetWindow::TargetWindow(
if (ImGui::Checkbox("Window mode", &Settings::window.windowMode)) {
toggle_window_mode_after_frame_ = true;
}
#ifdef _WIN32
if (ImGui::Checkbox("Hide from Alt+Tab", &Settings::window.hideAltTab)) {
toggle_hidealttab_after_frame_ = true;
}
#endif
ImGui::Text("Max. FPS");
ImGui::SameLine();
int max_fps_copy = Settings::window.maxFps;
@ -68,7 +73,7 @@ TargetWindow::TargetWindow(
}
if (Settings::window.maxFps < 15 && Settings::window.maxFps > 0) {
Settings::window.maxFps = 0;
setFpsLimit(screen_refresh_rate_);
setFpsLimit(TargetWindow::calcAutoRefreshRate(screen_refresh_rate_));
} else {
setFpsLimit(Settings::window.maxFps);
}
@ -116,17 +121,69 @@ void TargetWindow::setClickThrough(bool click_through)
}
#ifdef _WIN32
HWND hwnd = window_.getSystemHandle();
if (click_through) {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_COMPOSITED);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
// hiding GlosSI from Alt-Tab list
// https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
if (Settings::window.hideAltTab) {
toggle_hidealttab_after_frame_ = false;
if (click_through) {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_COMPOSITED | WS_EX_TOOLWINDOW);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
else {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_COMPOSITED | WS_EX_TOOLWINDOW);
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
}
else {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_COMPOSITED);
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
if (click_through) {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_COMPOSITED);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
else {
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_COMPOSITED);
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
}
#endif
}
void TargetWindow::setTransparent(bool transparent) const
{
HWND hwnd = window_.getSystemHandle();
if (transparent) {
// if (windowed_) {
// DWM_BLURBEHIND bb{.dwFlags = DWM_BB_ENABLE, .fEnable = true, .hRgnBlur = nullptr};
// DwmEnableBlurBehindWindow(hwnd, &bb);
// } // semi-transparent in window mode, but deprecated api
// On Linux the window will (should) automagically be semi-transparent
// transparent windows window...
auto style = GetWindowLong(hwnd, GWL_STYLE);
style &= ~WS_OVERLAPPED;
style |= WS_POPUP;
SetWindowLong(hwnd, GWL_STYLE, style);
MARGINS margins = { -1 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
spdlog::debug("Setting window to transparent");
} else {
auto style = GetWindowLong(hwnd, GWL_STYLE);
style |= WS_OVERLAPPED;
style &= ~WS_POPUP;
SetWindowLong(hwnd, GWL_STYLE, style);
MARGINS margins = {0};
DwmExtendFrameIntoClientArea(hwnd, &margins);
spdlog::debug("Setting window to opaque");
}
}
void TargetWindow::update()
{
sf::Event event{};
@ -142,9 +199,15 @@ void TargetWindow::update()
screenShotWorkaround();
overlay_->update();
window_.display();
#ifdef _WIN32
if (toggle_hidealttab_after_frame_) {
toggle_hidealttab_after_frame_ = false;
}
#endif
if (toggle_window_mode_after_frame_) {
createWindow();
}
// As SFML screws us out of most windows-events, just poll resolution every once in a while
// If changed, recreate window.
// Fixes Blackscreen issues when user does funky stuff and still uses GlosSI in non windowed mod...
@ -279,6 +342,18 @@ WORD TargetWindow::GetWindowDPI(HWND hWnd)
}
#endif
unsigned int TargetWindow::calcAutoRefreshRate(unsigned int rate)
{
unsigned int auto_refresh_rate = rate;
while (auto_refresh_rate > 60) {
auto_refresh_rate /= 2;
}
if (auto_refresh_rate < 30) {
auto_refresh_rate = 30;
}
return auto_refresh_rate;
}
void TargetWindow::createWindow()
{
toggle_window_mode_after_frame_ = false;
@ -324,21 +399,7 @@ void TargetWindow::createWindow()
auto dpi = GetWindowDPI(hwnd);
spdlog::debug("Screen DPI: {}", dpi);
//if (windowed_) {
// DWM_BLURBEHIND bb{.dwFlags = DWM_BB_ENABLE, .fEnable = true, .hRgnBlur = nullptr};
// DwmEnableBlurBehindWindow(hwnd, &bb);
//} // semi-transparent in window mode, but deprecated api
// On Linux the window will (should) automagically be semi-transparent
// transparent windows window...
auto style = GetWindowLong(hwnd, GWL_STYLE);
style &= ~WS_OVERLAPPED;
style |= WS_POPUP;
SetWindowLong(hwnd, GWL_STYLE, style);
MARGINS margins;
margins.cxLeftWidth = -1;
DwmExtendFrameIntoClientArea(hwnd, &margins);
setTransparent(true);
DEVMODE dev_mode = {};
dev_mode.dmSize = sizeof(DEVMODE);
@ -350,13 +411,14 @@ void TargetWindow::createWindow()
screen_refresh_rate_ = 60;
}
else {
setFpsLimit(dev_mode.dmDisplayFrequency);
setFpsLimit(TargetWindow::calcAutoRefreshRate(dev_mode.dmDisplayFrequency));
screen_refresh_rate_ = dev_mode.dmDisplayFrequency;
}
overlay_ = std::make_shared<Overlay>(
window_, [this]() { close(); }, toggle_overlay_state_, Settings::window.windowMode);
spdlog::debug("auto screen Scale: {}", dpi/96.f);
ImGuiIO& io = ImGui::GetIO();
io.FontGlobalScale = dpi / 96.f;
ImGui::SFML::UpdateFontTexture();
@ -370,12 +432,13 @@ void TargetWindow::createWindow()
setFpsLimit(Settings::window.maxFps);
}
if (Settings::window.scale > 0.3f) { // Now that's just getting ridicoulus
spdlog::debug("setting screen scale by config: {}", Settings::window.scale);
ImGuiIO& io = ImGui::GetIO();
io.FontGlobalScale = Settings::window.scale;
ImGui::SFML::UpdateFontTexture();
}
else {
spdlog::warn("Not applying too low screen scale setting");
spdlog::debug("Not applying too low screen scale setting");
}
// window_.setSize({desktop_mode.width - 1, desktop_mode.height - 1 });

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -39,6 +39,7 @@ class TargetWindow {
void setFpsLimit(unsigned int fps_limit);
void setClickThrough(bool click_through);
void setTransparent(bool transparent) const;
void update();
void close();
@ -79,7 +80,9 @@ class TargetWindow {
std::shared_ptr<Overlay> overlay_;
static unsigned int calcAutoRefreshRate(unsigned int rate);
void createWindow();
bool toggle_window_mode_after_frame_ = false;
bool toggle_hidealttab_after_frame_ = false;
};

@ -18,44 +18,13 @@ inline std::filesystem::path EnablerPath()
return path.substr(0, 1 + path.find_last_of(L'\\')) + L"UWPOverlayEnablerDLL.dll";
}
inline DWORD ExplorerPid()
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(snapshot, &entry) == TRUE) {
while (Process32Next(snapshot, &entry) == TRUE) {
if (std::wstring(entry.szExeFile).find(L"explorer.exe") != std::string::npos) {
return entry.th32ProcessID;
}
}
}
CloseHandle(snapshot);
return 0;
}
} // namespace internal
inline void EnableUwpOverlay()
{
const auto enabler_path = internal::EnablerPath();
if (std::filesystem::exists(enabler_path)) {
const auto explorer_pid = internal::ExplorerPid();
if (explorer_pid != 0) {
if (DllInjector::TakeDebugPrivilege()) {
// No need to eject, as the dll is self-ejecting.
if (DllInjector::Inject(explorer_pid, enabler_path.wstring())) {
spdlog::info("Successfully injected UWPOverlay enabler into explorer.exe");
}
}
}
else {
spdlog::error("explorer not found"); // needs loglevel WTF
}
}
else {
spdlog::error("UWPOverlayEnablerDLL not found");
}
DllInjector::injectDllInto(enabler_path, L"explorer.exe");
}
inline void AddUwpOverlayOvWidget()

@ -1,55 +0,0 @@
/*
Copyright 2021-2022 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 "UnhookUtil.h"
#include <spdlog/spdlog.h>
#include "Settings.h"
void UnhookUtil::UnPatchHook(const std::string& name, HMODULE module)
{
spdlog::trace("Patching \"{}\"...", name);
BYTE* address = reinterpret_cast<BYTE*>(GetProcAddress(module, name.c_str()));
if (!address) {
spdlog::error("failed to unpatch \"{}\"", name);
}
std::string bytes;
if (Settings::isWin10 && UNHOOK_BYTES_ORIGINAL_WIN10.contains(name)) {
bytes = UNHOOK_BYTES_ORIGINAL_WIN10.at(name);
}
else {
bytes = UNHOOK_BYTES_ORIGINAL_22000.at(name);
}
DWORD dw_old_protect, dw_bkup;
const auto len = bytes.size();
if (!VirtualProtect(address, len, PAGE_EXECUTE_READWRITE, &dw_old_protect)) { // Change permissions of memory..
spdlog::error("Couldn't change permissions of memory for \"{}\"", name);
return;
}
const auto opcode = *(address);
if (!std::ranges::any_of(JUMP_INSTR_OPCODES, [&opcode](const auto& op) { return op == opcode; })) {
spdlog::debug("\"{}\" Doesn't appear to be hooked, skipping!", name);
}
else {
for (DWORD i = 0; i < len; i++) // unpatch Valve's hook
{
*(address + i) = bytes[i];
}
spdlog::trace("Unpatched \"{}\"", name);
}
VirtualProtect(address, len, dw_old_protect, &dw_bkup); // Revert permission change...
}

@ -1,61 +0,0 @@
/*
Copyright 2021-2022 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
#define NOMINMAX
#include <map>
#include <Windows.h>
#include <string>
#include <vector>
namespace UnhookUtil {
void UnPatchHook(const std::string& name, HMODULE module);
static inline const std::vector<uint8_t> JUMP_INSTR_OPCODES = {
0xE9,
0xE8,
0xEB,
0xEA,
0xFF};
// Valve Hooks various functions and hides Gaming devices like this.
// To be able to query them, unpatch the hook with the original bytes...
// 22000 ^= Windows build number
static inline const std::map<std::string, std::string> UNHOOK_BYTES_ORIGINAL_22000 = {
{"SetupDiEnumDeviceInfo", "\x48\x89\x5C\x24\x08"},
{"SetupDiGetClassDevsW", "\x48\x89\x5C\x24\x08"},
{"HidD_GetPreparsedData", "\x48\x89\x5C\x24\x18"},
{"HidP_GetCaps", "\x4C\x8B\xD1\x48\x85\xC9"},
{"HidD_GetAttributes", "\x40\x53\x48\x83\xEC"},
{"HidD_GetProductString", "\x48\x83\xEC\x48\x48"},
{"HidP_GetUsages", "\x4C\x89\x4C\x24\x20"},
{"HidP_GetData", "\x4C\x89\x44\x24\x18"},
{"HidP_GetValueCaps", "\x48\x83\xEC\x48\x49"},
{"HidP_GetUsageValue", "\x40\x53\x55\x56\x48"},
{"HidP_GetButtonCaps", "\x48\x83\xEC\x48\x49"},
// Valve hooks "CreateProcess" to detect child-processes
{"CreateProcessW", "\x4C\x8B\xDC\x48\x83"},
};
// SetupApi.dll is different on Win10 than on Win11
static inline const std::map<std::string, std::string> UNHOOK_BYTES_ORIGINAL_WIN10 = {
{"SetupDiEnumDeviceInfo", "\x40\x53\x56\x57\x41\x54\x41\x55"},
{"SetupDiGetClassDevsW", "\x48\x8B\xC4\x48\x89\x58\x08"},
};
} // namespace UnhookUtil

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -14,10 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <DbgHelp.h>
#include <ShlObj.h>
#include <shellapi.h>
#endif
#include <httplib.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
@ -26,7 +30,7 @@ limitations under the License.
#include "SteamTarget.h"
#include "OverlayLogSink.h"
#include "Settings.h"
#include "..\common\Settings.h"
#include <iostream>
#include "../version.hpp"
@ -67,15 +71,7 @@ LONG Win32FaultHandler(struct _EXCEPTION_POINTERS* ExInfo)
MINIDUMP_EXCEPTION_INFORMATION M;
HANDLE hDump_File;
auto path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
path /= "Roaming";
path /= "GlosSI";
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto path = util::path::getDataDirPath();
path /= "glossitarget.dmp";
M.ThreadId = GetCurrentThreadId();
@ -119,15 +115,7 @@ int main(int argc, char* argv[])
const auto console_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
console_sink->set_level(spdlog::level::trace);
#ifdef _WIN32
auto path = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
path /= "Roaming";
path /= "GlosSI";
if (!std::filesystem::exists(path))
std::filesystem::create_directories(path);
auto path = util::path::getDataDirPath();
path /= "glossitarget.log";
// For "path.wstring()" to be usable here, SPDLOG_WCHAR_FILENAMES must be defined.
const auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.wstring(), true);
@ -153,12 +141,21 @@ int main(int argc, char* argv[])
auto exit = 1;
try {
#ifdef _WIN32
auto existingwindow = FindWindowA(nullptr, "GlosSITarget");
if (existingwindow) {
spdlog::error("GlosSITarget is already running! Closing old process...");
httplib::Client client("http://localhost:8756");
client.Post("/quit");
}
int numArgs;
LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &numArgs);
std::wstring argsv = L"";
std::vector<std::wstring> argsv;
argsv.reserve(numArgs);
if (numArgs > 1) {
for (int i = 1; i < numArgs; i++)
argsv += i == 1 ? args[i] : std::wstring(L" ") + args[i];
argsv.emplace_back(args[i]);
}
Settings::Parse(argsv);
Settings::checkWinVer();

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build GlosSIWatchdog (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSIWatchdog",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build GlosSIWatchdog (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:GlosSIWatchdog:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -40,13 +40,13 @@
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
@ -71,10 +71,12 @@
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\deps\spdlog\include;..\deps\json\include;$(IncludePath)</IncludePath>
<IncludePath>..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;..\CEFInjectLib;$(IncludePath)</IncludePath>
<LibraryPath>..\x64\Debug;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>..\deps\spdlog\include;..\deps\json\include;$(IncludePath)</IncludePath>
<IncludePath>..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;..\CEFInjectLib;$(IncludePath)</IncludePath>
<LibraryPath>..\x64\Release;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@ -111,11 +113,15 @@
<PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions);WATCHDOG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>hid.lib;Cfgmgr32.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>hid.lib;Cfgmgr32.lib;setupapi.lib;CefInjectLib.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<CustomBuildStep>
<Command>powershell.exe $(SolutionDir)version_help.ps1</Command>
@ -131,13 +137,16 @@
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions);WATCHDOG;SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_WCHAR_FILENAMES;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>hid.lib;Cfgmgr32.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>hid.lib;Cfgmgr32.lib;setupapi.lib;CefInjectLib.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<CustomBuildStep>
<Outputs>Upading version based on git;%(Outputs)</Outputs>
@ -145,9 +154,9 @@
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\GlosSITarget\HidHide.cpp" />
<ClCompile Include="..\GlosSITarget\UnhookUtil.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="..\common\HidHide.cpp" />
<ClCompile Include="..\common\UnhookUtil.cpp" />
<ClCompile Include="dllmain.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h" />

@ -15,13 +15,13 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\GlosSITarget\HidHide.cpp">
<ClCompile Include="..\common\HidHide.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\GlosSITarget\UnhookUtil.cpp">
<ClCompile Include="..\common\UnhookUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

@ -0,0 +1,162 @@
/*
Copyright 2021-2023 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 <httplib.h>
#include "../common/util.h"
#include <filesystem>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#include "../version.hpp"
#include "../common/Settings.h"
#include "../common/HidHide.h"
bool IsProcessRunning(DWORD pid)
{
const HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (process == nullptr)
return false;
const DWORD ret = WaitForSingleObject(process, 0);
CloseHandle(process);
return ret == WAIT_TIMEOUT;
}
void fetchSettings(httplib::Client& http_client, int retried_count = 0) {
http_client.set_connection_timeout(1 + (retried_count > 0 ? 2 : 0));
auto http_res = http_client.Get("/settings");
if (http_res.error() == httplib::Error::Success && http_res->status == 200)
{
const auto json = nlohmann::json::parse(http_res->body);
spdlog::debug("Received settings from GlosSITarget: {}", json.dump());
Settings::Parse(json);
}
else
{
spdlog::error("Couldn't get settings from GlosSITarget. Error: {}", (int)http_res.error());
if (retried_count < 2)
{
spdlog::info("Retrying... {}", retried_count);
fetchSettings(http_client, retried_count + 1);
}
}
}
DWORD WINAPI watchdog(HMODULE hModule)
{
auto configDirPath = util::path::getDataDirPath();
auto logPath = configDirPath;
logPath /= "GlosSIWatchdog.log";
const auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logPath.wstring(), true);
std::vector<spdlog::sink_ptr> sinks{ file_sink };
auto logger = std::make_shared<spdlog::logger>("log", sinks.begin(), sinks.end());
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::trace);
spdlog::set_default_logger(logger);
spdlog::info("GlosSIWatchdog loaded");
spdlog::info("Version: {}", version::VERSION_STR);
auto glossi_hwnd = FindWindowA(nullptr, "GlosSITarget");
if (!glossi_hwnd)
{
spdlog::error("Couldn't find GlosSITarget window. Exiting...");
FreeLibraryAndExitThread(hModule, 1);
return 1;
}
spdlog::debug("Found GlosSITarget window; Starting watch loop");
httplib::Client http_client("http://localhost:8756");
fetchSettings(http_client);
std::vector<DWORD> pids;
while (glossi_hwnd)
{
http_client.set_connection_timeout(120);
const auto http_res = http_client.Get("/launched-pids");
if (http_res.error() == httplib::Error::Success && http_res->status == 200)
{
const auto json = nlohmann::json::parse(http_res->body);
if (Settings::common.extendedLogging)
{
spdlog::trace("Received pids: {}", json.dump());
}
pids = json.get<std::vector<DWORD>>();
}
else {
spdlog::error("Couldn't fetch launched PIDs: {}", (int)http_res.error());
}
glossi_hwnd = FindWindowA(nullptr, "GlosSITarget");
Sleep(333);
}
spdlog::info("GlosSITarget was closed. Resetting HidHide state...");
HidHide hidhide;
hidhide.disableHidHide();
if (Settings::launch.closeOnExit)
{
spdlog::info("Closing launched processes");
for (const auto pid : pids)
{
if (Settings::common.extendedLogging)
{
spdlog::debug("Checking if process {} is running", pid);
}
if (IsProcessRunning(pid))
{
util::win::process::KillProcess(pid);
}
else
{
if (Settings::common.extendedLogging)
{
spdlog::debug("Process {} is not running", pid);
}
}
}
}
spdlog::info("Unloading Watchdog...");
FreeLibraryAndExitThread(hModule, 0);
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)watchdog, hModule, 0, nullptr));
}
else if (ul_reason_for_call == DLL_PROCESS_DETACH) {
}
return TRUE;
return 0;
}

@ -1,77 +0,0 @@
/*
Copyright 2021-2022 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.
*/
#define NOMINMAX
#include <Windows.h>
#include <filesystem>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include "../version.hpp"
#include "../GlosSITarget/HidHide.h"
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
auto configDirPath = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
configDirPath /= "Roaming";
configDirPath /= "GlosSI";
if (!std::filesystem::exists(configDirPath))
std::filesystem::create_directories(configDirPath);
auto logPath = configDirPath;
logPath /= "GlosSIWatchdog.log";
const auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logPath.wstring(), true);
std::vector<spdlog::sink_ptr> sinks{ file_sink };
auto logger = std::make_shared<spdlog::logger>("log", sinks.begin(), sinks.end());
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::trace);
spdlog::set_default_logger(logger);
spdlog::info("GlosSIWatchdog loaded");
spdlog::info("Version: {}", version::VERSION_STR);
auto glossi_hwnd = FindWindowA(nullptr, "GlosSITarget");
if (!glossi_hwnd)
{
spdlog::error("Couldn't find GlosSITarget window. Exiting...");
return 1;
}
spdlog::debug("Found GlosSITarget window; Starting watch loop");
while (glossi_hwnd)
{
glossi_hwnd = FindWindowA(nullptr, "GlosSITarget");
Sleep(1337);
}
spdlog::info("GlosSITarget was closed. Cleaning up...");
HidHide hidhide;
hidhide.disableHidHide();
return 0;
}

@ -3,7 +3,7 @@
!define APP_NAME "GlosSI"
!define COMP_NAME "Peter Repukat - Flatspotsoftware"
!define WEB_SITE "https://glossi.flatspot.pictures/"
!define VERSION "0.0.9.1-48-geb4ae9c"
!define VERSION "0.1.2.0-68-g0f02eca"
!define COPYRIGHT "Peter Repukat - FlatspotSoftware © 2017-2022"
!define DESCRIPTION "SteamInput compatibility tool"
!define INSTALLER_NAME "GlosSI-Installer.exe"
@ -193,3 +193,55 @@ SectionEnd

@ -1,7 +1,8 @@
[![Build status](https://ci.appveyor.com/api/projects/status/l9hq9qglvn6q5wdg/branch/main?svg=true)](https://ci.appveyor.com/project/Alia5/glossi/branch/main) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Discord](https://img.shields.io/discord/368823110817808384.svg)](https://discord.gg/T9b4D5y) ![version](https://img.shields.io/github/v/tag/alia5/glossi?label=version) [![WebsiteAndDownloads](https://img.shields.io/website?label=Website%20%26%20downloads&url=https%3A%2F%2Fglossi.flatspot.pictures)](https://glossi.flatspot.pictures)
<div style="display: flex; align-items: center">
<img align="center" src="https://github.com/Alia5/GlosSI/blob/main/GlosSI_Logo_512.png?raw=true" width="256" height="256" alt="GlosSI Logo" /><h1>GlosSI - Global (systemwide) Steam Input</h1>
<h1 align="center"><img align="center" src="https://github.com/Alia5/GlosSI/blob/main/GlosSI_Logo_512.png?raw=true" width="256" height="256" alt="GlosSI Logo" />
GlosSI&nbsp;-&nbsp;Global&nbsp;(systemwide)&nbsp;Steam&nbsp;Input</h1>
</div>
GlosSI formerly knows as GloSC (Global Steam Controller), is a tool that allows one to use Steam-Input controller rebinding at a system-level alongside a system wide (borderless window) Steam overlay
@ -11,6 +12,44 @@ The primary use case of GlosSI is to use SteamInput (required for SteamControlle
GlosSI can, but isn't required to, launch any of your favorite games or applications and directly add them to Steam, be it Win32 or Windows Store (UWP)!
It is **the tool** to enjoy any game that has trouble with Steam and/or *add extra functionality* to your Steam-Input needs
---
```
```
# ViGEm End of Life
As you may or may not have already noticed ViGEm, a substantial part in making GlosSI work it's magic is End of Life.
You can read the announcement [here](https://docs.nefarius.at/projects/ViGEm/End-of-Life/)
As I don't think holding on to deprecated dependencies is a good way of moving forward, this effectively kills GlosSI as well since without ViGEm and HidHide GlosSI cannot function.
There will be a last deprecated version of ViGEm circumventing an issue stated in their announcement.
GlosSI will be updated a last time as well, providing this version bundled with it.
The GlosSI website will be taken down, however, you can then fin the last release, here on GitHub
### GlosSI won't be taken down or magically stop working, nor will it be unsafe to use. Just a maintenance stop.
---
I also want to take the opportunity to give a MASSIVE shoutout to @Nefarius , the creator of ViGEm, HidHide and many other awesome tools
Back when good old GloSC was just a cobbled together PoC using parts of the very old ScpToolkit he has been massively helpful and even shared ViGEm with me way before it was ready to be run on any machine that doesn't belong to a wizard (or should I say sorcerer?) like him.
---
Will GlosSI continue?
Probably. But most likely not in its current form.
@Nefarius wizardry is continuing and a successor to ViGEm is being worked on, but nothing has been publicly released, yet.
As I severely lack the time to properly maintain a project like GlosSI (as you probably have already noticed, I'm sure), I'm quite fond of the idea of rebooting the project yet again, once ViGEms successor becomes available.
However, I'm not sure if I find the time and motivation again to continue with GlosSI
Until I can (and want to) get my hands on it, the future is unsure...
I'll be back to silence for now then.
Thanks for all the support, it was a blast!
```
```
---
## How does it work? / What does it do?
@ -66,7 +105,7 @@ For Building instructions refer to [BUILDING.md](./docs/BUILDING.md)
## License
```license
Copyright 2017-2022 Peter Repukat - FlatspotSoftware
Copyright 2017-2023 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.

@ -0,0 +1,187 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
env: {
browser: true,
node: false
},
plugins: [
'@typescript-eslint',
'no-null',
'prefer-arrow',
'import',
],
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
parserOptions: {
ecmaVersion: 2022, // Allows for the parsing of modern ECMAScript features
sourceType: 'module',
ecmaFeatures: {
jsx: false,
},
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname
},
rules: {
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/ban-types': 'error',
'@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/no-inferrable-types': 'error',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/prefer-namespace-keyword': 'error',
'no-inner-declarations': 'off', // we have es6blocked scoped functions.
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/unbound-method': 'warn',
'@typescript-eslint/semi': [
'error',
'always'
],
'@typescript-eslint/quotes': [
'warn',
'single'
],
'@typescript-eslint/member-delimiter-style': [
'error',
{
'multiline': {
'delimiter': 'semi',
'requireLast': true
},
'singleline': {
'delimiter': 'semi',
'requireLast': false
}
}
],
'@typescript-eslint/indent': [
'warn',
4,
{
'FunctionDeclaration': {
'parameters': 'first'
},
'FunctionExpression': {
'parameters': 'first'
},
'SwitchCase': 1
}
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
'accessibility': 'explicit'
}
],
'@typescript-eslint/no-use-before-define': ['error', { 'functions': false }],
// "@typescript-eslint/naming-convention": [
// "error",
// {
// "selector": "default",
// "format": ["camelCase", "PascalCase"]
// },
// {
// "selector": "variable",
// "format": ["camelCase", "UPPER_CASE"]
// },
// {
// "selector": "parameter",
// "format": ["camelCase"],
// "leadingUnderscore": "allow"
// },
// {
// "selector": "memberLike",
// "modifiers": ["private"],
// "format": ["camelCase"],
// "leadingUnderscore": "require"
// },
// {
// "selector": "typeLike",
// "format": ["PascalCase"]
// }
// ],
'no-console': 'off',
'no-return-await': 'error',
'arrow-body-style': 'error',
'arrow-parens': [
'error',
'always'
],
'camelcase': ['warn', { "ignoreImports": true }],
'comma-dangle': [
'error',
{
'objects': 'never',
'arrays': 'never',
'functions': 'never'
}
],
'prefer-arrow/prefer-arrow-functions': 'error',
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'quote-props': [
'error',
'consistent-as-needed'
],
'no-var': 'error',
'new-parens': 'error',
'no-caller': 'error',
'no-cond-assign': 'error',
'no-debugger': 'error',
'no-empty': 'error',
'no-eval': 'error',
'no-multiple-empty-lines': 'warn',
'no-new-wrappers': 'error',
'no-redeclare': 'error',
'no-shadow': [
'error',
{
'hoist': 'all'
}
],
'no-null/no-null': 'error',
'no-throw-literal': 'error',
'no-trailing-spaces': 'error',
'no-undef-init': 'error',
'no-underscore-dangle': 'error',
'no-unsafe-finally': 'error',
'no-unused-labels': 'error',
'spaced-comment': 'error',
'use-isnan': 'error',
'max-lines': [
'error',
{
'max': 300,
'skipBlankLines': true,
'skipComments': true
}
],
'max-len': [
'warn',
{
'code': 140
}
],
'dot-notation': 'error',
'eqeqeq': 'error',
'eol-last': 'error',
'linebreak-style': ['error', 'windows'],
'block-spacing': ['error', 'always'],
'object-curly-spacing': ["error", "always"],
'import/no-deprecated': 'warn', // eslint deprecation rule sucks. just wrns on deprecated IMPORTs
},
settings: {
},
};

@ -0,0 +1,4 @@
node_modules/
dist/
tsconfig.tsbuildinfo
.rollup.tscache/

@ -0,0 +1,10 @@
{
"configurations": [
{
"name": "Attach to Steam CEF",
"port": 8080,
"request": "attach",
"type": "chrome",
}
]
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,28 @@
{
"name": "glossi_steamtweaks",
"version": "0.0.0",
"type": "module",
"scripts": {
"clean": "npx rimraf dist .rollup.tscache tsconfig.tsbuildinfo ",
"build": "npx rollup -c rollup.config.js",
"build:clean": "npm run clean && npm run build",
"build:copy": "npx rimraf ../x64/Debug/SteamTweaks && npm run build && cd dist && npx copyfiles -a -V ./**/* ../../x64/Debug/SteamTweaks"
},
"author": "Peter Repukat - FlatspotSoftware",
"license": "Apache-2.0",
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"copyfiles": "^2.4.1",
"eslint": "^8.33.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"rollup": "^3.12.0",
"typescript": "^4.9.4",
"rimraf": "^4.1.2"
},
"dependencies": {
}
}

@ -0,0 +1,46 @@
import typescript from '@rollup/plugin-typescript';
import { readdirSync, lstatSync } from 'fs';
import path from 'path';
const getFileListForDir = (dir) => {
return readdirSync(dir).map((file) => {
const absolute = path.resolve(dir, file);
if (file.endsWith('.ts')) {
return absolute;
}
if (lstatSync(absolute).isDirectory()) {
return getFileListForDir(absolute)
}
}).flat(999);
}
const tsPluginConf = typescript({
cacheDir: '.rollup.tscache'
});
export default [
{
input: 'src/GlosSITweaks.ts',
output: {
dir: 'dist',
sourcemap: "inline",
format: 'iife',
// name: 'GlosSITweaks' // don't use name; don't pollute global namespace
},
plugins: [tsPluginConf],
},
...getFileListForDir('src/Tweaks').map((file) => {
return {
input: file,
output: {
file: file.replace('src', 'dist').replace(/\.ts$/, '.js'),
sourcemap: "inline",
format: 'iife',
// name: path.basename(file).replace(/\.ts$/, '') // don't use name; don't pollute global namespace
},
plugins: [tsPluginConf],
}
})
];

@ -0,0 +1,44 @@
export interface GlosSISettings {
controller: {
allowDesktopConfig: boolean;
emulateDS4: boolean;
maxControllers: number;
};
devices: {
hideDevices: boolean;
realDeviceIds: boolean;
};
extendedLogging: boolean;
globalModeGameId: string;
globalModeUseGamepadUI: boolean;
icon?: string;
ignoreEGS: boolean;
killEGS: boolean;
launch: {
closeOnExit: boolean;
ignoreLauncher: boolean;
killLauncher: boolean;
launch: boolean;
launchAppArgs?: string;
launchPath?: string;
launcherProcesses: string[];
waitForChildProcs: boolean;
};
name?: string;
snapshotNotify: boolean;
standaloneModeGameId: string;
standaloneUseGamepadUI: boolean;
minimizeSteamGamepadUI: boolean;
steamPath: string;
steamUserId: string;
steamgridApiKey: string;
version: number;
window: {
disableGlosSIOverlay: boolean;
disableOverlay: boolean;
hideAltTab: boolean;
maxFps?: number;
scale?: number;
windowMode: boolean;
};
}

@ -0,0 +1,26 @@
export interface SteamClient {
Settings: {
// Current stable (As time of commit); Beta does not have this anymore...
SetInGameOverlayShowFPSCorner?: (value: 0|1|2|3|4) => void;
SetInGameOverlayShowFPSContrast?: (value: boolean) => void;
// TODO: find a way to change setting on beta (and soon stable...)
};
UI: {
GetUiMode: () => Promise<SteamUiMode>;
SetUiMode: (mode: SteamUiMode) => void;
};
Window: {
Minimize();
HideWindow();
};
}
export type FullSteamClient = Required<SteamClient>;
declare global {
interface Window {
SteamClient: SteamClient;
}
// eslint-disable-next-line
declare const SteamClient: SteamClient;
}

@ -0,0 +1,157 @@
import type { SteamConfig } from './common/util/types';
import { fetchWithTimeout } from './common/util/util';
import type { GlosSISettings } from './@types/GlosSISettings';
class SteamTargetApi {
public static GlosSIActive = true;
public static readonly ACTIVE_FAIL_THRESHOLD = 2;
private activeFailCounter = 0;
private static ActiveCheckTimer = 0;
public constructor() {
if (SteamTargetApi.ActiveCheckTimer !== 0) {
clearInterval(SteamTargetApi.ActiveCheckTimer);
}
SteamTargetApi.ActiveCheckTimer = setInterval(() => {
void this.getGlosSIActive().then((active) => {
if (!active) {
this.activeFailCounter++;
if (this.activeFailCounter >= SteamTargetApi.ACTIVE_FAIL_THRESHOLD) {
window?.GlosSITweaks?.GlosSI?.uninstall?.();
}
}
});
}, 666);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public log(level: string, ...args: any[]) {
void fetch('http://localhost:8756/log', {
method: 'POST',
body: JSON.stringify({
level,
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `${args}`
})
});
switch (level) {
case 'error':
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.error(...args);
break;
case 'warn':
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.warn(...args);
break;
case 'info':
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.info(...args);
break;
case 'debug':
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.debug(...args);
break;
default:
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.log(...args);
}
}
public async getGlosSIActive() {
return fetchWithTimeout('http://localhost:8756/running', { timeout: 500 })
.then(
() => {
SteamTargetApi.GlosSIActive = true;
return true;
}
).catch(() => {
SteamTargetApi.GlosSIActive = false;
return false;
});
}
public getSteamSettings(): Promise<SteamConfig> {
return fetch('http://localhost:8756/steam_settings')
.then(
(res) => res.json().then(
(json) => (json as SteamConfig).UserLocalConfigStore as SteamConfig
)
);
}
public getGlosSISettings() {
return fetch('http://localhost:8756/settings')
.then(
(res) => res.json().then(
(json) => json as GlosSISettings
)
);
}
}
class GlosSIApiCtor {
public readonly SteamTarget: SteamTargetApi = new SteamTargetApi();
}
interface GlosSITweaks {
[tweakName: string]: { readonly install: () => unknown; readonly uninstall?: () => void };
}
declare global {
interface Window {
GlosSITweaks: GlosSITweaks;
GlosSIApi: InstanceType<typeof GlosSIApiCtor>;
}
// eslint-disable-next-line
const GlosSIApi: InstanceType<typeof GlosSIApiCtor>;
// eslint-disable-next-line
const GlosSITweaks: GlosSITweaks;
}
const installGlosSIApi = () => {
window.GlosSITweaks = {
GlosSI: {
install: () => {
const api = new GlosSIApiCtor();
Object.assign(window, { GlosSIApi: api });
},
uninstall: () => {
Object.entries(window.GlosSITweaks)
.filter(([tweakName]) => (tweakName !== 'GlosSI'))
.forEach(([, obj]) => obj.uninstall?.());
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete window.GlosSIApi;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete window.GlosSITweaks;
}
}
};
window.GlosSITweaks.GlosSI.install();
const glossiCheckInterval = setInterval(() => {
if (window.GlosSIApi) {
void window.GlosSIApi.SteamTarget.getGlosSIActive().then((active) => {
if (!active) {
window?.GlosSITweaks?.GlosSI?.uninstall?.();
}
});
return;
}
clearTimeout(glossiCheckInterval);
}, 5000);
};
if (!window.GlosSITweaks || !window.GlosSIApi) {
installGlosSIApi();
}
export default !!window.GlosSITweaks && !!window.GlosSIApi;

@ -0,0 +1,21 @@
import { initTweak } from '../../common/tweakApi';
initTweak('MinimizeSteamGamepadUI', async () => {
const [isGamepadUI, minimizeGPUI] = await Promise.all([
// (async () => (await SteamClient.UI.GetUiMode()) === SteamUiMode.GamepadUI)(),
true, // Steam is always GamepadUI if injected into GamepadUI, duh!
(async () => (await GlosSIApi.SteamTarget.getGlosSISettings()).minimizeSteamGamepadUI)()
]);
if (isGamepadUI && minimizeGPUI) {
SteamClient.Window.Minimize();
return true;
}
if (!isGamepadUI && minimizeGPUI) {
GlosSIApi.SteamTarget.log('warn', 'MinimizeSteamGamepadUI is enabled but Steam is not in GamepadUI mode');
}
return false;
}).then((minimized: boolean) => {
GlosSIApi.SteamTarget.log('debug', 'MinimizeSteamGamepadUI installed; Minimized GamepadUI:', minimized);
}).catch((e) =>GlosSIApi.SteamTarget.log('error', 'MinimizeSteamGamepadUI failed to install', e));

@ -0,0 +1,33 @@
import type { SteamConfig } from '../../../common/util/types';
import { initTweak } from '../../../common/tweakApi';
const backup: { originalFpsCorner?: number } = {};
initTweak('HideFPSCounter', {
install: async () => {
backup.originalFpsCorner = Number(
((await GlosSIApi.SteamTarget.getSteamSettings()).system as SteamConfig)
.InGameOverlayShowFPSCorner
) as 0 | 1 | 2 | 3 | 4;
if (!SteamClient.Settings.SetInGameOverlayShowFPSCorner) {
GlosSIApi.SteamTarget.log('warn',
'HideFPSCounter: SteamClient.Settings.SetInGameOverlayShowFPSCorner is not available.'
+'Can\'t hide FPS Counter corner.'
);
}
SteamClient.Settings.SetInGameOverlayShowFPSCorner?.(0);
},
uninstall: () => {
if (!SteamClient.Settings.SetInGameOverlayShowFPSCorner) {
return;
}
GlosSIApi.SteamTarget.log('debug','uninstalling HideFPSCounter Tweak. Restoring FPS Counter corner: ', backup.originalFpsCorner);
SteamClient.Settings.SetInGameOverlayShowFPSCorner?.((backup.originalFpsCorner ?? 0) as 0 | 1 | 2 | 3 | 4);
setTimeout(() => {
// steam might not actually register the setting?! Try again like 10 seconds later... ¯\_(ツ)_/¯
SteamClient.Settings.SetInGameOverlayShowFPSCorner?.((backup.originalFpsCorner ?? 0) as 0 | 1 | 2 | 3 | 4);
}, 10 * 1000);
}
}).then(() => {
GlosSIApi.SteamTarget.log('debug', 'HideFPSCounter installed');
}).catch((e) => GlosSIApi.SteamTarget.log('error', 'HideFPSCounter failed to install', e));

@ -0,0 +1,8 @@
// eslint-disable-next-line no-shadow
export enum SteamUiMode {
Desktop = 0,
Unknown1 = 1,
Unknown2 = 2,
Unknown3 = 3,
GamepadUI = 4,
}

@ -0,0 +1,29 @@
export const initTweak = <T>(name: string, tweakMain: (() => T)|{
install: () => T;
uninstall: () => void;
}, force = false): T => {
if (!force && window.GlosSITweaks[name]) {
throw new Error(`Tweak ${name} is already installed!`);
}
if (typeof tweakMain === 'object') {
window.GlosSITweaks[name] = { install: tweakMain.install, uninstall: () => {
try {
tweakMain.uninstall();
} catch (e) {
GlosSIApi.SteamTarget.log('error', e);
}
delete window.GlosSITweaks[name];
} };
} else {
window.GlosSITweaks[name] = { install: tweakMain };
}
try {
return window.GlosSITweaks[name].install() as T;
} catch (e) {
GlosSIApi.SteamTarget.log('error', e);
throw e;
}
};

@ -0,0 +1,3 @@
export interface SteamConfig {
[key: string]: string|SteamConfig;
}

@ -0,0 +1,12 @@
export const fetchWithTimeout = async (input: RequestInfo | URL, init?: RequestInit & { timeout: number }) => {
const { timeout = 8000 } = init || {};
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(input, {
...(init ||{}),
signal: controller.signal
});
clearTimeout(id);
return response;
};

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2022",
"noImplicitAny": true,
"rootDir": "./src",
"outDir": "./dist",
"inlineSourceMap": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"useDefineForClassFields": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"lib": [
"esnext",
"DOM"
],
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
]
}

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build UWPOverlayEnablerDLL (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:UWPOverlayEnablerDLL",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
},
{
"type": "shell",
"label": "Re-Build UWPOverlayEnablerDLL (Debug)",
"command": "msbuild.exe",
"args": [
"GlosSI.sln",
"/target:UWPOverlayEnablerDLL:Rebuild",
"/p:Configuration=Debug",
"/p:Platform=x64"
],
"options": {
"cwd": "${workspaceFolder}/..",
},
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
},
}
]
}

@ -127,6 +127,10 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -145,6 +149,9 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<FloatingPointModel>Fast</FloatingPointModel>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -45,6 +45,8 @@ There are two (known to me, at time of writing) ways to get a working overlay fo
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "../common/util.h"
#define SUBHOOK_STATIC
#include <atomic>
#include <filesystem>
@ -140,19 +142,7 @@ BOOL APIENTRY DllMain( HMODULE hModule,
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
auto configDirPath = std::filesystem::temp_directory_path()
.parent_path()
.parent_path()
.parent_path();
configDirPath /= "Roaming";
configDirPath /= "GlosSI";
if (!std::filesystem::exists(configDirPath))
std::filesystem::create_directories(configDirPath);
auto configDirPath = util::path::getDataDirPath();
auto logPath = configDirPath;
logPath /= "UWPOverlayEnabler.log";
const auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logPath.string(), true);

@ -1,8 +1,38 @@
diff --git a/src/ViGEmClient.vcxproj b/src/ViGEmClient.vcxproj
index 7c186414e62a6334fbcd518d506f55db57491dfe..6b71920a9b848e4a5a3ffb314a2e009a7a22502f 100644
--- a/src/ViGEmClient.vcxproj
+++ b/src/ViGEmClient.vcxproj
@@ -75,26 +75,26 @@
diff --git forkSrcPrefix/src/ViGEmClient.vcxproj forkDstPrefix/src/ViGEmClient.vcxproj
index 7c186414e62a6334fbcd518d506f55db57491dfe..2955df4d391b37bf0b71e17df8bd161c1014a0c4 100644
--- forkSrcPrefix/src/ViGEmClient.vcxproj
+++ forkDstPrefix/src/ViGEmClient.vcxproj
@@ -49,52 +49,52 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>

@ -5,6 +5,10 @@ Remove-Item -Recurse -Force "x64\Release"
$env:_CL_="/MD"
msbuild.exe GlosSI.sln /t:Build /p:Configuration=Release /p:Platform=x64
cd ./SteamTweaks
npm i
npm run build
cd ..
cd ./x64/Release/
@ -26,6 +30,8 @@ Copy-Item "..\..\vc_redist.x64.exe" -Destination "."
Copy-Item "..\..\LICENSE" -Destination "./LICENSE"
Copy-Item "..\..\QT_License" -Destination "./QT_License"
Copy-Item "..\..\THIRD_PARTY_LICENSES.txt" -Destination "./THIRD_PARTY_LICENSES.txt"
Copy-Item "..\..\SteamTweaks\dist" -Destination "./SteamTweaks" -Recurse
#7z a GlosSI-snapshot.zip *

@ -18,6 +18,8 @@ Copy-Item "..\..\vc_redist.x64.exe" -Destination "."
Copy-Item "..\..\LICENSE" -Destination "./LICENSE"
Copy-Item "..\..\QT_License" -Destination "./QT_License"
Copy-Item "..\..\THIRD_PARTY_LICENSES.txt" -Destination "./THIRD_PARTY_LICENSES.txt"
Copy-Item "..\..\steamgrid.exe" -Destination "./steamgrid.exe"
Copy-Item "..\..\SteamTweaks\dist" -Destination "./SteamTweaks" -Recurse
Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libcrypto-1_1-x64.dll" -Destination "./libcrypto-1_1-x64.dll"
Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libssl-1_1-x64.dll" -Destination "./libssl-1_1-x64.dll"

@ -0,0 +1,33 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../deps/SFML/include",
"${workspaceFolder}/../deps/WinReg",
"${workspaceFolder}/../deps/spdlog/include",
"${workspaceFolder}/../deps/ValveFileVDF",
"${workspaceFolder}/../deps/subhook",
"${workspaceFolder}/../deps/ViGEmClient/include",
"${workspaceFolder}/../deps/imgui",
"${workspaceFolder}/../deps/imgui-sfml",
"${workspaceFolder}/../deps/json/include",
"${workspaceFolder}/../deps/traypp/tray/include",
"${workspaceFolder}/../deps/cpp-httplib",
"${workspaceFolder}/../CEFInjectLib"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.24.28314/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++20",
"intelliSenseMode": "msvc-x64"
}
],
"version": 4
}

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -21,16 +21,17 @@ limitations under the License.
#include "HidHide.h"
#include <numeric>
#define SPDLOG_WCHAR_TO_UTF8_SUPPORT
#define SPDLOG_WCHAR_FILENAMES
#include <spdlog/spdlog.h>
#include <vector>
#include <initguid.h>
// Device configuration related
#include <cfgmgr32.h>
#include <initguid.h>
//
#ifndef WATCHDOG
#include "Overlay.h"
#include "../GlosSITarget/Overlay.h"
#endif
#include "Settings.h"
@ -38,8 +39,13 @@ limitations under the License.
#include <devguid.h>
#include <devpkey.h>
#include <regex>
#include <cguid.h>
#include <atlbase.h>
#include "../common/UnhookUtil.h"
#include "UnhookUtil.h"
#pragma comment(lib, "Setupapi.lib")
// {D61CA365-5AF4-4486-998B-9DB4734C6CA3}add the XUSB class GUID as it is missing in the public interfaces
DEFINE_GUID(GUID_DEVCLASS_XUSBCLASS, 0xD61CA365, 0x5AF4, 0x4486, 0x99, 0x8B, 0x9D, 0xB4, 0x73, 0x4C, 0x6C, 0xA3);
@ -48,7 +54,7 @@ DEFINE_GUID(GUID_DEVINTERFACE_XUSB, 0xEC87F1E3, 0xC13B, 0x4100, 0xB5, 0xF7, 0x8B
// {00000000-0000-0000-FFFF-FFFFFFFFFFFF} the system container id
DEFINE_GUID(GUID_CONTAINER_ID_SYSTEM, 0x00000000, 0x0000, 0x0000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
HidHide::HidHide(){};
HidHide::HidHide() {};
void HidHide::openCtrlDevice()
{
@ -81,7 +87,7 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path)
spdlog::info("Hiding devices is disabled; Not un-patching valve hooks, not looking for HidHide");
return;
}
spdlog::debug("Setting up device hiding...");
UnPatchValveHooks();
@ -108,34 +114,34 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path)
for (const auto& exe : whitelist_executeables_) {
auto path = std::regex_replace(steam_path_string, std::wregex(L"(.:)(\\/|\\\\)"), dos_device + L"\\");
path = std::regex_replace(path, std::wregex(L"\\/"), L"\\") + L"\\" + std::wstring{exe};
path = std::regex_replace(path, std::wregex(L"\\/"), L"\\") + L"\\" + std::wstring{ exe };
if (std::ranges::none_of(whitelist, [&path](auto ep) { // make copy!
auto p = path; // non-const(!) copy of path
std::ranges::transform(path, p.begin(), tolower);
std::ranges::transform(ep, ep.begin(), tolower);
return p == ep;
auto p = path; // non-const(!) copy of path
std::ranges::transform(path, p.begin(), tolower);
std::ranges::transform(ep, ep.begin(), tolower);
return p == ep;
})) {
whitelist.push_back(path);
}
}
if (Settings::extendedLogging) {
if (Settings::common.extendedLogging) {
std::ranges::for_each(whitelist, [](const auto& exe) {
spdlog::trace(L"Whitelisted executable: {}", exe);
});
});
}
setAppWhiteList(whitelist);
avail_devices_ = GetHidDeviceList();
if (Settings::extendedLogging) {
if (Settings::common.extendedLogging) {
std::ranges::for_each(avail_devices_, [](const auto& dev) {
spdlog::trace(L"AvailDevice device: {}", dev.name);
});
});
}
blacklisted_devices_ = getBlackListDevices();
for (const auto& dev : avail_devices_) {
if (std::ranges::none_of(blacklisted_devices_, [&dev](const auto& blackdev) {
return blackdev == dev.device_instance_path || blackdev == dev.base_container_device_instance_path;
return blackdev == dev.device_instance_path || blackdev == dev.base_container_device_instance_path;
})) {
// Valve emulated gamepad PID/VID; mirrord by ViGEm
if (!(dev.vendor_id == 0x28de && (dev.product_id == 0x11FF || dev.product_id == 0x028E))) {
@ -153,10 +159,10 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path)
setBlacklistDevices(blacklisted_devices_);
setActive(true);
spdlog::info("Hid Gaming Devices; Enabling Overlay element...");
if (Settings::extendedLogging) {
if (Settings::common.extendedLogging) {
std::ranges::for_each(blacklisted_devices_, [](const auto& dev) {
spdlog::trace(L"Blacklisted device: {}", dev);
});
});
}
}
closeCtrlDevice();
@ -195,80 +201,81 @@ void HidHide::enableOverlayElement()
{
Overlay::AddOverlayElem([this](bool window_has_focus, ImGuiID dockspace_id) {
ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Hidden Devices")) {
if (device_hiding_setup_) {
if (window_has_focus && (overlay_elem_clock_.getElapsedTime().asSeconds() > OVERLAY_ELEM_REFRESH_INTERVAL_S_)) {
// UnPatchValveHooks();
openCtrlDevice();
bool hidehide_state_store = hidhide_active_;
if (Settings::extendedLogging) {
spdlog::debug("Refreshing HID devices");
}
if (hidhide_active_) {
setActive(false);
}
avail_devices_ = GetHidDeviceList();
if (Settings::extendedLogging) {
std::ranges::for_each(avail_devices_, [](const auto& dev) {
spdlog::trace(L"AvailDevice device: {}", dev.name);
if (ImGui::Begin("Hidden Devices")) {
if (device_hiding_setup_) {
if (window_has_focus && (overlay_elem_clock_.getElapsedTime().asSeconds() > OVERLAY_ELEM_REFRESH_INTERVAL_S_)) {
// UnPatchValveHooks();
openCtrlDevice();
bool hidehide_state_store = hidhide_active_;
if (Settings::common.extendedLogging) {
spdlog::debug("Refreshing HID devices");
}
if (hidhide_active_) {
setActive(false);
}
avail_devices_ = GetHidDeviceList();
if (Settings::common.extendedLogging) {
std::ranges::for_each(avail_devices_, [](const auto& dev) {
spdlog::trace(L"AvailDevice device: {}", dev.name);
});
}
blacklisted_devices_ = getBlackListDevices();
if (hidehide_state_store && Settings::devices.hideDevices) {
setActive(true);
}
closeCtrlDevice();
overlay_elem_clock_.restart();
}
ImGui::BeginChild("Inner", {0.f, ImGui::GetItemRectSize().y - 64}, true);
std::ranges::for_each(avail_devices_, [this](const auto& device) {
std::string label = (std::string(device.name.begin(), std::ranges::find(device.name, L'\0')) + "##" + std::string(device.device_instance_path.begin(), device.device_instance_path.end()));
const auto findDeviceFn = [&device](const auto& blackdev) {
return device.device_instance_path == blackdev || device.base_container_device_instance_path == blackdev;
};
bool hidden = std::ranges::find_if(blacklisted_devices_, findDeviceFn) != blacklisted_devices_.end();
if (ImGui::Checkbox(label.data(), &hidden)) {
openCtrlDevice();
if (hidden) {
if (std::ranges::none_of(blacklisted_devices_, findDeviceFn)) {
if (!device.device_instance_path.empty()) {
blacklisted_devices_.push_back(device.device_instance_path);
}
if (!device.device_instance_path.empty()) {
blacklisted_devices_.push_back(device.base_container_device_instance_path);
}
}
}
else {
blacklisted_devices_.erase(std::ranges::remove_if(blacklisted_devices_, findDeviceFn).begin(),
blacklisted_devices_.end());
blacklisted_devices_ = getBlackListDevices();
if (hidehide_state_store && Settings::devices.hideDevices) {
setActive(true);
}
closeCtrlDevice();
overlay_elem_clock_.restart();
}
ImGui::BeginChild("Inner", { 0.f, ImGui::GetItemRectSize().y - 64 }, true);
std::ranges::for_each(avail_devices_, [this](const auto& device) {
std::string label = (std::string(device.name.begin(), std::ranges::find(device.name, L'\0')) + "##" + std::string(device.device_instance_path.begin(), device.device_instance_path.end()));
const auto findDeviceFn = [&device](const auto& blackdev) {
return device.device_instance_path == blackdev || device.base_container_device_instance_path == blackdev;
};
bool hidden = std::ranges::find_if(blacklisted_devices_, findDeviceFn) != blacklisted_devices_.end();
if (ImGui::Checkbox(label.data(), &hidden)) {
openCtrlDevice();
if (hidden) {
if (std::ranges::none_of(blacklisted_devices_, findDeviceFn)) {
if (!device.device_instance_path.empty()) {
blacklisted_devices_.push_back(device.device_instance_path);
}
setBlacklistDevices(blacklisted_devices_);
if (Settings::extendedLogging) {
std::ranges::for_each(blacklisted_devices_, [](const auto& dev) {
spdlog::trace(L"Blacklisted device: {}", dev);
});
if (!device.device_instance_path.empty()) {
blacklisted_devices_.push_back(device.base_container_device_instance_path);
}
closeCtrlDevice();
}
});
ImGui::EndChild();
} else {
ImGui::Text("Enable \"Hide Devices\" to see a list of gaming-devices");
}
if (ImGui::Checkbox("Hide devices", &Settings::devices.hideDevices)) {
if (!device_hiding_setup_) {
hideDevices(steam_path_);
}
if (hidhide_active_ != Settings::devices.hideDevices) {
openCtrlDevice();
setActive(Settings::devices.hideDevices);
closeCtrlDevice();
else {
blacklisted_devices_.erase(std::ranges::remove_if(blacklisted_devices_, findDeviceFn).begin(),
blacklisted_devices_.end());
}
setBlacklistDevices(blacklisted_devices_);
if (Settings::common.extendedLogging) {
std::ranges::for_each(blacklisted_devices_, [](const auto& dev) {
spdlog::trace(L"Blacklisted device: {}", dev);
});
}
closeCtrlDevice();
}
});
ImGui::EndChild();
}
else {
ImGui::Text("Enable \"Hide Devices\" to see a list of gaming-devices");
}
if (ImGui::Checkbox("Hide devices", &Settings::devices.hideDevices)) {
if (!device_hiding_setup_) {
hideDevices(steam_path_);
}
if (hidhide_active_ != Settings::devices.hideDevices) {
openCtrlDevice();
setActive(Settings::devices.hideDevices);
closeCtrlDevice();
}
}
ImGui::End();
});
}
ImGui::End();
});
}
#endif
@ -276,7 +283,7 @@ std::wstring HidHide::DosDeviceForVolume(const std::wstring& volume)
{
std::vector<WCHAR> buffer(UNICODE_STRING_MAX_CHARS);
QueryDosDeviceW(volume.c_str(), buffer.data(), static_cast<DWORD>(buffer.size()));
return {buffer.data()};
return { buffer.data() };
}
std::vector<std::wstring> HidHide::getAppWhiteList() const
@ -287,7 +294,7 @@ std::vector<std::wstring> HidHide::getAppWhiteList() const
}
std::vector<WCHAR> buffer(bytes_needed);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_WHITELIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_WHITELIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide Whitelist");
return std::vector<std::wstring>{};
}
@ -302,7 +309,7 @@ std::vector<std::wstring> HidHide::getBlackListDevices() const
}
std::vector<WCHAR> buffer(bytes_needed);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_BLACKLIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_BLACKLIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide Blacklist");
return std::vector<std::wstring>{};
}
@ -314,7 +321,7 @@ bool HidHide::getActive()
DWORD bytes_needed;
BOOLEAN res;
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_ACTIVE), nullptr, 0, &res, sizeof(BOOLEAN), &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_ACTIVE), nullptr, 0, &res, sizeof(BOOLEAN), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide State");
return false;
}
@ -327,7 +334,7 @@ void HidHide::setAppWhiteList(const std::vector<std::wstring>& whitelist) const
DWORD bytes_needed;
auto buffer = StringListToMultiString(whitelist);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_WHITELIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_WHITELIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide WhiteList");
}
}
@ -337,7 +344,7 @@ void HidHide::setBlacklistDevices(const std::vector<std::wstring>& blacklist) co
DWORD bytes_needed;
auto buffer = StringListToMultiString(blacklist);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_BLACKLIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_BLACKLIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide BlackList");
}
}
@ -346,12 +353,12 @@ void HidHide::setActive(bool active)
{
DWORD bytes_needed;
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_ACTIVE), &active, sizeof(BOOLEAN), nullptr, 0, &bytes_needed, nullptr)) {
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_ACTIVE), &active, sizeof(BOOLEAN), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide State");
return;
}
hidhide_active_ = active;
if (Settings::extendedLogging) {
if (Settings::common.extendedLogging) {
spdlog::debug("HidHide State set to {}", active);
}
}
@ -390,9 +397,9 @@ std::vector<WCHAR> HidHide::StringListToMultiString(const std::vector<std::wstri
{
auto res = std::accumulate(stringlist.begin(), stringlist.end(), std::vector<WCHAR>{}, [](auto acc, const auto& curr) {
acc.insert(acc.end(), curr.begin(), curr.end());
acc.push_back(L'\0');
return acc;
});
acc.push_back(L'\0');
return acc;
});
res.push_back(L'\0');
return res;
}
@ -422,7 +429,7 @@ std::vector<HidHide::SmallHidInfo> HidHide::GetHidDeviceList()
std::ranges::remove_if(
device_instance_paths,
[](const auto& dev) { return !DevicePresent(dev); })
.begin(),
.begin(),
device_instance_paths.end());
GUID hid_device_interface_guid{};
@ -439,7 +446,7 @@ std::vector<HidHide::SmallHidInfo> HidHide::GetHidDeviceList()
std::ranges::remove_if(
res,
[](const auto& dev) { return !dev.gaming_device; })
.begin(),
.begin(),
res.end());
return res;
@ -500,8 +507,8 @@ HidHide::SmallHidInfo HidHide::GetDeviceInfo(const DeviceInstancePath& instance_
std::wstring buffer;
buffer.resize(127 * sizeof WCHAR);
res.name = (HidD_GetProductString(device_object.get(), buffer.data(), static_cast<ULONG>(sizeof(WCHAR) * buffer.size()))
? buffer
: L"");
? buffer
: L"");
for (size_t i = 0; i < res.name.size(); ++i) {
if (res.name[i] == L'\0') {
res.name.resize(i + 1);
@ -562,13 +569,13 @@ std::filesystem::path HidHide::SymbolicLink(GUID const& interface_guid, DeviceIn
std::vector<BYTE> buffer(needed);
// Acquire the detailed data containing the symbolic link (aka. device path)
auto& [cbSize, DevicePath]{*reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buffer.data())};
auto& [cbSize, DevicePath] {*reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buffer.data())};
cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
if (!SetupDiGetDeviceInterfaceDetailW(handle.get(), &device_interface_data, reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buffer.data()), static_cast<DWORD>(buffer.size()), nullptr, nullptr)) {
spdlog::error(L"Couldn't get Device interface details; device: {}", instance_path);
return {};
}
return {std::wstring(DevicePath)};
return { std::wstring(DevicePath) };
}
HidHide::DeviceInstancePath HidHide::BaseContainerDeviceInstancePath(DeviceInstancePath const& device_instance_path)
@ -576,7 +583,7 @@ HidHide::DeviceInstancePath HidHide::BaseContainerDeviceInstancePath(DeviceInsta
const GUID base_container_id(BaseContainerId(device_instance_path));
if ((GUID_NULL == base_container_id) || (GUID_CONTAINER_ID_SYSTEM == base_container_id))
return (std::wstring{});
for (auto it{device_instance_path};;) {
for (auto it{ device_instance_path };;) {
if (const auto device_instance_path_parent = DeviceInstancePathParent(it); (base_container_id == BaseContainerId(device_instance_path_parent)))
it = device_instance_path_parent;
else
@ -593,7 +600,7 @@ GUID HidHide::BaseContainerId(DeviceInstancePath const& device_instance_path)
DEVINST devInst{};
DEVPROPTYPE devPropType{};
GUID buffer{};
ULONG needed{sizeof(buffer)};
ULONG needed{ sizeof(buffer) };
if (const auto result = CM_Locate_DevNodeW(&devInst, const_cast<DEVINSTID_W>(device_instance_path.c_str()), CM_LOCATE_DEVNODE_PHANTOM); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't locate device DevNode; Device {}; Code: {}", device_instance_path, result);
return {};
@ -620,7 +627,7 @@ HidHide::DeviceInstancePath HidHide::DeviceInstancePathParent(DeviceInstancePath
DEVINST dev_inst_parent{};
std::wstring res;
res.resize(UNICODE_STRING_MAX_CHARS);
ULONG needed{static_cast<ULONG>(res.size())};
ULONG needed{ static_cast<ULONG>(res.size()) };
if (const auto result = CM_Locate_DevNodeW(&dev_inst, const_cast<DEVINSTID_W>(device_instance_path.c_str()), CM_LOCATE_DEVNODE_PHANTOM); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't locate device DevNode; Device {}; Code: {}", device_instance_path, result);
return {};

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Copyright 2021-2023 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.
@ -15,7 +15,10 @@ limitations under the License.
*/
#pragma once
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <ntddscsi.h>
#include <winioctl.h>
#include <hidsdi.h>
@ -25,12 +28,13 @@ limitations under the License.
#include <map>
#include <string>
#include <vector>
#ifndef WATCHDOG
#include <SFML/System/Clock.hpp>
#endif
class HidHide {
private:
private:
using DeviceInstancePath = std::wstring;
using SetupDiDestroyDeviceInfoListPtr = std::unique_ptr<std::remove_pointer_t<HDEVINFO>, decltype(&SetupDiDestroyDeviceInfoList)>;
using CloseHandlePtr = std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>;
@ -58,7 +62,7 @@ class HidHide {
bool gaming_device = false;
};
public:
public:
HidHide();
void openCtrlDevice();
@ -68,7 +72,7 @@ class HidHide {
void disableHidHide();
// TODO: MAYBE: restore hidhide state/lists when app closes. not only disable device_hiding
private:
private:
HANDLE hidhide_handle = nullptr;
std::filesystem::path steam_path_;
@ -85,12 +89,12 @@ class HidHide {
std::vector<std::wstring> blacklisted_devices_;
std::vector<SmallHidInfo> avail_devices_;
bool hidhide_active_ = false;
static constexpr int OVERLAY_ELEM_REFRESH_INTERVAL_S_ = 5;
static constexpr int OVERLAY_ELEM_REFRESH_INTERVAL_S_ = 5;
static inline constexpr std::array<std::wstring_view, 3> whitelist_executeables_{
L"GameOverlayUI.exe",
L"steam.exe",
L"streaming_client.exe"};
L"streaming_client.exe" };
static [[nodiscard]] std::wstring DosDeviceForVolume(const std::wstring& volume);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save