clientapi basics

This commit is contained in:
mrbesen 2022-06-27 20:45:01 +02:00
parent c1e92c1706
commit 00f6cc4a7d
Signed by: MrBesen
GPG Key ID: 596B2350DCD67504
9 changed files with 402 additions and 63 deletions

28
include/clientaccess.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
class ClientAccess {
ClientAccess();
ClientAccess(const std::string& token, uint16_t port);
public:
static std::shared_ptr<ClientAccess> find(bool uselockfile = true);
private:
// returns the name and value of a argument or empty string if it could not be parsed
static std::string parseArg(const std::string& input, std::string& value);
static std::shared_ptr<ClientAccess> findUsingArgs(const std::vector<std::string>& cmdline);
static std::shared_ptr<ClientAccess> findUsingLockfile(const std::vector<std::string>& cmdline, pid_t pid);
public:
std::string getBasicAuth() const;
uint16_t getPort() const;
std::string getURL() const;
private:
std::string authcode;
uint16_t port = 0;
};

34
include/clientapi.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include "clientaccess.h"
#include "restclient.h"
class ClientAPI : public RestClient {
public:
enum class SearchState : uint32_t {
INVALID = 0,
SEARCHING,
FOUND,
ABANDONEDLOWPRIORITYQUEUE,
CANCELED,
ERROR,
SERVICEERROR,
SERVICESHUTDOWN,
};
ClientAPI(const ClientAccess& access);
~ClientAPI();
SearchState getSearchState();
void acceptMatch();
void declineMatch();
protected:
private:
ClientAccess access;
};

View File

@ -6,14 +6,14 @@
#include <string>
#include <thread>
#include <vector>
#include <curl/curl.h>
#include <QJsonDocument>
#include <opencv2/opencv.hpp>
#include "datadragonimagecache.h"
#include "memoryimagecache.h"
#include "restclient.h"
class DataDragon {
class DataDragon : public RestClient {
public:
using notifyImgfunc_t = std::function<void(cv::Mat)>;
@ -54,8 +54,6 @@ public:
protected:
std::string getImageUrl(const std::string& champid, ImageType type);
std::string getCDNString() const;
QByteArray requestRaw(const std::string& url);
QJsonDocument request(const std::string& url);
void getVersionInternal();
void getChampsInternal();
@ -77,7 +75,6 @@ private:
ImageType type;
};
CURL* curl = nullptr; // the curl (does curling)
DataDragonImageCache cache[3];
MemoryImageCache memcache;

22
include/restclient.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <QJsonDocument>
#include <curl/curl.h>
class RestClient {
public:
RestClient(const std::string& base);
virtual ~RestClient();
protected:
QByteArray requestRaw(const std::string& url);
QJsonDocument request(const std::string& url);
std::string baseurl;
CURL* curl = nullptr; // the curl (does curling)
std::string basicauth; // basic auth code (user:pw) or empty string to disable
bool disableCertCheck = false;
};

View File

@ -24,6 +24,8 @@ defineReplace(prependAll) {
SOURCES += \
src/arg.cpp \
src/clientaccess.cpp \
src/clientapi.cpp \
src/config.cpp \
src/datadragon.cpp \
src/datadragonimagecache.cpp \
@ -39,6 +41,7 @@ SOURCES += \
src/mainwindow.cpp \
src/matcher.cpp \
src/memoryimagecache.cpp \
src/restclient.cpp \
src/scaleableinputs.cpp \
src/screen.cpp \
src/stagesettings.cpp \
@ -48,6 +51,8 @@ SOURCES += \
HEADERS += \
include/arg.h \
include/clientaccess.h \
include/clientapi.h \
include/config.h \
include/datadragon.h \
include/datadragonimagecache.h \
@ -58,6 +63,7 @@ HEADERS += \
include/mainwindow.h \
include/matcher.h \
include/memoryimagecache.h \
include/restclient.h \
include/scaleableinputs.h \
include/screen.h \
include/stagesettings.h \

187
src/clientaccess.cpp Normal file
View File

@ -0,0 +1,187 @@
#include "clientaccess.h"
#include <cstring>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>
#include <Log.h>
static bool endsWith(const std::string& all, const std::string& end) {
return all.rfind(end) == all.size() - end.size();
}
// reads a procfile into a vector (strings are \0 seperated)
static std::vector<std::string> readProcFile(const std::string& path) {
std::ifstream in(path);
std::vector<std::string> out;
std::string line;
while(std::getline(in, line, '\0')) {
if(!line.empty()) {
out.push_back(line);
}
}
return out;
}
ClientAccess::ClientAccess() {}
ClientAccess::ClientAccess(const std::string& token, uint16_t port) : authcode(token), port(port) {}
std::shared_ptr<ClientAccess> ClientAccess::find(bool uselockfile) {
DIR* procdir = opendir("/proc");
if(!procdir) return nullptr;
dirent* entry = nullptr;
while ((entry = readdir(procdir)) != NULL) {
if (entry->d_type != DT_DIR) continue;
std::string name(entry->d_name);
pid_t pid = -1;
try {
pid = std::stoi(name);
} catch(std::exception& e) {
// could not parse -> not a pid
continue;
}
// get info on the exe
std::string cmdfile = "/proc/" + std::to_string(pid) + "/cmdline";
std::vector<std::string> args = readProcFile(cmdfile);
Log::debug << "process: " << pid << " has " << args.size() << " args";
if(args.empty()) continue;
std::string& exename = args.at(0);
if(endsWith(exename, "LeagueClientUx.exe")) {
Log::info << "LeagueClientUx.exe found";
std::shared_ptr<ClientAccess> out;
if(uselockfile) {
out = findUsingLockfile(args, pid);
} else {
out = findUsingArgs(args);
}
if(out) {
return out;
}
}
}
closedir(procdir);
return nullptr;
}
std::string ClientAccess::parseArg(const std::string& input, std::string& value) {
if(input.find("--") != 0) return {};
size_t pos = input.find('=');
if(pos == std::string::npos) return {};
value = input.substr(pos+1);
return input.substr(2, pos-2);
}
std::shared_ptr<ClientAccess> ClientAccess::findUsingArgs(const std::vector<std::string>& cmdline) {
// parse args
std::shared_ptr<ClientAccess> access(new ClientAccess());
for(const std::string& arg : cmdline) {
std::string value;
std::string argname = parseArg(arg, value);
if(argname == "riotclient-auth-token") {
access->authcode = value;
} else if(argname == "riotclient-app-port") {
try {
access->port = std::stoi(value);
} catch(std::exception& e) {
Log::warn << "could not parse port: " << std::quoted(value);
}
}
}
if(access->port > 0 && !access->authcode.empty()) {
return access;
}
return nullptr;
}
std::shared_ptr<ClientAccess> ClientAccess::findUsingLockfile(const std::vector<std::string>& cmdline, pid_t pid) {
// get WINEPREFIX env
std::vector<std::string> envs = readProcFile("/proc/" + std::to_string(pid) + "/environ");
const static std::string WINEPREFIX = "WINEPREFIX=";
// find WINEPREFIX
auto found = std::find_if(envs.begin(), envs.end(), [](const std::string& s) {
return s.find(WINEPREFIX) == 0;
});
// WINEPREFIX env not present
if(found == envs.end()) {
Log::debug << "WINEPREFIX environment variable not set";
return {};
}
const std::string wineprefix = found->substr(WINEPREFIX.size());
const std::string binarypath = cmdline.at(0);
std::string gamefolder = binarypath.substr(2, binarypath.rfind('/')-1); // remove the "C:" and the name of the binary
// TODO: gamefoldre could contain '\' so replacing every occurance with '/' would be a good idea
const std::string lockfilepath = wineprefix + "/drive_c/" + gamefolder + "/lockfile";
Log::debug << "lockfilepath: " << std::quoted(lockfilepath);
// read lockfile
std::ifstream lockfile(lockfilepath);
std::vector<std::string> parts;
std::string content;
while(std::getline(lockfile, content, ':')) {
parts.push_back(content);
}
if(parts.size() != 5) {
Log::error << "lockfile contained " << parts.size() << " parts, expected 5";
return {};
}
const std::string portstr = parts.at(2);
const std::string token = parts.at(3);
// try to parse port
try {
uint16_t port = std::stoi(portstr);
return std::shared_ptr<ClientAccess>(new ClientAccess(token, port));
} catch(std::exception& e) {
Log::error << "could not parse port: " << std::quoted(portstr);
}
return {};
}
std::string ClientAccess::getBasicAuth() const {
return "riot:" + authcode;
}
uint16_t ClientAccess::getPort() const {
return port;
}
std::string ClientAccess::getURL() const {
return "https://127.0.0.1:" + std::to_string(port) + "/";
}

51
src/clientapi.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "clientapi.h"
#include <iomanip>
#include <QJsonObject>
#include <Log.h>
#include "json.h"
static const std::string SearchStateNames[] = {"Invalid", "Searching", "Found", "AbandonedLowPriorityQueue", "Canceled", "Error", "ServiceError", "ServiceShutdown"};
static const uint32_t SearchStateNamesCount = sizeof(SearchStateNames) / sizeof(SearchStateNames[0]);
template<typename T>
static T mapEnum(const std::string& input, const std::string* names, uint32_t count, T defaul) {
for(uint32_t i = 0; i < count; ++i) {
if(names[i] == input) {
return (T) i;
}
}
Log::warn << "no mapping of enum-string: " << std::quoted(input);
return defaul;
}
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca) {
basicauth = ca.getBasicAuth();
disableCertCheck = true;
}
ClientAPI::~ClientAPI() {}
ClientAPI::SearchState ClientAPI::getSearchState() {
QJsonDocument doc = request("lol-lobby/v2/lobby/matchmaking/search-state");
if(doc.isObject()) {
QJsonObject obj = doc.object();
std::string searchState = getValue<std::string>(obj, "searchState", "Invalid");
return mapEnum(searchState, SearchStateNames, SearchStateNamesCount, ClientAPI::SearchState::INVALID);
}
return ClientAPI::SearchState::INVALID;
}
void ClientAPI::acceptMatch() {
}
void ClientAPI::declineMatch() {
}

View File

@ -16,27 +16,12 @@
static const std::string BASEURL = "https://ddragon.leagueoflegends.com/";
const DataDragon::ChampData DataDragon::EMPTYCHAMP;
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
if(userdata) {
QByteArray* arr = (QByteArray*) userdata;
arr->append(contents, size * nmemb);
return size * nmemb;
}
return 0;
}
DataDragon::DataDragon(const std::string& locale) : locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
DataDragon::DataDragon(const std::string& locale) : RestClient(BASEURL), locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) {
startThread();
}
DataDragon::~DataDragon() {
stopAndJoinThread();
curl_easy_cleanup(curl);
}
DataDragon::ChampData::ChampData() : key(-1) {}
@ -204,47 +189,6 @@ std::string DataDragon::getCDNString() const {
return "cdn/" + version + "/";
}
QByteArray DataDragon::requestRaw(const std::string& url) {
if(!curl) return {};
std::string requrl = BASEURL + url;
QByteArray ba; //buffer
// std::cout << "[DEBUG] requrl is: " << requrl << std::endl;
curl_easy_setopt(curl, CURLOPT_URL, requrl.c_str());
// curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
// set callback data
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
// Check for errors
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if(res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
Log::warn << "DataDragon request failed: " << url << " -> " << responsecode ;
} else {
Log::warn << "DataDragon request failed: " << url << " " << curl_easy_strerror(res);
}
return {};
}
return ba;
}
QJsonDocument DataDragon::request(const std::string& url) {
QByteArray arr = requestRaw(url);
if(arr.isEmpty()) return {};
QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if(parsed.isNull() || err.error != QJsonParseError::NoError) {
Log::error << "DataDragon Json parse error " << err.errorString().toStdString() << " offset: " << err.offset;
return {};
}
return parsed;
}
void DataDragon::getVersionInternal() {
std::unique_lock lock(cachedatamutex);

70
src/restclient.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "restclient.h"
#include <Log.h>
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
if(userdata) {
QByteArray* arr = (QByteArray*) userdata;
arr->append(contents, size * nmemb);
return size * nmemb;
}
return 0;
}
RestClient::RestClient(const std::string& base) : baseurl(base) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
}
RestClient::~RestClient() {
curl_easy_cleanup(curl);
curl = nullptr;
}
QByteArray RestClient::requestRaw(const std::string& url) {
if(!curl) return {};
std::string requrl = baseurl + url;
QByteArray ba; //buffer
// std::cout << "[DEBUG] requrl is: " << requrl << std::endl;
curl_easy_setopt(curl, CURLOPT_URL, requrl.c_str());
// curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
// set callback data
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if(disableCertCheck) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
}
// Check for errors
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if(res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
Log::warn << "API request failed: " << baseurl << " " << url << " -> " << responsecode ;
} else {
Log::warn << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res);
}
return {};
}
return ba;
}
QJsonDocument RestClient::request(const std::string& url) {
QByteArray arr = requestRaw(url);
if(arr.isEmpty()) return {};
QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if(parsed.isNull() || err.error != QJsonParseError::NoError) {
Log::error << "API Json parse error " << err.errorString().toStdString() << " offset: " << err.offset;
return {};
}
return parsed;
}