This commit is contained in:
Sude 2022-10-01 20:18:12 +03:00
commit 6ae6ab2469
9 changed files with 1195 additions and 32 deletions

View File

@ -19,6 +19,7 @@ struct DirectoryConfig
{
bool bSubDirectories;
std::string sDirectory;
std::string sWinePrefix;
std::string sGameSubdir;
std::string sInstallersSubdir;
std::string sExtrasSubdir;
@ -102,6 +103,16 @@ class GalaxyConfig
return this->token_json;
}
std::string getUserId() {
std::unique_lock<std::mutex> lock(m);
if(this->token_json.isMember("user_id")) {
return this->token_json["user_id"].asString();
}
return {};
}
void setJSON(Json::Value json)
{
std::unique_lock<std::mutex> lock(m);
@ -131,16 +142,34 @@ class GalaxyConfig
return this->filepath;
}
void resetClient() {
std::lock_guard<std::mutex> lock(m);
if(token_json.isMember("client_id")) {
token_json["client_id"] = default_client_id;
}
if(token_json.isMember("client_secret")) {
token_json["client_secret"] = default_client_secret;
}
}
std::string getClientId()
{
std::unique_lock<std::mutex> lock(m);
return this->client_id;
std::lock_guard<std::mutex> lock(m);
if(token_json.isMember("client_id")) {
return token_json["client_id"].asString();
}
return default_client_id;
}
std::string getClientSecret()
{
std::unique_lock<std::mutex> lock(m);
return this->client_secret;
std::lock_guard<std::mutex> lock(m);
if(token_json.isMember("client_secret")) {
return token_json["client_secret"].asString();
}
return default_client_secret;
}
std::string getRedirectUri()
@ -154,8 +183,6 @@ class GalaxyConfig
GalaxyConfig(const GalaxyConfig& other)
{
std::lock_guard<std::mutex> guard(other.m);
client_id = other.client_id;
client_secret = other.client_secret;
redirect_uri = other.redirect_uri;
filepath = other.filepath;
token_json = other.token_json;
@ -169,8 +196,6 @@ class GalaxyConfig
std::unique_lock<std::mutex> lock1(m, std::defer_lock);
std::unique_lock<std::mutex> lock2(other.m, std::defer_lock);
std::lock(lock1, lock2);
client_id = other.client_id;
client_secret = other.client_secret;
redirect_uri = other.redirect_uri;
filepath = other.filepath;
token_json = other.token_json;
@ -178,8 +203,9 @@ class GalaxyConfig
}
protected:
private:
std::string client_id = "46899977096215655";
std::string client_secret = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
const std::string default_client_id = "46899977096215655";
const std::string default_client_secret = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
std::string redirect_uri = "https://embed.gog.com/on_login_success?origin=client";
std::string filepath;
Json::Value token_json;
@ -281,6 +307,12 @@ class Config
Blacklist blacklist;
Blacklist ignorelist;
Blacklist gamehasdlc;
// Cloud save options
std::vector<std::string> cloudWhiteList;
std::vector<std::string> cloudBlackList;
bool bCloudForce;
std::string sGameHasDLCList;
// Integers

View File

@ -32,9 +32,11 @@
#include <curl/curl.h>
#include <json/json.h>
#include <ctime>
#include <functional>
#include <fstream>
#include <deque>
class cloudSaveFile;
class Timer
{
public:
@ -99,6 +101,14 @@ class Downloader
void clearUpdateNotifications();
void repair();
void download();
void downloadCloudSaves(const std::string& product_id, int build_index = -1);
void downloadCloudSavesById(const std::string& product_id, int build_index = -1);
void uploadCloudSaves(const std::string& product_id, int build_index = -1);
void uploadCloudSavesById(const std::string& product_id, int build_index = -1);
void deleteCloudSaves(const std::string& product_id, int build_index = -1);
void deleteCloudSavesById(const std::string& product_id, int build_index = -1);
void checkOrphans();
void checkStatus();
void updateCache();
@ -113,9 +123,16 @@ class Downloader
void galaxyInstallGame(const std::string& product_id, int build_index = -1, const unsigned int& iGalaxyArch = GlobalConstants::ARCH_X64);
void galaxyInstallGameById(const std::string& product_id, int build_index = -1, const unsigned int& iGalaxyArch = GlobalConstants::ARCH_X64);
void galaxyShowBuilds(const std::string& product_id, int build_index = -1);
void galaxyShowCloudSaves(const std::string& product_id, int build_index = -1);
void galaxyShowLocalCloudSaves(const std::string& product_id, int build_index = -1);
void galaxyShowLocalCloudSavesById(const std::string& product_id, int build_index = -1);
void galaxyShowBuildsById(const std::string& product_id, int build_index = -1);
void galaxyShowCloudSavesById(const std::string& product_id, int build_index = -1);
protected:
private:
std::map<std::string, std::string> cloudSaveLocations(const std::string& product_id, int build_index);
int cloudSaveListByIdForEach(const std::string& product_id, int build_index, const std::function<void(cloudSaveFile &)> &f);
CURLcode downloadFile(const std::string& url, const std::string& filepath, const std::string& xml_data = std::string(), const std::string& gamename = std::string());
int repairFile(const std::string& url, const std::string& filepath, const std::string& xml_data = std::string(), const std::string& gamename = std::string());
int getGameDetails();
@ -133,6 +150,8 @@ class Downloader
static std::string getChangelogFromJSON(const Json::Value& json);
void saveChangelog(const std::string& changelog, const std::string& filepath);
static void processDownloadQueue(Config conf, const unsigned int& tid);
static void processCloudSaveDownloadQueue(Config conf, const unsigned int& tid);
static void processCloudSaveUploadQueue(Config conf, const unsigned int& tid);
static int progressCallbackForThread(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
template <typename T> void printProgress(const ThreadSafeQueue<T>& download_queue);
static void getGameDetailsThread(Config config, const unsigned int& tid);

View File

@ -46,16 +46,20 @@ class galaxyAPI
galaxyAPI(CurlConfig& conf);
virtual ~galaxyAPI();
int init();
bool isTokenExpired();
bool refreshLogin();
bool refreshLogin(const std::string &clientId, const std::string &clientSecret, const std::string &refreshToken, bool newSession);
Json::Value getProductBuilds(const std::string& product_id, const std::string& platform = "windows", const std::string& generation = "2");
Json::Value getManifestV1(const std::string& product_id, const std::string& build_id, const std::string& manifest_id = "repository", const std::string& platform = "windows");
Json::Value getManifestV1(const std::string& manifest_url);
Json::Value getManifestV2(std::string manifest_hash, const bool& is_dependency = false);
Json::Value getCloudPathAsJson(const std::string &clientId);
Json::Value getSecureLink(const std::string& product_id, const std::string& path);
Json::Value getDependencyLink(const std::string& path);
std::string getResponse(const std::string& url);
Json::Value getResponseJson(const std::string& url);
std::string getResponse(const std::string& url, const char *encoding = nullptr);
Json::Value getResponseJson(const std::string& url, const char *encoding = nullptr);
std::string hashToGalaxyPath(const std::string& hash);
std::vector<galaxyDepotItem> getDepotItemsVector(const std::string& hash, const bool& is_dependency = false);
Json::Value getProductInfo(const std::string& product_id);

View File

@ -66,6 +66,7 @@ namespace Util
int createXML(std::string filepath, uintmax_t chunk_size, std::string xml_dir = std::string());
int getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf, std::string directory = std::string());
int replaceString(std::string& str, const std::string& to_replace, const std::string& replace_with);
int replaceAllString(std::string& str, const std::string& to_replace, const std::string& replace_with);
void filepathReplaceReservedStrings(std::string& str, const std::string& gamename, const unsigned int& platformId = 0, const std::string& dlcname = "");
void setFilePermissions(const boost::filesystem::path& path, const boost::filesystem::perms& permissions);
int getTerminalWidth();
@ -90,6 +91,7 @@ namespace Util
CURLcode CurlHandleGetResponse(CURL* curlhandle, std::string& response, int max_retries = -1);
curl_off_t CurlWriteMemoryCallback(char *ptr, curl_off_t size, curl_off_t nmemb, void *userp);
curl_off_t CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp);
curl_off_t CurlReadChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, ChunkMemoryStruct *userp);
template<typename ... Args> std::string formattedString(const std::string& format, Args ... args)
{

View File

@ -24,6 +24,18 @@ template<typename T> void set_vm_value(std::map<std::string, bpo::variable_value
vm[option].value() = boost::any(value);
}
void ensure_trailing_slash(std::string &path, const char *default_ = nullptr) {
if (!path.empty())
{
if (path.at(path.length()-1)!='/')
path += "/";
}
else
{
path = default_; // Directory wasn't specified, use current directory
}
}
int main(int argc, char *argv[])
{
struct sigaction act;
@ -151,6 +163,11 @@ int main(int argc, char *argv[])
std::string galaxy_product_id_install;
std::string galaxy_product_id_show_builds;
std::string galaxy_product_id_show_cloud_paths;
std::string galaxy_product_id_show_local_cloud_paths;
std::string galaxy_product_cloud_saves;
std::string galaxy_product_cloud_saves_delete;
std::string galaxy_upload_product_cloud_saves;
std::string tags;
std::vector<std::string> vFileIdStrings;
@ -213,6 +230,11 @@ int main(int argc, char *argv[])
("cacert", bpo::value<std::string>(&Globals::globalConfig.curlConf.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format")
("respect-umask", bpo::value<bool>(&Globals::globalConfig.bRespectUmask)->zero_tokens()->default_value(false), "Do not adjust permissions of sensitive files")
("user-agent", bpo::value<std::string>(&Globals::globalConfig.curlConf.sUserAgent)->default_value(DEFAULT_USER_AGENT), "Set user agent")
("wine-prefix", bpo::value<std::string>(&Globals::globalConfig.dirConf.sWinePrefix)->default_value("."), "Set wineprefix directory")
("cloud-whitelist", bpo::value<std::vector<std::string>>(&Globals::globalConfig.cloudWhiteList)->multitoken(), "Include this list of cloud saves, by default all cloud saves are included\n Example: --cloud-whitelist saves/AutoSave-0 saves/AutoSave-1/screenshot.png")
("cloud-blacklist", bpo::value<std::vector<std::string>>(&Globals::globalConfig.cloudBlackList)->multitoken(), "Exclude this list of cloud saves\n Example: --cloud-blacklist saves/AutoSave-0 saves/AutoSave-1/screenshot.png")
("cloud-force", bpo::value<bool>(&Globals::globalConfig.bCloudForce)->zero_tokens()->default_value(false), "Download or Upload cloud saves even if they're up-to-date\nDelete remote cloud saves even if no saves are whitelisted")
#ifdef USE_QT_GUI_LOGIN
("enable-login-gui", bpo::value<bool>(&Globals::globalConfig.bEnableLoginGUI)->zero_tokens()->default_value(false), "Enable login GUI when encountering reCAPTCHA on login form")
#endif
@ -271,6 +293,11 @@ int main(int argc, char *argv[])
options_cli_experimental.add_options()
("galaxy-install", bpo::value<std::string>(&galaxy_product_id_install)->default_value(""), "Install game using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-show-builds", bpo::value<std::string>(&galaxy_product_id_show_builds)->default_value(""), "Show game builds using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-download-cloud-saves", bpo::value<std::string>(&galaxy_product_cloud_saves)->default_value(""), "Download cloud saves using product-id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-upload-cloud-saves", bpo::value<std::string>(&galaxy_upload_product_cloud_saves)->default_value(""), "Upload cloud saves using product-id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-show-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_cloud_paths)->default_value(""), "Show game cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-show-local-cloud-saves", bpo::value<std::string>(&galaxy_product_id_show_local_cloud_paths)->default_value(""), "Show local cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-delete-cloud-saves", bpo::value<std::string>(&galaxy_product_cloud_saves_delete)->default_value(""), "Delete cloud-saves using product id [product_id/build_index] or gamename regex [gamename/build_id]\nBuild index is used to select a build and defaults to 0 if not specified.\n\nExample: 12345/2 selects build 2 for product 12345")
("galaxy-platform", bpo::value<std::string>(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str())
("galaxy-language", bpo::value<std::string>(&sGalaxyLanguage)->default_value("en"), galaxy_language_text.c_str())
("galaxy-arch", bpo::value<std::string>(&sGalaxyArch)->default_value("x64"), galaxy_arch_text.c_str())
@ -578,15 +605,8 @@ int main(int argc, char *argv[])
}
// Make sure that directory has trailing slash
if (!Globals::globalConfig.dirConf.sDirectory.empty())
{
if (Globals::globalConfig.dirConf.sDirectory.at(Globals::globalConfig.dirConf.sDirectory.length()-1)!='/')
Globals::globalConfig.dirConf.sDirectory += "/";
}
else
{
Globals::globalConfig.dirConf.sDirectory = "./"; // Directory wasn't specified, use current directory
}
ensure_trailing_slash(Globals::globalConfig.dirConf.sDirectory, "./");
ensure_trailing_slash(Globals::globalConfig.dirConf.sWinePrefix, "./");
// CA certificate bundle
if (Globals::globalConfig.curlConf.sCACertPath.empty())
@ -777,6 +797,39 @@ int main(int argc, char *argv[])
}
downloader.galaxyShowBuilds(product_id, build_index);
}
else if (!galaxy_product_id_show_cloud_paths.empty())
{
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_show_cloud_paths, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.galaxyShowCloudSaves(product_id, build_index);
}
else if (!galaxy_product_id_show_local_cloud_paths.empty())
{
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_show_local_cloud_paths, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.galaxyShowLocalCloudSaves(product_id, build_index);
}
else if (!galaxy_product_cloud_saves_delete.empty())
{
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_cloud_saves_delete, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.deleteCloudSaves(product_id, build_index);
}
else if (!galaxy_product_id_install.empty())
{
int build_index = -1;
@ -788,6 +841,26 @@ int main(int argc, char *argv[])
}
downloader.galaxyInstallGame(product_id, build_index, Globals::globalConfig.dlConf.iGalaxyArch);
}
else if (!galaxy_product_cloud_saves.empty()) {
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_cloud_saves, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.downloadCloudSaves(product_id, build_index);
}
else if (!galaxy_upload_product_cloud_saves.empty()) {
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_upload_product_cloud_saves, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.uploadCloudSaves(product_id, build_index);
}
else
{
if (!Globals::globalConfig.bLogin)

File diff suppressed because it is too large Load Diff

View File

@ -53,23 +53,33 @@ int galaxyAPI::init()
return res;
}
bool galaxyAPI::refreshLogin()
bool galaxyAPI::refreshLogin(const std::string &clientId, const std::string &clientSecret, const std::string &refreshToken, bool newSession)
{
std::string refresh_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+ "&client_secret=" + Globals::galaxyConf.getClientSecret()
std::string refresh_url = "https://auth.gog.com/token?client_id=" + clientId
+ "&client_secret=" + clientSecret
+ "&grant_type=refresh_token"
+ "&refresh_token=" + Globals::galaxyConf.getRefreshToken();
+ "&refresh_token=" + refreshToken
+ (newSession ? "" : "&without_new_session=1");
// std::cout << refresh_url << std::endl;
Json::Value token_json = this->getResponseJson(refresh_url);
if (token_json.empty())
return false;
token_json["client_id"] = clientId;
token_json["client_secret"] = clientSecret;
Globals::galaxyConf.setJSON(token_json);
return true;
}
bool galaxyAPI::refreshLogin()
{
return refreshLogin(Globals::galaxyConf.getClientId(), Globals::galaxyConf.getClientSecret(), Globals::galaxyConf.getRefreshToken(), true);
}
bool galaxyAPI::isTokenExpired()
{
bool res = false;
@ -80,7 +90,7 @@ bool galaxyAPI::isTokenExpired()
return res;
}
std::string galaxyAPI::getResponse(const std::string& url)
std::string galaxyAPI::getResponse(const std::string& url, const char *encoding)
{
struct curl_slist *header = NULL;
@ -92,13 +102,26 @@ std::string galaxyAPI::getResponse(const std::string& url)
std::string bearer = "Authorization: Bearer " + access_token;
header = curl_slist_append(header, bearer.c_str());
}
if(encoding) {
auto accept = "Accept: " + std::string(encoding);
header = curl_slist_append(header, accept.c_str());
}
curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, header);
curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(curlhandle, CURLOPT_ACCEPT_ENCODING, "");
int max_retries = std::min(3, Globals::globalConfig.iRetries);
std::string response;
Util::CurlHandleGetResponse(curlhandle, response, max_retries);
auto res = Util::CurlHandleGetResponse(curlhandle, response, max_retries);
if(res) {
long int response_code = 0;
curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
std::cout << "Response code for " << url << " is [" << response_code << ']' << std::endl;
}
curl_easy_setopt(curlhandle, CURLOPT_ACCEPT_ENCODING, NULL);
curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, NULL);
@ -107,9 +130,9 @@ std::string galaxyAPI::getResponse(const std::string& url)
return response;
}
Json::Value galaxyAPI::getResponseJson(const std::string& url)
Json::Value galaxyAPI::getResponseJson(const std::string& url, const char *encoding)
{
std::istringstream response(this->getResponse(url));
std::istringstream response(this->getResponse(url, encoding));
Json::Value json;
if (!response.str().empty())
@ -149,8 +172,13 @@ Json::Value galaxyAPI::getResponseJson(const std::string& url)
catch(const Json::Exception& exc)
{
// Failed to parse json
std::cout << "Failed to parse json: " << exc.what();
}
}
else {
std::cout << "Failed to parse json: " << exc.what();
}
}
}
@ -190,6 +218,12 @@ Json::Value galaxyAPI::getManifestV2(std::string manifest_hash, const bool& is_d
return this->getResponseJson(url);
}
Json::Value galaxyAPI::getCloudPathAsJson(const std::string &clientId) {
std::string url = "https://remote-config.gog.com/components/galaxy_client/clients/" + clientId + "?component_version=2.0.51";
return this->getResponseJson(url);
}
Json::Value galaxyAPI::getSecureLink(const std::string& product_id, const std::string& path)
{
std::string url = "https://content-system.gog.com/products/" + product_id + "/secure_link?generation=2&path=" + path + "&_version=2";

View File

@ -8,6 +8,9 @@
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <tinyxml2.h>
#include <json/json.h>
#include <fstream>
@ -391,6 +394,22 @@ int Util::replaceString(std::string& str, const std::string& to_replace, const s
return 1;
}
int Util::replaceAllString(std::string& str, const std::string& to_replace, const std::string& replace_with) {
size_t pos = str.find(to_replace);
if (pos == std::string::npos)
{
return 0;
}
do {
str.replace(str.begin()+pos, str.begin()+pos+to_replace.length(), replace_with);
pos = str.find(to_replace, pos + to_replace.length());
} while(pos != std::string::npos);
return 1;
}
void Util::filepathReplaceReservedStrings(std::string& str, const std::string& gamename, const unsigned int& platformId, const std::string& dlcname)
{
std::string platform;
@ -749,7 +768,6 @@ void Util::CurlHandleSetDefaultOptions(CURL* curlhandle, const CurlConfig& conf)
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, conf.sUserAgent.c_str());
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
@ -823,8 +841,9 @@ CURLcode Util::CurlHandleGetResponse(CURL* curlhandle, std::string& response, in
// Retry on CURLE_HTTP_RETURNED_ERROR if response code is not "404 Not Found"
case CURLE_HTTP_RETURNED_ERROR:
curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
if (response_code == 404)
if (response_code == 404 || response_code == 403) {
bShouldRetry = false;
}
else
bShouldRetry = true;
break;
@ -865,3 +884,14 @@ curl_off_t Util::CurlWriteChunkMemoryCallback(void *contents, curl_off_t size, c
return realsize;
}
curl_off_t Util::CurlReadChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, ChunkMemoryStruct *mem) {
curl_off_t realsize = std::min(size * nmemb, mem->size);
std::copy(mem->memory, mem->memory + realsize, (char*)contents);
mem->size -= realsize;
mem->memory += realsize;
return realsize;
}

View File

@ -264,6 +264,9 @@ std::vector<gameItem> Website::getGames()
// Login to GOG website
int Website::Login(const std::string& email, const std::string& password)
{
// Reset client id and client secret to ensure we can log-in
Globals::galaxyConf.resetClient();
int res = 0;
std::string postdata;
std::ostringstream memory;