diff --git a/CEFInjectLib/CEFInject.cpp b/CEFInjectLib/CEFInject.cpp index 297e484..df86899 100644 --- a/CEFInjectLib/CEFInject.cpp +++ b/CEFInjectLib/CEFInject.cpp @@ -1,4 +1,4 @@ -/* +/* Copyright 2021-2023 Peter Repukat - FlatspotSoftware Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,13 @@ limitations under the License. #include #define _SSIZE_T_DEFINED -#include // seems like a hack to me, but eh +#include // seems like a hack to me, but eh, what is in the doc will be done ¯\_(ツ)_/¯ #include "../common/nlohmann_json_wstring.h" +#include +#include + namespace CEFInject { namespace internal @@ -51,80 +54,222 @@ namespace CEFInject return false; } - std::vector AvailableTabs(uint16_t port) + std::vector AvailableTabNames(uint16_t port) { std::vector tabs; + const auto json = AvailableTabs(port); + for (const auto& j : json) { + tabs.push_back(j["title"].get()); + } + return tabs; + } + + nlohmann::json::array_t AvailableTabs(uint16_t port) + { + if (!CEFDebugAvailable()) { + return nlohmann::json::array(); + } 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& j : json) { - tabs.push_back(j["title"].get()); + return nlohmann::json::parse(res->body); + } + } + return nlohmann::json::array(); + } + + nlohmann::json InjectJs(std::string_view debug_url, std::wstring_view js, uint16_t port) + { +#ifdef _WIN32 + INT rc; + WSADATA wsaData; + + rc = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rc) { + printf("WSAStartup Failed.\n"); + return nullptr; + } +#endif + + std::shared_ptr ws{ + easywsclient::WebSocket::from_url(debug_url.data()) + }; + if (ws) + { + 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(); + ws->send(payload_string); + nlohmann::json res = nullptr; + 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 + { + res = msg.at("result").at("result").at("value"); + } catch (...){ + // + } + try + { + if (res.is_null() && msg.at("result").at("result").at("type").get() != "undefined") { + res = nlohmann::json::parse(message); + } + } catch (...) { + res = nlohmann::json::parse(message); + } + exit = true; + }); + if (exit) { + ws->close(); + return res; } } +#ifdef _WIN32 + WSACleanup(); +#endif + return res; } - return tabs; +#ifdef _WIN32 + WSACleanup(); +#endif + return nullptr; } - nlohmann::json InjectJs(const std::wstring &tabname, const std::wstring &js, uint16_t port) - { + nlohmann::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().starts_with(tabname)) { -#ifdef _WIN32 - INT rc; - WSADATA wsaData; + return InjectJs(tab["webSocketDebuggerUrl"].get(), js, port); + } + } + } + } + return nullptr; + } + + + bool SteamTweaks::injectGlosSITweaks(std::string_view tab_name, uint16_t port) + { + if (tab_name.empty()) { + for (const auto& tn : AvailableTabNames()) + { + // 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; + } + + auto glossi_path = util::path::getGlosSIDir(); + glossi_path /= steam_tweaks_path_; + glossi_path /= "GlosSITweaks.js"; + + if (!std::filesystem::exists(glossi_path)) + { + return false; + } - rc = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (rc) { - printf("WSAStartup Failed.\n"); - return nullptr; + std::wifstream js_file(glossi_path); + std::wstring glossitweaks_js{ (std::istreambuf_iterator(js_file)), + std::istreambuf_iterator() }; + if (glossitweaks_js.empty()) { + return false; + } + + const auto find_tab = ( + [&info]() -> std::function + { + if (!info.name.empty()) + { + return [&info](const nlohmann::json::array_t& tabList) + { + for (const auto& tab : tabList) { + if (tab["title"].get().starts_with(info.name.data())) { + return tab; + } } -#endif + return nlohmann::json{}; + }; + } + if (!info.id.empty()) + { + return [&info](const nlohmann::json::array_t& tabList) + { + for (const auto& tab : tabList) { + if (tab["id"].get() == info.id) { + return tab; + } + } + return nlohmann::json{}; + }; - std::shared_ptr ws{ - easywsclient::WebSocket::from_url(tab["webSocketDebuggerUrl"].get()) - }; - if (ws) - { - ws->send( - nlohmann::json{ - {"id", internal::msg_id++}, - {"method", "Runtime.evaluate"}, - {"params", { - {"userGesture", true}, - {"expression", js} - }} - }.dump()); - nlohmann::json res = nullptr; - bool exit = false; - while (ws->getReadyState() != easywsclient::WebSocket::CLOSED) { - ws->poll(); - ws->dispatch([&ws, &res, &exit](const std::string& message) { - res = nlohmann::json::parse(message)["result"]["result"]["value"]; - exit = true; - }); - if (exit) { - ws->close(); - return res; - } + } + if (!info.webSocketDebuggerUrl.empty()) + { + return [&info](const nlohmann::json::array_t& tabList) + { + for (const auto& tab : tabList) { + if (tab["webSocketDebuggerUrl"].get() == info.webSocketDebuggerUrl) { + return tab; } -#ifdef _WIN32 - WSACleanup(); -#endif - return res; } -#ifdef _WIN32 - WSACleanup(); -#endif - return nullptr; - } + return nlohmann::json{}; + }; + } - } + return nullptr; + } + )(); + if (find_tab == nullptr) { + return false; } - return nullptr; - } -} \ No newline at end of file + const auto tabs = AvailableTabs(port); + const auto tab = find_tab(tabs); + if (tab.empty()) { + return false; + } + + InjectJs(tab["webSocketDebuggerUrl"].get(), glossitweaks_js, port); + glossi_tweaks_injected_map_[tab["id"].get()] = true; + return true; + } + + bool SteamTweaks::uninstallTweaks() + { + if (!CEFDebugAvailable()) { + return false; + } + if (glossi_tweaks_injected_map_.empty()) { + return false; + } + + for (auto& tab : AvailableTabNames()) { + InjectJsByName(tab, uninstall_glossi_tweaks_js_); + } + + glossi_tweaks_injected_map_.clear(); + return true; + } + +} diff --git a/CEFInjectLib/CEFInject.h b/CEFInjectLib/CEFInject.h index fc077f6..1a877f0 100644 --- a/CEFInjectLib/CEFInject.h +++ b/CEFInjectLib/CEFInject.h @@ -30,7 +30,41 @@ namespace CEFInject internal::port_ = port; } bool CEFDebugAvailable(uint16_t port = internal::port_); - std::vector AvailableTabs(uint16_t port = internal::port_); - nlohmann::json InjectJs(const std::wstring& tabname, const std::wstring& js, uint16_t port = internal::port_); + std::vector AvailableTabNames(uint16_t port = internal::port_); + nlohmann::json::array_t AvailableTabs(uint16_t port = internal::port_); + nlohmann::json InjectJs(std::string_view debug_url, std::wstring_view js, uint16_t port = internal::port_); + nlohmann::json InjectJsByName(std::wstring_view tabname, std::wstring_view js, uint16_t port = internal::port_); + + 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_); + public: + bool uninstallTweaks(); + + // TODO: Provide API to auto-inject + + // TODO: build system to auto inject "user plugins" + + private: + using tab_id = std::string; + std::map glossi_tweaks_injected_map_; + + static constexpr std::string_view steam_tweaks_path_ = "SteamTweaks"; + static constexpr std::wstring_view uninstall_glossi_tweaks_js_ = LR"( + (() => { + return window.GlosSITweaks?.GlosSI?.uninstall(); + })(); + )"; + }; } \ No newline at end of file