From f2e8dde934dd4f99d5b797b3f42d1607c05ef148 Mon Sep 17 00:00:00 2001 From: Sude Date: Fri, 17 Feb 2017 11:14:28 +0200 Subject: [PATCH] Some initial Galaxy code Add some code for initial Galaxy support. Rewrite and move code around in preparation for GOG Galaxy API support. --- CMakeLists.txt | 3 +- include/api.h | 1 + include/config.h | 289 ++++++++++--- include/downloader.h | 17 +- include/galaxyapi.h | 61 +++ include/gamedetails.h | 4 +- include/gamefile.h | 1 + include/globalconstants.h | 3 +- include/globals.h | 21 + include/util.h | 25 +- include/website.h | 4 +- main.cpp | 373 +++++++++-------- src/api.cpp | 5 + src/downloader.cpp | 840 +++++++++++++++++++++++++------------- src/galaxyapi.cpp | 253 ++++++++++++ src/gamedetails.cpp | 17 +- src/util.cpp | 12 +- src/website.cpp | 272 ++++++------ 18 files changed, 1495 insertions(+), 706 deletions(-) create mode 100644 include/galaxyapi.h create mode 100644 include/globals.h create mode 100644 src/galaxyapi.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 992cc90..37946db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/api.h b/include/api.h index 93cfb47..d50e668 100644 --- a/include/api.h +++ b/include/api.h @@ -9,6 +9,7 @@ #include "globalconstants.h" #include "gamedetails.h" +#include "globals.h" #include #include diff --git a/include/config.h b/include/config.h index 2881f3e..ef66e29 100644 --- a/include/config.h +++ b/include/config.h @@ -9,93 +9,262 @@ #include #include +#include +#include +#include #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 vPlatformPriority; + std::vector 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 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 lock(m); + return this->token_json["access_token"].asString(); + } + + std::string getRefreshToken() + { + std::unique_lock lock(m); + return this->token_json["refresh_token"].asString(); + } + + Json::Value getJSON() + { + std::unique_lock lock(m); + return this->token_json; + } + + void setJSON(Json::Value json) + { + std::unique_lock 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 lock(m); + this->filepath = path; + } + + std::string getFilepath() + { + std::unique_lock lock(m); + return this->filepath; + } + + std::string getClientId() + { + std::unique_lock lock(m); + return this->client_id; + } + + std::string getClientSecret() + { + std::unique_lock lock(m); + return this->client_secret; + } + + std::string getRedirectUri() + { + std::unique_lock lock(m); + return this->redirect_uri; + } + + GalaxyConfig() = default; + + GalaxyConfig(const GalaxyConfig& other) + { + std::lock_guard 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 lock1(m, std::defer_lock); + std::unique_lock 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 vLanguagePriority; - std::vector 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__ diff --git a/include/downloader.h b/include/downloader.h index cb0fa6b..3d91a5c 100644 --- a/include/downloader.h +++ b/include/downloader.h @@ -26,6 +26,9 @@ #include "progressbar.h" #include "website.h" #include "threadsafequeue.h" +#include "galaxyapi.h" +#include "globals.h" + #include #include #include @@ -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 > 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 gameItems; std::vector games; std::string coverXML; diff --git a/include/galaxyapi.h b/include/galaxyapi.h new file mode 100644 index 0000000..9787324 --- /dev/null +++ b/include/galaxyapi.h @@ -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 +#include +#include +#include +#include + +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 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 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 diff --git a/include/gamedetails.h b/include/gamedetails.h index 7009364..6ab0ad2 100644 --- a/include/gamedetails.h +++ b/include/gamedetails.h @@ -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 languagepacks; std::vector 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(); diff --git a/include/gamefile.h b/include/gamefile.h index 90d6004..11f2744 100644 --- a/include/gamefile.h +++ b/include/gamefile.h @@ -8,6 +8,7 @@ #define GAMEFILE_H #include "globalconstants.h" +#include "globals.h" #include #include diff --git a/include/globalconstants.h b/include/globalconstants.h index 52b9dc1..ab522bd 100644 --- a/include/globalconstants.h +++ b/include/globalconstants.h @@ -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://"; diff --git a/include/globals.h b/include/globals.h new file mode 100644 index 0000000..9824d8a --- /dev/null +++ b/include/globals.h @@ -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 +#include + +namespace Globals +{ + extern GalaxyConfig galaxyConf; + extern Config globalConfig; +} + +#endif // GLOBALS_H_INCLUDED + diff --git a/include/util.h b/include/util.h index b30f12c..b31ea1a 100644 --- a/include/util.h +++ b/include/util.h @@ -8,6 +8,8 @@ #define UTIL_H #include "globalconstants.h" +#include "config.h" +#include "globals.h" #include #include @@ -21,29 +23,6 @@ #include #include -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 vLanguagePriority; - std::vector vPlatformPriority; -}; - struct gameItem { std::string name; diff --git a/include/website.h b/include/website.h index 0664784..a25fdbc 100644 --- a/include/website.h +++ b/include/website.h @@ -9,6 +9,7 @@ #include "config.h" #include "util.h" +#include "globals.h" #include #include #include @@ -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 getFreeGames(); std::vector getWishlistItems(); bool IsLoggedIn(); - void setConfig(Config &conf); virtual ~Website(); protected: private: diff --git a/main.cpp b/main.cpp index 23b13c5..2c1b6cc 100644 --- a/main.cpp +++ b/main.cpp @@ -9,12 +9,15 @@ #include "util.h" #include "globalconstants.h" #include "ssl_thread_setup.h" +#include "galaxyapi.h" +#include "globals.h" #include #include #include namespace bpo = boost::program_options; +Config Globals::globalConfig; template void set_vm_value(std::map& 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 vFileIdStrings; std::vector unrecognized_options_cfg; std::vector 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(&bLogin)->zero_tokens()->default_value(false), "Login") - ("list", bpo::value(&config.bList)->zero_tokens()->default_value(false), "List games") - ("list-details", bpo::value(&config.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info") - ("download", bpo::value(&config.bDownload)->zero_tokens()->default_value(false), "Download") - ("repair", bpo::value(&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(&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(&config.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation") - ("update-check", bpo::value(&config.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications") - ("check-orphans", bpo::value(&config.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str()) - ("status", bpo::value(&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(&config.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings") - ("reset-config", bpo::value(&config.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default") - ("report", bpo::value(&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(&config.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache") + ("list", bpo::value(&Globals::globalConfig.bList)->zero_tokens()->default_value(false), "List games") + ("list-details", bpo::value(&Globals::globalConfig.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info") + ("download", bpo::value(&Globals::globalConfig.bDownload)->zero_tokens()->default_value(false), "Download") + ("repair", bpo::value(&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(&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(&Globals::globalConfig.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation") + ("update-check", bpo::value(&Globals::globalConfig.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications") + ("check-orphans", bpo::value(&Globals::globalConfig.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str()) + ("status", bpo::value(&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(&Globals::globalConfig.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings") + ("reset-config", bpo::value(&Globals::globalConfig.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default") + ("report", bpo::value(&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(&Globals::globalConfig.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache") ("no-platform-detection", bpo::value(&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(&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(&config.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.") - ("wishlist", bpo::value(&config.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist") - ("login-api", bpo::value(&config.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)") - ("login-website", bpo::value(&config.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)") - ("cacert", bpo::value(&config.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format") - ("respect-umask", bpo::value(&config.bRespectUmask)->zero_tokens()->default_value(false), "Do not adjust permissions of sensitive files") + ("download-file", bpo::value(&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(&Globals::globalConfig.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.") + ("wishlist", bpo::value(&Globals::globalConfig.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist") + ("login-api", bpo::value(&Globals::globalConfig.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)") + ("login-website", bpo::value(&Globals::globalConfig.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)") + ("cacert", bpo::value(&Globals::globalConfig.curlConf.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format") + ("respect-umask", bpo::value(&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(&config.sDirectory)->default_value("."), "Set download directory") - ("limit-rate", bpo::value(&config.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited") - ("xml-directory", bpo::value(&config.sXMLDirectory), "Set directory for GOG XML files") - ("chunk-size", bpo::value(&config.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML") + ("directory", bpo::value(&Globals::globalConfig.dirConf.sDirectory)->default_value("."), "Set download directory") + ("limit-rate", bpo::value(&Globals::globalConfig.curlConf.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited") + ("xml-directory", bpo::value(&Globals::globalConfig.sXMLDirectory), "Set directory for GOG XML files") + ("chunk-size", bpo::value(&Globals::globalConfig.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML") ("platform", bpo::value(&sInstallerPlatform)->default_value("w+l"), platform_text.c_str()) ("language", bpo::value(&sInstallerLanguage)->default_value("en"), language_text.c_str()) ("no-remote-xml", bpo::value(&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(&bNoColor)->zero_tokens()->default_value(false), "Don't use coloring in the progress bar or status messages") ("no-duplicate-handling", bpo::value(&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(&bNoSubDirectories)->zero_tokens()->default_value(false), "Don't create subdirectories for extras, patches and language packs") - ("verbose", bpo::value(&config.bVerbose)->zero_tokens()->default_value(false), "Print lots of information") + ("verbose", bpo::value(&Globals::globalConfig.bVerbose)->zero_tokens()->default_value(false), "Print lots of information") ("insecure", bpo::value(&bInsecure)->zero_tokens()->default_value(false), "Don't verify authenticity of SSL certificates") - ("timeout", bpo::value(&config.iTimeout)->default_value(10), "Set timeout for connection\nMaximum time in seconds that connection phase is allowed to take") - ("retries", bpo::value(&config.iRetries)->default_value(3), "Set maximum number of retries on failed download") - ("wait", bpo::value(&config.iWait)->default_value(0), "Time to wait between requests (milliseconds)") - ("cover-list", bpo::value(&config.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list") - ("subdir-installers", bpo::value(&config.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str()) - ("subdir-extras", bpo::value(&config.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str()) - ("subdir-patches", bpo::value(&config.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str()) - ("subdir-language-packs", bpo::value(&config.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str()) - ("subdir-dlc", bpo::value(&config.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str()) - ("subdir-game", bpo::value(&config.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str()) - ("use-cache", bpo::value(&config.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache")) - ("cache-valid", bpo::value(&config.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)")) - ("save-serials", bpo::value(&config.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading") - ("ignore-dlc-count", bpo::value(&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(&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(&Globals::globalConfig.iRetries)->default_value(3), "Set maximum number of retries on failed download") + ("wait", bpo::value(&Globals::globalConfig.iWait)->default_value(0), "Time to wait between requests (milliseconds)") + ("cover-list", bpo::value(&Globals::globalConfig.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list") + ("subdir-installers", bpo::value(&Globals::globalConfig.dirConf.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str()) + ("subdir-extras", bpo::value(&Globals::globalConfig.dirConf.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str()) + ("subdir-patches", bpo::value(&Globals::globalConfig.dirConf.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str()) + ("subdir-language-packs", bpo::value(&Globals::globalConfig.dirConf.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str()) + ("subdir-dlc", bpo::value(&Globals::globalConfig.dirConf.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str()) + ("subdir-game", bpo::value(&Globals::globalConfig.dirConf.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str()) + ("use-cache", bpo::value(&Globals::globalConfig.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache")) + ("cache-valid", bpo::value(&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(&Globals::globalConfig.dlConf.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading") + ("ignore-dlc-count", bpo::value(&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(&sIncludeOptions)->default_value("all"), ("Select what to download/list/repair\n" + include_options_text).c_str()) ("exclude", bpo::value(&sExcludeOptions)->default_value("covers"), ("Select what not to download/list/repair\n" + include_options_text).c_str()) - ("automatic-xml-creation", bpo::value(&config.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed") - ("save-changelogs", bpo::value(&config.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading") - ("threads", bpo::value(&config.iThreads)->default_value(4), "Number of download threads") - ("dlc-list", bpo::value(&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(&Globals::globalConfig.dlConf.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed") + ("save-changelogs", bpo::value(&Globals::globalConfig.dlConf.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading") + ("threads", bpo::value(&Globals::globalConfig.iThreads)->default_value(4), "Number of download threads") + ("dlc-list", bpo::value(&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(&config.sToken)->default_value(""), "oauth token") - ("secret", bpo::value(&config.sSecret)->default_value(""), "oauth secret") + ("token", bpo::value(&Globals::globalConfig.apiConf.sToken)->default_value(""), "oauth token") + ("secret", bpo::value(&Globals::globalConfig.apiConf.sSecret)->default_value(""), "oauth secret") + ; + + options_cli_no_cfg_hidden.add_options() + ("galaxy-install", bpo::value(&galaxy_product_id_install)->default_value(""), "Install game using product id") + ("galaxy-show-builds", bpo::value(&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::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 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 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(); diff --git a/src/api.cpp b/src/api.cpp index f465ce1..6ff2480 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #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()); diff --git a/src/downloader.cpp b/src/downloader.cpp index d0019d0..0dfbfbe 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -6,7 +6,7 @@ #include "downloader.h" #include "util.h" -#include "globalconstants.h" +#include "globals.h" #include "downloadinfo.h" #include "message.h" @@ -30,6 +30,11 @@ #include #include +#include +#include +#include +#include + namespace bptime = boost::posix_time; std::vector vDownloadInfo; @@ -40,12 +45,36 @@ ThreadSafeQueue gameItemQueue; ThreadSafeQueue gameDetailsQueue; std::mutex mtx_create_directories; // Mutex for creating directories in Downloader::processDownloadQueue -Downloader::Downloader(Config &conf) +static curl_off_t WriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp) { - this->config = conf; - if (config.bLoginHTTP && boost::filesystem::exists(config.sCookiePath)) - if (!boost::filesystem::remove(config.sCookiePath)) - std::cerr << "Failed to delete " << config.sCookiePath << std::endl; + curl_off_t realsize = size * nmemb; + struct ChunkMemoryStruct *mem = (struct ChunkMemoryStruct *)userp; + + mem->memory = (char *) realloc(mem->memory, mem->size + realsize + 1); + if(mem->memory == NULL) + { + std::cout << "Not enough memory (realloc returned NULL)" << std::endl; + return 0; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +Downloader::Downloader() +{ + if (Globals::globalConfig.bLoginHTTP) + { + if (boost::filesystem::exists(Globals::globalConfig.curlConf.sCookiePath)) + if (!boost::filesystem::remove(Globals::globalConfig.curlConf.sCookiePath)) + std::cerr << "Failed to delete " << Globals::globalConfig.curlConf.sCookiePath << std::endl; + if (boost::filesystem::exists(Globals::galaxyConf.getFilepath())) + if (!boost::filesystem::remove(Globals::galaxyConf.getFilepath())) + std::cerr << "Failed to delete " << Globals::galaxyConf.getFilepath() << std::endl; + } this->resume_position = 0; this->retries = 0; @@ -53,16 +82,16 @@ Downloader::Downloader(Config &conf) // Initialize curl and set curl options 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, 0); 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_SSL_VERIFYPEER, config.bVerifyPeer); - curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, config.bVerbose); + 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_WRITEFUNCTION, Downloader::writeData); curl_easy_setopt(curlhandle, CURLOPT_READFUNCTION, Downloader::readData); - curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, config.iDownloadRate); + curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, Globals::globalConfig.curlConf.iDownloadRate); curl_easy_setopt(curlhandle, CURLOPT_XFERINFOFUNCTION, Downloader::progressCallback); curl_easy_setopt(curlhandle, CURLOPT_XFERINFODATA, this); @@ -70,37 +99,68 @@ Downloader::Downloader(Config &conf) 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()); // Create new GOG website handle - gogWebsite = new Website(config); + gogWebsite = new Website(); // Create new API handle and set curl options for the API - gogAPI = new API(config.sToken, config.sSecret); - gogAPI->curlSetOpt(CURLOPT_VERBOSE, config.bVerbose); - gogAPI->curlSetOpt(CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer); - gogAPI->curlSetOpt(CURLOPT_CONNECTTIMEOUT, config.iTimeout); - if (!config.sCACertPath.empty()) - gogAPI->curlSetOpt(CURLOPT_CAINFO, config.sCACertPath.c_str()); + gogAPI = new API(Globals::globalConfig.apiConf.sToken, Globals::globalConfig.apiConf.sSecret); + gogAPI->curlSetOpt(CURLOPT_VERBOSE, Globals::globalConfig.curlConf.bVerbose); + gogAPI->curlSetOpt(CURLOPT_SSL_VERIFYPEER, Globals::globalConfig.curlConf.bVerifyPeer); + gogAPI->curlSetOpt(CURLOPT_CONNECTTIMEOUT, Globals::globalConfig.curlConf.iTimeout); + if (!Globals::globalConfig.curlConf.sCACertPath.empty()) + gogAPI->curlSetOpt(CURLOPT_CAINFO, Globals::globalConfig.curlConf.sCACertPath.c_str()); gogAPI->init(); - progressbar = new ProgressBar(config.bUnicode, config.bColor); + progressbar = new ProgressBar(Globals::globalConfig.bUnicode, Globals::globalConfig.bColor); + + if (boost::filesystem::exists(Globals::galaxyConf.getFilepath())) + { + std::ifstream ifs(Globals::galaxyConf.getFilepath(), std::ifstream::binary); + Json::Value json; + Json::Reader *jsonparser = new Json::Reader; + if (jsonparser->parse(ifs, json)) + { + if (!json.isMember("expires_at")) + { + std::time_t last_modified = boost::filesystem::last_write_time(Globals::galaxyConf.getFilepath()); + json["expires_at"] = json["expires_in"].asUInt() + last_modified; + } + + Globals::galaxyConf.setJSON(json); + } + else + { + std::cerr << "Failed to parse " << Globals::galaxyConf.getFilepath() << std::endl; + std::cerr << jsonparser->getFormattedErrorMessages() << std::endl; + } + delete jsonparser; + + if (ifs) + ifs.close(); + } + + gogGalaxy = new galaxyAPI(Globals::globalConfig.curlConf); } Downloader::~Downloader() { - if (config.bReport) + if (Globals::globalConfig.bReport) if (this->report_ofs) this->report_ofs.close(); delete progressbar; + delete gogGalaxy; delete gogAPI; delete gogWebsite; curl_easy_cleanup(curlhandle); // Make sure that cookie file is only readable/writable by owner - if (!config.bRespectUmask) - Util::setFilePermissions(config.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write); + if (!Globals::globalConfig.bRespectUmask) + { + Util::setFilePermissions(Globals::globalConfig.curlConf.sCookiePath, boost::filesystem::owner_read | boost::filesystem::owner_write); + } } /* Login check @@ -110,16 +170,16 @@ Downloader::~Downloader() bool Downloader::isLoggedIn() { bool bIsLoggedIn = false; - config.bLoginAPI = false; - config.bLoginHTTP = false; + Globals::globalConfig.bLoginAPI = false; + Globals::globalConfig.bLoginHTTP = false; bool bWebsiteIsLoggedIn = gogWebsite->IsLoggedIn(); if (!bWebsiteIsLoggedIn) - config.bLoginHTTP = true; + Globals::globalConfig.bLoginHTTP = true; bool bIsLoggedInAPI = gogAPI->isLoggedIn(); if (!bIsLoggedInAPI) - config.bLoginAPI = true; + Globals::globalConfig.bLoginAPI = true; if (bIsLoggedInAPI && bWebsiteIsLoggedIn) bIsLoggedIn = true; @@ -133,24 +193,45 @@ bool Downloader::isLoggedIn() */ int Downloader::init() { - if (!config.sGameHasDLCList.empty()) + if (!Globals::globalConfig.sGameHasDLCList.empty()) { - if (config.gamehasdlc.empty()) + if (Globals::globalConfig.gamehasdlc.empty()) { - std::string game_has_dlc_list = this->getResponse(config.sGameHasDLCList); + std::string game_has_dlc_list = this->getResponse(Globals::globalConfig.sGameHasDLCList); if (!game_has_dlc_list.empty()) - config.gamehasdlc.initialize(Util::tokenize(game_has_dlc_list, "\n")); + Globals::globalConfig.gamehasdlc.initialize(Util::tokenize(game_has_dlc_list, "\n")); } } - gogWebsite->setConfig(config); // Update config for website handle - if (config.bReport && (config.bDownload || config.bRepair)) + if (!gogGalaxy->init()) { - this->report_ofs.open(config.sReportFilePath); + if (gogGalaxy->refreshLogin()) + { + this->saveGalaxyJSON(); + } + else + return 0; + } + + if (!Globals::galaxyConf.getJSON().empty()) + { + if (Globals::galaxyConf.isExpired()) + { + // Access token has expired, refresh + if (gogGalaxy->refreshLogin()) + { + this->saveGalaxyJSON(); + } + } + } + + if (Globals::globalConfig.bReport && (Globals::globalConfig.bDownload || Globals::globalConfig.bRepair)) + { + this->report_ofs.open(Globals::globalConfig.sReportFilePath); if (!this->report_ofs) { - config.bReport = false; - std::cerr << "Failed to create " << config.sReportFilePath << std::endl; + Globals::globalConfig.bReport = false; + std::cerr << "Failed to create " << Globals::globalConfig.sReportFilePath << std::endl; return 0; } } @@ -191,13 +272,14 @@ int Downloader::login() else { // Login to website - if (config.bLoginHTTP) + if (Globals::globalConfig.bLoginHTTP) { // Delete old cookies - if (boost::filesystem::exists(config.sCookiePath)) - if (!boost::filesystem::remove(config.sCookiePath)) - std::cerr << "Failed to delete " << config.sCookiePath << std::endl; + if (boost::filesystem::exists(Globals::globalConfig.curlConf.sCookiePath)) + if (!boost::filesystem::remove(Globals::globalConfig.curlConf.sCookiePath)) + std::cerr << "Failed to delete " << Globals::globalConfig.curlConf.sCookiePath << std::endl; + //if (!gogWebsite->Login(email, password)) if (!gogWebsite->Login(email, password)) { std::cerr << "HTTP: Login failed" << std::endl; @@ -206,12 +288,18 @@ int Downloader::login() else { std::cerr << "HTTP: Login successful" << std::endl; - if (!config.bLoginAPI) + + if (!Globals::galaxyConf.getJSON().empty()) + { + this->saveGalaxyJSON(); + } + + if (!Globals::globalConfig.bLoginAPI) return 1; } } // Login to API - if (config.bLoginAPI) + if (Globals::globalConfig.bLoginAPI) { if (!gogAPI->login(email, password)) { @@ -221,8 +309,8 @@ int Downloader::login() else { std::cerr << "API: Login successful" << std::endl; - config.sToken = gogAPI->getToken(); - config.sSecret = gogAPI->getSecret(); + Globals::globalConfig.apiConf.sToken = gogAPI->getToken(); + Globals::globalConfig.apiConf.sSecret = gogAPI->getSecret(); return 1; } } @@ -238,14 +326,13 @@ void Downloader::updateCheck() if (gogAPI->user.notifications_games) { - config.sGameRegex = ".*"; // Always check all games - gogWebsite->setConfig(config); // Make sure that website handle has updated config - if (config.bList || config.bListDetails || config.bDownload) + Globals::globalConfig.sGameRegex = ".*"; // Always check all games + if (Globals::globalConfig.bList || Globals::globalConfig.bListDetails || Globals::globalConfig.bDownload) { - if (config.bList) - config.bListDetails = true; // Always list details + if (Globals::globalConfig.bList) + Globals::globalConfig.bListDetails = true; // Always list details this->getGameList(); - if (config.bDownload) + if (Globals::globalConfig.bDownload) this->download(); else this->listGames(); @@ -255,7 +342,7 @@ void Downloader::updateCheck() void Downloader::getGameList() { - if (config.sGameRegex == "free") + if (Globals::globalConfig.sGameRegex == "free") { gameItems = gogWebsite->getFreeGames(); } @@ -272,22 +359,14 @@ void Downloader::getGameList() int Downloader::getGameDetails() { // Set default game specific directory options to values from config - gameSpecificDirectoryConfig dirConfDefault; - dirConfDefault.sDirectory = config.sDirectory; - dirConfDefault.bSubDirectories = config.bSubDirectories; - dirConfDefault.sGameSubdir = config.sGameSubdir; - dirConfDefault.sInstallersSubdir = config.sInstallersSubdir; - dirConfDefault.sExtrasSubdir = config.sExtrasSubdir; - dirConfDefault.sLanguagePackSubdir = config.sLanguagePackSubdir; - dirConfDefault.sDLCSubdir = config.sDLCSubdir; - dirConfDefault.sPatchesSubdir = config.sPatchesSubdir; + DirectoryConfig dirConfDefault = Globals::globalConfig.dirConf; - if (config.bUseCache && !config.bUpdateCache) + if (Globals::globalConfig.bUseCache && !Globals::globalConfig.bUpdateCache) { // GameRegex filter alias for all games - if (config.sGameRegex == "all") - config.sGameRegex = ".*"; - else if (config.sGameRegex == "free") + if (Globals::globalConfig.sGameRegex == "all") + Globals::globalConfig.sGameRegex = ".*"; + else if (Globals::globalConfig.sGameRegex == "free") std::cerr << "Warning: regex alias \"free\" doesn't work with cached details" << std::endl; int result = this->loadGameDetailsCache(); @@ -334,14 +413,14 @@ int Downloader::getGameDetails() } // Create threads - unsigned int threads = std::min(config.iThreads, static_cast(gameItemQueue.size())); + unsigned int threads = std::min(Globals::globalConfig.iThreads, static_cast(gameItemQueue.size())); std::vector vThreads; for (unsigned int i = 0; i < threads; ++i) { DownloadInfo dlInfo; dlInfo.setStatus(DLSTATUS_NOTSTARTED); vDownloadInfo.push_back(dlInfo); - vThreads.push_back(std::thread(Downloader::getGameDetailsThread, this->config, i)); + vThreads.push_back(std::thread(Downloader::getGameDetailsThread, Globals::globalConfig, i)); } unsigned int dl_status = DLSTATUS_NOTSTARTED; @@ -357,8 +436,8 @@ int Downloader::getGameDetails() Message msg; while (msgQueue.try_pop(msg)) { - std::cerr << msg.getFormattedString(config.bColor, true) << std::endl; - if (config.bReport) + std::cerr << msg.getFormattedString(Globals::globalConfig.bColor, true) << std::endl; + if (Globals::globalConfig.bReport) { this->report_ofs << msg.getTimestampString() << ": " << msg.getMessage() << std::endl; } @@ -398,7 +477,7 @@ int Downloader::getGameDetails() int Downloader::listGames() { - if (config.bListDetails) // Detailed list + if (Globals::globalConfig.bListDetails) // Detailed list { if (this->games.empty()) { int res = this->getGameDetails(); @@ -409,23 +488,24 @@ int Downloader::listGames() for (unsigned int i = 0; i < games.size(); ++i) { std::cout << "gamename: " << games[i].gamename << std::endl + << "product id: " << games[i].product_id << std::endl << "title: " << games[i].title << std::endl << "icon: " << "http://static.gog.com" << games[i].icon << std::endl; if (!games[i].serials.empty()) std::cout << "serials:" << std::endl << games[i].serials << std::endl; // List installers - if (config.bInstallers) + if (Globals::globalConfig.dlConf.bInstallers) { std::cout << "installers: " << std::endl; for (unsigned int j = 0; j < games[i].installers.size(); ++j) { - if (!config.bUpdateCheck || games[i].installers[j].updated) // Always list updated files + if (!Globals::globalConfig.bUpdateCheck || games[i].installers[j].updated) // Always list updated files { std::string filepath = games[i].installers[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -443,15 +523,15 @@ int Downloader::listGames() } } // List extras - if (config.bExtras && !config.bUpdateCheck && !games[i].extras.empty()) + if (Globals::globalConfig.dlConf.bExtras && !Globals::globalConfig.bUpdateCheck && !games[i].extras.empty()) { std::cout << "extras: " << std::endl; for (unsigned int j = 0; j < games[i].extras.size(); ++j) { std::string filepath = games[i].extras[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -464,15 +544,15 @@ int Downloader::listGames() } } // List patches - if (config.bPatches && !config.bUpdateCheck && !games[i].patches.empty()) + if (Globals::globalConfig.dlConf.bPatches && !Globals::globalConfig.bUpdateCheck && !games[i].patches.empty()) { std::cout << "patches: " << std::endl; for (unsigned int j = 0; j < games[i].patches.size(); ++j) { std::string filepath = games[i].patches[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -489,15 +569,15 @@ int Downloader::listGames() } } // List language packs - if (config.bLanguagePacks && !config.bUpdateCheck && !games[i].languagepacks.empty()) + if (Globals::globalConfig.dlConf.bLanguagePacks && !Globals::globalConfig.bUpdateCheck && !games[i].languagepacks.empty()) { std::cout << "language packs: " << std::endl; for (unsigned int j = 0; j < games[i].languagepacks.size(); ++j) { std::string filepath = games[i].languagepacks[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -509,7 +589,7 @@ int Downloader::listGames() << std::endl; } } - if (config.bDLC && !games[i].dlcs.empty()) + if (Globals::globalConfig.dlConf.bDLC && !games[i].dlcs.empty()) { std::cout << "DLCs: " << std::endl; for (unsigned int j = 0; j < games[i].dlcs.size(); ++j) @@ -523,9 +603,9 @@ int Downloader::listGames() for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k) { std::string filepath = games[i].dlcs[j].installers[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -541,8 +621,8 @@ int Downloader::listGames() for (unsigned int k = 0; k < games[i].dlcs[j].patches.size(); ++k) { std::string filepath = games[i].dlcs[j].patches[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -557,8 +637,8 @@ int Downloader::listGames() for (unsigned int k = 0; k < games[i].dlcs[j].extras.size(); ++k) { std::string filepath = games[i].dlcs[j].extras[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -573,8 +653,8 @@ int Downloader::listGames() for (unsigned int k = 0; k < games[i].dlcs[j].languagepacks.size(); ++k) { std::string filepath = games[i].dlcs[j].languagepacks[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -601,7 +681,7 @@ int Downloader::listGames() if (gameItems[i].updates > 0) { gamename += " [" + std::to_string(gameItems[i].updates) + "]"; - if (config.bColor) + if (Globals::globalConfig.bColor) gamename = "\033[32m" + gamename + "\033[0m"; } std::cout << gamename << std::endl; @@ -621,21 +701,21 @@ void Downloader::repair() for (unsigned int i = 0; i < games.size(); ++i) { // Installers (use remote or local file) - if (config.bInstallers) + if (Globals::globalConfig.dlConf.bInstallers) { for (unsigned int j = 0; j < games[i].installers.size(); ++j) { std::string filepath = games[i].installers[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } // Get XML data std::string XML = ""; - if (config.bRemoteXML) + if (Globals::globalConfig.dlConf.bRemoteXML) { XML = gogAPI->getXML(games[i].gamename, games[i].installers[j].id); if (gogAPI->getError()) @@ -647,7 +727,7 @@ void Downloader::repair() } // Repair - bool bUseLocalXML = !config.bRemoteXML; + bool bUseLocalXML = !Globals::globalConfig.dlConf.bRemoteXML; if (!XML.empty() || bUseLocalXML) { std::string url = gogAPI->getInstallerLink(games[i].gamename, games[i].installers[j].id); @@ -665,14 +745,14 @@ void Downloader::repair() } // Extras (GOG doesn't provide XML data for extras, use local file) - if (config.bExtras) + if (Globals::globalConfig.dlConf.bExtras) { for (unsigned int j = 0; j < games[i].extras.size(); ++j) { std::string filepath = games[i].extras[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -691,21 +771,21 @@ void Downloader::repair() } // Patches (use remote or local file) - if (config.bPatches) + if (Globals::globalConfig.dlConf.bPatches) { for (unsigned int j = 0; j < games[i].patches.size(); ++j) { std::string filepath = games[i].patches[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } // Get XML data std::string XML = ""; - if (config.bRemoteXML) + if (Globals::globalConfig.dlConf.bRemoteXML) { XML = gogAPI->getXML(games[i].gamename, games[i].patches[j].id); if (gogAPI->getError()) @@ -729,14 +809,14 @@ void Downloader::repair() } // Language packs (GOG doesn't provide XML data for language packs, use local file) - if (config.bLanguagePacks) + if (Globals::globalConfig.dlConf.bLanguagePacks) { for (unsigned int j = 0; j < games[i].languagepacks.size(); ++j) { std::string filepath = games[i].languagepacks[j].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -753,25 +833,25 @@ void Downloader::repair() std::cout << std::endl; } } - if (config.bDLC && !games[i].dlcs.empty()) + if (Globals::globalConfig.dlConf.bDLC && !games[i].dlcs.empty()) { for (unsigned int j = 0; j < games[i].dlcs.size(); ++j) { - if (config.bInstallers) + if (Globals::globalConfig.dlConf.bInstallers) { for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k) { std::string filepath = games[i].dlcs[j].installers[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } // Get XML data std::string XML = ""; - if (config.bRemoteXML) + if (Globals::globalConfig.dlConf.bRemoteXML) { XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id); if (gogAPI->getError()) @@ -783,7 +863,7 @@ void Downloader::repair() } // Repair - bool bUseLocalXML = !config.bRemoteXML; + bool bUseLocalXML = !Globals::globalConfig.dlConf.bRemoteXML; if (!XML.empty() || bUseLocalXML) { std::string url = gogAPI->getInstallerLink(games[i].dlcs[j].gamename, games[i].dlcs[j].installers[k].id); @@ -799,20 +879,20 @@ void Downloader::repair() } } } - if (config.bPatches) + if (Globals::globalConfig.dlConf.bPatches) { for (unsigned int k = 0; k < games[i].dlcs[j].patches.size(); ++k) { std::string filepath = games[i].dlcs[j].patches[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } // Get XML data std::string XML = ""; - if (config.bRemoteXML) + if (Globals::globalConfig.dlConf.bRemoteXML) { XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].patches[k].id); if (gogAPI->getError()) @@ -834,13 +914,13 @@ void Downloader::repair() std::cout << std::endl; } } - if (config.bExtras) + if (Globals::globalConfig.dlConf.bExtras) { for (unsigned int k = 0; k < games[i].dlcs[j].extras.size(); ++k) { std::string filepath = games[i].dlcs[j].extras[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } @@ -857,20 +937,20 @@ void Downloader::repair() std::cout << std::endl; } } - if (config.bLanguagePacks) + if (Globals::globalConfig.dlConf.bLanguagePacks) { for (unsigned int k = 0; k < games[i].dlcs[j].languagepacks.size(); ++k) { std::string filepath = games[i].dlcs[j].languagepacks[k].getFilepath(); - if (config.blacklist.isBlacklisted(filepath)) { - if (config.bVerbose) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath)) { + if (Globals::globalConfig.bVerbose) std::cerr << "skipped blacklisted file " << filepath << std::endl; continue; } // Get XML data std::string XML = ""; - if (config.bRemoteXML) + if (Globals::globalConfig.dlConf.bRemoteXML) { XML = gogAPI->getXML(games[i].dlcs[j].gamename, games[i].dlcs[j].languagepacks[k].id); if (gogAPI->getError()) @@ -902,25 +982,25 @@ void Downloader::download() if (this->games.empty()) this->getGameDetails(); - if (config.bCover && !config.bUpdateCheck) - coverXML = this->getResponse(config.sCoverList); + if (Globals::globalConfig.dlConf.bCover && !Globals::globalConfig.bUpdateCheck) + coverXML = this->getResponse(Globals::globalConfig.sCoverList); for (unsigned int i = 0; i < games.size(); ++i) { - if (config.bSaveSerials && !games[i].serials.empty()) + if (Globals::globalConfig.dlConf.bSaveSerials && !games[i].serials.empty()) { std::string filepath = games[i].getSerialsFilepath(); this->saveSerials(games[i].serials, filepath); } - if (config.bSaveChangelogs && !games[i].changelog.empty()) + if (Globals::globalConfig.dlConf.bSaveChangelogs && !games[i].changelog.empty()) { std::string filepath = games[i].getChangelogFilepath(); this->saveChangelog(games[i].changelog, filepath); } // Download covers - if (config.bCover && !config.bUpdateCheck) + if (Globals::globalConfig.dlConf.bCover && !Globals::globalConfig.bUpdateCheck) { if (!games[i].installers.empty()) { @@ -934,71 +1014,71 @@ void Downloader::download() } } - if (config.bInstallers) + if (Globals::globalConfig.dlConf.bInstallers) { for (unsigned int j = 0; j < games[i].installers.size(); ++j) { dlQueue.push(games[i].installers[j]); } } - if (config.bPatches) + if (Globals::globalConfig.dlConf.bPatches) { for (unsigned int j = 0; j < games[i].patches.size(); ++j) { dlQueue.push(games[i].patches[j]); } } - if (config.bExtras) + if (Globals::globalConfig.dlConf.bExtras) { for (unsigned int j = 0; j < games[i].extras.size(); ++j) { dlQueue.push(games[i].extras[j]); } } - if (config.bLanguagePacks) + if (Globals::globalConfig.dlConf.bLanguagePacks) { for (unsigned int j = 0; j < games[i].languagepacks.size(); ++j) { dlQueue.push(games[i].languagepacks[j]); } } - if (config.bDLC && !games[i].dlcs.empty()) + if (Globals::globalConfig.dlConf.bDLC && !games[i].dlcs.empty()) { for (unsigned int j = 0; j < games[i].dlcs.size(); ++j) { - if (config.bSaveSerials && !games[i].dlcs[j].serials.empty()) + if (Globals::globalConfig.dlConf.bSaveSerials && !games[i].dlcs[j].serials.empty()) { std::string filepath = games[i].dlcs[j].getSerialsFilepath(); this->saveSerials(games[i].dlcs[j].serials, filepath); } - if (config.bSaveChangelogs && !games[i].dlcs[j].changelog.empty()) + if (Globals::globalConfig.dlConf.bSaveChangelogs && !games[i].dlcs[j].changelog.empty()) { std::string filepath = games[i].dlcs[j].getChangelogFilepath(); this->saveChangelog(games[i].dlcs[j].changelog, filepath); } - if (config.bInstallers) + if (Globals::globalConfig.dlConf.bInstallers) { for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k) { dlQueue.push(games[i].dlcs[j].installers[k]); } } - if (config.bPatches) + if (Globals::globalConfig.dlConf.bPatches) { for (unsigned int k = 0; k < games[i].dlcs[j].patches.size(); ++k) { dlQueue.push(games[i].dlcs[j].patches[k]); } } - if (config.bExtras) + if (Globals::globalConfig.dlConf.bExtras) { for (unsigned int k = 0; k < games[i].dlcs[j].extras.size(); ++k) { dlQueue.push(games[i].dlcs[j].extras[k]); } } - if (config.bLanguagePacks) + if (Globals::globalConfig.dlConf.bLanguagePacks) { for (unsigned int k = 0; k < games[i].dlcs[j].languagepacks.size(); ++k) { @@ -1012,7 +1092,7 @@ void Downloader::download() if (!dlQueue.empty()) { // Limit thread count to number of items in download queue - unsigned int iThreads = std::min(config.iThreads, static_cast(dlQueue.size())); + unsigned int iThreads = std::min(Globals::globalConfig.iThreads, static_cast(dlQueue.size())); // Create download threads std::vector vThreads; @@ -1021,7 +1101,7 @@ void Downloader::download() DownloadInfo dlInfo; dlInfo.setStatus(DLSTATUS_NOTSTARTED); vDownloadInfo.push_back(dlInfo); - vThreads.push_back(std::thread(Downloader::processDownloadQueue, this->config, i)); + vThreads.push_back(std::thread(Downloader::processDownloadQueue, Globals::globalConfig, i)); } this->printProgress(); @@ -1041,8 +1121,8 @@ void Downloader::download() gameFile gf; while (createXMLQueue.try_pop(gf)) { - std::string xml_directory = config.sXMLDirectory + "/" + gf.gamename; - Util::createXML(gf.getFilepath(), config.iChunkSize, xml_directory); + std::string xml_directory = Globals::globalConfig.sXMLDirectory + "/" + gf.gamename; + Util::createXML(gf.getFilepath(), Globals::globalConfig.iChunkSize, xml_directory); } } } @@ -1062,9 +1142,9 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil std::string filenameXML = pathname.filename().string() + ".xml"; std::string xml_directory; if (!gamename.empty()) - xml_directory = config.sXMLDirectory + "/" + gamename; + xml_directory = Globals::globalConfig.sXMLDirectory + "/" + gamename; else - xml_directory = config.sXMLDirectory; + xml_directory = Globals::globalConfig.sXMLDirectory; // Using local XML data for version check before resuming boost::filesystem::path local_xml_file; @@ -1179,7 +1259,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil // Save remote XML if (!xml_data.empty()) { - if ((bLocalXMLExists && (!bSameVersion || config.bRepair)) || !bLocalXMLExists) + if ((bLocalXMLExists && (!bSameVersion || Globals::globalConfig.bRepair)) || !bLocalXMLExists) { // Check that directory exists and create subdirectories boost::filesystem::path path = xml_directory; @@ -1225,7 +1305,7 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil std::cerr << "Failed to delete " << path << std::endl; } - if (config.bReport) + if (Globals::globalConfig.bReport) { std::string status = static_cast(curl_easy_strerror(res)); if (bResume && res == CURLE_RANGE_ERROR) // CURLE_RANGE_ERROR on resume attempts is not an error that user needs to know about @@ -1236,11 +1316,11 @@ CURLcode Downloader::downloadFile(const std::string& url, const std::string& fil // Retry partially downloaded file // Retry if we aborted the transfer due to low speed limit - if ((res == CURLE_PARTIAL_FILE || res == CURLE_OPERATION_TIMEDOUT) && (this->retries < config.iRetries) ) + if ((res == CURLE_PARTIAL_FILE || res == CURLE_OPERATION_TIMEDOUT) && (this->retries < Globals::globalConfig.iRetries) ) { this->retries++; - std::cerr << std::endl << "Retry " << this->retries << "/" << config.iRetries; + std::cerr << std::endl << "Retry " << this->retries << "/" << Globals::globalConfig.iRetries; if (res == CURLE_PARTIAL_FILE) std::cerr << " (partial download)"; else if (res == CURLE_OPERATION_TIMEDOUT) @@ -1274,9 +1354,9 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, std::string filename = pathname.filename().string(); std::string xml_directory; if (!gamename.empty()) - xml_directory = config.sXMLDirectory + "/" + gamename; + xml_directory = Globals::globalConfig.sXMLDirectory + "/" + gamename; else - xml_directory = config.sXMLDirectory; + xml_directory = Globals::globalConfig.sXMLDirectory; std::string xml_file = xml_directory + "/" + filename + ".xml"; bool bFileExists = boost::filesystem::exists(pathname); bool bLocalXMLExists = boost::filesystem::exists(xml_file); @@ -1300,7 +1380,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, if (!fileElem) { // File node doesn't exist std::cout << "XML: Parsing failed / not valid XML" << std::endl; - if (config.bDownload) + if (Globals::globalConfig.bDownload) bParsingFailed = true; else return res; @@ -1335,7 +1415,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, // No local XML file and parsing failed. if (bParsingFailed && !bLocalXMLExists) { - if (this->config.bDownload) + if (Globals::globalConfig.bDownload) { std::cout << "Downloading: " << filepath << std::endl; CURLcode result = this->downloadFile(url, filepath, xml_data, gamename); @@ -1347,10 +1427,10 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, { bLocalXMLExists = boost::filesystem::exists(xml_file); // Check to see if downloadFile saved XML data - if (config.bAutomaticXMLCreation && !bLocalXMLExists) + if (Globals::globalConfig.dlConf.bAutomaticXMLCreation && !bLocalXMLExists) { std::cout << "Starting automatic XML creation" << std::endl; - Util::createXML(filepath, config.iChunkSize, xml_directory); + Util::createXML(filepath, Globals::globalConfig.iChunkSize, xml_directory); } res = 1; } @@ -1381,17 +1461,17 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, else { std::cout << "File doesn't exist " << filepath << std::endl; - if (this->config.bDownload) + if (Globals::globalConfig.bDownload) { std::cout << "Downloading: " << filepath << std::endl; CURLcode result = this->downloadFile(url, filepath, xml_data, gamename); std::cout << std::endl; if (result == CURLE_OK) { - if (config.bAutomaticXMLCreation && bParsingFailed) + if (Globals::globalConfig.dlConf.bAutomaticXMLCreation && bParsingFailed) { std::cout << "Starting automatic XML creation" << std::endl; - Util::createXML(filepath, config.iChunkSize, xml_directory); + Util::createXML(filepath, Globals::globalConfig.iChunkSize, xml_directory); } res = 1; } @@ -1405,7 +1485,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, std::cout << "Filesizes don't match" << std::endl << "Incomplete download or different version" << std::endl; fclose(outfile); - if (this->config.bDownload) + if (Globals::globalConfig.bDownload) { std::cout << "Redownloading file" << std::endl; @@ -1440,7 +1520,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, if (!bLocalXMLExists) { std::cout << "Starting automatic XML creation" << std::endl; - Util::createXML(filepath, config.iChunkSize, xml_directory); + Util::createXML(filepath, Globals::globalConfig.iChunkSize, xml_directory); } res = 1; } @@ -1491,7 +1571,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, curl_easy_setopt(curlhandle, CURLOPT_RANGE, range.c_str()); //download range this->beginDownload(); //begin chunk download std::cout << std::endl; - if (config.bReport) + if (Globals::globalConfig.bReport) iChunksRepaired++; i--; //verify downloaded chunk } @@ -1505,7 +1585,7 @@ int Downloader::repairFile(const std::string& url, const std::string& filepath, std::cout << std::endl; fclose(outfile); - if (config.bReport) + if (Globals::globalConfig.bReport) { std::string report_line = "Repaired [" + std::to_string(iChunksRepaired) + "/" + std::to_string(chunks) + "] " + filepath; this->report_ofs << report_line << std::endl; @@ -1622,13 +1702,13 @@ std::string Downloader::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 curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData); @@ -1797,12 +1877,12 @@ std::vector Downloader::getExtrasFromJSON(const Json::Value& json, con std::vector extras; // Create new API handle and set curl options for the API - API* api = new API(config.sToken, config.sSecret); - api->curlSetOpt(CURLOPT_VERBOSE, config.bVerbose); - api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer); - api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, config.iTimeout); - if (!config.sCACertPath.empty()) - api->curlSetOpt(CURLOPT_CAINFO, config.sCACertPath.c_str()); + API* api = new API(config.apiConf.sToken, config.apiConf.sSecret); + api->curlSetOpt(CURLOPT_VERBOSE, config.curlConf.bVerbose); + api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, config.curlConf.bVerifyPeer); + api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, config.curlConf.iTimeout); + if (!config.curlConf.sCACertPath.empty()) + api->curlSetOpt(CURLOPT_CAINFO, config.curlConf.sCACertPath.c_str()); if (!api->init()) { @@ -1960,10 +2040,11 @@ static int isPresent(std::vector& list, const boost::filesystem::path& void Downloader::checkOrphans() { // Always check everything when checking for orphaned files - config.bInstallers = true; - config.bExtras = true; - config.bPatches = true; - config.bLanguagePacks = true; + Config config = Globals::globalConfig; + config.dlConf.bInstallers = true; + config.dlConf.bExtras = true; + config.dlConf.bPatches = true; + config.dlConf.bLanguagePacks = true; if (this->games.empty()) this->getGameDetails(); @@ -1985,7 +2066,7 @@ void Downloader::checkOrphans() } for (unsigned int j = 0; j < platformIds.size(); ++j) { - std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/"; + std::string directory = config.dirConf.sDirectory + "/" + config.dirConf.sGameSubdir + "/"; Util::filepathReplaceReservedStrings(directory, games[i].gamename, platformIds[j]); boost::filesystem::path path (directory); if (boost::filesystem::exists(path)) @@ -2006,7 +2087,7 @@ void Downloader::checkOrphans() for (unsigned int j = 0; j < paths.size(); ++j) { - std::size_t pathlen = config.sDirectory.length(); + std::size_t pathlen = config.dirConf.sDirectory.length(); if (boost::filesystem::exists(paths[j])) { if (boost::filesystem::is_directory(paths[j])) @@ -2102,20 +2183,20 @@ void Downloader::checkStatus() for (unsigned int i = 0; i < vGameFiles.size(); ++i) { unsigned int type = vGameFiles[i].type; - if (!config.bDLC && (type & GFTYPE_DLC)) + if (!Globals::globalConfig.dlConf.bDLC && (type & GFTYPE_DLC)) continue; - if (!config.bInstallers && (type & GFTYPE_INSTALLER)) + if (!Globals::globalConfig.dlConf.bInstallers && (type & GFTYPE_INSTALLER)) continue; - if (!config.bExtras && (type & GFTYPE_EXTRA)) + if (!Globals::globalConfig.dlConf.bExtras && (type & GFTYPE_EXTRA)) continue; - if (!config.bPatches && (type & GFTYPE_PATCH)) + if (!Globals::globalConfig.dlConf.bPatches && (type & GFTYPE_PATCH)) continue; - if (!config.bLanguagePacks && (type & GFTYPE_LANGPACK)) + if (!Globals::globalConfig.dlConf.bLanguagePacks && (type & GFTYPE_LANGPACK)) continue; boost::filesystem::path filepath = vGameFiles[i].getFilepath(); - if (config.blacklist.isBlacklisted(filepath.native())) + if (Globals::globalConfig.blacklist.isBlacklisted(filepath.native())) continue; std::string gamename = vGameFiles[i].gamename; @@ -2144,9 +2225,9 @@ void Downloader::checkStatus() boost::filesystem::path path = filepath; boost::filesystem::path local_xml_file; if (!gamename.empty()) - local_xml_file = config.sXMLDirectory + "/" + gamename + "/" + path.filename().string() + ".xml"; + local_xml_file = Globals::globalConfig.sXMLDirectory + "/" + gamename + "/" + path.filename().string() + ".xml"; else - local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml"; + local_xml_file = Globals::globalConfig.sXMLDirectory + "/" + path.filename().string() + ".xml"; if (boost::filesystem::exists(local_xml_file)) { @@ -2185,17 +2266,17 @@ std::string Downloader::getLocalFileHash(const std::string& filepath, const std: boost::filesystem::path path = filepath; boost::filesystem::path local_xml_file; if (!gamename.empty()) - local_xml_file = config.sXMLDirectory + "/" + gamename + "/" + path.filename().string() + ".xml"; + local_xml_file = Globals::globalConfig.sXMLDirectory + "/" + gamename + "/" + path.filename().string() + ".xml"; else - local_xml_file = config.sXMLDirectory + "/" + path.filename().string() + ".xml"; + local_xml_file = Globals::globalConfig.sXMLDirectory + "/" + path.filename().string() + ".xml"; - if (config.bAutomaticXMLCreation && !boost::filesystem::exists(local_xml_file) && boost::filesystem::exists(path)) + if (Globals::globalConfig.dlConf.bAutomaticXMLCreation && !boost::filesystem::exists(local_xml_file) && boost::filesystem::exists(path)) { - std::string xml_directory = config.sXMLDirectory + "/" + gamename; - Util::createXML(filepath, config.iChunkSize, xml_directory); + std::string xml_directory = Globals::globalConfig.sXMLDirectory + "/" + gamename; + Util::createXML(filepath, Globals::globalConfig.iChunkSize, xml_directory); } - localHash = Util::getLocalFileHash(config.sXMLDirectory, filepath, gamename); + localHash = Util::getLocalFileHash(Globals::globalConfig.sXMLDirectory, filepath, gamename); return localHash; } @@ -2233,7 +2314,7 @@ std::string Downloader::getRemoteFileHash(const std::string& gamename, const std int Downloader::loadGameDetailsCache() { int res = 0; - std::string cachepath = config.sCacheDirectory + "/gamedetails.json"; + std::string cachepath = Globals::globalConfig.sCacheDirectory + "/gamedetails.json"; // Make sure file exists boost::filesystem::path path = cachepath; @@ -2252,7 +2333,7 @@ int Downloader::loadGameDetailsCache() if (root.isMember("date")) { cachedate = bptime::from_iso_string(root["date"].asString()); - if ((now - cachedate) > bptime::minutes(config.iCacheValid)) + if ((now - cachedate) > bptime::minutes(Globals::globalConfig.iCacheValid)) { // cache is too old delete jsonparser; @@ -2308,13 +2389,13 @@ int Downloader::saveGameDetailsCache() return 1; } - std::string cachepath = config.sCacheDirectory + "/gamedetails.json"; + std::string cachepath = Globals::globalConfig.sCacheDirectory + "/gamedetails.json"; Json::Value json; json["gamedetails-cache-version"] = GlobalConstants::GAMEDETAILS_CACHE_VERSION; - json["version-string"] = config.sVersionString; - json["version-number"] = config.sVersionNumber; + json["version-string"] = Globals::globalConfig.sVersionString; + json["version-number"] = Globals::globalConfig.sVersionNumber; json["date"] = bptime::to_iso_string(bptime::second_clock::local_time()); for (unsigned int i = 0; i < this->games.size(); ++i) @@ -2348,7 +2429,7 @@ std::vector Downloader::getGameDetailsFromJsonNode(Json::Value root // DLCs are handled as part of the game so make sure that filtering is done with base game name if (recursion_level == 0) // recursion level is 0 when handling base game { - boost::regex expression(config.sGameRegex); + boost::regex expression(Globals::globalConfig.sGameRegex); boost::match_results what; if (!boost::regex_search(game.gamename, what, expression)) // Check if name matches the specified regex continue; @@ -2357,6 +2438,7 @@ std::vector Downloader::getGameDetailsFromJsonNode(Json::Value root game.icon = gameDetailsNode["icon"].asString(); game.serials = gameDetailsNode["serials"].asString(); game.changelog = gameDetailsNode["changelog"].asString(); + game.product_id = gameDetailsNode["product_id"].asString(); // Make a vector of valid node names to make things easier std::vector nodes; @@ -2367,13 +2449,9 @@ std::vector Downloader::getGameDetailsFromJsonNode(Json::Value root nodes.push_back("dlcs"); gameSpecificConfig conf; - conf.bDLC = config.bDLC; - conf.iInstallerLanguage = config.iInstallerLanguage; - conf.iInstallerPlatform = config.iInstallerPlatform; - conf.vLanguagePriority = config.vLanguagePriority; - conf.vPlatformPriority = config.vPlatformPriority; + conf.dlConf = Globals::globalConfig.dlConf; if (Util::getGameSpecificConfig(game.gamename, &conf) > 0) - std::cerr << game.gamename << " - Language: " << conf.iInstallerLanguage << ", Platform: " << conf.iInstallerPlatform << ", DLC: " << (conf.bDLC ? "true" : "false") << std::endl; + std::cerr << game.gamename << " - Language: " << conf.dlConf.iInstallerLanguage << ", Platform: " << conf.dlConf.iInstallerPlatform << ", DLC: " << (conf.dlConf.bDLC ? "true" : "false") << std::endl; for (unsigned int j = 0; j < nodes.size(); ++j) { @@ -2399,21 +2477,21 @@ std::vector Downloader::getGameDetailsFromJsonNode(Json::Value root fileDetails.gamename = fileDetailsNode["gamename"].asString(); fileDetails.type = fileDetailsNode["type"].asUInt(); - if (nodeName != "extras" && !(fileDetails.platform & conf.iInstallerPlatform)) + if (nodeName != "extras" && !(fileDetails.platform & conf.dlConf.iInstallerPlatform)) continue; - if (nodeName != "extras" && !(fileDetails.language & conf.iInstallerLanguage)) + if (nodeName != "extras" && !(fileDetails.language & conf.dlConf.iInstallerLanguage)) continue; } - if (nodeName == "extras" && config.bExtras) + if (nodeName == "extras" && conf.dlConf.bExtras) game.extras.push_back(fileDetails); - else if (nodeName == "installers" && config.bInstallers) + else if (nodeName == "installers" && conf.dlConf.bInstallers) game.installers.push_back(fileDetails); - else if (nodeName == "patches" && config.bPatches) + else if (nodeName == "patches" && conf.dlConf.bPatches) game.patches.push_back(fileDetails); - else if (nodeName == "languagepacks" && config.bLanguagePacks) + else if (nodeName == "languagepacks" && conf.dlConf.bLanguagePacks) game.languagepacks.push_back(fileDetails); - else if (nodeName == "dlcs" && conf.bDLC) + else if (nodeName == "dlcs" && conf.dlConf.bDLC) { std::vector dlcs = this->getGameDetailsFromJsonNode(fileDetailsNode, recursion_level + 1); game.dlcs.insert(game.dlcs.end(), dlcs.begin(), dlcs.end()); @@ -2433,18 +2511,17 @@ std::vector Downloader::getGameDetailsFromJsonNode(Json::Value root void Downloader::updateCache() { // Make sure that all details get cached - config.bExtras = true; - config.bInstallers = true; - config.bPatches = true; - config.bLanguagePacks = true; - config.bDLC = true; - config.sGameRegex = ".*"; - config.iInstallerLanguage = Util::getOptionValue("all", GlobalConstants::LANGUAGES); - config.iInstallerPlatform = Util::getOptionValue("all", GlobalConstants::PLATFORMS); - config.vLanguagePriority.clear(); - config.vPlatformPriority.clear(); - config.sIgnoreDLCCountRegex = ".*"; // Ignore DLC count for all games because GOG doesn't report DLC count correctly - gogWebsite->setConfig(config); // Make sure that website handle has updated config + Globals::globalConfig.dlConf.bExtras = true; + Globals::globalConfig.dlConf.bInstallers = true; + Globals::globalConfig.dlConf.bPatches = true; + Globals::globalConfig.dlConf.bLanguagePacks = true; + Globals::globalConfig.dlConf.bDLC = true; + Globals::globalConfig.sGameRegex = ".*"; + Globals::globalConfig.dlConf.iInstallerLanguage = Util::getOptionValue("all", GlobalConstants::LANGUAGES); + Globals::globalConfig.dlConf.iInstallerPlatform = Util::getOptionValue("all", GlobalConstants::PLATFORMS); + Globals::globalConfig.dlConf.vLanguagePriority.clear(); + Globals::globalConfig.dlConf.vPlatformPriority.clear(); + Globals::globalConfig.sIgnoreDLCCountRegex = ".*"; // Ignore DLC count for all games because GOG doesn't report DLC count correctly this->getGameList(); this->getGameDetails(); @@ -2573,7 +2650,7 @@ int Downloader::downloadFileWithId(const std::string& fileid_string, const std:: std::string filename, filepath; filename.assign(url.begin()+url.find_last_of("/")+1, url.begin()+url.find_first_of("?")); if (output_filepath.empty()) - filepath = Util::makeFilepath(config.sDirectory, filename, gamename); + filepath = Util::makeFilepath(Globals::globalConfig.dirConf.sDirectory, filename, gamename); else filepath = output_filepath; std::cout << "Downloading: " << filepath << std::endl; @@ -2631,11 +2708,11 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid) { std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]"; - API* api = new API(conf.sToken, conf.sSecret); - api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, conf.bVerifyPeer); - api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, conf.iTimeout); - if (!conf.sCACertPath.empty()) - api->curlSetOpt(CURLOPT_CAINFO, conf.sCACertPath.c_str()); + API* api = new API(conf.apiConf.sToken, conf.apiConf.sSecret); + api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, conf.curlConf.bVerifyPeer); + api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, conf.curlConf.iTimeout); + if (!conf.curlConf.sCACertPath.empty()) + api->curlSetOpt(CURLOPT_CAINFO, conf.curlConf.sCACertPath.c_str()); if (!api->init()) { @@ -2651,20 +2728,20 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid) curl_easy_setopt(dlhandle, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(dlhandle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(dlhandle, CURLOPT_CONNECTTIMEOUT, conf.iTimeout); + curl_easy_setopt(dlhandle, CURLOPT_CONNECTTIMEOUT, conf.curlConf.iTimeout); curl_easy_setopt(dlhandle, CURLOPT_FAILONERROR, true); - curl_easy_setopt(dlhandle, CURLOPT_SSL_VERIFYPEER, conf.bVerifyPeer); - curl_easy_setopt(dlhandle, CURLOPT_VERBOSE, conf.bVerbose); + curl_easy_setopt(dlhandle, CURLOPT_SSL_VERIFYPEER, conf.curlConf.bVerifyPeer); + curl_easy_setopt(dlhandle, CURLOPT_VERBOSE, conf.curlConf.bVerbose); curl_easy_setopt(dlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData); curl_easy_setopt(dlhandle, CURLOPT_READFUNCTION, Downloader::readData); - curl_easy_setopt(dlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, conf.iDownloadRate); + curl_easy_setopt(dlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, conf.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(dlhandle, CURLOPT_LOW_SPEED_TIME, 30); curl_easy_setopt(dlhandle, CURLOPT_LOW_SPEED_LIMIT, 200); - if (!conf.sCACertPath.empty()) - curl_easy_setopt(dlhandle, CURLOPT_CAINFO, conf.sCACertPath.c_str()); + if (!conf.curlConf.sCACertPath.empty()) + curl_easy_setopt(dlhandle, CURLOPT_CAINFO, conf.curlConf.sCACertPath.c_str()); xferInfo xferinfo; xferinfo.tid = tid; @@ -2734,7 +2811,7 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid) bool bLocalXMLExists = boost::filesystem::exists(local_xml_file); // This is additional check to see if remote xml should be saved to speed up future version checks std::string xml; - if (gf.type & (GFTYPE_INSTALLER | GFTYPE_PATCH) && conf.bRemoteXML) + if (gf.type & (GFTYPE_INSTALLER | GFTYPE_PATCH) && conf.dlConf.bRemoteXML) { xml = api->getXML(gf.gamename, gf.id); if (api->getError()) @@ -2935,11 +3012,11 @@ void Downloader::processDownloadQueue(Config conf, const unsigned int& tid) } // Automatic xml creation - if (conf.bAutomaticXMLCreation) + if (conf.dlConf.bAutomaticXMLCreation) { if (result == CURLE_OK) { - if ((gf.type & GFTYPE_EXTRA) || (conf.bRemoteXML && !bLocalXMLExists && xml.empty())) + if ((gf.type & GFTYPE_EXTRA) || (conf.dlConf.bRemoteXML && !bLocalXMLExists && xml.empty())) createXMLQueue.push(gf); } } @@ -3008,7 +3085,7 @@ int Downloader::progressCallbackForThread(void *clientp, curl_off_t dltotal, cur void Downloader::printProgress() { // Print progress information until all threads have finished their tasks - ProgressBar bar(config.bUnicode, config.bColor); + ProgressBar bar(Globals::globalConfig.bUnicode, Globals::globalConfig.bColor); unsigned int dl_status = DLSTATUS_NOTSTARTED; while (dl_status != DLSTATUS_FINISHED) { @@ -3022,8 +3099,8 @@ void Downloader::printProgress() Message msg; while (msgQueue.try_pop(msg)) { - std::cout << msg.getFormattedString(config.bColor, true) << std::endl; - if (config.bReport) + std::cout << msg.getFormattedString(Globals::globalConfig.bColor, true) << std::endl; + if (Globals::globalConfig.bReport) { this->report_ofs << msg.getTimestampString() << ": " << msg.getMessage() << std::endl; } @@ -3119,7 +3196,7 @@ void Downloader::printProgress() if (dl_status != DLSTATUS_FINISHED) { std::ostringstream ss; - if (config.iThreads > 1) + if (Globals::globalConfig.iThreads > 1) { std::string rate_unit; if (total_rate > 1048576) // 1 MB @@ -3156,11 +3233,11 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) { std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]"; - API* api = new API(config.sToken, config.sSecret); - api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer); - api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, config.iTimeout); - if (!config.sCACertPath.empty()) - api->curlSetOpt(CURLOPT_CAINFO, config.sCACertPath.c_str()); + API* api = new API(config.apiConf.sToken, config.apiConf.sSecret); + api->curlSetOpt(CURLOPT_SSL_VERIFYPEER, config.curlConf.bVerifyPeer); + api->curlSetOpt(CURLOPT_CONNECTTIMEOUT, config.curlConf.iTimeout); + if (!config.curlConf.sCACertPath.empty()) + api->curlSetOpt(CURLOPT_CAINFO, config.curlConf.sCACertPath.c_str()); if (!api->init()) { @@ -3171,7 +3248,7 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } // Create new GOG website handle - Website* website = new Website(config); + Website* website = new Website(); if (!website->IsLoggedIn()) { delete api; @@ -3182,15 +3259,8 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } // Set default game specific directory options to values from config - gameSpecificDirectoryConfig dirConfDefault; - dirConfDefault.sDirectory = config.sDirectory; - dirConfDefault.bSubDirectories = config.bSubDirectories; - dirConfDefault.sGameSubdir = config.sGameSubdir; - dirConfDefault.sInstallersSubdir = config.sInstallersSubdir; - dirConfDefault.sExtrasSubdir = config.sExtrasSubdir; - dirConfDefault.sLanguagePackSubdir = config.sLanguagePackSubdir; - dirConfDefault.sDLCSubdir = config.sDLCSubdir; - dirConfDefault.sPatchesSubdir = config.sPatchesSubdir; + DirectoryConfig dirConfDefault; + dirConfDefault = config.dirConf; gameItem game_item; while (gameItemQueue.try_pop(game_item)) @@ -3199,13 +3269,10 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) bool bHasDLC = !game_item.dlcnames.empty(); gameSpecificConfig conf; - conf.bDLC = config.bDLC; - conf.bIgnoreDLCCount = false; - conf.iInstallerLanguage = config.iInstallerLanguage; - conf.iInstallerPlatform = config.iInstallerPlatform; + conf.dlConf = config.dlConf; conf.dirConf = dirConfDefault; - conf.vLanguagePriority = config.vLanguagePriority; - conf.vPlatformPriority = config.vPlatformPriority; + conf.dlConf.bIgnoreDLCCount = false; + if (!config.bUpdateCache) // Disable game specific config files for cache update { int iOptionsOverridden = Util::getGameSpecificConfig(game_item.name, &conf); @@ -3215,28 +3282,28 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) ss << game_item.name << " - " << iOptionsOverridden << " options overridden with game specific options" << std::endl; if (config.bVerbose) { - if (conf.bIgnoreDLCCount) + if (conf.dlConf.bIgnoreDLCCount) ss << "\tIgnore DLC count" << std::endl; - if (conf.bDLC != config.bDLC) - ss << "\tDLC: " << (conf.bDLC ? "true" : "false") << std::endl; - if (conf.iInstallerLanguage != config.iInstallerLanguage) - ss << "\tLanguage: " << Util::getOptionNameString(conf.iInstallerLanguage, GlobalConstants::LANGUAGES) << std::endl; - if (conf.vLanguagePriority != config.vLanguagePriority) + if (conf.dlConf.bDLC != config.dlConf.bDLC) + ss << "\tDLC: " << (conf.dlConf.bDLC ? "true" : "false") << std::endl; + if (conf.dlConf.iInstallerLanguage != config.dlConf.iInstallerLanguage) + ss << "\tLanguage: " << Util::getOptionNameString(conf.dlConf.iInstallerLanguage, GlobalConstants::LANGUAGES) << std::endl; + if (conf.dlConf.vLanguagePriority != config.dlConf.vLanguagePriority) { ss << "\tLanguage priority:" << std::endl; - for (unsigned int j = 0; j < conf.vLanguagePriority.size(); ++j) + for (unsigned int j = 0; j < conf.dlConf.vLanguagePriority.size(); ++j) { - ss << "\t " << j << ": " << Util::getOptionNameString(conf.vLanguagePriority[j], GlobalConstants::LANGUAGES) << std::endl; + ss << "\t " << j << ": " << Util::getOptionNameString(conf.dlConf.vLanguagePriority[j], GlobalConstants::LANGUAGES) << std::endl; } } - if (conf.iInstallerPlatform != config.iInstallerPlatform) - ss << "\tPlatform: " << Util::getOptionNameString(conf.iInstallerPlatform, GlobalConstants::PLATFORMS) << std::endl; - if (conf.vPlatformPriority != config.vPlatformPriority) + if (conf.dlConf.iInstallerPlatform != config.dlConf.iInstallerPlatform) + ss << "\tPlatform: " << Util::getOptionNameString(conf.dlConf.iInstallerPlatform, GlobalConstants::PLATFORMS) << std::endl; + if (conf.dlConf.vPlatformPriority != config.dlConf.vPlatformPriority) { ss << "\tPlatform priority:" << std::endl; - for (unsigned int j = 0; j < conf.vPlatformPriority.size(); ++j) + for (unsigned int j = 0; j < conf.dlConf.vPlatformPriority.size(); ++j) { - ss << "\t " << j << ": " << Util::getOptionNameString(conf.vPlatformPriority[j], GlobalConstants::PLATFORMS) << std::endl; + ss << "\t " << j << ": " << Util::getOptionNameString(conf.dlConf.vPlatformPriority[j], GlobalConstants::PLATFORMS) << std::endl; } } } @@ -3244,7 +3311,8 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } } - game = api->getGameDetails(game_item.name, conf.iInstallerPlatform, conf.iInstallerLanguage, config.bDuplicateHandler); + game = api->getGameDetails(game_item.name, conf.dlConf.iInstallerPlatform, conf.dlConf.iInstallerLanguage, conf.dlConf.bDuplicateHandler); + game.product_id = game_item.id; if (!api->getError()) { game.filterWithPriorities(conf); @@ -3253,19 +3321,19 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) if (!game_item.gamedetailsjson.empty()) gameDetailsJSON = game_item.gamedetailsjson; - if (game.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras + if (game.extras.empty() && conf.dlConf.bExtras) // Try to get extras from account page if API didn't return any extras { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); game.extras = Downloader::getExtrasFromJSON(gameDetailsJSON, game_item.name, config); } - if (config.bSaveSerials) + if (conf.dlConf.bSaveSerials) { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); game.serials = Downloader::getSerialsFromJSON(gameDetailsJSON); } - if (config.bSaveChangelogs) + if (conf.dlConf.bSaveChangelogs) { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); @@ -3273,7 +3341,7 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } // Ignore DLC count and try to get DLCs from JSON - if (game.dlcs.empty() && !bHasDLC && conf.bDLC && conf.bIgnoreDLCCount) + if (game.dlcs.empty() && !bHasDLC && conf.dlConf.bDLC && conf.dlConf.bIgnoreDLCCount) { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); @@ -3282,14 +3350,14 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) bHasDLC = !game_item.dlcnames.empty(); } - if (game.dlcs.empty() && bHasDLC && conf.bDLC) + if (game.dlcs.empty() && bHasDLC && conf.dlConf.bDLC) { for (unsigned int j = 0; j < game_item.dlcnames.size(); ++j) { gameDetails dlc; - dlc = api->getGameDetails(game_item.dlcnames[j], conf.iInstallerPlatform, conf.iInstallerLanguage, config.bDuplicateHandler); + dlc = api->getGameDetails(game_item.dlcnames[j], conf.dlConf.iInstallerPlatform, conf.dlConf.iInstallerLanguage, conf.dlConf.bDuplicateHandler); dlc.filterWithPriorities(conf); - if (dlc.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras + if (dlc.extras.empty() && conf.dlConf.bExtras) // Try to get extras from account page if API didn't return any extras { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); @@ -3311,7 +3379,7 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } } - if (config.bSaveSerials) + if (conf.dlConf.bSaveSerials) { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); @@ -3336,7 +3404,7 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) } } - if (config.bSaveChangelogs) + if (conf.dlConf.bSaveChangelogs) { if (gameDetailsJSON.empty()) gameDetailsJSON = website->getGameDetailsJSON(game_item.id); @@ -3403,3 +3471,201 @@ void Downloader::getGameDetailsThread(Config config, const unsigned int& tid) delete website; return; } + +void Downloader::saveGalaxyJSON() +{ + if (!Globals::galaxyConf.getJSON().empty()) + { + std::ofstream ofs(Globals::galaxyConf.getFilepath()); + if (!ofs) + { + std::cerr << "Failed to write " << Globals::galaxyConf.getFilepath() << std::endl; + } + else + { + Json::StyledStreamWriter jsonwriter; + jsonwriter.write(ofs, Globals::galaxyConf.getJSON()); + ofs.close(); + } + if (!Globals::globalConfig.bRespectUmask) + Util::setFilePermissions(Globals::galaxyConf.getFilepath(), boost::filesystem::owner_read | boost::filesystem::owner_write); + } +} + +void Downloader::galaxyInstallGame(const std::string& product_id, int build_index) +{ + if (build_index < 0) + build_index = 0; + + Json::Value json = gogGalaxy->getProductBuilds(product_id); + if (json["items"][build_index]["generation"].asInt() != 2) + { + std::cout << "Only generation 2 builds are supported currently" << std::endl; + return; + } + + std::string link = json["items"][build_index]["link"].asString(); + std::string buildHash; + buildHash.assign(link.begin()+link.find_last_of("/")+1, link.end()); + + json = gogGalaxy->getManifestV2(buildHash); + std::string game_title = json["products"][0]["name"].asString(); + + std::vector items; + for (unsigned int i = 0; i < json["depots"].size(); ++i) + { + bool bSupportedLanguage = false; + for (unsigned int j = 0; j < json["depots"][i]["languages"].size(); ++j) + { + std::string language = json["depots"][i]["languages"][j].asString(); + if (language == "*" || language == "en") + bSupportedLanguage = true; + } + + if (!bSupportedLanguage) + continue; + + std::string depotHash = json["depots"][i]["manifest"].asString(); + std::vector vec = gogGalaxy->getDepotItemsVector(depotHash); + items.insert(std::end(items), std::begin(vec), std::end(vec)); + } + + off_t totalSize = 0; + for (unsigned int i = 0; i < items.size(); ++i) + { + if (Globals::globalConfig.bVerbose) + { + std::cout << items[i].path << std::endl; + std::cout << "\tChunks: " << items[i].chunks.size() << std::endl; + std::cout << "\tmd5: " << items[i].md5 << std::endl; + } + totalSize += items[i].totalSizeUncompressed; + } + + double totalSizeMB = static_cast(totalSize)/1024/1024; + std::cout << game_title << std::endl; + std::cout << "Files: " << items.size() - 1 << std::endl; + std::cout << "Total size installed: " << totalSizeMB << " MB" << std::endl; + + for (unsigned int i = 0; i < items.size(); ++i) + { + boost::filesystem::path path = Globals::globalConfig.dirConf.sDirectory + items[i].path; + + // Check that directory exists and create it + boost::filesystem::path directory = path.parent_path(); + if (boost::filesystem::exists(directory)) + { + if (!boost::filesystem::is_directory(directory)) + { + std::cerr << directory << " is not directory" << std::endl; + return; + } + } + else + { + if (!boost::filesystem::create_directories(directory)) + { + std::cerr << "Failed to create directory: " << directory << std::endl; + return; + } + } + + if (boost::filesystem::exists(path)) + { + std::cout << "File already exists: " << path.string() << std::endl; + if (Util::getFileHash(path.string(), RHASH_MD5) == items[i].md5) + std::cout << "\tOK" << std::endl; + else + std::cout << "\tMD5 mismatch" << std::endl; + continue; + } + + for (unsigned int j = 0; j < items[i].chunks.size(); ++j) + { + ChunkMemoryStruct chunk; + chunk.memory = (char *) malloc(1); + chunk.size = 0; + + json = gogGalaxy->getSecureLink(product_id, gogGalaxy->hashToGalaxyPath(items[i].chunks[j].md5_compressed)); + std::string url = json["urls"][0]["parameters"]["base_url"].asString() + "/" + json["urls"][0]["parameters"]["path"].asString() + "?" + json["urls"][0]["parameters"]["token"].asString(); + + curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, WriteChunkMemoryCallback); + curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &chunk); + curl_easy_setopt(curlhandle, CURLOPT_XFERINFOFUNCTION, Downloader::progressCallback); + curl_easy_setopt(curlhandle, CURLOPT_XFERINFODATA, this); + + std::cout << path.string() << " (chunk " << (j + 1) << "/" << items[i].chunks.size() << ")" << std::endl; + + + if (Globals::globalConfig.iWait > 0) + usleep(Globals::globalConfig.iWait); // Delay the request by specified time + + this->TimeAndSize.clear(); + this->timer.reset(); + CURLcode result = curl_easy_perform(curlhandle); + + curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData); + curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 0); + + if (result != CURLE_OK) + { + std::cout << "\033[K" << curl_easy_strerror(result) << std::endl; + if (result == CURLE_HTTP_RETURNED_ERROR) + { + long int response_code = 0; + result = curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code); + std::cout << "HTTP ERROR: "; + if (result == CURLE_OK) + std::cout << response_code << " (" << url << ")" << std::endl; + else + std::cout << "failed to get error code: " << curl_easy_strerror(result) << " (" << url << ")" << std::endl; + } + } + std::cout << std::endl; + + curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData); + curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 0); + + std::ofstream ofs(path.string(), std::ofstream::out | std::ofstream::binary | std::ofstream::app); + if (ofs) + { + boost::iostreams::filtering_streambuf output; + output.push(boost::iostreams::zlib_decompressor(GlobalConstants::ZLIB_WINDOW_SIZE)); + output.push(ofs); + boost::iostreams::write(output, chunk.memory, chunk.size); + } + if (ofs) + ofs.close(); + + free(chunk.memory); + } + } +} + +void Downloader::galaxyShowBuilds(const std::string& product_id, int build_index) +{ + Json::Value json = gogGalaxy->getProductBuilds(product_id); + if (build_index < 0) + { + for (unsigned int i = 0; i < json["items"].size(); ++i) + { + std::cout << i << ": " << "Version " << json["items"][i]["version_name"].asString() << " - " << json["items"][i]["date_published"].asString() << std::endl; + } + return; + } + + if (json["items"][build_index]["generation"].asInt() != 2) + { + std::cout << "Only generation 2 builds are supported currently" << std::endl; + return; + } + + std::string link = json["items"][build_index]["link"].asString(); + std::string buildHash; + buildHash.assign(link.begin()+link.find_last_of("/")+1, link.end()); + json = gogGalaxy->getManifestV2(buildHash); + + std::cout << json << std::endl; +} diff --git a/src/galaxyapi.cpp b/src/galaxyapi.cpp new file mode 100644 index 0000000..44c35a8 --- /dev/null +++ b/src/galaxyapi.cpp @@ -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 +#include +#include +#include + +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 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 galaxyAPI::getDepotItemsVector(const std::string& hash) +{ + Json::Value json = this->getManifestV2(hash); + + std::vector 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; +} diff --git a/src/gamedetails.cpp b/src/gamedetails.cpp index 794ca70..8d60102 100644 --- a/src/gamedetails.cpp +++ b/src/gamedetails.cpp @@ -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& list, const ga for (std::vector::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& 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; diff --git a/src/util.cpp b/src/util.cpp index e90392d..9f11554 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -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")) diff --git a/src/website.cpp b/src/website.cpp index 8ca4c8c..307e3cb 100644 --- a/src/website.cpp +++ b/src/website.cpp @@ -10,30 +10,29 @@ #include #include -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 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 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 what; if (boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex { @@ -230,25 +229,25 @@ std::vector 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 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 dom = parser.parseTree(html); - tree::iterator it = dom.begin(); - tree::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::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 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 login_dom = parser.parseTree(login_form_html); - tree::iterator login_it = login_dom.begin(); - tree::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 login_dom = parser.parseTree(login_form_html); + tree::iterator login_it = login_dom.begin(); + tree::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 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 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 Website::getWishlistItems() return wishlistItems; } - -void Website::setConfig(Config &conf) -{ - this->config = conf; -}