CEFInject: Add (WIP) GlosSITweaks injection system

pull/239/head
Peter Repukat 1 year ago
parent 0e356801e7
commit e5368744f1

@ -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 <easywsclient.hpp>
#define _SSIZE_T_DEFINED
#include <easywsclient.cpp> // seems like a hack to me, but eh
#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>
namespace CEFInject
{
namespace internal
@ -51,80 +54,222 @@ namespace CEFInject
return false;
}
std::vector<std::wstring> AvailableTabs(uint16_t port)
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::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<std::wstring>());
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<easywsclient::WebSocket> 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<std::string>() != "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<std::wstring>().starts_with(tabname)) {
#ifdef _WIN32
INT rc;
WSADATA wsaData;
return InjectJs(tab["webSocketDebuggerUrl"].get<std::string>(), 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<wchar_t>(js_file)),
std::istreambuf_iterator<wchar_t>() };
if (glossitweaks_js.empty()) {
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>().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<std::string>() == info.id) {
return tab;
}
}
return nlohmann::json{};
};
std::shared_ptr<easywsclient::WebSocket> ws{
easywsclient::WebSocket::from_url(tab["webSocketDebuggerUrl"].get<std::string>())
};
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<std::string>() == 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;
}
}
const auto tabs = AvailableTabs(port);
const auto tab = find_tab(tabs);
if (tab.empty()) {
return false;
}
InjectJs(tab["webSocketDebuggerUrl"].get<std::string>(), glossitweaks_js, port);
glossi_tweaks_injected_map_[tab["id"].get<std::string>()] = 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;
}
}

@ -30,7 +30,41 @@ namespace CEFInject
internal::port_ = port;
}
bool CEFDebugAvailable(uint16_t port = internal::port_);
std::vector<std::wstring> AvailableTabs(uint16_t port = internal::port_);
nlohmann::json InjectJs(const std::wstring& tabname, const std::wstring& js, uint16_t port = internal::port_);
std::vector<std::wstring> 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<tab_id, bool> 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();
})();
)";
};
}
Loading…
Cancel
Save