Compare commits

...

6 Commits

30 changed files with 488 additions and 143 deletions

View File

@ -44,6 +44,10 @@ If the DLC file is present, the emulator will only unlock the DLCs in that file.
The contents of this file are: appid=DLC name
See the steam_settings.EXAMPLE folder for an example.
Languages:
You can include a steam_settings\supported_languages.txt file with a list of languages that the game supports. If the global emu language setting is not in this list of languages the emu will default to the first language in the list.
See the steam_settings.EXAMPLE folder for an example.
Depots:
This is pretty rare but some games might use depot ids to see if dlcs are installed. You can provide a list of installed depots to the game with a steam_settings\depots.txt file.
See the steam_settings.EXAMPLE folder for an example.

View File

@ -563,6 +563,23 @@ static bool is_whitelist_ip(unsigned char *ip)
return false;
}
static bool is_lan_ipv4(unsigned char *ip)
{
PRINT_DEBUG("CHECK LAN IP %hhu.%hhu.%hhu.%hhu\n", ip[0], ip[1], ip[2], ip[3]);
if (is_whitelist_ip(ip)) return true;
if (ip[0] == 127) return true;
if (ip[0] == 10) return true;
if (ip[0] == 192 && ip[1] == 168) return true;
if (ip[0] == 169 && ip[1] == 254 && ip[2] != 0) return true;
if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) return true;
if ((ip[0] == 100) && ((ip[1] & 0xC0) == 0x40)) return true;
if (ip[0] == 239) return true; //multicast
if (ip[0] == 0) return true; //Current network
if (ip[0] == 192 && (ip[1] == 18 || ip[1] == 19)) return true; //Used for benchmark testing of inter-network communications between two separate subnets.
if (ip[0] >= 224) return true; //ip multicast (224 - 239) future use (240.0.0.0 - 255.255.255.254) broadcast (255.255.255.255)
return false;
}
static bool is_lan_ip(const sockaddr *addr, int namelen)
{
if (!namelen) return false;
@ -571,24 +588,13 @@ static bool is_lan_ip(const sockaddr *addr, int namelen)
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
unsigned char ip[4];
memcpy(ip, &addr_in->sin_addr, sizeof(ip));
PRINT_DEBUG("CHECK LAN IP %hhu.%hhu.%hhu.%hhu:%u\n", ip[0], ip[1], ip[2], ip[3], ntohs(addr_in->sin_port));
if (is_whitelist_ip(ip)) return true;
if (ip[0] == 127) return true;
if (ip[0] == 10) return true;
if (ip[0] == 192 && ip[1] == 168) return true;
if (ip[0] == 169 && ip[1] == 254 && ip[2] != 0) return true;
if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) return true;
if ((ip[0] == 100) && ((ip[1] & 0xC0) == 0x40)) return true;
if (ip[0] == 239) return true; //multicast
if (ip[0] == 0) return true; //Current network
if (ip[0] == 192 && (ip[1] == 18 || ip[1] == 19)) return true; //Used for benchmark testing of inter-network communications between two separate subnets.
if (ip[0] >= 224) return true; //ip multicast (224 - 239) future use (240.0.0.0255.255.255.254) broadcast (255.255.255.255)
if (is_lan_ipv4(ip)) return true;
} else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
unsigned char ip[16];
unsigned char zeroes[16] = {};
memcpy(ip, &addr_in6->sin6_addr, sizeof(ip));
PRINT_DEBUG("CHECK LAN IP6 %hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu...%hhu\n", ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[15]);
PRINT_DEBUG("CHECK LAN IP6 %hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu\n", ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]);
if (((ip[0] == 0xFF) && (ip[1] < 3) && (ip[15] == 1)) ||
((ip[0] == 0xFE) && ((ip[1] & 0xC0) == 0x80))) return true;
if (memcmp(zeroes, ip, sizeof(ip)) == 0) return true;
@ -596,7 +602,13 @@ static bool is_lan_ip(const sockaddr *addr, int namelen)
if (ip[0] == 0xff) return true; //multicast
if (ip[0] == 0xfc) return true; //unique local
if (ip[0] == 0xfd) return true; //unique local
//TODO: ipv4 mapped?
unsigned char ipv4_mapped[12] = {};
ipv4_mapped[10] = 0xFF;
ipv4_mapped[11] = 0xFF;
if (memcmp(ipv4_mapped, ip, sizeof(ipv4_mapped)) == 0) {
if (is_lan_ipv4(ip + 12)) return true;
}
}
PRINT_DEBUG("NOT LAN IP\n");

View File

@ -129,23 +129,23 @@ STEAMAPI_API ISteamClient *g_pSteamClientGameServer;
ISteamClient *g_pSteamClientGameServer;
#endif
static Steam_Client *client;
static Steam_Client *steamclient_instance;
Steam_Client *get_steam_client()
{
std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (!client) {
client = new Steam_Client();
if (!steamclient_instance) {
steamclient_instance = new Steam_Client();
}
return client;
return steamclient_instance;
}
void destroy_client()
{
std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (client) {
delete client;
client = NULL;
if (steamclient_instance) {
delete steamclient_instance;
steamclient_instance = NULL;
}
}
@ -416,6 +416,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_UnregisterCallback( class CCallbackBase *p
{
PRINT_DEBUG("SteamAPI_UnregisterCallback %p\n", pCallback);
std::lock_guard<std::recursive_mutex> lock(global_mutex);
if (!steamclient_instance) return;
get_steam_client()->UnregisterCallback(pCallback);
}
@ -435,6 +436,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_UnregisterCallResult( class CCallbackBase
if (!hAPICall)
return;
if (!steamclient_instance) return;
get_steam_client()->UnregisterCallResult(pCallback, hAPICall);
}

View File

@ -156,6 +156,9 @@ public:
//app build id
int build_id = 10;
//supported languages
std::set<std::string> supported_languages;
//make lobby creation fail in the matchmaking interface
bool disable_lobby_creation = false;

View File

@ -260,6 +260,41 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
local_storage->store_data_settings("user_steam_id.txt", temp_text, strlen(temp_text));
}
std::set<std::string> supported_languages;
{
std::string lang_config_path = Local_Storage::get_game_settings_path() + "supported_languages.txt";
std::ifstream input( utf8_decode(lang_config_path) );
std::string first_language;
if (input.is_open()) {
consume_bom(input);
for( std::string line; getline( input, line ); ) {
if (!line.empty() && line[line.length()-1] == '\n') {
line.pop_back();
}
if (!line.empty() && line[line.length()-1] == '\r') {
line.pop_back();
}
try {
std::string lang = line;
if (!first_language.size()) first_language = lang;
supported_languages.insert(lang);
PRINT_DEBUG("Added supported_language %s\n", lang.c_str());
} catch (...) {}
}
}
if (!supported_languages.count(language)) {
if (first_language.size()) {
memset(language, 0, sizeof(language));
first_language.copy(language, sizeof(language) - 1);
}
}
}
bool steam_offline_mode = false;
bool disable_networking = false;
bool disable_overlay = false;
@ -336,6 +371,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
settings_server->warn_forced = warn_forced;
settings_client->warn_local_save = local_save;
settings_server->warn_local_save = local_save;
settings_client->supported_languages = supported_languages;
settings_server->supported_languages = supported_languages;
{
std::string dlc_config_path = Local_Storage::get_game_settings_path() + "DLC.txt";

View File

@ -0,0 +1,8 @@
english
german
french
spanish
schinese
russian
japanese
latam

View File

@ -134,7 +134,7 @@ private:
bool vulkan_hooked;
Base_Hook detection_hooks;
Renderer_Hook* renderer_hook;
ingame_overlay::Renderer_Hook* renderer_hook;
DX12_Hook* dx12_hook;
DX11_Hook* dx11_hook;
DX10_Hook* dx10_hook;
@ -238,7 +238,7 @@ private:
void HookDetected(T*& detected_renderer)
{
detection_hooks.UnhookAll();
renderer_hook = static_cast<Renderer_Hook*>(detected_renderer);
renderer_hook = static_cast<ingame_overlay::Renderer_Hook*>(detected_renderer);
detected_renderer = nullptr;
detection_done = true;
DestroyHWND();
@ -1032,7 +1032,7 @@ private:
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
ingame_overlay::Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::unique_lock<std::timed_mutex> detection_lock(detector_mutex, std::defer_lock);
@ -1173,7 +1173,7 @@ private:
bool openglx_hooked;
//bool vulkan_hooked;
Renderer_Hook* renderer_hook;
ingame_overlay::Renderer_Hook* renderer_hook;
OpenGLX_Hook* openglx_hook;
bool detection_done;
@ -1191,7 +1191,7 @@ private:
if (gladLoaderLoadGL() >= GLAD_MAKE_VERSION(3, 1))
{
inst->detection_hooks.UnhookAll();
inst->renderer_hook = static_cast<Renderer_Hook*>(Inst()->openglx_hook);
inst->renderer_hook = static_cast<ingame_overlay::Renderer_Hook*>(Inst()->openglx_hook);
inst->openglx_hook = nullptr;
inst->detection_done = true;
}
@ -1238,7 +1238,7 @@ private:
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
ingame_overlay::Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)> libraries[]{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)>{OpenGLX_Hook::DLL_NAME, &Renderer_Detector::hook_openglx},
@ -1352,7 +1352,7 @@ private:
bool opengl_hooked;
Renderer_Hook* renderer_hook;
ingame_overlay::Renderer_Hook* renderer_hook;
OpenGL_Hook* opengl_hook;
bool detection_done;
@ -1368,7 +1368,7 @@ private:
if (gladLoaderLoadGL() >= GLAD_MAKE_VERSION(2, 0))
{
inst->detection_hooks.UnhookAll();
inst->renderer_hook = static_cast<Renderer_Hook*>(Inst()->opengl_hook);
inst->renderer_hook = static_cast<ingame_overlay::Renderer_Hook*>(Inst()->opengl_hook);
inst->opengl_hook = nullptr;
inst->detection_done = true;
}
@ -1417,7 +1417,7 @@ private:
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
ingame_overlay::Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)> libraries[]{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)>{OpenGL_Hook::DLL_NAME, &Renderer_Detector::hook_opengl}
@ -1495,7 +1495,7 @@ Renderer_Detector* Renderer_Detector::instance = nullptr;
namespace ingame_overlay {
std::future<Renderer_Hook*> DetectRenderer(std::chrono::milliseconds timeout)
std::future<ingame_overlay::Renderer_Hook*> DetectRenderer(std::chrono::milliseconds timeout)
{
return std::async(std::launch::async, &Renderer_Detector::detect_renderer, Renderer_Detector::Inst(), timeout);
}

View File

@ -23,6 +23,16 @@
#include <string>
#include <memory>
#include <cstdint>
#include <set>
namespace ingame_overlay {
enum class ToggleKey
{
SHIFT, CTRL, ALT,
TAB,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
};
class Renderer_Hook
{
@ -37,7 +47,7 @@ public:
std::function<void()> OverlayProc;
std::function<void(bool)> OverlayHookReady;
virtual bool StartHook(std::function<bool(bool)> key_combination_callback) = 0;
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ToggleKey> toggle_keys) = 0;
virtual bool IsStarted() = 0;
// Returns a Handle to the renderer image ressource or nullptr if it failed to create the resource, the handle can be used in ImGui's Image calls, image_buffer must be RGBA ordered
virtual std::weak_ptr<uint64_t> CreateImageResource(const void* image_data, uint32_t width, uint32_t height) = 0;
@ -45,3 +55,5 @@ public:
virtual std::string GetLibraryName() const = 0;
};
}

View File

@ -29,7 +29,7 @@ OpenGLX_Hook* OpenGLX_Hook::_inst = nullptr;
constexpr decltype(OpenGLX_Hook::DLL_NAME) OpenGLX_Hook::DLL_NAME;
bool OpenGLX_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool OpenGLX_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -39,7 +39,7 @@ bool OpenGLX_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!X11_Hook::Inst()->StartHook(key_combination_callback))
if (!X11_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_X11Hooked = true;

View File

@ -26,7 +26,7 @@
#include <GL/glx.h>
class OpenGLX_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -59,7 +59,7 @@ public:
virtual ~OpenGLX_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static OpenGLX_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -29,10 +29,62 @@ constexpr decltype(X11_Hook::DLL_NAME) X11_Hook::DLL_NAME;
X11_Hook* X11_Hook::_inst = nullptr;
bool X11_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback)
uint32_t ToggleKeyToNativeKey(ingame_overlay::ToggleKey k)
{
struct {
ingame_overlay::ToggleKey lib_key;
uint32_t native_key;
} mapping[] = {
{ ingame_overlay::ToggleKey::ALT , XK_Alt_L },
{ ingame_overlay::ToggleKey::CTRL , XK_Control_L },
{ ingame_overlay::ToggleKey::SHIFT, XK_Shift_L },
{ ingame_overlay::ToggleKey::TAB , XK_Tab },
{ ingame_overlay::ToggleKey::F1 , XK_F1 },
{ ingame_overlay::ToggleKey::F2 , XK_F2 },
{ ingame_overlay::ToggleKey::F3 , XK_F3 },
{ ingame_overlay::ToggleKey::F4 , XK_F4 },
{ ingame_overlay::ToggleKey::F5 , XK_F5 },
{ ingame_overlay::ToggleKey::F6 , XK_F6 },
{ ingame_overlay::ToggleKey::F7 , XK_F7 },
{ ingame_overlay::ToggleKey::F8 , XK_F8 },
{ ingame_overlay::ToggleKey::F9 , XK_F9 },
{ ingame_overlay::ToggleKey::F10 , XK_F10 },
{ ingame_overlay::ToggleKey::F11 , XK_F11 },
{ ingame_overlay::ToggleKey::F12 , XK_F12 },
};
for (auto const& item : mapping)
{
if (item.lib_key == k)
return item.native_key;
}
return 0;
}
bool GetKeyState(Display* d, KeySym keySym, char szKey[32])
{
int iKeyCodeToFind = XKeysymToKeycode(d, keySym);
return szKey[iKeyCodeToFind / 8] & (1 << (iKeyCodeToFind % 8));
}
bool X11_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback, std::set<ingame_overlay::ToggleKey> const& toggle_keys)
{
if (!_Hooked)
{
if (!_key_combination_callback)
{
SPDLOG_ERROR("Failed to hook X11: No key combination callback.");
return false;
}
if (toggle_keys.empty())
{
SPDLOG_ERROR("Failed to hook X11: No key combination.");
return false;
}
void* hX11 = System::Library::GetLibraryHandle(DLL_NAME);
if (hX11 == nullptr)
{
@ -60,6 +112,16 @@ bool X11_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback)
SPDLOG_INFO("Hooked X11");
_KeyCombinationCallback = std::move(_key_combination_callback);
for (auto& key : toggle_keys)
{
uint32_t k = ToggleKeyToNativeKey(key);
if (k != 0)
{
_NativeKeyCombination.insert(k);
}
}
_Hooked = true;
UnhookAll();
@ -140,33 +202,47 @@ int X11_Hook::_CheckForOverlay(Display *d, int num_events)
static Time prev_time = {};
X11_Hook* inst = Inst();
if( inst->_Initialized )
char szKey[32];
if( _Initialized )
{
XEvent event;
while(num_events)
{
bool skip_input = inst->_KeyCombinationCallback(false);
bool skip_input = _KeyCombinationCallback(false);
XPeekEvent(d, &event);
ImGui_ImplX11_EventHandler(event);
// Is the event is a key press
if (event.type == KeyPress)
if (event.type == KeyPress || event.type == KeyRelease)
{
// Tab is pressed and was not pressed before
if (event.xkey.keycode == XKeysymToKeycode(d, XK_Tab) && event.xkey.state & ShiftMask)
XQueryKeymap(d, szKey);
int key_count = 0;
for (auto const& key : inst->_NativeKeyCombination)
{
// if key TAB is held, don't make the overlay flicker :p
if (event.xkey.time != prev_time)
if (GetKeyState(d, key, szKey))
++key_count;
}
if (key_count == inst->_NativeKeyCombination.size())
{// All shortcut keys are pressed
if (!inst->_KeyCombinationPushed)
{
skip_input = true;
inst->_KeyCombinationCallback(true);
if (inst->_KeyCombinationCallback(true))
{
skip_input = true;
// Save the last known cursor pos when opening the overlay
// so we can spoof the GetCursorPos return value.
//inst->GetCursorPos(&inst->_SavedCursorPos);
}
inst->_KeyCombinationPushed = true;
}
}
}
else if(event.type == KeyRelease && event.xkey.keycode == XKeysymToKeycode(d, XK_Tab))
{
prev_time = event.xkey.time;
else
{
inst->_KeyCombinationPushed = false;
}
}
if (!skip_input || !IgnoreEvent(event))
@ -215,6 +291,7 @@ X11_Hook::X11_Hook() :
_Initialized(false),
_Hooked(false),
_GameWnd(0),
_KeyCombinationPushed(false),
XEventsQueued(nullptr),
XPending(nullptr)
{
@ -241,3 +318,4 @@ std::string X11_Hook::GetLibraryName() const
{
return LibraryName;
}

View File

@ -41,6 +41,12 @@ private:
bool _Initialized;
Window _GameWnd;
// In (bool): Is toggle wanted
// Out(bool): Is the overlay visible, if true, inputs will be disabled
std::function<bool(bool)> _KeyCombinationCallback;
std::set<uint32_t> _NativeKeyCombination;
bool _KeyCombinationPushed;
// Functions
X11_Hook();
int _CheckForOverlay(Display *d, int num_events);
@ -52,8 +58,6 @@ private:
static int MyXEventsQueued(Display * display, int mode);
static int MyXPending(Display* display);
std::function<bool(bool)> _KeyCombinationCallback;
public:
std::string LibraryName;
@ -65,7 +69,7 @@ public:
Window GetGameWnd() const{ return _GameWnd; }
bool StartHook(std::function<bool(bool)>& key_combination_callback);
bool StartHook(std::function<bool(bool)>& key_combination_callback, std::set<ingame_overlay::ToggleKey> const& toggle_keys);
static X11_Hook* Inst();
virtual std::string GetLibraryName() const;
};

View File

@ -213,6 +213,7 @@ void Steam_Overlay::UnSetupOverlay()
void Steam_Overlay::HookReady(bool ready)
{
PRINT_DEBUG("%s %u\n", __FUNCTION__, ready);
{
// TODO: Uncomment this and draw our own cursor (cosmetics)
ImGuiIO &io = ImGui::GetIO();
@ -223,9 +224,6 @@ void Steam_Overlay::HookReady(bool ready)
io.IniFilename = NULL;
is_ready = ready;
if (is_ready) {
CreateFonts();
}
}
}
@ -710,18 +708,64 @@ void Steam_Overlay::BuildNotifications(int width, int height)
void Steam_Overlay::CreateFonts()
{
//TODO: remove from dx_whatever hook
if(ImGui::GetCurrentContext() == nullptr)
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImFontConfig fontcfg;
float font_size = 16.0;
fontcfg.OversampleH = fontcfg.OversampleV = 1;
fontcfg.PixelSnapH = true;
fontcfg.GlyphRanges = io.Fonts->GetGlyphRangesDefault();
fontcfg.SizePixels = font_size;
font_default = io.Fonts->AddFontDefault(&fontcfg);
font_notif = io.Fonts->AddFontDefault(&fontcfg);
ImFontGlyphRangesBuilder font_builder;
for (auto & x : achievements) {
font_builder.AddText(x.title.c_str());
font_builder.AddText(x.description.c_str());
}
font_builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
ImVector<ImWchar> ranges;
font_builder.BuildRanges(&ranges);
bool need_extra_fonts = false;
for (auto &x : ranges) {
if (x > 0xFF) {
need_extra_fonts = true;
break;
}
}
fontcfg.GlyphRanges = ranges.Data;
ImFont *font = NULL;
#if defined(__WINDOWS__)
font = io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\micross.ttf", font_size, &fontcfg);
#endif
if (!font) {
font = io.Fonts->AddFontDefault(&fontcfg);
}
font_notif = font_default = font;
if (need_extra_fonts) {
PRINT_DEBUG("loading extra fonts\n");
fontcfg.MergeMode = true;
#if defined(__WINDOWS__)
io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\simsun.ttc", font_size, &fontcfg);
io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\malgun.ttf", font_size, &fontcfg);
#endif
}
io.Fonts->Build();
ImGuiStyle& style = ImGui::GetStyle();
style.WindowRounding = 0.0; // Disable round window
reset_LastError();
}
// Try to make this function as short as possible or it might affect game's fps.
@ -893,7 +937,7 @@ void Steam_Overlay::OverlayProc()
bool show_warning = local_save || warning_forced || appid == 0;
if (show_warning) {
ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 64, ImGui::GetFontSize() * 64), ImVec2(8192, 8192));
ImGui::SetNextWindowSizeConstraints(ImVec2(ImGui::GetFontSize() * 32, ImGui::GetFontSize() * 32), ImVec2(8192, 8192));
ImGui::SetNextWindowFocus();
if (ImGui::Begin("WARNING", &show_warning)) {
if (appid == 0) {
@ -955,10 +999,45 @@ void Steam_Overlay::Callback(Common_Message *msg)
void Steam_Overlay::RunCallbacks()
{
if (!achievements.size()) {
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
uint32 achievements_num = steamUserStats->GetNumAchievements();
if (achievements_num) {
PRINT_DEBUG("POPULATE OVERLAY ACHIEVEMENTS\n");
for (unsigned i = 0; i < achievements_num; ++i) {
Overlay_Achievement ach;
ach.name = steamUserStats->GetAchievementName(i);
ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name");
ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc");
const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden");
if (strlen(hidden) && hidden[0] == '1') {
ach.hidden = true;
} else {
ach.hidden = false;
}
bool achieved = false;
uint32 unlock_time = 0;
if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) {
ach.achieved = achieved;
ach.unlock_time = unlock_time;
} else {
ach.achieved = false;
ach.unlock_time = 0;
}
achievements.push_back(ach);
}
PRINT_DEBUG("POPULATE OVERLAY ACHIEVEMENTS DONE\n");
}
}
if (!Ready() && future_renderer.valid()) {
if (future_renderer.wait_for(std::chrono::milliseconds{0}) == std::future_status::ready) {
_renderer = future_renderer.get();
PRINT_DEBUG("got renderer %p\n", _renderer);
CreateFonts();
}
}
@ -967,7 +1046,8 @@ void Steam_Overlay::RunCallbacks()
_renderer->OverlayProc = std::bind(&Steam_Overlay::OverlayProc, this);
auto callback = std::bind(&Steam_Overlay::OpenOverlayHook, this, std::placeholders::_1);
PRINT_DEBUG("start renderer\n", _renderer);
bool started = _renderer->StartHook(callback);
std::set<ingame_overlay::ToggleKey> keys = {ingame_overlay::ToggleKey::SHIFT, ingame_overlay::ToggleKey::TAB};
bool started = _renderer->StartHook(callback, keys);
PRINT_DEBUG("tried to start renderer %u\n", started);
}
@ -1094,40 +1174,6 @@ void Steam_Overlay::RunCallbacks()
}
has_friend_action.pop();
}
if (!achievements.size()) {
Steam_User_Stats* steamUserStats = get_steam_client()->steam_user_stats;
uint32 achievements_num = steamUserStats->GetNumAchievements();
if (achievements_num) {
PRINT_DEBUG("POPULATE OVERLAY ACHIEVEMENTS\n");
for (unsigned i = 0; i < achievements_num; ++i) {
Overlay_Achievement ach;
ach.name = steamUserStats->GetAchievementName(i);
ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name");
ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc");
const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden");
if (strlen(hidden) && hidden[0] == '1') {
ach.hidden = true;
} else {
ach.hidden = false;
}
bool achieved = false;
uint32 unlock_time = 0;
if (steamUserStats->GetAchievementAndUnlockTime(ach.name.c_str(), &achieved, &unlock_time)) {
ach.achieved = achieved;
ach.unlock_time = unlock_time;
} else {
ach.achieved = false;
ach.unlock_time = 0;
}
achievements.push_back(ach);
}
PRINT_DEBUG("POPULATE OVERLAY ACHIEVEMENTS DONE\n");
}
}
}
#endif

View File

@ -123,8 +123,8 @@ class Steam_Overlay
std::recursive_mutex overlay_mutex;
std::atomic<bool> i_have_lobby;
std::future<Renderer_Hook*> future_renderer;
Renderer_Hook* _renderer;
std::future<ingame_overlay::Renderer_Hook*> future_renderer;
ingame_overlay::Renderer_Hook* _renderer;
Steam_Overlay(Steam_Overlay const&) = delete;
Steam_Overlay(Steam_Overlay&&) = delete;

View File

@ -35,7 +35,7 @@ inline void SafeRelease(T*& pUnk)
}
}
bool DX10_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool DX10_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -45,7 +45,7 @@ bool DX10_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -25,7 +25,7 @@
#include <dxgi1_2.h>
class DX10_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -64,7 +64,7 @@ public:
virtual ~DX10_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static DX10_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -45,7 +45,7 @@ static HRESULT GetDeviceAndCtxFromSwapchain(IDXGISwapChain* pSwapChain, ID3D11De
return ret;
}
bool DX11_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool DX11_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -55,7 +55,7 @@ bool DX11_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -25,7 +25,7 @@
#include <dxgi1_2.h>
class DX11_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -65,7 +65,7 @@ public:
virtual ~DX11_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static DX11_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -35,7 +35,7 @@ inline void SafeRelease(T*& pUnk)
}
}
bool DX12_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool DX12_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -45,7 +45,7 @@ bool DX12_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -25,7 +25,7 @@
#include <dxgi1_4.h>
class DX12_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -127,7 +127,7 @@ public:
virtual ~DX12_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static DX12_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -36,7 +36,7 @@ inline void SafeRelease(T*& pUnk)
}
}
bool DX9_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool DX9_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -46,7 +46,7 @@ bool DX9_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -24,7 +24,7 @@
#include <d3d9.h>
class DX9_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -63,7 +63,7 @@ public:
virtual ~DX9_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static DX9_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -27,7 +27,7 @@
OpenGL_Hook* OpenGL_Hook::_inst = nullptr;
bool OpenGL_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool OpenGL_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
if (!_Hooked)
{
@ -37,7 +37,7 @@ bool OpenGL_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -22,7 +22,7 @@
#include "../internal_includes.h"
class OpenGL_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -56,7 +56,7 @@ public:
virtual ~OpenGL_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static OpenGL_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -25,7 +25,7 @@
Vulkan_Hook* Vulkan_Hook::_inst = nullptr;
bool Vulkan_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
bool Vulkan_Hook::StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys)
{
SPDLOG_WARN("Vulkan overlay is not yet supported.");
return false;
@ -37,7 +37,7 @@ bool Vulkan_Hook::StartHook(std::function<bool(bool)> key_combination_callback)
return false;
}
if (!Windows_Hook::Inst()->StartHook(key_combination_callback))
if (!Windows_Hook::Inst()->StartHook(key_combination_callback, toggle_keys))
return false;
_WindowsHooked = true;

View File

@ -24,7 +24,7 @@
#include <vulkan/vulkan.h>
class Vulkan_Hook :
public Renderer_Hook,
public ingame_overlay::Renderer_Hook,
public Base_Hook
{
public:
@ -54,7 +54,7 @@ public:
virtual ~Vulkan_Hook();
virtual bool StartHook(std::function<bool(bool)> key_combination_callback);
virtual bool StartHook(std::function<bool(bool)> key_combination_callback, std::set<ingame_overlay::ToggleKey> toggle_keys);
virtual bool IsStarted();
static Vulkan_Hook* Inst();
virtual std::string GetLibraryName() const;

View File

@ -29,10 +29,55 @@ constexpr decltype(Windows_Hook::DLL_NAME) Windows_Hook::DLL_NAME;
Windows_Hook* Windows_Hook::_inst = nullptr;
bool Windows_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback)
int ToggleKeyToNativeKey(ingame_overlay::ToggleKey k)
{
struct {
ingame_overlay::ToggleKey lib_key;
int native_key;
} mapping[] = {
{ ingame_overlay::ToggleKey::ALT , VK_MENU },
{ ingame_overlay::ToggleKey::CTRL , VK_CONTROL },
{ ingame_overlay::ToggleKey::SHIFT, VK_SHIFT },
{ ingame_overlay::ToggleKey::TAB , VK_TAB },
{ ingame_overlay::ToggleKey::F1 , VK_F1 },
{ ingame_overlay::ToggleKey::F2 , VK_F2 },
{ ingame_overlay::ToggleKey::F3 , VK_F3 },
{ ingame_overlay::ToggleKey::F4 , VK_F4 },
{ ingame_overlay::ToggleKey::F5 , VK_F5 },
{ ingame_overlay::ToggleKey::F6 , VK_F6 },
{ ingame_overlay::ToggleKey::F7 , VK_F7 },
{ ingame_overlay::ToggleKey::F8 , VK_F8 },
{ ingame_overlay::ToggleKey::F9 , VK_F9 },
{ ingame_overlay::ToggleKey::F10 , VK_F10 },
{ ingame_overlay::ToggleKey::F11 , VK_F11 },
{ ingame_overlay::ToggleKey::F12 , VK_F12 },
};
for (auto const& item : mapping)
{
if (item.lib_key == k)
return item.native_key;
}
return 0;
}
bool Windows_Hook::StartHook(std::function<bool(bool)>& _key_combination_callback, std::set<ingame_overlay::ToggleKey> const& toggle_keys)
{
if (!_Hooked)
{
if (!_key_combination_callback)
{
SPDLOG_ERROR("Failed to hook Windows: No key combination callback.");
return false;
}
if (toggle_keys.empty())
{
SPDLOG_ERROR("Failed to hook Windows: No key combination.");
return false;
}
void* hUser32 = System::Library::GetLibraryHandle(DLL_NAME);
if (hUser32 == nullptr)
{
@ -71,6 +116,15 @@ bool Windows_Hook::StartHook(std::function<bool(bool)>& _key_combination_callbac
SPDLOG_INFO("Hooked Windows");
_KeyCombinationCallback = std::move(_key_combination_callback);
for (auto& key : toggle_keys)
{
uint32_t k = ToggleKeyToNativeKey(key);
if (k != 0)
{
_NativeKeyCombination.insert(k);
}
}
BeginHook();
HookFuncs(
std::make_pair<void**, void*>(&(PVOID&)GetRawInputBuffer, &Windows_Hook::MyGetRawInputBuffer),
@ -134,12 +188,9 @@ bool Windows_Hook::PrepareForOverlay(HWND hWnd)
POINT pos;
if (this->GetCursorPos(&pos) && ScreenToClient(hWnd, &pos))
{
io.MousePos = ImVec2((float)pos.x, (float)pos.y);
io.AddMousePosEvent((float)pos.x, (float)pos.y);
}
io.KeyCtrl = (this->GetKeyState(VK_CONTROL) & 0x8000) != 0;
io.KeyShift = (this->GetKeyState(VK_SHIFT) & 0x8000) != 0;
io.KeyAlt = (this->GetKeyState(VK_MENU) & 0x8000) != 0;
return true;
}
@ -219,13 +270,18 @@ LRESULT CALLBACK Windows_Hook::HookWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
if (inst->_Initialized)
{
// Is the event is a key press
if (uMsg == WM_KEYDOWN)
if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)
{
// Tab is pressed and was not pressed before
if (wParam == VK_TAB && !(lParam & (1 << 30)))
int key_count = 0;
for (auto const& key : inst->_NativeKeyCombination)
{
// If Left Shift is pressed
if (inst->GetAsyncKeyState(VK_LSHIFT) & (1 << 15))
if (inst->GetAsyncKeyState(key) & (1 << 15))
++key_count;
}
if (key_count == inst->_NativeKeyCombination.size())
{// All shortcut keys are pressed
if (!inst->_KeyCombinationPushed)
{
if (inst->_KeyCombinationCallback(true))
{
@ -234,12 +290,13 @@ LRESULT CALLBACK Windows_Hook::HookWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
// so we can spoof the GetCursorPos return value.
inst->GetCursorPos(&inst->_SavedCursorPos);
}
else
{
clean_keys = true;
}
inst->_KeyCombinationPushed = true;
}
}
else
{
inst->_KeyCombinationPushed = false;
}
}
if (skip_input && IgnoreMsg(uMsg))
@ -248,7 +305,8 @@ LRESULT CALLBACK Windows_Hook::HookWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
if (clean_keys)
{
auto& io = ImGui::GetIO();
memset(io.KeysDown, 0, sizeof(io.KeysDown));
io.ClearInputKeys();
io.ClearInputCharacters();
}
return 0;
}
@ -369,6 +427,7 @@ Windows_Hook::Windows_Hook() :
_RecurseCallCount(0),
_GameHwnd(nullptr),
_GameWndProc(nullptr),
_KeyCombinationPushed(false),
GetRawInputBuffer(nullptr),
GetRawInputData(nullptr),
GetKeyState(nullptr),

View File

@ -38,6 +38,12 @@ private:
WNDPROC _GameWndProc;
POINT _SavedCursorPos;
// In (bool): Is toggle wanted
// Out(bool): Is the overlay visible, if true, inputs will be disabled
std::function<bool(bool)> _KeyCombinationCallback;
std::set<int> _NativeKeyCombination;
bool _KeyCombinationPushed;
// Functions
Windows_Hook();
@ -59,10 +65,6 @@ private:
static BOOL WINAPI MyGetCursorPos(LPPOINT lpPoint);
static BOOL WINAPI MySetCursorPos(int X, int Y);
// In (bool): Is toggle wanted
// Out(bool): Is the overlay visible, if true, inputs will be disabled
std::function<bool(bool)> _KeyCombinationCallback;
public:
std::string LibraryName;
@ -75,7 +77,7 @@ public:
HWND GetGameHwnd() const;
WNDPROC GetGameWndProc() const;
bool StartHook(std::function<bool(bool)>& key_combination_callback);
bool StartHook(std::function<bool(bool)>& key_combination_callback, std::set<ingame_overlay::ToggleKey> const& toggle_keys);
static Windows_Hook* Inst();
virtual std::string GetLibraryName() const;
};

View File

@ -17,6 +17,9 @@ import os
import sys
import json
import urllib.request
import urllib.error
import threading
import queue
prompt_for_unavailable = True
@ -82,7 +85,49 @@ def get_stats_schema(client, game_id, owner_id):
client.send(message)
return client.wait_msg(EMsg.ClientGetUserStatsResponse, timeout=5)
def download_achievement_images(game_id, image_names, output_folder):
q = queue.Queue()
def downloader_thread():
while True:
name = q.get()
succeeded = False
if name is None:
q.task_done()
return
for u in ["https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/", "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"]:
url = "{}{}/{}".format(u, game_id, name)
try:
with urllib.request.urlopen(url) as response:
image_data = response.read()
with open(os.path.join(output_folder, name), "wb") as f:
f.write(image_data)
succeeded = True
break
except urllib.error.HTTPError as e:
print("HTTPError downloading", url, e.code)
except urllib.error.URLError as e:
print("URLError downloading", url, e.code)
if not succeeded:
print("error could not download", name)
q.task_done()
num_threads = 5
for i in range(num_threads):
threading.Thread(target=downloader_thread, daemon=True).start()
for name in image_names:
q.put(name)
q.join()
for i in range(num_threads):
q.put(None)
q.join()
def generate_achievement_stats(client, game_id, output_directory, backup_directory):
achievement_images_dir = os.path.join(output_directory, "achievement_images")
images_to_download = []
steam_id_list = TOP_OWNER_IDS + [client.steam_id]
for x in steam_id_list:
out = get_stats_schema(client, game_id, x)
@ -91,10 +136,20 @@ def generate_achievement_stats(client, game_id, output_directory, backup_directo
with open(os.path.join(backup_directory, 'UserGameStatsSchema_{}.bin'.format(appid)), 'wb') as f:
f.write(out.body.schema)
achievements, stats = achievements_gen.generate_stats_achievements(out.body.schema, output_directory)
return
for ach in achievements:
if "icon" in ach:
images_to_download.append(ach["icon"])
if "icon_gray" in ach:
images_to_download.append(ach["icon_gray"])
break
else:
print("no schema", out)
if (len(images_to_download) > 0):
if not os.path.exists(achievement_images_dir):
os.makedirs(achievement_images_dir)
download_achievement_images(game_id, images_to_download, achievement_images_dir)
def get_ugc_info(client, published_file_id):
return client.send_um_and_wait('PublishedFile.GetDetails#1', {
'publishedfileids': [published_file_id],
@ -173,6 +228,13 @@ for appid in appids:
game_info_common = game_info["common"]
if "community_visible_stats" in game_info_common:
generate_achievement_stats(client, appid, out_dir, backup_dir)
if "languages" in game_info_common:
with open(os.path.join(out_dir, "supported_languages.txt"), 'w') as f:
languages = game_info_common["languages"]
for l in languages:
if languages[l] != "0":
f.write("{}\n".format(l))
with open(os.path.join(out_dir, "steam_appid.txt"), 'w') as f:
f.write(str(appid))
@ -187,6 +249,7 @@ for appid in appids:
dlc_config_list = []
dlc_list, depot_app_list = get_dlc(game_info)
dlc_infos_backup = ""
if (len(dlc_list) > 0):
dlc_raw = client.get_product_info(apps=dlc_list)["apps"]
for dlc in dlc_raw:
@ -194,6 +257,7 @@ for appid in appids:
dlc_config_list.append((dlc, dlc_raw[dlc]["common"]["name"]))
except:
dlc_config_list.append((dlc, None))
dlc_infos_backup = json.dumps(dlc_raw, indent=4)
with open(os.path.join(out_dir, "DLC.txt"), 'w', encoding="utf-8") as f:
for x in dlc_config_list:
@ -218,7 +282,21 @@ for appid in appids:
if (controller_type in ["controller_xbox360", "controller_xboxone"] and (("default" in enabled_branches) or ("public" in enabled_branches))):
parse_controller_vdf.generate_controller_config(out_vdf.decode('utf-8'), os.path.join(out_dir, "controller"))
config_generated = True
if "steamcontrollertouchconfigdetails" in game_info["config"]:
controller_details = game_info["config"]["steamcontrollertouchconfigdetails"]
for id in controller_details:
details = controller_details[id]
controller_type = ""
enabled_branches = ""
if "controller_type" in details:
controller_type = details["controller_type"]
if "enabled_branches" in details:
enabled_branches = details["enabled_branches"]
print(id, controller_type)
out_vdf = download_published_file(client, int(id), os.path.join(backup_dir, controller_type + str(id)))
game_info_backup = json.dumps(game_info, indent=4)
with open(os.path.join(backup_dir, "product_info.json"), "w") as f:
f.write(game_info_backup)
with open(os.path.join(backup_dir, "dlc_product_info.json"), "w") as f:
f.write(dlc_infos_backup)

View File

@ -75,7 +75,7 @@ def generate_stats_achievements(schema, config_directory):
with open(os.path.join(config_directory, "stats.txt"), 'w') as f:
f.write(output_stats)
return (output_ach, output_stats)
return (achievements_out, stats_out)
if __name__ == '__main__':
if len(sys.argv) < 2: