Some initial Galaxy code

Add some code for initial Galaxy support.
Rewrite and move code around in preparation for GOG Galaxy API support.
This commit is contained in:
Sude 2017-02-17 11:14:28 +02:00
parent 22f47de4fc
commit f2e8dde934
18 changed files with 1495 additions and 706 deletions

View File

@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
project (lgogdownloader LANGUAGES C CXX VERSION 3.1)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1")
set(LINK_LIBCRYPTO 0)
find_program(READELF readelf DOC "Location of the readelf program")
@ -14,6 +13,7 @@ find_package(Boost
regex
program_options
date_time
iostreams
)
find_package(CURL 7.32.0 REQUIRED)
if(CURL_FOUND)
@ -46,6 +46,7 @@ file(GLOB SRC_FILES
src/blacklist.cpp
src/gamefile.cpp
src/gamedetails.cpp
src/galaxyapi.cpp
)
set(GIT_CHECKOUT FALSE)

View File

@ -9,6 +9,7 @@
#include "globalconstants.h"
#include "gamedetails.h"
#include "globals.h"
#include <iostream>
#include <vector>

View File

@ -9,93 +9,262 @@
#include <iostream>
#include <curl/curl.h>
#include <json/json.h>
#include <mutex>
#include <ctime>
#include "blacklist.h"
struct DirectoryConfig
{
bool bSubDirectories;
std::string sDirectory;
std::string sGameSubdir;
std::string sInstallersSubdir;
std::string sExtrasSubdir;
std::string sPatchesSubdir;
std::string sLanguagePackSubdir;
std::string sDLCSubdir;
};
struct DownloadConfig
{
unsigned int iInstallerPlatform;
unsigned int iInstallerLanguage;
std::vector<unsigned int> vPlatformPriority;
std::vector<unsigned int> vLanguagePriority;
unsigned int iInclude;
bool bRemoteXML;
bool bCover;
bool bSaveChangelogs;
bool bSaveSerials;
bool bAutomaticXMLCreation;
bool bInstallers;
bool bExtras;
bool bPatches;
bool bLanguagePacks;
bool bDLC;
bool bIgnoreDLCCount;
bool bDuplicateHandler;
};
struct gameSpecificConfig
{
DownloadConfig dlConf;
DirectoryConfig dirConf;
};
class GalaxyConfig
{
public:
bool isExpired()
{
std::unique_lock<std::mutex> lock(m);
bool bExpired = false;
if (this->token_json.isMember("expires_at"))
bExpired = (time(NULL) > this->token_json["expires_at"].asUInt());
return bExpired;
}
std::string getAccessToken()
{
std::unique_lock<std::mutex> lock(m);
return this->token_json["access_token"].asString();
}
std::string getRefreshToken()
{
std::unique_lock<std::mutex> lock(m);
return this->token_json["refresh_token"].asString();
}
Json::Value getJSON()
{
std::unique_lock<std::mutex> lock(m);
return this->token_json;
}
void setJSON(Json::Value json)
{
std::unique_lock<std::mutex> lock(m);
if (!json.isMember("expires_at"))
json["expires_at"] = json["expires_in"].asUInt() + time(NULL);
this->token_json = json;
}
void setFilepath(const std::string& path)
{
std::unique_lock<std::mutex> lock(m);
this->filepath = path;
}
std::string getFilepath()
{
std::unique_lock<std::mutex> lock(m);
return this->filepath;
}
std::string getClientId()
{
std::unique_lock<std::mutex> lock(m);
return this->client_id;
}
std::string getClientSecret()
{
std::unique_lock<std::mutex> lock(m);
return this->client_secret;
}
std::string getRedirectUri()
{
std::unique_lock<std::mutex> lock(m);
return this->redirect_uri;
}
GalaxyConfig() = default;
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;
}
GalaxyConfig& operator= (GalaxyConfig& other)
{
if(&other == this)
return *this;
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;
return *this;
}
protected:
private:
std::string client_id = "46899977096215655";
std::string client_secret = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
std::string redirect_uri = "https://embed.gog.com/on_login_success?origin=client";
std::string filepath;
Json::Value token_json;
mutable std::mutex m;
};
struct CurlConfig
{
bool bVerifyPeer;
bool bVerbose;
std::string sCACertPath;
std::string sCookiePath;
long int iTimeout;
curl_off_t iDownloadRate;
};
struct GogAPIConfig
{
std::string sToken;
std::string sSecret;
};
class Config
{
public:
Config() {};
virtual ~Config() {};
bool bVerbose;
bool bRemoteXML;
bool bCover;
bool bUpdateCheck;
bool bDownload;
bool bList;
bool bListDetails;
// Booleans
bool bLoginHTTP;
bool bLoginAPI;
bool bRepair;
bool bInstallers;
bool bExtras;
bool bPatches;
bool bLanguagePacks;
bool bDLC;
bool bUnicode; // use Unicode in console output
bool bColor; // use colors
bool bVerifyPeer;
bool bCheckStatus;
bool bDuplicateHandler;
bool bSaveConfig;
bool bResetConfig;
bool bDownload;
bool bRepair;
bool bUpdateCheck;
bool bList;
bool bListDetails;
bool bCheckStatus;
bool bShowWishlist;
bool bVerbose;
bool bUnicode; // use Unicode in console output
bool bColor; // use colors
bool bReport;
bool bSubDirectories;
bool bRespectUmask;
bool bPlatformDetection;
// Cache
bool bUseCache;
bool bUpdateCache;
bool bSaveSerials;
bool bPlatformDetection;
bool bShowWishlist;
bool bAutomaticXMLCreation;
bool bSaveChangelogs;
bool bRespectUmask;
std::string sGameRegex;
std::string sDirectory;
int iCacheValid;
// Download with file id options
std::string sFileIdString;
std::string sOutputFilename;
// Curl
CurlConfig curlConf;
// Download
DownloadConfig dlConf;
// Galaxy
//GalaxyConfig galaxyConf;
// Directories
DirectoryConfig dirConf;
std::string sCacheDirectory;
std::string sXMLFile;
std::string sXMLDirectory;
std::string sToken;
std::string sSecret;
std::string sVersionString;
std::string sVersionNumber;
std::string sConfigDirectory;
std::string sCookiePath;
// File paths
std::string sConfigFilePath;
std::string sBlacklistFilePath;
std::string sIgnorelistFilePath;
std::string sGameHasDLCListFilePath;
std::string sOrphanRegex;
std::string sCoverList;
std::string sGameHasDLCList;
std::string sReportFilePath;
std::string sInstallersSubdir;
std::string sExtrasSubdir;
std::string sPatchesSubdir;
std::string sLanguagePackSubdir;
std::string sDLCSubdir;
std::string sGameSubdir;
std::string sFileIdString;
std::string sOutputFilename;
std::string sLanguagePriority;
std::string sPlatformPriority;
std::string sIgnoreDLCCountRegex;
std::string sCACertPath;
std::vector<unsigned int> vLanguagePriority;
std::vector<unsigned int> vPlatformPriority;
unsigned int iInstallerPlatform;
unsigned int iInstallerLanguage;
unsigned int iInclude;
unsigned int iThreads;
int iRetries;
int iWait;
int iCacheValid;
size_t iChunkSize;
curl_off_t iDownloadRate;
long int iTimeout;
std::string sXMLFile;
// Regex
std::string sGameRegex;
std::string sOrphanRegex;
std::string sIgnoreDLCCountRegex;
// Priorities
std::string sPlatformPriority;
std::string sLanguagePriority;
// General strings
std::string sVersionString;
std::string sVersionNumber;
GogAPIConfig apiConf;
// Lists
Blacklist blacklist;
Blacklist ignorelist;
Blacklist gamehasdlc;
std::string sCoverList;
std::string sGameHasDLCList;
// Integers
int iRetries;
unsigned int iThreads;
int iWait;
size_t iChunkSize;
};
#endif // CONFIG_H__

View File

@ -26,6 +26,9 @@
#include "progressbar.h"
#include "website.h"
#include "threadsafequeue.h"
#include "galaxyapi.h"
#include "globals.h"
#include <curl/curl.h>
#include <json/json.h>
#include <ctime>
@ -58,10 +61,16 @@ struct xferInfo
curl_off_t offset;
};
struct ChunkMemoryStruct
{
char *memory;
curl_off_t size;
};
class Downloader
{
public:
Downloader(Config &conf);
Downloader();
virtual ~Downloader();
bool isLoggedIn();
int init();
@ -77,9 +86,12 @@ class Downloader
void showWishlist();
CURL* curlhandle;
Timer timer;
Config config;
ProgressBar* progressbar;
std::deque< std::pair<time_t, uintmax_t> > TimeAndSize;
void saveGalaxyJSON();
void galaxyInstallGame(const std::string& product_id, int build_index = -1);
void galaxyShowBuilds(const std::string& product_id, int build_index = -1);
protected:
private:
CURLcode downloadFile(const std::string& url, const std::string& filepath, const std::string& xml_data = std::string(), const std::string& gamename = std::string());
@ -112,6 +124,7 @@ class Downloader
Website *gogWebsite;
API *gogAPI;
galaxyAPI *gogGalaxy;
std::vector<gameItem> gameItems;
std::vector<gameDetails> games;
std::string coverXML;

61
include/galaxyapi.h Normal file
View File

@ -0,0 +1,61 @@
/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
#ifndef GALAXYAPI_H
#define GALAXYAPI_H
#include "globalconstants.h"
#include "globals.h"
#include "config.h"
#include "util.h"
#include <iostream>
#include <vector>
#include <cstring>
#include <curl/curl.h>
#include <sys/time.h>
struct galaxyDepotItemChunk
{
std::string md5_compressed;
std::string md5_uncompressed;
off_t size_compressed;
off_t size_uncompressed;
};
struct galaxyDepotItem
{
std::string path;
std::vector<galaxyDepotItemChunk> chunks;
off_t totalSizeCompressed;
off_t totalSizeUncompressed;
std::string md5;
};
class galaxyAPI
{
public:
galaxyAPI(CurlConfig& conf);
virtual ~galaxyAPI();
int init();
bool isTokenExpired();
bool refreshLogin();
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 getManifestV2(std::string manifest_hash);
Json::Value getSecureLink(const std::string& product_id, const std::string& path);
std::string getResponse(const std::string& url, const bool& zlib_decompress = false);
std::string hashToGalaxyPath(const std::string& hash);
std::vector<galaxyDepotItem> getDepotItemsVector(const std::string& hash);
protected:
private:
CurlConfig curlConf;
static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
CURL* curlhandle;
};
#endif // GALAXYAPI_H

View File

@ -8,6 +8,7 @@
#define GAMEDETAILS_H
#include "globalconstants.h"
#include "globals.h"
#include "gamefile.h"
#include "config.h"
#include "util.h"
@ -26,12 +27,13 @@ class gameDetails
std::vector<gameFile> languagepacks;
std::vector<gameDetails> dlcs;
std::string gamename;
std::string product_id;
std::string title;
std::string icon;
std::string serials;
std::string changelog;
void filterWithPriorities(const gameSpecificConfig& config);
void makeFilepaths(const gameSpecificDirectoryConfig& config);
void makeFilepaths(const DirectoryConfig& config);
std::string getSerialsFilepath();
std::string getChangelogFilepath();
Json::Value getDetailsAsJson();

View File

@ -8,6 +8,7 @@
#define GAMEFILE_H
#include "globalconstants.h"
#include "globals.h"
#include <iostream>
#include <vector>

View File

@ -12,7 +12,8 @@
namespace GlobalConstants
{
const int GAMEDETAILS_CACHE_VERSION = 1;
const int GAMEDETAILS_CACHE_VERSION = 2;
const int ZLIB_WINDOW_SIZE = 15;
struct optionsStruct {const unsigned int id; const std::string code; const std::string str; const std::string regexp;};
const std::string PROTOCOL_PREFIX = "gogdownloader://";

21
include/globals.h Normal file
View File

@ -0,0 +1,21 @@
/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
#ifndef GLOBALS_H_INCLUDED
#define GLOBALS_H_INCLUDED
#include "config.h"
#include <iostream>
#include <vector>
namespace Globals
{
extern GalaxyConfig galaxyConf;
extern Config globalConfig;
}
#endif // GLOBALS_H_INCLUDED

View File

@ -8,6 +8,8 @@
#define UTIL_H
#include "globalconstants.h"
#include "config.h"
#include "globals.h"
#include <cstdio>
#include <cstdlib>
@ -21,29 +23,6 @@
#include <boost/regex.hpp>
#include <json/json.h>
struct gameSpecificDirectoryConfig
{
bool bSubDirectories;
std::string sDirectory;
std::string sGameSubdir;
std::string sInstallersSubdir;
std::string sExtrasSubdir;
std::string sPatchesSubdir;
std::string sLanguagePackSubdir;
std::string sDLCSubdir;
};
struct gameSpecificConfig
{
unsigned int iInstallerPlatform;
unsigned int iInstallerLanguage;
bool bDLC;
bool bIgnoreDLCCount;
gameSpecificDirectoryConfig dirConf;
std::vector<unsigned int> vLanguagePriority;
std::vector<unsigned int> vPlatformPriority;
};
struct gameItem
{
std::string name;

View File

@ -9,6 +9,7 @@
#include "config.h"
#include "util.h"
#include "globals.h"
#include <curl/curl.h>
#include <json/json.h>
#include <fstream>
@ -16,7 +17,7 @@
class Website
{
public:
Website(Config &conf);
Website();
int Login(const std::string& email, const std::string& password);
std::string getResponse(const std::string& url);
Json::Value getGameDetailsJSON(const std::string& gameid);
@ -24,7 +25,6 @@ class Website
std::vector<gameItem> getFreeGames();
std::vector<wishlistItem> getWishlistItems();
bool IsLoggedIn();
void setConfig(Config &conf);
virtual ~Website();
protected:
private:

373
main.cpp
View File

@ -9,12 +9,15 @@
#include "util.h"
#include "globalconstants.h"
#include "ssl_thread_setup.h"
#include "galaxyapi.h"
#include "globals.h"
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
namespace bpo = boost::program_options;
Config Globals::globalConfig;
template<typename T> void set_vm_value(std::map<std::string, bpo::variable_value>& vm, const std::string& option, const T& value)
{
@ -44,19 +47,20 @@ int main(int argc, char *argv[])
{ OPTION_DLCS, "d", "DLCs", "d|dlc|dlcs" }
};
Config config;
config.sVersionString = VERSION_STRING;
config.sVersionNumber = VERSION_NUMBER;
Globals::globalConfig.sVersionString = VERSION_STRING;
Globals::globalConfig.sVersionNumber = VERSION_NUMBER;
config.sCacheDirectory = Util::getCacheHome() + "/lgogdownloader";
config.sXMLDirectory = config.sCacheDirectory + "/xml";
Globals::globalConfig.sCacheDirectory = Util::getCacheHome() + "/lgogdownloader";
Globals::globalConfig.sXMLDirectory = Globals::globalConfig.sCacheDirectory + "/xml";
config.sConfigDirectory = Util::getConfigHome() + "/lgogdownloader";
config.sCookiePath = config.sConfigDirectory + "/cookies.txt";
config.sConfigFilePath = config.sConfigDirectory + "/config.cfg";
config.sBlacklistFilePath = config.sConfigDirectory + "/blacklist.txt";
config.sIgnorelistFilePath = config.sConfigDirectory + "/ignorelist.txt";
config.sGameHasDLCListFilePath = config.sConfigDirectory + "/game_has_dlc.txt";
Globals::globalConfig.sConfigDirectory = Util::getConfigHome() + "/lgogdownloader";
Globals::globalConfig.curlConf.sCookiePath = Globals::globalConfig.sConfigDirectory + "/cookies.txt";
Globals::globalConfig.sConfigFilePath = Globals::globalConfig.sConfigDirectory + "/config.cfg";
Globals::globalConfig.sBlacklistFilePath = Globals::globalConfig.sConfigDirectory + "/blacklist.txt";
Globals::globalConfig.sIgnorelistFilePath = Globals::globalConfig.sConfigDirectory + "/ignorelist.txt";
Globals::globalConfig.sGameHasDLCListFilePath = Globals::globalConfig.sConfigDirectory + "/game_has_dlc.txt";
Globals::galaxyConf.setFilepath(Globals::globalConfig.sConfigDirectory + "/galaxy_tokens.json");
std::string priority_help_text = "Set priority by separating values with \",\"\nCombine values by separating with \"+\"";
// Create help text for --platform option
@ -97,12 +101,17 @@ int main(int argc, char *argv[])
}
include_options_text += "Separate with \",\" to use multiple values";
std::string galaxy_product_id_install;
std::string galaxy_product_id_show_builds;
std::vector<std::string> vFileIdStrings;
std::vector<std::string> unrecognized_options_cfg;
std::vector<std::string> unrecognized_options_cli;
bpo::variables_map vm;
bpo::options_description options_cli_all("Options");
bpo::options_description options_cli_no_cfg;
bpo::options_description options_cli_no_cfg_hidden;
bpo::options_description options_cli_all_include_hidden;
bpo::options_description options_cli_cfg;
bpo::options_description options_cfg_only;
bpo::options_description options_cfg_all("Configuration");
@ -120,40 +129,40 @@ int main(int argc, char *argv[])
std::string sInstallerLanguage;
std::string sIncludeOptions;
std::string sExcludeOptions;
config.bReport = false;
Globals::globalConfig.bReport = false;
// Commandline options (no config file)
options_cli_no_cfg.add_options()
("help,h", "Print help message")
("version", "Print version information")
("login", bpo::value<bool>(&bLogin)->zero_tokens()->default_value(false), "Login")
("list", bpo::value<bool>(&config.bList)->zero_tokens()->default_value(false), "List games")
("list-details", bpo::value<bool>(&config.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info")
("download", bpo::value<bool>(&config.bDownload)->zero_tokens()->default_value(false), "Download")
("repair", bpo::value<bool>(&config.bRepair)->zero_tokens()->default_value(false), "Repair downloaded files\nUse --repair --download to redownload files when filesizes don't match (possibly different version). Redownload will rename the old file (appends .old to filename)")
("game", bpo::value<std::string>(&config.sGameRegex)->default_value(""), "Set regular expression filter\nfor download/list/repair (Perl syntax)\nAliases: \"all\", \"free\"\nAlias \"free\" doesn't work with cached details")
("create-xml", bpo::value<std::string>(&config.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation")
("update-check", bpo::value<bool>(&config.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications")
("check-orphans", bpo::value<std::string>(&config.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str())
("status", bpo::value<bool>(&config.bCheckStatus)->zero_tokens()->default_value(false), "Show status of files\n\nOutput format:\nstatuscode gamename filename filesize filehash\n\nStatus codes:\nOK - File is OK\nND - File is not downloaded\nMD5 - MD5 mismatch, different version\nFS - File size mismatch, incomplete download")
("save-config", bpo::value<bool>(&config.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings")
("reset-config", bpo::value<bool>(&config.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default")
("report", bpo::value<std::string>(&config.sReportFilePath)->implicit_value("lgogdownloader-report.log"), "Save report of downloaded/repaired files to specified file\nDefault filename: lgogdownloader-report.log")
("update-cache", bpo::value<bool>(&config.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache")
("list", bpo::value<bool>(&Globals::globalConfig.bList)->zero_tokens()->default_value(false), "List games")
("list-details", bpo::value<bool>(&Globals::globalConfig.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info")
("download", bpo::value<bool>(&Globals::globalConfig.bDownload)->zero_tokens()->default_value(false), "Download")
("repair", bpo::value<bool>(&Globals::globalConfig.bRepair)->zero_tokens()->default_value(false), "Repair downloaded files\nUse --repair --download to redownload files when filesizes don't match (possibly different version). Redownload will rename the old file (appends .old to filename)")
("game", bpo::value<std::string>(&Globals::globalConfig.sGameRegex)->default_value(""), "Set regular expression filter\nfor download/list/repair (Perl syntax)\nAliases: \"all\", \"free\"\nAlias \"free\" doesn't work with cached details")
("create-xml", bpo::value<std::string>(&Globals::globalConfig.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation")
("update-check", bpo::value<bool>(&Globals::globalConfig.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications")
("check-orphans", bpo::value<std::string>(&Globals::globalConfig.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str())
("status", bpo::value<bool>(&Globals::globalConfig.bCheckStatus)->zero_tokens()->default_value(false), "Show status of files\n\nOutput format:\nstatuscode gamename filename filesize filehash\n\nStatus codes:\nOK - File is OK\nND - File is not downloaded\nMD5 - MD5 mismatch, different version\nFS - File size mismatch, incomplete download")
("save-config", bpo::value<bool>(&Globals::globalConfig.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings")
("reset-config", bpo::value<bool>(&Globals::globalConfig.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default")
("report", bpo::value<std::string>(&Globals::globalConfig.sReportFilePath)->implicit_value("lgogdownloader-report.log"), "Save report of downloaded/repaired files to specified file\nDefault filename: lgogdownloader-report.log")
("update-cache", bpo::value<bool>(&Globals::globalConfig.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache")
("no-platform-detection", bpo::value<bool>(&bNoPlatformDetection)->zero_tokens()->default_value(false), "Don't try to detect supported platforms from game shelf.\nSkips the initial fast platform detection and detects the supported platforms from game details which is slower but more accurate.\nUseful in case platform identifier is missing for some games in the game shelf.\nUsing --platform with --list doesn't work with this option.")
("download-file", bpo::value<std::string>(&config.sFileIdString)->default_value(""), "Download files using fileid\n\nFormat:\n\"gamename/fileid\"\nor: \"gogdownloader://gamename/fileid\"\n\nMultiple files:\n\"gamename1/fileid1,gamename2/fileid2\"\nor: \"gogdownloader://gamename1/fileid1,gamename2/fileid2\"\n\nThis option ignores all subdir options. The files are downloaded to directory specified with --directory option.")
("output-file,o", bpo::value<std::string>(&config.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.")
("wishlist", bpo::value<bool>(&config.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist")
("login-api", bpo::value<bool>(&config.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)")
("login-website", bpo::value<bool>(&config.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)")
("cacert", bpo::value<std::string>(&config.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format")
("respect-umask", bpo::value<bool>(&config.bRespectUmask)->zero_tokens()->default_value(false), "Do not adjust permissions of sensitive files")
("download-file", bpo::value<std::string>(&Globals::globalConfig.sFileIdString)->default_value(""), "Download files using fileid\n\nFormat:\n\"gamename/fileid\"\nor: \"gogdownloader://gamename/fileid\"\n\nMultiple files:\n\"gamename1/fileid1,gamename2/fileid2\"\nor: \"gogdownloader://gamename1/fileid1,gamename2/fileid2\"\n\nThis option ignores all subdir options. The files are downloaded to directory specified with --directory option.")
("output-file,o", bpo::value<std::string>(&Globals::globalConfig.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.")
("wishlist", bpo::value<bool>(&Globals::globalConfig.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist")
("login-api", bpo::value<bool>(&Globals::globalConfig.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)")
("login-website", bpo::value<bool>(&Globals::globalConfig.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)")
("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")
;
// Commandline options (config file)
options_cli_cfg.add_options()
("directory", bpo::value<std::string>(&config.sDirectory)->default_value("."), "Set download directory")
("limit-rate", bpo::value<curl_off_t>(&config.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited")
("xml-directory", bpo::value<std::string>(&config.sXMLDirectory), "Set directory for GOG XML files")
("chunk-size", bpo::value<size_t>(&config.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML")
("directory", bpo::value<std::string>(&Globals::globalConfig.dirConf.sDirectory)->default_value("."), "Set download directory")
("limit-rate", bpo::value<curl_off_t>(&Globals::globalConfig.curlConf.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited")
("xml-directory", bpo::value<std::string>(&Globals::globalConfig.sXMLDirectory), "Set directory for GOG XML files")
("chunk-size", bpo::value<size_t>(&Globals::globalConfig.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML")
("platform", bpo::value<std::string>(&sInstallerPlatform)->default_value("w+l"), platform_text.c_str())
("language", bpo::value<std::string>(&sInstallerLanguage)->default_value("en"), language_text.c_str())
("no-remote-xml", bpo::value<bool>(&bNoRemoteXML)->zero_tokens()->default_value(false), "Don't use remote XML for repair")
@ -161,46 +170,52 @@ int main(int argc, char *argv[])
("no-color", bpo::value<bool>(&bNoColor)->zero_tokens()->default_value(false), "Don't use coloring in the progress bar or status messages")
("no-duplicate-handling", bpo::value<bool>(&bNoDuplicateHandler)->zero_tokens()->default_value(false), "Don't use duplicate handler for installers\nDuplicate installers from different languages are handled separately")
("no-subdirectories", bpo::value<bool>(&bNoSubDirectories)->zero_tokens()->default_value(false), "Don't create subdirectories for extras, patches and language packs")
("verbose", bpo::value<bool>(&config.bVerbose)->zero_tokens()->default_value(false), "Print lots of information")
("verbose", bpo::value<bool>(&Globals::globalConfig.bVerbose)->zero_tokens()->default_value(false), "Print lots of information")
("insecure", bpo::value<bool>(&bInsecure)->zero_tokens()->default_value(false), "Don't verify authenticity of SSL certificates")
("timeout", bpo::value<long int>(&config.iTimeout)->default_value(10), "Set timeout for connection\nMaximum time in seconds that connection phase is allowed to take")
("retries", bpo::value<int>(&config.iRetries)->default_value(3), "Set maximum number of retries on failed download")
("wait", bpo::value<int>(&config.iWait)->default_value(0), "Time to wait between requests (milliseconds)")
("cover-list", bpo::value<std::string>(&config.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list")
("subdir-installers", bpo::value<std::string>(&config.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-extras", bpo::value<std::string>(&config.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-patches", bpo::value<std::string>(&config.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str())
("subdir-language-packs", bpo::value<std::string>(&config.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str())
("subdir-dlc", bpo::value<std::string>(&config.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str())
("subdir-game", bpo::value<std::string>(&config.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str())
("use-cache", bpo::value<bool>(&config.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache"))
("cache-valid", bpo::value<int>(&config.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
("save-serials", bpo::value<bool>(&config.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading")
("ignore-dlc-count", bpo::value<std::string>(&config.sIgnoreDLCCountRegex)->implicit_value(".*"), "Set regular expression filter for games to ignore DLC count information\nIgnoring DLC count information helps in situations where the account page doesn't provide accurate information about DLCs")
("timeout", bpo::value<long int>(&Globals::globalConfig.curlConf.iTimeout)->default_value(10), "Set timeout for connection\nMaximum time in seconds that connection phase is allowed to take")
("retries", bpo::value<int>(&Globals::globalConfig.iRetries)->default_value(3), "Set maximum number of retries on failed download")
("wait", bpo::value<int>(&Globals::globalConfig.iWait)->default_value(0), "Time to wait between requests (milliseconds)")
("cover-list", bpo::value<std::string>(&Globals::globalConfig.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list")
("subdir-installers", bpo::value<std::string>(&Globals::globalConfig.dirConf.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-extras", bpo::value<std::string>(&Globals::globalConfig.dirConf.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str())
("subdir-patches", bpo::value<std::string>(&Globals::globalConfig.dirConf.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str())
("subdir-language-packs", bpo::value<std::string>(&Globals::globalConfig.dirConf.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str())
("subdir-dlc", bpo::value<std::string>(&Globals::globalConfig.dirConf.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str())
("subdir-game", bpo::value<std::string>(&Globals::globalConfig.dirConf.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str())
("use-cache", bpo::value<bool>(&Globals::globalConfig.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache"))
("cache-valid", bpo::value<int>(&Globals::globalConfig.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
("save-serials", bpo::value<bool>(&Globals::globalConfig.dlConf.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading")
("ignore-dlc-count", bpo::value<std::string>(&Globals::globalConfig.sIgnoreDLCCountRegex)->implicit_value(".*"), "Set regular expression filter for games to ignore DLC count information\nIgnoring DLC count information helps in situations where the account page doesn't provide accurate information about DLCs")
("include", bpo::value<std::string>(&sIncludeOptions)->default_value("all"), ("Select what to download/list/repair\n" + include_options_text).c_str())
("exclude", bpo::value<std::string>(&sExcludeOptions)->default_value("covers"), ("Select what not to download/list/repair\n" + include_options_text).c_str())
("automatic-xml-creation", bpo::value<bool>(&config.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed")
("save-changelogs", bpo::value<bool>(&config.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading")
("threads", bpo::value<unsigned int>(&config.iThreads)->default_value(4), "Number of download threads")
("dlc-list", bpo::value<std::string>(&config.sGameHasDLCList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/game_has_dlc.txt"), "Set URL for list of games that have DLC")
("automatic-xml-creation", bpo::value<bool>(&Globals::globalConfig.dlConf.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed")
("save-changelogs", bpo::value<bool>(&Globals::globalConfig.dlConf.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading")
("threads", bpo::value<unsigned int>(&Globals::globalConfig.iThreads)->default_value(4), "Number of download threads")
("dlc-list", bpo::value<std::string>(&Globals::globalConfig.sGameHasDLCList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/game_has_dlc.txt"), "Set URL for list of games that have DLC")
;
// Options read from config file
options_cfg_only.add_options()
("token", bpo::value<std::string>(&config.sToken)->default_value(""), "oauth token")
("secret", bpo::value<std::string>(&config.sSecret)->default_value(""), "oauth secret")
("token", bpo::value<std::string>(&Globals::globalConfig.apiConf.sToken)->default_value(""), "oauth token")
("secret", bpo::value<std::string>(&Globals::globalConfig.apiConf.sSecret)->default_value(""), "oauth secret")
;
options_cli_no_cfg_hidden.add_options()
("galaxy-install", bpo::value<std::string>(&galaxy_product_id_install)->default_value(""), "Install game using product id")
("galaxy-show-builds", bpo::value<std::string>(&galaxy_product_id_show_builds)->default_value(""), "Show game builds using product id")
;
options_cli_all.add(options_cli_no_cfg).add(options_cli_cfg);
options_cfg_all.add(options_cfg_only).add(options_cli_cfg);
options_cli_all_include_hidden.add(options_cli_all).add(options_cli_no_cfg_hidden);
bpo::parsed_options parsed = bpo::parse_command_line(argc, argv, options_cli_all);
bpo::parsed_options parsed = bpo::parse_command_line(argc, argv, options_cli_all_include_hidden);
bpo::store(parsed, vm);
unrecognized_options_cli = bpo::collect_unrecognized(parsed.options, bpo::include_positional);
bpo::notify(vm);
if (vm.count("help"))
{
std::cout << config.sVersionString << std::endl
std::cout << Globals::globalConfig.sVersionString << std::endl
<< options_cli_all << std::endl;
return 0;
}
@ -212,7 +227,7 @@ int main(int argc, char *argv[])
}
// Create lgogdownloader directories
boost::filesystem::path path = config.sXMLDirectory;
boost::filesystem::path path = Globals::globalConfig.sXMLDirectory;
if (!boost::filesystem::exists(path))
{
if (!boost::filesystem::create_directories(path))
@ -222,7 +237,7 @@ int main(int argc, char *argv[])
}
}
path = config.sConfigDirectory;
path = Globals::globalConfig.sConfigDirectory;
if (!boost::filesystem::exists(path))
{
if (!boost::filesystem::create_directories(path))
@ -232,7 +247,7 @@ int main(int argc, char *argv[])
}
}
path = config.sCacheDirectory;
path = Globals::globalConfig.sCacheDirectory;
if (!boost::filesystem::exists(path))
{
if (!boost::filesystem::create_directories(path))
@ -242,12 +257,12 @@ int main(int argc, char *argv[])
}
}
if (boost::filesystem::exists(config.sConfigFilePath))
if (boost::filesystem::exists(Globals::globalConfig.sConfigFilePath))
{
std::ifstream ifs(config.sConfigFilePath.c_str());
std::ifstream ifs(Globals::globalConfig.sConfigFilePath.c_str());
if (!ifs)
{
std::cerr << "Could not open config file: " << config.sConfigFilePath << std::endl;
std::cerr << "Could not open config file: " << Globals::globalConfig.sConfigFilePath << std::endl;
return 1;
}
else
@ -259,12 +274,12 @@ int main(int argc, char *argv[])
unrecognized_options_cfg = bpo::collect_unrecognized(parsed.options, bpo::include_positional);
}
}
if (boost::filesystem::exists(config.sBlacklistFilePath))
if (boost::filesystem::exists(Globals::globalConfig.sBlacklistFilePath))
{
std::ifstream ifs(config.sBlacklistFilePath.c_str());
std::ifstream ifs(Globals::globalConfig.sBlacklistFilePath.c_str());
if (!ifs)
{
std::cerr << "Could not open blacklist file: " << config.sBlacklistFilePath << std::endl;
std::cerr << "Could not open blacklist file: " << Globals::globalConfig.sBlacklistFilePath << std::endl;
return 1;
}
else
@ -276,16 +291,16 @@ int main(int argc, char *argv[])
std::getline(ifs, line);
lines.push_back(std::move(line));
}
config.blacklist.initialize(lines);
Globals::globalConfig.blacklist.initialize(lines);
}
}
if (boost::filesystem::exists(config.sIgnorelistFilePath))
if (boost::filesystem::exists(Globals::globalConfig.sIgnorelistFilePath))
{
std::ifstream ifs(config.sIgnorelistFilePath.c_str());
std::ifstream ifs(Globals::globalConfig.sIgnorelistFilePath.c_str());
if (!ifs)
{
std::cerr << "Could not open ignorelist file: " << config.sIgnorelistFilePath << std::endl;
std::cerr << "Could not open ignorelist file: " << Globals::globalConfig.sIgnorelistFilePath << std::endl;
return 1;
}
else
@ -297,18 +312,18 @@ int main(int argc, char *argv[])
std::getline(ifs, line);
lines.push_back(std::move(line));
}
config.ignorelist.initialize(lines);
Globals::globalConfig.ignorelist.initialize(lines);
}
}
if (config.sIgnoreDLCCountRegex.empty())
if (Globals::globalConfig.sIgnoreDLCCountRegex.empty())
{
if (boost::filesystem::exists(config.sGameHasDLCListFilePath))
if (boost::filesystem::exists(Globals::globalConfig.sGameHasDLCListFilePath))
{
std::ifstream ifs(config.sGameHasDLCListFilePath.c_str());
std::ifstream ifs(Globals::globalConfig.sGameHasDLCListFilePath.c_str());
if (!ifs)
{
std::cerr << "Could not open list of games that have dlc: " << config.sGameHasDLCListFilePath << std::endl;
std::cerr << "Could not open list of games that have dlc: " << Globals::globalConfig.sGameHasDLCListFilePath << std::endl;
return 1;
}
else
@ -320,55 +335,56 @@ int main(int argc, char *argv[])
std::getline(ifs, line);
lines.push_back(std::move(line));
}
config.gamehasdlc.initialize(lines);
Globals::globalConfig.gamehasdlc.initialize(lines);
}
}
}
if (vm.count("chunk-size"))
config.iChunkSize <<= 20; // Convert chunk size from bytes to megabytes
Globals::globalConfig.iChunkSize <<= 20; // Convert chunk size from bytes to megabytes
if (vm.count("limit-rate"))
config.iDownloadRate <<= 10; // Convert download rate from bytes to kilobytes
Globals::globalConfig.curlConf.iDownloadRate <<= 10; // Convert download rate from bytes to kilobytes
if (vm.count("check-orphans"))
if (config.sOrphanRegex.empty())
config.sOrphanRegex = orphans_regex_default;
if (Globals::globalConfig.sOrphanRegex.empty())
Globals::globalConfig.sOrphanRegex = orphans_regex_default;
if (vm.count("report"))
config.bReport = true;
Globals::globalConfig.bReport = true;
if (config.iWait > 0)
config.iWait *= 1000;
if (Globals::globalConfig.iWait > 0)
Globals::globalConfig.iWait *= 1000;
if (config.iThreads < 1)
if (Globals::globalConfig.iThreads < 1)
{
config.iThreads = 1;
set_vm_value(vm, "threads", config.iThreads);
Globals::globalConfig.iThreads = 1;
set_vm_value(vm, "threads", Globals::globalConfig.iThreads);
}
config.bVerifyPeer = !bInsecure;
config.bColor = !bNoColor;
config.bUnicode = !bNoUnicode;
config.bDuplicateHandler = !bNoDuplicateHandler;
config.bRemoteXML = !bNoRemoteXML;
config.bSubDirectories = !bNoSubDirectories;
config.bPlatformDetection = !bNoPlatformDetection;
Globals::globalConfig.curlConf.bVerbose = Globals::globalConfig.bVerbose;
Globals::globalConfig.curlConf.bVerifyPeer = !bInsecure;
Globals::globalConfig.bColor = !bNoColor;
Globals::globalConfig.bUnicode = !bNoUnicode;
Globals::globalConfig.dlConf.bDuplicateHandler = !bNoDuplicateHandler;
Globals::globalConfig.dlConf.bRemoteXML = !bNoRemoteXML;
Globals::globalConfig.dirConf.bSubDirectories = !bNoSubDirectories;
Globals::globalConfig.bPlatformDetection = !bNoPlatformDetection;
for (auto i = unrecognized_options_cli.begin(); i != unrecognized_options_cli.end(); ++i)
if (i->compare(0, GlobalConstants::PROTOCOL_PREFIX.length(), GlobalConstants::PROTOCOL_PREFIX) == 0)
config.sFileIdString = *i;
Globals::globalConfig.sFileIdString = *i;
if (!config.sFileIdString.empty())
if (!Globals::globalConfig.sFileIdString.empty())
{
if (config.sFileIdString.compare(0, GlobalConstants::PROTOCOL_PREFIX.length(), GlobalConstants::PROTOCOL_PREFIX) == 0)
if (Globals::globalConfig.sFileIdString.compare(0, GlobalConstants::PROTOCOL_PREFIX.length(), GlobalConstants::PROTOCOL_PREFIX) == 0)
{
config.sFileIdString.replace(0, GlobalConstants::PROTOCOL_PREFIX.length(), "");
Globals::globalConfig.sFileIdString.replace(0, GlobalConstants::PROTOCOL_PREFIX.length(), "");
}
vFileIdStrings = Util::tokenize(config.sFileIdString, ",");
vFileIdStrings = Util::tokenize(Globals::globalConfig.sFileIdString, ",");
}
if (!config.sOutputFilename.empty() && vFileIdStrings.size() > 1)
if (!Globals::globalConfig.sOutputFilename.empty() && vFileIdStrings.size() > 1)
{
std::cerr << "Cannot specify an output file name when downloading multiple files." << std::endl;
return 1;
@ -376,15 +392,15 @@ int main(int argc, char *argv[])
if (bLogin)
{
config.bLoginAPI = true;
config.bLoginHTTP = true;
Globals::globalConfig.bLoginAPI = true;
Globals::globalConfig.bLoginHTTP = true;
}
if (config.sXMLFile == "automatic")
config.bAutomaticXMLCreation = true;
if (Globals::globalConfig.sXMLFile == "automatic")
Globals::globalConfig.dlConf.bAutomaticXMLCreation = true;
Util::parseOptionString(sInstallerLanguage, config.vLanguagePriority, config.iInstallerLanguage, GlobalConstants::LANGUAGES);
Util::parseOptionString(sInstallerPlatform, config.vPlatformPriority, config.iInstallerPlatform, GlobalConstants::PLATFORMS);
Util::parseOptionString(sInstallerLanguage, Globals::globalConfig.dlConf.vLanguagePriority, Globals::globalConfig.dlConf.iInstallerLanguage, GlobalConstants::LANGUAGES);
Util::parseOptionString(sInstallerPlatform, Globals::globalConfig.dlConf.vPlatformPriority, Globals::globalConfig.dlConf.iInstallerPlatform, GlobalConstants::PLATFORMS);
unsigned int include_value = 0;
unsigned int exclude_value = 0;
@ -398,16 +414,16 @@ int main(int argc, char *argv[])
{
exclude_value |= Util::getOptionValue(*it, INCLUDE_OPTIONS);
}
config.iInclude = include_value & ~exclude_value;
Globals::globalConfig.dlConf.iInclude = include_value & ~exclude_value;
// Assign values
// TODO: Use config.iInclude in Downloader class directly and get rid of this value assignment
config.bCover = (config.iInclude & OPTION_COVERS);
config.bInstallers = (config.iInclude & OPTION_INSTALLERS);
config.bExtras = (config.iInclude & OPTION_EXTRAS);
config.bPatches = (config.iInclude & OPTION_PATCHES);
config.bLanguagePacks = (config.iInclude & OPTION_LANGPACKS);
config.bDLC = (config.iInclude & OPTION_DLCS);
Globals::globalConfig.dlConf.bCover = (Globals::globalConfig.dlConf.iInclude & OPTION_COVERS);
Globals::globalConfig.dlConf.bInstallers = (Globals::globalConfig.dlConf.iInclude & OPTION_INSTALLERS);
Globals::globalConfig.dlConf.bExtras = (Globals::globalConfig.dlConf.iInclude & OPTION_EXTRAS);
Globals::globalConfig.dlConf.bPatches = (Globals::globalConfig.dlConf.iInclude & OPTION_PATCHES);
Globals::globalConfig.dlConf.bLanguagePacks = (Globals::globalConfig.dlConf.iInclude & OPTION_LANGPACKS);
Globals::globalConfig.dlConf.bDLC = (Globals::globalConfig.dlConf.iInclude & OPTION_DLCS);
}
catch (std::exception& e)
{
@ -420,55 +436,55 @@ int main(int argc, char *argv[])
return 1;
}
if (config.iInstallerPlatform < GlobalConstants::PLATFORMS[0].id || config.iInstallerPlatform > platform_all)
if (Globals::globalConfig.dlConf.iInstallerPlatform < GlobalConstants::PLATFORMS[0].id || Globals::globalConfig.dlConf.iInstallerPlatform > platform_all)
{
std::cerr << "Invalid value for --platform" << std::endl;
return 1;
}
if (config.iInstallerLanguage < GlobalConstants::LANGUAGES[0].id || config.iInstallerLanguage > language_all)
if (Globals::globalConfig.dlConf.iInstallerLanguage < GlobalConstants::LANGUAGES[0].id || Globals::globalConfig.dlConf.iInstallerLanguage > language_all)
{
std::cerr << "Invalid value for --language" << std::endl;
return 1;
}
if (!config.sXMLDirectory.empty())
if (!Globals::globalConfig.sXMLDirectory.empty())
{
// Make sure that xml directory doesn't have trailing slash
if (config.sXMLDirectory.at(config.sXMLDirectory.length()-1)=='/')
config.sXMLDirectory.assign(config.sXMLDirectory.begin(),config.sXMLDirectory.end()-1);
if (Globals::globalConfig.sXMLDirectory.at(Globals::globalConfig.sXMLDirectory.length()-1)=='/')
Globals::globalConfig.sXMLDirectory.assign(Globals::globalConfig.sXMLDirectory.begin(), Globals::globalConfig.sXMLDirectory.end()-1);
}
// Create GOG XML for a file
if (!config.sXMLFile.empty() && (config.sXMLFile != "automatic"))
if (!Globals::globalConfig.sXMLFile.empty() && (Globals::globalConfig.sXMLFile != "automatic"))
{
Util::createXML(config.sXMLFile, config.iChunkSize, config.sXMLDirectory);
Util::createXML(Globals::globalConfig.sXMLFile, Globals::globalConfig.iChunkSize, Globals::globalConfig.sXMLDirectory);
return 0;
}
// Make sure that directory has trailing slash
if (!config.sDirectory.empty())
if (!Globals::globalConfig.dirConf.sDirectory.empty())
{
if (config.sDirectory.at(config.sDirectory.length()-1)!='/')
config.sDirectory += "/";
if (Globals::globalConfig.dirConf.sDirectory.at(Globals::globalConfig.dirConf.sDirectory.length()-1)!='/')
Globals::globalConfig.dirConf.sDirectory += "/";
}
else
{
config.sDirectory = "./"; // Directory wasn't specified, use current directory
Globals::globalConfig.dirConf.sDirectory = "./"; // Directory wasn't specified, use current directory
}
// CA certificate bundle
if (config.sCACertPath.empty())
if (Globals::globalConfig.curlConf.sCACertPath.empty())
{
// Use CURL_CA_BUNDLE environment variable for CA certificate path if it is set
char *ca_bundle = getenv("CURL_CA_BUNDLE");
if (ca_bundle)
config.sCACertPath = (std::string)ca_bundle;
Globals::globalConfig.curlConf.sCACertPath = (std::string)ca_bundle;
}
if (!unrecognized_options_cfg.empty() && (!config.bSaveConfig || !config.bResetConfig))
if (!unrecognized_options_cfg.empty() && (!Globals::globalConfig.bSaveConfig || !Globals::globalConfig.bResetConfig))
{
std::cerr << "Unrecognized options in " << config.sConfigFilePath << std::endl;
std::cerr << "Unrecognized options in " << Globals::globalConfig.sConfigFilePath << std::endl;
for (unsigned int i = 0; i < unrecognized_options_cfg.size(); i+=2)
{
std::cerr << unrecognized_options_cfg[i] << " = " << unrecognized_options_cfg[i+1] << std::endl;
@ -480,25 +496,25 @@ int main(int argc, char *argv[])
ssl_thread_setup();
curl_global_init(CURL_GLOBAL_ALL);
if (config.bLoginAPI)
if (Globals::globalConfig.bLoginAPI)
{
config.sToken = "";
config.sSecret = "";
Globals::globalConfig.apiConf.sToken = "";
Globals::globalConfig.apiConf.sSecret = "";
}
Downloader downloader(config);
Downloader downloader;
int iLoginTries = 0;
bool bLoginOK = false;
// Login because --login, --login-api or --login-website was used
if (config.bLoginAPI || config.bLoginHTTP)
if (Globals::globalConfig.bLoginAPI || Globals::globalConfig.bLoginHTTP)
bLoginOK = downloader.login();
bool bIsLoggedin = downloader.isLoggedIn();
// Login because we are not logged in
while (iLoginTries++ < config.iRetries && !bIsLoggedin)
while (iLoginTries++ < Globals::globalConfig.iRetries && !bIsLoggedin)
{
bLoginOK = downloader.login();
if (bLoginOK)
@ -516,23 +532,24 @@ int main(int argc, char *argv[])
}
// Make sure that config file and cookie file are only readable/writable by owner
if (!config.bRespectUmask)
if (!Globals::globalConfig.bRespectUmask)
{
Util::setFilePermissions(config.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
Util::setFilePermissions(config.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
Util::setFilePermissions(Globals::globalConfig.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
Util::setFilePermissions(Globals::globalConfig.curlConf.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
Util::setFilePermissions(Globals::galaxyConf.getFilepath(), boost::filesystem::owner_read | boost::filesystem::owner_write);
}
if (config.bSaveConfig || bLoginOK)
if (Globals::globalConfig.bSaveConfig || bLoginOK)
{
if (bLoginOK)
{
set_vm_value(vm, "token", downloader.config.sToken);
set_vm_value(vm, "secret", downloader.config.sSecret);
set_vm_value(vm, "token", Globals::globalConfig.apiConf.sToken);
set_vm_value(vm, "secret", Globals::globalConfig.apiConf.sSecret);
}
std::ofstream ofs(config.sConfigFilePath.c_str());
std::ofstream ofs(Globals::globalConfig.sConfigFilePath.c_str());
if (ofs)
{
std::cerr << "Saving config: " << config.sConfigFilePath << std::endl;
std::cerr << "Saving config: " << Globals::globalConfig.sConfigFilePath << std::endl;
for (bpo::variables_map::iterator it = vm.begin(); it != vm.end(); ++it)
{
std::string option = it->first;
@ -577,9 +594,9 @@ int main(int argc, char *argv[])
}
}
ofs.close();
if (!config.bRespectUmask)
Util::setFilePermissions(config.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
if (config.bSaveConfig)
if (!Globals::globalConfig.bRespectUmask)
Util::setFilePermissions(Globals::globalConfig.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
if (Globals::globalConfig.bSaveConfig)
{
curl_global_cleanup();
ssl_thread_cleanup();
@ -588,25 +605,25 @@ int main(int argc, char *argv[])
}
else
{
std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
std::cerr << "Failed to create config: " << Globals::globalConfig.sConfigFilePath << std::endl;
curl_global_cleanup();
ssl_thread_cleanup();
return 1;
}
}
else if (config.bResetConfig)
else if (Globals::globalConfig.bResetConfig)
{
std::ofstream ofs(config.sConfigFilePath.c_str());
std::ofstream ofs(Globals::globalConfig.sConfigFilePath.c_str());
if (ofs)
{
if (!config.sToken.empty() && !config.sSecret.empty())
if (!Globals::globalConfig.apiConf.sToken.empty() && !Globals::globalConfig.apiConf.sSecret.empty())
{
ofs << "token = " << config.sToken << std::endl;
ofs << "secret = " << config.sSecret << std::endl;
ofs << "token = " << Globals::globalConfig.apiConf.sToken << std::endl;
ofs << "secret = " << Globals::globalConfig.apiConf.sSecret << std::endl;
}
ofs.close();
if (!config.bRespectUmask)
Util::setFilePermissions(config.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
if (!Globals::globalConfig.bRespectUmask)
Util::setFilePermissions(Globals::globalConfig.sConfigFilePath, boost::filesystem::owner_read | boost::filesystem::owner_write);
curl_global_cleanup();
ssl_thread_cleanup();
@ -614,7 +631,7 @@ int main(int argc, char *argv[])
}
else
{
std::cerr << "Failed to create config: " << config.sConfigFilePath << std::endl;
std::cerr << "Failed to create config: " << Globals::globalConfig.sConfigFilePath << std::endl;
curl_global_cleanup();
ssl_thread_cleanup();
return 1;
@ -631,41 +648,63 @@ int main(int argc, char *argv[])
int res = 0;
if (config.bShowWishlist)
if (Globals::globalConfig.bShowWishlist)
downloader.showWishlist();
else if (config.bUpdateCache)
else if (Globals::globalConfig.bUpdateCache)
downloader.updateCache();
else if (config.bUpdateCheck) // Update check has priority over download and list
else if (Globals::globalConfig.bUpdateCheck) // Update check has priority over download and list
downloader.updateCheck();
else if (!vFileIdStrings.empty())
{
for (std::vector<std::string>::iterator it = vFileIdStrings.begin(); it != vFileIdStrings.end(); it++)
{
res |= downloader.downloadFileWithId(*it, config.sOutputFilename) ? 1 : 0;
res |= downloader.downloadFileWithId(*it, Globals::globalConfig.sOutputFilename) ? 1 : 0;
}
}
else if (config.bRepair) // Repair file
else if (Globals::globalConfig.bRepair) // Repair file
downloader.repair();
else if (config.bDownload) // Download games
else if (Globals::globalConfig.bDownload) // Download games
downloader.download();
else if (config.bListDetails || config.bList) // Detailed list of games/extras
else if (Globals::globalConfig.bListDetails || Globals::globalConfig.bList) // Detailed list of games/extras
res = downloader.listGames();
else if (!config.sOrphanRegex.empty()) // Check for orphaned files if regex for orphans is set
else if (!Globals::globalConfig.sOrphanRegex.empty()) // Check for orphaned files if regex for orphans is set
downloader.checkOrphans();
else if (config.bCheckStatus)
else if (Globals::globalConfig.bCheckStatus)
downloader.checkStatus();
else if (!galaxy_product_id_show_builds.empty())
{
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_show_builds, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.galaxyShowBuilds(product_id, build_index);
}
else if (!galaxy_product_id_install.empty())
{
int build_index = -1;
std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_install, "/");
std::string product_id = tokens[0];
if (tokens.size() == 2)
{
build_index = std::stoi(tokens[1]);
}
downloader.galaxyInstallGame(product_id, build_index);
}
else
{
if (!(config.bLoginAPI || config.bLoginHTTP))
if (!(Globals::globalConfig.bLoginAPI || Globals::globalConfig.bLoginHTTP))
{
// Show help message
std::cerr << config.sVersionString << std::endl
std::cerr << Globals::globalConfig.sVersionString << std::endl
<< options_cli_all << std::endl;
}
}
// Orphan check was called at the same time as download. Perform it after download has finished
if (!config.sOrphanRegex.empty() && config.bDownload)
if (!Globals::globalConfig.sOrphanRegex.empty() && Globals::globalConfig.bDownload)
downloader.checkOrphans();
curl_global_cleanup();

View File

@ -11,6 +11,7 @@
#include <cstdlib>
#include <sstream>
#include <json/json.h>
#include <unistd.h>
#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40900
# define _regex_namespace_ std
@ -150,6 +151,8 @@ int API::login(const std::string& email, const std::string& password)
return res;
}
usleep(500); // Wait to avoid "429 Too Many Requests"
// Authorize temporary token and get verifier
url = this->config.oauth_authorize_temp_token + "?username=" + oauth_url_escape(email.c_str()) + "&password=" + oauth_url_escape(password.c_str());
url = oauth_sign_url2(url.c_str(), NULL, OA_HMAC, NULL, CONSUMER_KEY.c_str(), CONSUMER_SECRET.c_str(), token.c_str(), secret.c_str());
@ -167,6 +170,8 @@ int API::login(const std::string& email, const std::string& password)
return res;
}
usleep(500); // Wait to avoid "429 Too Many Requests"
// Get final token and secret
url = this->config.oauth_get_token + "?oauth_verifier=" + verifier;
url = oauth_sign_url2(url.c_str(), NULL, OA_HMAC, NULL, CONSUMER_KEY.c_str(), CONSUMER_SECRET.c_str(), token.c_str(), secret.c_str());

File diff suppressed because it is too large Load Diff

253
src/galaxyapi.cpp Normal file
View File

@ -0,0 +1,253 @@
/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
#include "galaxyapi.h"
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
GalaxyConfig Globals::galaxyConf;
size_t galaxyAPI::writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp) {
std::ostringstream *stream = (std::ostringstream*)userp;
std::streamsize count = (std::streamsize) size * nmemb;
stream->write(ptr, count);
return count;
}
galaxyAPI::galaxyAPI(CurlConfig& conf)
{
this->curlConf = conf;
curlhandle = curl_easy_init();
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curlhandle, CURLOPT_PROGRESSDATA, this);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, curlConf.iTimeout);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, curlConf.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, curlConf.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, curlConf.bVerifyPeer);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, curlConf.bVerbose);
curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, curlConf.iDownloadRate);
// Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
if (!curlConf.sCACertPath.empty())
curl_easy_setopt(curlhandle, CURLOPT_CAINFO, curlConf.sCACertPath.c_str());
}
galaxyAPI::~galaxyAPI()
{
curl_easy_cleanup(curlhandle);
}
/* Initialize the API
returns 0 if failed
returns 1 if successful
*/
int galaxyAPI::init()
{
int res = 0;
if (!this->isTokenExpired())
{
res = 1;
}
else
res = 0;
return res;
}
bool galaxyAPI::refreshLogin()
{
bool res = false;
std::string refresh_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+ "&client_secret=" + Globals::galaxyConf.getClientSecret()
+ "&grant_type=refresh_token"
+ "&refresh_token=" + Globals::galaxyConf.getRefreshToken();
std::string json = this->getResponse(refresh_url);
if (!json.empty())
{
Json::Value token_json;
Json::Reader *jsonparser = new Json::Reader;
if (jsonparser->parse(json, token_json))
{
Globals::galaxyConf.setJSON(token_json);
res = true;
}
delete jsonparser;
}
return res;
}
bool galaxyAPI::isTokenExpired()
{
int res = false;
if (Globals::galaxyConf.isExpired())
res = true;
return res;
}
std::string galaxyAPI::getResponse(const std::string& url, const bool& zlib_decompress)
{
std::ostringstream memory;
struct curl_slist *header = NULL;
std::string access_token;
if (!Globals::galaxyConf.isExpired())
access_token = Globals::galaxyConf.getAccessToken();
if (!access_token.empty())
{
std::string bearer = "Authorization: Bearer " + access_token;
header = curl_slist_append(header, bearer.c_str());
}
curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, header);
curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, galaxyAPI::writeMemoryCallback);
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
curl_easy_perform(curlhandle);
std::string response = memory.str();
memory.str(std::string());
curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, NULL);
curl_slist_free_all(header);
if (zlib_decompress)
{
std::string response_decompressed;
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
in.push(boost::iostreams::zlib_decompressor(GlobalConstants::ZLIB_WINDOW_SIZE));
in.push(boost::make_iterator_range(response));
boost::iostreams::copy(in, boost::iostreams::back_inserter(response_decompressed));
response = response_decompressed;
}
return response;
}
Json::Value galaxyAPI::getProductBuilds(const std::string& product_id, const std::string& platform, const std::string& generation)
{
Json::Value json;
std::string url = "https://content-system.gog.com/products/" + product_id + "/os/" + platform + "/builds?generation=" + generation;
std::string response = this->getResponse(url);
Json::Reader *jsonparser = new Json::Reader;
jsonparser->parse(response, json);
delete jsonparser;
return json;
}
Json::Value galaxyAPI::getManifestV1(const std::string& product_id, const std::string& build_id, const std::string& manifest_id, const std::string& platform)
{
Json::Value json;
std::string url = "https://cdn.gog.com/content-system/v1/manifests/" + product_id + "/" + platform + "/" + build_id + "/" + manifest_id + ".json";
std::string response = this->getResponse(url);
Json::Reader *jsonparser = new Json::Reader;
jsonparser->parse(response, json);
delete jsonparser;
return json;
}
Json::Value galaxyAPI::getManifestV2(std::string manifest_hash)
{
Json::Value json;
if (!manifest_hash.empty() && manifest_hash.find("/") == std::string::npos)
manifest_hash = this->hashToGalaxyPath(manifest_hash);
std::string url = "https://cdn.gog.com/content-system/v2/meta/" + manifest_hash;
std::string response = this->getResponse(url, true);
Json::Reader *jsonparser = new Json::Reader;
jsonparser->parse(response, json);
delete jsonparser;
return json;
}
Json::Value galaxyAPI::getSecureLink(const std::string& product_id, const std::string& path)
{
Json::Value json;
std::string url = "https://content-system.gog.com/products/" + product_id + "/secure_link?generation=2&path=" + path + "&_version=2";
std::string response = this->getResponse(url);
Json::Reader *jsonparser = new Json::Reader;
jsonparser->parse(response, json);
delete jsonparser;
return json;
}
std::string galaxyAPI::hashToGalaxyPath(const std::string& hash)
{
std::string galaxy_path = hash;
if (galaxy_path.find("/") == std::string::npos)
galaxy_path.assign(hash.begin(), hash.begin()+2).append("/").append(hash.begin()+2, hash.begin()+4).append("/").append(hash);
return galaxy_path;
}
std::vector<galaxyDepotItem> galaxyAPI::getDepotItemsVector(const std::string& hash)
{
Json::Value json = this->getManifestV2(hash);
std::vector<galaxyDepotItem> items;
for (unsigned int i = 0; i < json["depot"]["items"].size(); ++i)
{
if (!json["depot"]["items"][i]["chunks"].empty())
{
galaxyDepotItem item;
item.totalSizeCompressed = 0;
item.totalSizeUncompressed = 0;
item.path = json["depot"]["items"][i]["path"].asString();
while (Util::replaceString(item.path, "\\", "/"));
for (unsigned int j = 0; j < json["depot"]["items"][i]["chunks"].size(); ++j)
{
galaxyDepotItemChunk chunk;
chunk.md5_compressed = json["depot"]["items"][i]["chunks"][j]["compressedMd5"].asString();
chunk.md5_uncompressed = json["depot"]["items"][i]["chunks"][j]["md5"].asString();
chunk.size_compressed = json["depot"]["items"][i]["chunks"][j]["compressedSize"].asLargestUInt();
chunk.size_uncompressed = json["depot"]["items"][i]["chunks"][j]["size"].asLargestUInt();
item.totalSizeCompressed += chunk.size_compressed;
item.totalSizeUncompressed += chunk.size_uncompressed;
item.chunks.push_back(chunk);
}
if (json["depot"]["items"][i].isMember("md5"))
item.md5 = json["depot"]["items"][i]["md5"].asString();
else if (json["depot"]["items"][i]["chunks"].size() == 1)
item.md5 = json["depot"]["items"][i]["chunks"][0]["md5"].asString();
items.push_back(item);
}
}
return items;
}

View File

@ -18,7 +18,7 @@ gameDetails::~gameDetails()
void gameDetails::filterWithPriorities(const gameSpecificConfig& config)
{
if (config.vPlatformPriority.empty() && config.vLanguagePriority.empty())
if (config.dlConf.vPlatformPriority.empty() && config.dlConf.vLanguagePriority.empty())
return;
filterListWithPriorities(installers, config);
@ -40,19 +40,19 @@ void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const ga
for (std::vector<gameFile>::iterator fileDetails = list.begin(); fileDetails != list.end(); fileDetails++)
{
fileDetails->score = 0;
if (!config.vPlatformPriority.empty())
if (!config.dlConf.vPlatformPriority.empty())
{
for (size_t i = 0; i != config.vPlatformPriority.size(); i++)
if (fileDetails->platform & config.vPlatformPriority[i])
for (size_t i = 0; i != config.dlConf.vPlatformPriority.size(); i++)
if (fileDetails->platform & config.dlConf.vPlatformPriority[i])
{
fileDetails->score += i;
break;
}
}
if (!config.vLanguagePriority.empty())
if (!config.dlConf.vLanguagePriority.empty())
{
for (size_t i = 0; i != config.vLanguagePriority.size(); i++)
if (fileDetails->language & config.vLanguagePriority[i])
for (size_t i = 0; i != config.dlConf.vLanguagePriority.size(); i++)
if (fileDetails->language & config.dlConf.vLanguagePriority[i])
{
fileDetails->score += i;
break;
@ -71,7 +71,7 @@ void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const ga
}
}
void gameDetails::makeFilepaths(const gameSpecificDirectoryConfig& config)
void gameDetails::makeFilepaths(const DirectoryConfig& config)
{
std::string filepath;
std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
@ -147,6 +147,7 @@ Json::Value gameDetails::getDetailsAsJson()
Json::Value json;
json["gamename"] = this->gamename;
json["product_id"] = this->product_id;
json["title"] = this->title;
json["icon"] = this->icon;
json["serials"] = this->serials;

View File

@ -241,31 +241,31 @@ int Util::getGameSpecificConfig(std::string gamename, gameSpecificConfig* conf,
if (root.isMember("language"))
{
if (root["language"].isInt())
conf->iInstallerLanguage = root["language"].asUInt();
conf->dlConf.iInstallerLanguage = root["language"].asUInt();
else
{
Util::parseOptionString(root["language"].asString(), conf->vLanguagePriority, conf->iInstallerLanguage, GlobalConstants::LANGUAGES);
Util::parseOptionString(root["language"].asString(), conf->dlConf.vLanguagePriority, conf->dlConf.iInstallerLanguage, GlobalConstants::LANGUAGES);
}
res++;
}
if (root.isMember("platform"))
{
if (root["platform"].isInt())
conf->iInstallerPlatform = root["platform"].asUInt();
conf->dlConf.iInstallerPlatform = root["platform"].asUInt();
else
{
Util::parseOptionString(root["platform"].asString(), conf->vPlatformPriority, conf->iInstallerPlatform, GlobalConstants::PLATFORMS);
Util::parseOptionString(root["platform"].asString(), conf->dlConf.vPlatformPriority, conf->dlConf.iInstallerPlatform, GlobalConstants::PLATFORMS);
}
res++;
}
if (root.isMember("dlc"))
{
conf->bDLC = root["dlc"].asBool();
conf->dlConf.bDLC = root["dlc"].asBool();
res++;
}
if (root.isMember("ignore-dlc-count"))
{
conf->bIgnoreDLCCount = root["ignore-dlc-count"].asBool();
conf->dlConf.bIgnoreDLCCount = root["ignore-dlc-count"].asBool();
res++;
}
if (root.isMember("subdirectories"))

View File

@ -10,30 +10,29 @@
#include <htmlcxx/html/ParserDom.h>
#include <boost/algorithm/string/case_conv.hpp>
Website::Website(Config &conf)
Website::Website()
{
this->config = conf;
this->retries = 0;
curlhandle = curl_easy_init();
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, Globals::globalConfig.sVersionString.c_str());
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, Globals::globalConfig.curlConf.iTimeout);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, config.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, config.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, config.bVerbose);
curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, config.iDownloadRate);
curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, Globals::globalConfig.curlConf.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, Globals::globalConfig.curlConf.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, Globals::globalConfig.curlConf.bVerifyPeer);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, Globals::globalConfig.curlConf.bVerbose);
curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, Globals::globalConfig.curlConf.iDownloadRate);
// Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
if (!config.sCACertPath.empty())
curl_easy_setopt(curlhandle, CURLOPT_CAINFO, config.sCACertPath.c_str());
if (!Globals::globalConfig.curlConf.sCACertPath.empty())
curl_easy_setopt(curlhandle, CURLOPT_CAINFO, Globals::globalConfig.curlConf.sCACertPath.c_str());
}
Website::~Website()
@ -61,13 +60,13 @@ std::string Website::getResponse(const std::string& url)
CURLcode result;
do
{
if (config.iWait > 0)
usleep(config.iWait); // Delay the request by specified time
if (Globals::globalConfig.iWait > 0)
usleep(Globals::globalConfig.iWait); // Delay the request by specified time
result = curl_easy_perform(curlhandle);
response = memory.str();
memory.str(std::string());
}
while ((result != CURLE_OK) && response.empty() && (this->retries++ < config.iRetries));
while ((result != CURLE_OK) && response.empty() && (this->retries++ < Globals::globalConfig.iRetries));
this->retries = 0; // reset retries counter
if (result != CURLE_OK)
@ -198,31 +197,31 @@ std::vector<gameItem> Website::getGames()
platform |= GlobalConstants::PLATFORM_LINUX;
// Skip if platform doesn't match
if (config.bPlatformDetection && !(platform & config.iInstallerPlatform))
if (Globals::globalConfig.bPlatformDetection && !(platform & Globals::globalConfig.dlConf.iInstallerPlatform))
continue;
// Filter the game list
if (!config.sGameRegex.empty())
if (!Globals::globalConfig.sGameRegex.empty())
{
// GameRegex filter aliases
if (config.sGameRegex == "all")
config.sGameRegex = ".*";
if (Globals::globalConfig.sGameRegex == "all")
Globals::globalConfig.sGameRegex = ".*";
boost::regex expression(config.sGameRegex);
boost::regex expression(Globals::globalConfig.sGameRegex);
boost::match_results<std::string::const_iterator> what;
if (!boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
continue;
}
if (config.bDLC)
if (Globals::globalConfig.dlConf.bDLC)
{
int dlcCount = product["dlcCount"].asInt();
bool bDownloadDLCInfo = (dlcCount != 0);
if (!bDownloadDLCInfo && !config.sIgnoreDLCCountRegex.empty())
if (!bDownloadDLCInfo && !Globals::globalConfig.sIgnoreDLCCountRegex.empty())
{
boost::regex expression(config.sIgnoreDLCCountRegex);
boost::regex expression(Globals::globalConfig.sIgnoreDLCCountRegex);
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
{
@ -230,25 +229,25 @@ std::vector<gameItem> Website::getGames()
}
}
if (!bDownloadDLCInfo && !config.gamehasdlc.empty())
if (!bDownloadDLCInfo && !Globals::globalConfig.gamehasdlc.empty())
{
if (config.gamehasdlc.isBlacklisted(game.name))
if (Globals::globalConfig.gamehasdlc.isBlacklisted(game.name))
bDownloadDLCInfo = true;
}
// Check game specific config
if (!config.bUpdateCache) // Disable game specific config files for cache update
if (!Globals::globalConfig.bUpdateCache) // Disable game specific config files for cache update
{
gameSpecificConfig conf;
conf.bIgnoreDLCCount = bDownloadDLCInfo;
conf.dlConf.bIgnoreDLCCount = bDownloadDLCInfo;
Util::getGameSpecificConfig(game.name, &conf);
bDownloadDLCInfo = conf.bIgnoreDLCCount;
bDownloadDLCInfo = conf.dlConf.bIgnoreDLCCount;
}
if (bDownloadDLCInfo && !config.sGameRegex.empty())
if (bDownloadDLCInfo && !Globals::globalConfig.sGameRegex.empty())
{
// don't download unnecessary info if user is only interested in a subset of his account
boost::regex expression(config.sGameRegex);
boost::regex expression(Globals::globalConfig.sGameRegex);
boost::match_results<std::string::const_iterator> what;
if (!boost::regex_search(game.name, what, expression))
{
@ -317,96 +316,40 @@ int Website::Login(const std::string& email, const std::string& password)
std::string postdata;
std::ostringstream memory;
std::string token;
std::string tagname_username;
std::string tagname_password;
std::string tagname_login;
std::string tagname_username = "login[username]";
std::string tagname_password = "login[password]";
std::string tagname_login = "login[login]";
std::string tagname_token;
std::string auth_url = "https://auth.gog.com/auth?client_id=" + Globals::galaxyConf.getClientId() + "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size()) + "&response_type=code&layout=default&brand=gog";
std::string auth_code;
// Get login token
std::string html = this->getResponse("https://www.gog.com/");
htmlcxx::HTML::ParserDom parser;
tree<htmlcxx::HTML::Node> dom = parser.parseTree(html);
tree<htmlcxx::HTML::Node>::iterator it = dom.begin();
tree<htmlcxx::HTML::Node>::iterator end = dom.end();
// Find auth_url
bool bFoundAuthUrl = false;
for (; it != end; ++it)
std::string login_form_html = this->getResponse(auth_url);
#ifdef DEBUG
std::cerr << "DEBUG INFO (Website::Login)" << std::endl;
std::cerr << login_form_html << std::endl;
#endif
if (login_form_html.find("google.com/recaptcha") != std::string::npos)
{
if (it->tagName()=="script")
{
std::string auth_url;
for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
{
tree<htmlcxx::HTML::Node>::iterator script_it = dom.child(it, i);
if (!script_it->isTag() && !script_it->isComment())
{
if (script_it->text().find("GalaxyAccounts") != std::string::npos)
{
boost::match_results<std::string::const_iterator> what;
boost::regex expression(".*'(https://auth.gog.com/.*?)'.*");
boost::regex_match(script_it->text(), what, expression);
auth_url = what[1];
break;
}
}
}
if (!auth_url.empty())
{ // Found auth_url, get the necessary info for login
bFoundAuthUrl = true;
std::string login_form_html = this->getResponse(auth_url);
#ifdef DEBUG
std::cerr << "DEBUG INFO (Website::Login)" << std::endl;
std::cerr << login_form_html << std::endl;
#endif
if (login_form_html.find("google.com/recaptcha") != std::string::npos)
{
std::cout << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
<< "Login with browser and export cookies to \"" << config.sCookiePath << "\"" << std::endl;
return res = 0;
}
tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
for (; login_it != login_it_end; ++login_it)
{
if (login_it->tagName()=="input")
{
login_it->parseAttributes();
std::string id_login = login_it->attribute("id").second;
if (id_login == "login_username")
{
tagname_username = login_it->attribute("name").second;
}
else if (id_login == "login_password")
{
tagname_password = login_it->attribute("name").second;
}
else if (id_login == "login__token")
{
token = login_it->attribute("value").second; // login token
tagname_token = login_it->attribute("name").second;
}
}
else if (login_it->tagName()=="button")
{
login_it->parseAttributes();
std::string id_login = login_it->attribute("id").second;
if (id_login == "login_login")
{
tagname_login = login_it->attribute("name").second;
}
}
}
break;
}
}
std::cout << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
<< "Login with browser and export cookies to \"" << Globals::globalConfig.curlConf.sCookiePath << "\"" << std::endl;
return res = 0;
}
if (!bFoundAuthUrl)
htmlcxx::HTML::ParserDom parser;
tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
for (; login_it != login_it_end; ++login_it)
{
std::cout << "Failed to find url for login form" << std::endl;
if (login_it->tagName()=="input")
{
login_it->parseAttributes();
if (login_it->attribute("id").second == "login__token")
{
token = login_it->attribute("value").second; // login token
tagname_token = login_it->attribute("name").second;
}
}
}
if (token.empty())
@ -448,7 +391,14 @@ int Website::Login(const std::string& email, const std::string& password)
// Handle two step authorization
if (std::string(redirect_url).find("two_step") != std::string::npos)
{
std::string security_code, tagname_two_step_send, tagname_two_step_auth_letter_1, tagname_two_step_auth_letter_2, tagname_two_step_auth_letter_3, tagname_two_step_auth_letter_4, tagname_two_step_token, token_two_step;
std::string security_code;
std::string tagname_two_step_send = "second_step_authentication[send]";
std::string tagname_two_step_auth_letter_1 = "second_step_authentication[token][letter_1]";
std::string tagname_two_step_auth_letter_2 = "second_step_authentication[token][letter_2]";
std::string tagname_two_step_auth_letter_3 = "second_step_authentication[token][letter_3]";
std::string tagname_two_step_auth_letter_4 = "second_step_authentication[token][letter_4]";
std::string tagname_two_step_token;
std::string token_two_step;
std::string two_step_html = this->getResponse(redirect_url);
redirect_url = NULL;
@ -460,39 +410,14 @@ int Website::Login(const std::string& email, const std::string& password)
if (two_step_it->tagName()=="input")
{
two_step_it->parseAttributes();
std::string id_two_step = two_step_it->attribute("id").second;
if (id_two_step == "second_step_authentication_token_letter_1")
{
tagname_two_step_auth_letter_1 = two_step_it->attribute("name").second;
}
else if (id_two_step == "second_step_authentication_token_letter_2")
{
tagname_two_step_auth_letter_2 = two_step_it->attribute("name").second;
}
else if (id_two_step == "second_step_authentication_token_letter_3")
{
tagname_two_step_auth_letter_3 = two_step_it->attribute("name").second;
}
else if (id_two_step == "second_step_authentication_token_letter_4")
{
tagname_two_step_auth_letter_4 = two_step_it->attribute("name").second;
}
else if (id_two_step == "second_step_authentication__token")
if (two_step_it->attribute("id").second == "second_step_authentication__token")
{
token_two_step = two_step_it->attribute("value").second; // two step token
tagname_two_step_token = two_step_it->attribute("name").second;
}
}
else if (two_step_it->tagName()=="button")
{
two_step_it->parseAttributes();
std::string id_two_step = two_step_it->attribute("id").second;
if (id_two_step == "second_step_authentication_send")
{
tagname_two_step_send = two_step_it->attribute("name").second;
}
}
}
std::cerr << "Security code: ";
std::getline(std::cin,security_code);
if (security_code.size() != 4)
@ -500,6 +425,7 @@ int Website::Login(const std::string& email, const std::string& password)
std::cerr << "Security code must be 4 characters long" << std::endl;
exit(1);
}
postdata = (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_1.c_str(), tagname_two_step_auth_letter_1.size()) + "=" + security_code[0]
+ "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_2.c_str(), tagname_two_step_auth_letter_2.size()) + "=" + security_code[1]
+ "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_3.c_str(), tagname_two_step_auth_letter_3.size()) + "=" + security_code[2]
@ -523,6 +449,31 @@ int Website::Login(const std::string& email, const std::string& password)
curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
}
if (!std::string(redirect_url).empty())
{
long response_code;
do
{
curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
result = curl_easy_perform(curlhandle);
memory.str(std::string());
result = curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
if ((response_code / 100) == 3)
curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
std::string redir_url = std::string(redirect_url);
boost::regex re(".*code=(.*?)([\?&].*|$)", boost::regex_constants::icase);
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(redir_url, what, re))
{
auth_code = what[1];
if (!auth_code.empty())
break;
}
} while (result == CURLE_OK && (response_code / 100) == 3);
}
curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
@ -544,11 +495,41 @@ int Website::Login(const std::string& email, const std::string& password)
res = 1; // Login was successful
}
if (auth_code.empty())
res = 0;
if (res == 1)
{
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, "FLUSH"); // Write all known cookies to the file specified by CURLOPT_COOKIEJAR
std::string token_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+ "&client_secret=" + Globals::galaxyConf.getClientSecret()
+ "&grant_type=authorization_code&code=" + auth_code
+ "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size());
std::string json = this->getResponse(token_url);
if (json.empty())
res = 0;
else
{
Json::Value token_json;
Json::Reader *jsonparser = new Json::Reader;
if (jsonparser->parse(json, token_json))
{
Globals::galaxyConf.setJSON(token_json);
res = 1;
}
else
{
std::cerr << "Failed to parse json" << std::endl << json << std::endl;
std::cerr << jsonparser->getFormattedErrorMessages() << std::endl;
res = 0;
}
delete jsonparser;
}
}
if (res == 1)
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, "FLUSH"); // Write all known cookies to the file specified by CURLOPT_COOKIEJAR
return res;
}
@ -677,7 +658,7 @@ std::vector<wishlistItem> Website::getWishlistItems()
item.platform |= GlobalConstants::PLATFORM_LINUX;
// Skip if platform doesn't match
if (config.bPlatformDetection && !(item.platform & config.iInstallerPlatform))
if (Globals::globalConfig.bPlatformDetection && !(item.platform & Globals::globalConfig.dlConf.iInstallerPlatform))
continue;
}
@ -741,8 +722,3 @@ std::vector<wishlistItem> Website::getWishlistItems()
return wishlistItems;
}
void Website::setConfig(Config &conf)
{
this->config = conf;
}