[lgogdownloader] 01/03: New upstream version 3.2

Stephen Kitt skitt at moszumanska.debian.org
Sat Jun 10 21:24:28 UTC 2017


This is an automated email from the git hooks/post-receive script.

skitt pushed a commit to branch master
in repository lgogdownloader.

commit 845e5b0d888ef90dd80cf42ff300e7844a02100d
Author: Stephen Kitt <steve at sk2.org>
Date:   Sat Jun 10 23:12:49 2017 +0200

    New upstream version 3.2
---
 CMakeLists.txt            |    5 +-
 README.md                 |    5 +-
 include/api.h             |    1 +
 include/config.h          |  294 ++++++++++---
 include/downloader.h      |   17 +-
 include/galaxyapi.h       |   63 +++
 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                  |  422 +++++++++++-------
 src/api.cpp               |    5 +
 src/downloader.cpp        | 1067 ++++++++++++++++++++++++++++++++-------------
 src/galaxyapi.cpp         |  256 +++++++++++
 src/gamedetails.cpp       |   17 +-
 src/util.cpp              |   12 +-
 src/website.cpp           |  294 ++++++-------
 19 files changed, 1794 insertions(+), 722 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 992cc90..7614b6f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,7 @@
 cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
-project (lgogdownloader LANGUAGES C CXX VERSION 3.1)
+project (lgogdownloader LANGUAGES C CXX VERSION 3.2)
 
 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/README.md b/README.md
index 46b59ee..b3052bf 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
 * [jsoncpp](https://github.com/open-source-parsers/jsoncpp)
 * [htmlcxx](http://htmlcxx.sourceforge.net/)
 * [tinyxml2](https://github.com/leethomason/tinyxml2)
-* [boost](http://www.boost.org/) (regex, date-time, system, filesystem, program-options)
+* [boost](http://www.boost.org/) (regex, date-time, system, filesystem, program-options, iostreams)
 * [libcrypto](https://www.openssl.org/) if libcurl is built with OpenSSL
 
 ## Make dependencies
@@ -24,7 +24,8 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
     # apt install build-essential libcurl4-openssl-dev libboost-regex-dev \
     libjsoncpp-dev liboauth-dev librhash-dev libtinyxml2-dev libhtmlcxx-dev \
     libboost-system-dev libboost-filesystem-dev libboost-program-options-dev \
-    libboost-date-time-dev help2man cmake libssl-dev pkg-config
+    libboost-date-time-dev libboost-iostreams-dev help2man cmake libssl-dev \
+    pkg-config
 
 ## Build and install
 
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 <iostream>
 #include <vector>
diff --git a/include/config.h b/include/config.h
index 2881f3e..5f00b5b 100644
--- a/include/config.h
+++ b/include/config.h
@@ -9,93 +9,273 @@
 
 #include <iostream>
 #include <curl/curl.h>
+#include <json/json.h>
+#include <mutex>
+#include <ctime>
 
 #include "blacklist.h"
 
+struct DirectoryConfig
+{
+    bool bSubDirectories;
+    std::string sDirectory;
+    std::string sGameSubdir;
+    std::string sInstallersSubdir;
+    std::string sExtrasSubdir;
+    std::string sPatchesSubdir;
+    std::string sLanguagePackSubdir;
+    std::string sDLCSubdir;
+};
+
+struct DownloadConfig
+{
+    unsigned int iInstallerPlatform;
+    unsigned int iInstallerLanguage;
+    std::vector<unsigned int> vPlatformPriority;
+    std::vector<unsigned int> vLanguagePriority;
+    unsigned int iInclude;
+    unsigned int iGalaxyPlatform;
+    unsigned int iGalaxyLanguage;
+
+    bool bRemoteXML;
+    bool bCover;
+    bool bSaveChangelogs;
+    bool bSaveSerials;
+    bool bAutomaticXMLCreation;
+
+    bool bInstallers;
+    bool bExtras;
+    bool bPatches;
+    bool bLanguagePacks;
+    bool bDLC;
+
+    bool bIgnoreDLCCount;
+    bool bDuplicateHandler;
+};
+
+struct gameSpecificConfig
+{
+    DownloadConfig dlConf;
+    DirectoryConfig dirConf;
+};
+
+class GalaxyConfig
+{
+    public:
+        bool isExpired()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            bool bExpired = false;
+            intmax_t time_now = time(NULL);
+            if (this->token_json.isMember("expires_at"))
+                bExpired = (time_now > this->token_json["expires_at"].asLargestInt());
+            return bExpired;
+        }
+
+        std::string getAccessToken()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->token_json["access_token"].asString();
+        }
+
+        std::string getRefreshToken()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->token_json["refresh_token"].asString();
+        }
+
+        Json::Value getJSON()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->token_json;
+        }
+
+        void setJSON(Json::Value json)
+        {
+            std::unique_lock<std::mutex> lock(m);
+            if (!json.isMember("expires_at"))
+            {
+                intmax_t time_now = time(NULL);
+                Json::Value::LargestInt expires_in = 3600;
+                if (json.isMember("expires_in"))
+                    if (!json["expires_in"].isNull())
+                        expires_in = json["expires_in"].asLargestInt();
+
+                Json::Value::LargestInt expires_at = time_now + expires_in;
+                json["expires_at"] = expires_at;
+            }
+            this->token_json = json;
+        }
+
+        void setFilepath(const std::string& path)
+        {
+            std::unique_lock<std::mutex> lock(m);
+            this->filepath = path;
+        }
+
+        std::string getFilepath()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->filepath;
+        }
+
+        std::string getClientId()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->client_id;
+        }
+
+        std::string getClientSecret()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->client_secret;
+        }
+
+        std::string getRedirectUri()
+        {
+            std::unique_lock<std::mutex> lock(m);
+            return this->redirect_uri;
+        }
+
+        GalaxyConfig() = default;
+
+        GalaxyConfig(const GalaxyConfig& other)
+        {
+            std::lock_guard<std::mutex> guard(other.m);
+            client_id = other.client_id;
+            client_secret = other.client_secret;
+            redirect_uri = other.redirect_uri;
+            filepath = other.filepath;
+            token_json = other.token_json;
+        }
+
+        GalaxyConfig& operator= (GalaxyConfig& other)
+        {
+            if(&other == this)
+                return *this;
+
+            std::unique_lock<std::mutex> lock1(m, std::defer_lock);
+            std::unique_lock<std::mutex> lock2(other.m, std::defer_lock);
+            std::lock(lock1, lock2);
+            client_id = other.client_id;
+            client_secret = other.client_secret;
+            redirect_uri = other.redirect_uri;
+            filepath = other.filepath;
+            token_json = other.token_json;
+            return *this;
+        }
+    protected:
+    private:
+        std::string client_id = "46899977096215655";
+        std::string client_secret = "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
+        std::string redirect_uri = "https://embed.gog.com/on_login_success?origin=client";
+        std::string filepath;
+        Json::Value token_json;
+        mutable std::mutex m;
+};
+
+struct CurlConfig
+{
+    bool bVerifyPeer;
+    bool bVerbose;
+    std::string sCACertPath;
+    std::string sCookiePath;
+    long int iTimeout;
+    curl_off_t iDownloadRate;
+};
+
+struct GogAPIConfig
+{
+    std::string sToken;
+    std::string sSecret;
+};
+
 class Config
 {
     public:
         Config() {};
         virtual ~Config() {};
-        bool bVerbose;
-        bool bRemoteXML;
-        bool bCover;
-        bool bUpdateCheck;
-        bool bDownload;
-        bool bList;
-        bool bListDetails;
+
+        // Booleans
         bool bLoginHTTP;
         bool bLoginAPI;
+        bool bSaveConfig;
+        bool bResetConfig;
+
+        bool bDownload;
         bool bRepair;
-        bool bInstallers;
-        bool bExtras;
-        bool bPatches;
-        bool bLanguagePacks;
-        bool bDLC;
+        bool bUpdateCheck;
+        bool bList;
+        bool bListDetails;
+        bool bCheckStatus;
+        bool bShowWishlist;
+
+        bool bVerbose;
         bool bUnicode; // use Unicode in console output
         bool bColor;   // use colors
-        bool bVerifyPeer;
-        bool bCheckStatus;
-        bool bDuplicateHandler;
-        bool bSaveConfig;
-        bool bResetConfig;
         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;
+
+        // 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 sReportFilePath;
+
+        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;
+        std::string sEmail;
+        std::string sPassword;
+
+        GogAPIConfig apiConf;
+
+        // Lists
+        Blacklist blacklist;
+        Blacklist ignorelist;
+        Blacklist gamehasdlc;
         std::string sCoverList;
         std::string sGameHasDLCList;
-        std::string sReportFilePath;
-        std::string sInstallersSubdir;
-        std::string sExtrasSubdir;
-        std::string sPatchesSubdir;
-        std::string sLanguagePackSubdir;
-        std::string sDLCSubdir;
-        std::string sGameSubdir;
-        std::string sFileIdString;
-        std::string sOutputFilename;
-        std::string sLanguagePriority;
-        std::string sPlatformPriority;
-        std::string sIgnoreDLCCountRegex;
-        std::string sCACertPath;
-        std::vector<unsigned int> vLanguagePriority;
-        std::vector<unsigned int> vPlatformPriority;
 
-        unsigned int iInstallerPlatform;
-        unsigned int iInstallerLanguage;
-        unsigned int iInclude;
-        unsigned int iThreads;
+        // Integers
         int iRetries;
+        unsigned int iThreads;
         int iWait;
-        int iCacheValid;
         size_t iChunkSize;
-        curl_off_t iDownloadRate;
-        long int iTimeout;
-        Blacklist blacklist;
-        Blacklist ignorelist;
-        Blacklist gamehasdlc;
 };
 
 #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 <curl/curl.h>
 #include <json/json.h>
 #include <ctime>
@@ -58,10 +61,16 @@ struct xferInfo
     curl_off_t offset;
 };
 
+struct ChunkMemoryStruct
+{
+    char *memory;
+    curl_off_t size;
+};
+
 class Downloader
 {
     public:
-        Downloader(Config &conf);
+        Downloader();
         virtual ~Downloader();
         bool isLoggedIn();
         int init();
@@ -77,9 +86,12 @@ class Downloader
         void showWishlist();
         CURL* curlhandle;
         Timer timer;
-        Config config;
         ProgressBar* progressbar;
         std::deque< std::pair<time_t, uintmax_t> > TimeAndSize;
+        void saveGalaxyJSON();
+
+        void galaxyInstallGame(const std::string& product_id, int build_index = -1);
+        void galaxyShowBuilds(const std::string& product_id, int build_index = -1);
     protected:
     private:
         CURLcode downloadFile(const std::string& url, const std::string& filepath, const std::string& xml_data = std::string(), const std::string& gamename = std::string());
@@ -112,6 +124,7 @@ class Downloader
 
         Website *gogWebsite;
         API *gogAPI;
+        galaxyAPI *gogGalaxy;
         std::vector<gameItem> gameItems;
         std::vector<gameDetails> games;
         std::string coverXML;
diff --git a/include/galaxyapi.h b/include/galaxyapi.h
new file mode 100644
index 0000000..50260e6
--- /dev/null
+++ b/include/galaxyapi.h
@@ -0,0 +1,63 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
+#ifndef GALAXYAPI_H
+#define GALAXYAPI_H
+
+#include "globalconstants.h"
+#include "globals.h"
+#include "config.h"
+#include "util.h"
+
+#include <iostream>
+#include <vector>
+#include <cstring>
+#include <curl/curl.h>
+#include <sys/time.h>
+
+struct galaxyDepotItemChunk
+{
+    std::string md5_compressed;
+    std::string md5_uncompressed;
+    uintmax_t size_compressed;
+    uintmax_t size_uncompressed;
+    uintmax_t offset_compressed;
+    uintmax_t offset_uncompressed;
+};
+
+struct galaxyDepotItem
+{
+    std::string path;
+    std::vector<galaxyDepotItemChunk> chunks;
+    uintmax_t totalSizeCompressed;
+    uintmax_t totalSizeUncompressed;
+    std::string md5;
+    std::string product_id;
+};
+
+class galaxyAPI
+{
+    public:
+        galaxyAPI(CurlConfig& conf);
+        virtual ~galaxyAPI();
+        int init();
+        bool isTokenExpired();
+        bool refreshLogin();
+        Json::Value getProductBuilds(const std::string& product_id, const std::string& platform = "windows", const std::string& generation = "2");
+        Json::Value getManifestV1(const std::string& product_id, const std::string& build_id, const std::string& manifest_id = "repository", const std::string& platform = "windows");
+        Json::Value getManifestV2(std::string manifest_hash);
+        Json::Value getSecureLink(const std::string& product_id, const std::string& path);
+        std::string getResponse(const std::string& url, const bool& zlib_decompress = false);
+        std::string hashToGalaxyPath(const std::string& hash);
+        std::vector<galaxyDepotItem> getDepotItemsVector(const std::string& hash);
+    protected:
+    private:
+        CurlConfig curlConf;
+        static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
+        CURL* curlhandle;
+};
+
+#endif // GALAXYAPI_H
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<gameFile> languagepacks;
         std::vector<gameDetails> dlcs;
         std::string gamename;
+        std::string product_id;
         std::string title;
         std::string icon;
         std::string serials;
         std::string changelog;
         void filterWithPriorities(const gameSpecificConfig& config);
-        void makeFilepaths(const gameSpecificDirectoryConfig& config);
+        void makeFilepaths(const DirectoryConfig& config);
         std::string getSerialsFilepath();
         std::string getChangelogFilepath();
         Json::Value getDetailsAsJson();
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 <iostream>
 #include <vector>
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 <iostream>
+#include <vector>
+
+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 <cstdio>
 #include <cstdlib>
@@ -21,29 +23,6 @@
 #include <boost/regex.hpp>
 #include <json/json.h>
 
-struct gameSpecificDirectoryConfig
-{
-    bool bSubDirectories;
-    std::string sDirectory;
-    std::string sGameSubdir;
-    std::string sInstallersSubdir;
-    std::string sExtrasSubdir;
-    std::string sPatchesSubdir;
-    std::string sLanguagePackSubdir;
-    std::string sDLCSubdir;
-};
-
-struct gameSpecificConfig
-{
-    unsigned int iInstallerPlatform;
-    unsigned int iInstallerLanguage;
-    bool bDLC;
-    bool bIgnoreDLCCount;
-    gameSpecificDirectoryConfig dirConf;
-    std::vector<unsigned int> vLanguagePriority;
-    std::vector<unsigned int> vPlatformPriority;
-};
-
 struct gameItem
 {
     std::string name;
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 <curl/curl.h>
 #include <json/json.h>
 #include <fstream>
@@ -16,7 +17,7 @@
 class Website
 {
     public:
-        Website(Config &conf);
+        Website();
         int Login(const std::string& email, const std::string& password);
         std::string getResponse(const std::string& url);
         Json::Value getGameDetailsJSON(const std::string& gameid);
@@ -24,7 +25,6 @@ class Website
         std::vector<gameItem> getFreeGames();
         std::vector<wishlistItem> getWishlistItems();
         bool IsLoggedIn();
-        void setConfig(Config &conf);
         virtual ~Website();
     protected:
     private:
diff --git a/main.cpp b/main.cpp
index 23b13c5..3298781 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 <fstream>
 #include <boost/filesystem.hpp>
 #include <boost/program_options.hpp>
 
 namespace bpo = boost::program_options;
+Config Globals::globalConfig;
 
 template<typename T> void set_vm_value(std::map<std::string, bpo::variable_value>& vm, const std::string& option, const T& value)
 {
@@ -44,19 +47,20 @@ int main(int argc, char *argv[])
         { OPTION_DLCS,       "d", "DLCs",           "d|dlc|dlcs"                }
     };
 
-    Config config;
-    config.sVersionString = VERSION_STRING;
-    config.sVersionNumber = VERSION_NUMBER;
+    Globals::globalConfig.sVersionString = VERSION_STRING;
+    Globals::globalConfig.sVersionNumber = VERSION_NUMBER;
 
-    config.sCacheDirectory = Util::getCacheHome() + "/lgogdownloader";
-    config.sXMLDirectory = config.sCacheDirectory + "/xml";
+    Globals::globalConfig.sCacheDirectory = Util::getCacheHome() + "/lgogdownloader";
+    Globals::globalConfig.sXMLDirectory = Globals::globalConfig.sCacheDirectory + "/xml";
 
-    config.sConfigDirectory = Util::getConfigHome() + "/lgogdownloader";
-    config.sCookiePath = config.sConfigDirectory + "/cookies.txt";
-    config.sConfigFilePath = config.sConfigDirectory + "/config.cfg";
-    config.sBlacklistFilePath = config.sConfigDirectory + "/blacklist.txt";
-    config.sIgnorelistFilePath = config.sConfigDirectory + "/ignorelist.txt";
-    config.sGameHasDLCListFilePath = config.sConfigDirectory + "/game_has_dlc.txt";
+    Globals::globalConfig.sConfigDirectory = Util::getConfigHome() + "/lgogdownloader";
+    Globals::globalConfig.curlConf.sCookiePath = Globals::globalConfig.sConfigDirectory + "/cookies.txt";
+    Globals::globalConfig.sConfigFilePath = Globals::globalConfig.sConfigDirectory + "/config.cfg";
+    Globals::globalConfig.sBlacklistFilePath = Globals::globalConfig.sConfigDirectory + "/blacklist.txt";
+    Globals::globalConfig.sIgnorelistFilePath = Globals::globalConfig.sConfigDirectory + "/ignorelist.txt";
+    Globals::globalConfig.sGameHasDLCListFilePath = Globals::globalConfig.sConfigDirectory + "/game_has_dlc.txt";
+
+    Globals::galaxyConf.setFilepath(Globals::globalConfig.sConfigDirectory + "/galaxy_tokens.json");
 
     std::string priority_help_text = "Set priority by separating values with \",\"\nCombine values by separating with \"+\"";
     // Create help text for --platform option
@@ -70,6 +74,13 @@ int main(int argc, char *argv[])
     platform_text += "\n\n" + priority_help_text;
     platform_text += "\nExample: Linux if available otherwise Windows and Mac: l,w+m";
 
+    // Create help text for --galaxy-platform option
+    std::string galaxy_platform_text = "Select platform\n";
+    for (unsigned int i = 0; i < GlobalConstants::PLATFORMS.size(); ++i)
+    {
+        galaxy_platform_text += GlobalConstants::PLATFORMS[i].str + " = " + GlobalConstants::PLATFORMS[i].regexp + "|" + std::to_string(GlobalConstants::PLATFORMS[i].id) + "\n";
+    }
+
     // Create help text for --language option
     std::string language_text = "Select which language installers are downloaded\n";
     unsigned int language_all = Util::getOptionValue("all", GlobalConstants::LANGUAGES);
@@ -82,6 +93,13 @@ int main(int argc, char *argv[])
     language_text += "\n\n" + priority_help_text;
     language_text += "\nExample: German if available otherwise English and French: de,en+fr";
 
+    // Create help text for --galaxy-language option
+    std::string galaxy_language_text = "Select language\n";
+    for (unsigned int i = 0; i < GlobalConstants::LANGUAGES.size(); ++i)
+    {
+        galaxy_language_text +=  GlobalConstants::LANGUAGES[i].str + " = " + GlobalConstants::LANGUAGES[i].regexp + "|" + std::to_string(GlobalConstants::LANGUAGES[i].id) + "\n";
+    }
+
     // Create help text for --check-orphans
     std::string orphans_regex_default = ".*\\.(zip|exe|bin|dmg|old|deb|tar\\.gz|pkg|sh)$"; // Limit to files with these extensions (".old" is for renamed older version files)
     std::string check_orphans_text = "Check for orphaned files (files found on local filesystem that are not found on GOG servers). Sets regular expression filter (Perl syntax) for files to check. If no argument is given then the regex defaults to '" + orphans_regex_default + "'";
@@ -97,12 +115,17 @@ int main(int argc, char *argv[])
     }
     include_options_text += "Separate with \",\" to use multiple values";
 
+    std::string galaxy_product_id_install;
+    std::string galaxy_product_id_show_builds;
+
     std::vector<std::string> vFileIdStrings;
     std::vector<std::string> unrecognized_options_cfg;
     std::vector<std::string> unrecognized_options_cli;
     bpo::variables_map vm;
     bpo::options_description options_cli_all("Options");
     bpo::options_description options_cli_no_cfg;
+    bpo::options_description options_cli_no_cfg_hidden;
+    bpo::options_description options_cli_all_include_hidden;
     bpo::options_description options_cli_cfg;
     bpo::options_description options_cfg_only;
     bpo::options_description options_cfg_all("Configuration");
@@ -120,40 +143,42 @@ int main(int argc, char *argv[])
         std::string sInstallerLanguage;
         std::string sIncludeOptions;
         std::string sExcludeOptions;
-        config.bReport = false;
+        std::string sGalaxyPlatform;
+        std::string sGalaxyLanguage;
+        Globals::globalConfig.bReport = false;
         // Commandline options (no config file)
         options_cli_no_cfg.add_options()
             ("help,h", "Print help message")
             ("version", "Print version information")
             ("login", bpo::value<bool>(&bLogin)->zero_tokens()->default_value(false), "Login")
-            ("list", bpo::value<bool>(&config.bList)->zero_tokens()->default_value(false), "List games")
-            ("list-details", bpo::value<bool>(&config.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info")
-            ("download", bpo::value<bool>(&config.bDownload)->zero_tokens()->default_value(false), "Download")
-            ("repair", bpo::value<bool>(&config.bRepair)->zero_tokens()->default_value(false), "Repair downloaded files\nUse --repair --download to redownload files when filesizes don't match (possibly different version). Redownload will rename the old file (appends .old to filename)")
-            ("game", bpo::value<std::string>(&config.sGameRegex)->default_value(""), "Set regular expression filter\nfor download/list/repair (Perl syntax)\nAliases: \"all\", \"free\"\nAlias \"free\" doesn't work with cached details")
-            ("create-xml", bpo::value<std::string>(&config.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation")
-            ("update-check", bpo::value<bool>(&config.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications")
-            ("check-orphans", bpo::value<std::string>(&config.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str())
-            ("status", bpo::value<bool>(&config.bCheckStatus)->zero_tokens()->default_value(false), "Show status of files\n\nOutput format:\nstatuscode gamename filename filesize filehash\n\nStatus codes:\nOK - File is OK\nND - File is not downloaded\nMD5 - MD5 mismatch, different version\nFS - File size mismatch, incomplete download")
-            ("save-config", bpo::value<bool>(&config.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings")
-            ("reset-config", bpo::value<bool>(&config.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default")
-            ("report", bpo::value<std::string>(&config.sReportFilePath)->implicit_value("lgogdownloader-report.log"), "Save report of downloaded/repaired files to specified file\nDefault filename: lgogdownloader-report.log")
-            ("update-cache", bpo::value<bool>(&config.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache")
+            ("list", bpo::value<bool>(&Globals::globalConfig.bList)->zero_tokens()->default_value(false), "List games")
+            ("list-details", bpo::value<bool>(&Globals::globalConfig.bListDetails)->zero_tokens()->default_value(false), "List games with detailed info")
+            ("download", bpo::value<bool>(&Globals::globalConfig.bDownload)->zero_tokens()->default_value(false), "Download")
+            ("repair", bpo::value<bool>(&Globals::globalConfig.bRepair)->zero_tokens()->default_value(false), "Repair downloaded files\nUse --repair --download to redownload files when filesizes don't match (possibly different version). Redownload will rename the old file (appends .old to filename)")
+            ("game", bpo::value<std::string>(&Globals::globalConfig.sGameRegex)->default_value(""), "Set regular expression filter\nfor download/list/repair (Perl syntax)\nAliases: \"all\", \"free\"\nAlias \"free\" doesn't work with cached details")
+            ("create-xml", bpo::value<std::string>(&Globals::globalConfig.sXMLFile)->implicit_value("automatic"), "Create GOG XML for file\n\"automatic\" to enable automatic XML creation")
+            ("update-check", bpo::value<bool>(&Globals::globalConfig.bUpdateCheck)->zero_tokens()->default_value(false), "Check for update notifications")
+            ("check-orphans", bpo::value<std::string>(&Globals::globalConfig.sOrphanRegex)->implicit_value(""), check_orphans_text.c_str())
+            ("status", bpo::value<bool>(&Globals::globalConfig.bCheckStatus)->zero_tokens()->default_value(false), "Show status of files\n\nOutput format:\nstatuscode gamename filename filesize filehash\n\nStatus codes:\nOK - File is OK\nND - File is not downloaded\nMD5 - MD5 mismatch, different version\nFS - File size mismatch, incomplete download")
+            ("save-config", bpo::value<bool>(&Globals::globalConfig.bSaveConfig)->zero_tokens()->default_value(false), "Create config file with current settings")
+            ("reset-config", bpo::value<bool>(&Globals::globalConfig.bResetConfig)->zero_tokens()->default_value(false), "Reset config settings to default")
+            ("report", bpo::value<std::string>(&Globals::globalConfig.sReportFilePath)->implicit_value("lgogdownloader-report.log"), "Save report of downloaded/repaired files to specified file\nDefault filename: lgogdownloader-report.log")
+            ("update-cache", bpo::value<bool>(&Globals::globalConfig.bUpdateCache)->zero_tokens()->default_value(false), "Update game details cache")
             ("no-platform-detection", bpo::value<bool>(&bNoPlatformDetection)->zero_tokens()->default_value(false), "Don't try to detect supported platforms from game shelf.\nSkips the initial fast platform detection and detects the supported platforms from game details which is slower but more accurate.\nUseful in case platform identifier is missing for some games in the game shelf.\nUsing --platform with --list doesn't work with this option.")
-            ("download-file", bpo::value<std::string>(&config.sFileIdString)->default_value(""), "Download files using fileid\n\nFormat:\n\"gamename/fileid\"\nor: \"gogdownloader://gamename/fileid\"\n\nMultiple files:\n\"gamename1/fileid1,gamename2/fileid2\"\nor: \"gogdownloader://gamename1/fileid1,gamename2/fileid2\"\n\nThis option ignores all subdir options. The files are downloaded to directory specified with --directory option.")
-            ("output-file,o", bpo::value<std::string>(&config.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.")
-            ("wishlist", bpo::value<bool>(&config.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist")
-            ("login-api", bpo::value<bool>(&config.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)")
-            ("login-website", bpo::value<bool>(&config.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)")
-            ("cacert", bpo::value<std::string>(&config.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format")
-            ("respect-umask", bpo::value<bool>(&config.bRespectUmask)->zero_tokens()->default_value(false), "Do not adjust permissions of sensitive files")
+            ("download-file", bpo::value<std::string>(&Globals::globalConfig.sFileIdString)->default_value(""), "Download files using fileid\n\nFormat:\n\"gamename/fileid\"\nor: \"gogdownloader://gamename/fileid\"\n\nMultiple files:\n\"gamename1/fileid1,gamename2/fileid2\"\nor: \"gogdownloader://gamename1/fileid1,gamename2/fileid2\"\n\nThis option ignores all subdir options. The files are downloaded to directory specified with --directory option.")
+            ("output-file,o", bpo::value<std::string>(&Globals::globalConfig.sOutputFilename)->default_value(""), "Set filename of file downloaded with --download-file.")
+            ("wishlist", bpo::value<bool>(&Globals::globalConfig.bShowWishlist)->zero_tokens()->default_value(false), "Show wishlist")
+            ("login-api", bpo::value<bool>(&Globals::globalConfig.bLoginAPI)->zero_tokens()->default_value(false), "Login (API only)")
+            ("login-website", bpo::value<bool>(&Globals::globalConfig.bLoginHTTP)->zero_tokens()->default_value(false), "Login (website only)")
+            ("cacert", bpo::value<std::string>(&Globals::globalConfig.curlConf.sCACertPath)->default_value(""), "Path to CA certificate bundle in PEM format")
+            ("respect-umask", bpo::value<bool>(&Globals::globalConfig.bRespectUmask)->zero_tokens()->default_value(false), "Do not adjust permissions of sensitive files")
         ;
         // Commandline options (config file)
         options_cli_cfg.add_options()
-            ("directory", bpo::value<std::string>(&config.sDirectory)->default_value("."), "Set download directory")
-            ("limit-rate", bpo::value<curl_off_t>(&config.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited")
-            ("xml-directory", bpo::value<std::string>(&config.sXMLDirectory), "Set directory for GOG XML files")
-            ("chunk-size", bpo::value<size_t>(&config.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML")
+            ("directory", bpo::value<std::string>(&Globals::globalConfig.dirConf.sDirectory)->default_value("."), "Set download directory")
+            ("limit-rate", bpo::value<curl_off_t>(&Globals::globalConfig.curlConf.iDownloadRate)->default_value(0), "Limit download rate to value in kB\n0 = unlimited")
+            ("xml-directory", bpo::value<std::string>(&Globals::globalConfig.sXMLDirectory), "Set directory for GOG XML files")
+            ("chunk-size", bpo::value<size_t>(&Globals::globalConfig.iChunkSize)->default_value(10), "Chunk size (in MB) when creating XML")
             ("platform", bpo::value<std::string>(&sInstallerPlatform)->default_value("w+l"), platform_text.c_str())
             ("language", bpo::value<std::string>(&sInstallerLanguage)->default_value("en"), language_text.c_str())
             ("no-remote-xml", bpo::value<bool>(&bNoRemoteXML)->zero_tokens()->default_value(false), "Don't use remote XML for repair")
@@ -161,46 +186,56 @@ int main(int argc, char *argv[])
             ("no-color", bpo::value<bool>(&bNoColor)->zero_tokens()->default_value(false), "Don't use coloring in the progress bar or status messages")
             ("no-duplicate-handling", bpo::value<bool>(&bNoDuplicateHandler)->zero_tokens()->default_value(false), "Don't use duplicate handler for installers\nDuplicate installers from different languages are handled separately")
             ("no-subdirectories", bpo::value<bool>(&bNoSubDirectories)->zero_tokens()->default_value(false), "Don't create subdirectories for extras, patches and language packs")
-            ("verbose", bpo::value<bool>(&config.bVerbose)->zero_tokens()->default_value(false), "Print lots of information")
+            ("verbose", bpo::value<bool>(&Globals::globalConfig.bVerbose)->zero_tokens()->default_value(false), "Print lots of information")
             ("insecure", bpo::value<bool>(&bInsecure)->zero_tokens()->default_value(false), "Don't verify authenticity of SSL certificates")
-            ("timeout", bpo::value<long int>(&config.iTimeout)->default_value(10), "Set timeout for connection\nMaximum time in seconds that connection phase is allowed to take")
-            ("retries", bpo::value<int>(&config.iRetries)->default_value(3), "Set maximum number of retries on failed download")
-            ("wait", bpo::value<int>(&config.iWait)->default_value(0), "Time to wait between requests (milliseconds)")
-            ("cover-list", bpo::value<std::string>(&config.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list")
-            ("subdir-installers", bpo::value<std::string>(&config.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str())
-            ("subdir-extras", bpo::value<std::string>(&config.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str())
-            ("subdir-patches", bpo::value<std::string>(&config.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str())
-            ("subdir-language-packs", bpo::value<std::string>(&config.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str())
-            ("subdir-dlc", bpo::value<std::string>(&config.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str())
-            ("subdir-game", bpo::value<std::string>(&config.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str())
-            ("use-cache", bpo::value<bool>(&config.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache"))
-            ("cache-valid", bpo::value<int>(&config.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
-            ("save-serials", bpo::value<bool>(&config.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading")
-            ("ignore-dlc-count", bpo::value<std::string>(&config.sIgnoreDLCCountRegex)->implicit_value(".*"), "Set regular expression filter for games to ignore DLC count information\nIgnoring DLC count information helps in situations where the account page doesn't provide accurate information about DLCs")
+            ("timeout", bpo::value<long int>(&Globals::globalConfig.curlConf.iTimeout)->default_value(10), "Set timeout for connection\nMaximum time in seconds that connection phase is allowed to take")
+            ("retries", bpo::value<int>(&Globals::globalConfig.iRetries)->default_value(3), "Set maximum number of retries on failed download")
+            ("wait", bpo::value<int>(&Globals::globalConfig.iWait)->default_value(0), "Time to wait between requests (milliseconds)")
+            ("cover-list", bpo::value<std::string>(&Globals::globalConfig.sCoverList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/covers.xml"), "Set URL for cover list")
+            ("subdir-installers", bpo::value<std::string>(&Globals::globalConfig.dirConf.sInstallersSubdir)->default_value(""), ("Set subdirectory for extras" + subdir_help_text).c_str())
+            ("subdir-extras", bpo::value<std::string>(&Globals::globalConfig.dirConf.sExtrasSubdir)->default_value("extras"), ("Set subdirectory for extras" + subdir_help_text).c_str())
+            ("subdir-patches", bpo::value<std::string>(&Globals::globalConfig.dirConf.sPatchesSubdir)->default_value("patches"), ("Set subdirectory for patches" + subdir_help_text).c_str())
+            ("subdir-language-packs", bpo::value<std::string>(&Globals::globalConfig.dirConf.sLanguagePackSubdir)->default_value("languagepacks"), ("Set subdirectory for language packs" + subdir_help_text).c_str())
+            ("subdir-dlc", bpo::value<std::string>(&Globals::globalConfig.dirConf.sDLCSubdir)->default_value("dlc/%dlcname%"), ("Set subdirectory for dlc" + subdir_help_text).c_str())
+            ("subdir-game", bpo::value<std::string>(&Globals::globalConfig.dirConf.sGameSubdir)->default_value("%gamename%"), ("Set subdirectory for game" + subdir_help_text).c_str())
+            ("use-cache", bpo::value<bool>(&Globals::globalConfig.bUseCache)->zero_tokens()->default_value(false), ("Use game details cache"))
+            ("cache-valid", bpo::value<int>(&Globals::globalConfig.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
+            ("save-serials", bpo::value<bool>(&Globals::globalConfig.dlConf.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading")
+            ("ignore-dlc-count", bpo::value<std::string>(&Globals::globalConfig.sIgnoreDLCCountRegex)->implicit_value(".*"), "Set regular expression filter for games to ignore DLC count information\nIgnoring DLC count information helps in situations where the account page doesn't provide accurate information about DLCs")
             ("include", bpo::value<std::string>(&sIncludeOptions)->default_value("all"), ("Select what to download/list/repair\n" + include_options_text).c_str())
             ("exclude", bpo::value<std::string>(&sExcludeOptions)->default_value("covers"), ("Select what not to download/list/repair\n" + include_options_text).c_str())
-            ("automatic-xml-creation", bpo::value<bool>(&config.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed")
-            ("save-changelogs", bpo::value<bool>(&config.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading")
-            ("threads", bpo::value<unsigned int>(&config.iThreads)->default_value(4), "Number of download threads")
-            ("dlc-list", bpo::value<std::string>(&config.sGameHasDLCList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/game_has_dlc.txt"), "Set URL for list of games that have DLC")
+            ("automatic-xml-creation", bpo::value<bool>(&Globals::globalConfig.dlConf.bAutomaticXMLCreation)->zero_tokens()->default_value(false), "Automatically create XML data after download has completed")
+            ("save-changelogs", bpo::value<bool>(&Globals::globalConfig.dlConf.bSaveChangelogs)->zero_tokens()->default_value(false), "Save changelogs when downloading")
+            ("threads", bpo::value<unsigned int>(&Globals::globalConfig.iThreads)->default_value(4), "Number of download threads")
+            ("dlc-list", bpo::value<std::string>(&Globals::globalConfig.sGameHasDLCList)->default_value("https://raw.githubusercontent.com/Sude-/lgogdownloader-lists/master/game_has_dlc.txt"), "Set URL for list of games that have DLC")
         ;
         // Options read from config file
         options_cfg_only.add_options()
-            ("token", bpo::value<std::string>(&config.sToken)->default_value(""), "oauth token")
-            ("secret", bpo::value<std::string>(&config.sSecret)->default_value(""), "oauth secret")
+            ("token", bpo::value<std::string>(&Globals::globalConfig.apiConf.sToken)->default_value(""), "oauth token")
+            ("secret", bpo::value<std::string>(&Globals::globalConfig.apiConf.sSecret)->default_value(""), "oauth secret")
+        ;
+
+        options_cli_no_cfg_hidden.add_options()
+            ("galaxy-install", bpo::value<std::string>(&galaxy_product_id_install)->default_value(""), "Install game using product id")
+            ("galaxy-show-builds", bpo::value<std::string>(&galaxy_product_id_show_builds)->default_value(""), "Show game builds using product id")
+            ("galaxy-platform", bpo::value<std::string>(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str())
+            ("galaxy-language", bpo::value<std::string>(&sGalaxyLanguage)->default_value("en"), galaxy_language_text.c_str())
+            ("login-email", bpo::value<std::string>(&Globals::globalConfig.sEmail)->default_value(""), "login email")
+            ("login-password", bpo::value<std::string>(&Globals::globalConfig.sPassword)->default_value(""), "login password")
         ;
 
         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 +247,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 +257,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 +267,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 +277,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 +294,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 +311,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 +332,43 @@ 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 (Globals::globalConfig.sIgnoreDLCCountRegex.empty())
+        {
+            if (boost::filesystem::exists(Globals::globalConfig.sGameHasDLCListFilePath))
+            {
+                std::ifstream ifs(Globals::globalConfig.sGameHasDLCListFilePath.c_str());
+                if (!ifs)
+                {
+                    std::cerr << "Could not open list of games that have dlc: " << Globals::globalConfig.sGameHasDLCListFilePath << std::endl;
+                    return 1;
+                }
+                else
+                {
+                    std::string line;
+                    std::vector<std::string> lines;
+                    while (!ifs.eof())
+                    {
+                        std::getline(ifs, line);
+                        lines.push_back(std::move(line));
+                    }
+                    Globals::globalConfig.gamehasdlc.initialize(lines);
+                }
             }
         }
 
-        if (config.sIgnoreDLCCountRegex.empty())
+        if (bLogin || Globals::globalConfig.bLoginAPI || Globals::globalConfig.bLoginHTTP)
         {
-            if (boost::filesystem::exists(config.sGameHasDLCListFilePath))
+            std::string login_conf = Globals::globalConfig.sConfigDirectory + "/login.txt";
+            if (boost::filesystem::exists(login_conf))
             {
-                std::ifstream ifs(config.sGameHasDLCListFilePath.c_str());
+                std::ifstream ifs(login_conf);
                 if (!ifs)
                 {
-                    std::cerr << "Could not open list of games that have dlc: " << config.sGameHasDLCListFilePath << std::endl;
+                    std::cerr << "Could not open login conf: " << login_conf << std::endl;
                     return 1;
                 }
                 else
@@ -320,55 +380,57 @@ int main(int argc, char *argv[])
                         std::getline(ifs, line);
                         lines.push_back(std::move(line));
                     }
-                    config.gamehasdlc.initialize(lines);
+                    Globals::globalConfig.sEmail = lines[0];
+                    Globals::globalConfig.sPassword = lines[1];
                 }
             }
         }
 
         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 +438,18 @@ 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, Globals::globalConfig.dlConf.vLanguagePriority, Globals::globalConfig.dlConf.iInstallerLanguage, GlobalConstants::LANGUAGES);
+        Util::parseOptionString(sInstallerPlatform, Globals::globalConfig.dlConf.vPlatformPriority, Globals::globalConfig.dlConf.iInstallerPlatform, GlobalConstants::PLATFORMS);
 
-        Util::parseOptionString(sInstallerLanguage, config.vLanguagePriority, config.iInstallerLanguage, GlobalConstants::LANGUAGES);
-        Util::parseOptionString(sInstallerPlatform, config.vPlatformPriority, config.iInstallerPlatform, GlobalConstants::PLATFORMS);
+        Globals::globalConfig.dlConf.iGalaxyPlatform = Util::getOptionValue(sGalaxyPlatform, GlobalConstants::PLATFORMS);
+        Globals::globalConfig.dlConf.iGalaxyLanguage = Util::getOptionValue(sGalaxyLanguage, GlobalConstants::LANGUAGES);
 
         unsigned int include_value = 0;
         unsigned int exclude_value = 0;
@@ -398,16 +463,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 +485,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 +545,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 +581,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 +643,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 +654,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 +680,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 +697,63 @@ int main(int argc, char *argv[])
 
     int res = 0;
 
-    if (config.bShowWishlist)
+    if (Globals::globalConfig.bShowWishlist)
         downloader.showWishlist();
-    else if (config.bUpdateCache)
+    else if (Globals::globalConfig.bUpdateCache)
         downloader.updateCache();
-    else if (config.bUpdateCheck) // Update check has priority over download and list
+    else if (Globals::globalConfig.bUpdateCheck) // Update check has priority over download and list
         downloader.updateCheck();
     else if (!vFileIdStrings.empty())
     {
         for (std::vector<std::string>::iterator it = vFileIdStrings.begin(); it != vFileIdStrings.end(); it++)
         {
-            res |= downloader.downloadFileWithId(*it, config.sOutputFilename) ? 1 : 0;
+            res |= downloader.downloadFileWithId(*it, Globals::globalConfig.sOutputFilename) ? 1 : 0;
         }
     }
-    else if (config.bRepair) // Repair file
+    else if (Globals::globalConfig.bRepair) // Repair file
         downloader.repair();
-    else if (config.bDownload) // Download games
+    else if (Globals::globalConfig.bDownload) // Download games
         downloader.download();
-    else if (config.bListDetails || config.bList) // Detailed list of games/extras
+    else if (Globals::globalConfig.bListDetails || Globals::globalConfig.bList) // Detailed list of games/extras
         res = downloader.listGames();
-    else if (!config.sOrphanRegex.empty()) // Check for orphaned files if regex for orphans is set
+    else if (!Globals::globalConfig.sOrphanRegex.empty()) // Check for orphaned files if regex for orphans is set
         downloader.checkOrphans();
-    else if (config.bCheckStatus)
+    else if (Globals::globalConfig.bCheckStatus)
         downloader.checkStatus();
+    else if (!galaxy_product_id_show_builds.empty())
+    {
+        int build_index = -1;
+        std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_show_builds, "/");
+        std::string product_id = tokens[0];
+        if (tokens.size() == 2)
+        {
+            build_index = std::stoi(tokens[1]);
+        }
+        downloader.galaxyShowBuilds(product_id, build_index);
+    }
+    else if (!galaxy_product_id_install.empty())
+    {
+        int build_index = -1;
+        std::vector<std::string> tokens = Util::tokenize(galaxy_product_id_install, "/");
+        std::string product_id = tokens[0];
+        if (tokens.size() == 2)
+        {
+            build_index = std::stoi(tokens[1]);
+        }
+        downloader.galaxyInstallGame(product_id, build_index);
+    }
     else
     {
-        if (!(config.bLoginAPI || config.bLoginHTTP))
+        if (!(Globals::globalConfig.bLoginAPI || Globals::globalConfig.bLoginHTTP))
         {
             // Show help message
-            std::cerr   << config.sVersionString << std::endl
+            std::cerr   << Globals::globalConfig.sVersionString << std::endl
                         << options_cli_all << std::endl;
         }
     }
 
     // Orphan check was called at the same time as download. Perform it after download has finished
-    if (!config.sOrphanRegex.empty() && config.bDownload)
+    if (!Globals::globalConfig.sOrphanRegex.empty() && Globals::globalConfig.bDownload)
         downloader.checkOrphans();
 
     curl_global_cleanup();
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 <cstdlib>
 #include <sstream>
 #include <json/json.h>
+#include <unistd.h>
 
 #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40900
 #   define _regex_namespace_ std
@@ -150,6 +151,8 @@ int API::login(const std::string& email, const std::string& password)
         return res;
     }
 
+    usleep(500); // Wait to avoid "429 Too Many Requests"
+
     // Authorize temporary token and get verifier
     url = this->config.oauth_authorize_temp_token + "?username=" + oauth_url_escape(email.c_str()) + "&password=" + oauth_url_escape(password.c_str());
     url = oauth_sign_url2(url.c_str(), NULL, OA_HMAC, NULL, CONSUMER_KEY.c_str(), CONSUMER_SECRET.c_str(), token.c_str(), secret.c_str());
@@ -167,6 +170,8 @@ int API::login(const std::string& email, const std::string& password)
         return res;
     }
 
+    usleep(500); // Wait to avoid "429 Too Many Requests"
+
     // Get final token and secret
     url = this->config.oauth_get_token + "?oauth_verifier=" + verifier;
     url = oauth_sign_url2(url.c_str(), NULL, OA_HMAC, NULL, CONSUMER_KEY.c_str(), CONSUMER_SECRET.c_str(), token.c_str(), secret.c_str());
diff --git a/src/downloader.cpp b/src/downloader.cpp
index d0019d0..66fc36e 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 <thread>
 #include <mutex>
 
+#include <boost/iostreams/filtering_streambuf.hpp>
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/filter/zlib.hpp>
+#include <boost/iostreams/device/back_inserter.hpp>
+
 namespace bptime = boost::posix_time;
 
 std::vector<DownloadInfo> vDownloadInfo;
@@ -40,12 +45,36 @@ ThreadSafeQueue<gameItem> gameItemQueue;
 ThreadSafeQueue<gameDetails> 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,69 @@ 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::Value::LargestInt expires_in = json["expires_in"].asLargestInt();
+                json["expires_at"] = expires_in + 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 +171,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 +194,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"));
+        }
+    }
+
+    if (!gogGalaxy->init())
+    {
+        if (gogGalaxy->refreshLogin())
+        {
+            this->saveGalaxyJSON();
         }
+        else
+            return 0;
     }
-    gogWebsite->setConfig(config); // Update config for website handle
 
-    if (config.bReport && (config.bDownload || config.bRepair))
+    if (!Globals::galaxyConf.getJSON().empty())
     {
-        this->report_ofs.open(config.sReportFilePath);
+        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;
         }
     }
@@ -165,23 +247,32 @@ int Downloader::init()
 int Downloader::login()
 {
     std::string email;
-    if (!isatty(STDIN_FILENO)) {
-        std::cerr << "Unable to read email and password" << std::endl;
-        return 0;
-    }
-    std::cerr << "Email: ";
-    std::getline(std::cin,email);
-
     std::string password;
-    std::cerr << "Password: ";
-    struct termios termios_old, termios_new;
-    tcgetattr(STDIN_FILENO, &termios_old); // Get current terminal attributes
-    termios_new = termios_old;
-    termios_new.c_lflag &= ~ECHO; // Set ECHO off
-    tcsetattr(STDIN_FILENO, TCSANOW, &termios_new); // Set terminal attributes
-    std::getline(std::cin, password);
-    tcsetattr(STDIN_FILENO, TCSANOW, &termios_old); // Restore old terminal attributes
-    std::cerr << std::endl;
+
+    if (!Globals::globalConfig.sEmail.empty() && !Globals::globalConfig.sPassword.empty())
+    {
+        email = Globals::globalConfig.sEmail;
+        password = Globals::globalConfig.sPassword;
+    }
+    else
+    {
+        if (!isatty(STDIN_FILENO)) {
+            std::cerr << "Unable to read email and password" << std::endl;
+            return 0;
+        }
+        std::cerr << "Email: ";
+        std::getline(std::cin,email);
+
+        std::cerr << "Password: ";
+        struct termios termios_old, termios_new;
+        tcgetattr(STDIN_FILENO, &termios_old); // Get current terminal attributes
+        termios_new = termios_old;
+        termios_new.c_lflag &= ~ECHO; // Set ECHO off
+        tcsetattr(STDIN_FILENO, TCSANOW, &termios_new); // Set terminal attributes
+        std::getline(std::cin, password);
+        tcsetattr(STDIN_FILENO, TCSANOW, &termios_old); // Restore old terminal attributes
+        std::cerr << std::endl;
+    }
 
     if (email.empty() || password.empty())
     {
@@ -191,13 +282,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 +298,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 +319,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 +336,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 +352,7 @@ void Downloader::updateCheck()
 
 void Downloader::getGameList()
 {
-    if (config.sGameRegex == "free")
+    if (Globals::globalConfig.sGameRegex == "free")
     {
         gameItems = gogWebsite->getFreeGames();
     }
@@ -272,22 +369,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;
-
-    if (config.bUseCache && !config.bUpdateCache)
+    DirectoryConfig dirConfDefault = Globals::globalConfig.dirConf;
+
+    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 +423,14 @@ int Downloader::getGameDetails()
         }
 
         // Create threads
-        unsigned int threads = std::min(config.iThreads, static_cast<unsigned int>(gameItemQueue.size()));
+        unsigned int threads = std::min(Globals::globalConfig.iThreads, static_cast<unsigned int>(gameItemQueue.size()));
         std::vector<std::thread> 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 +446,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 +487,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 +498,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 +533,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 +554,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 +579,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 +599,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 +613,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 +631,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 +647,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 +663,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 +691,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 +711,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 +737,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 +755,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 +781,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 +819,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 +843,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 +873,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 +889,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 +924,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 +947,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 +992,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 +1024,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 +1102,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<unsigned int>(dlQueue.size()));
+        unsigned int iThreads = std::min(Globals::globalConfig.iThreads, static_cast<unsigned int>(dlQueue.size()));
 
         // Create download threads
         std::vector<std::thread> vThreads;
@@ -1021,7 +1111,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 +1131,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 +1152,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 +1269,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 +1315,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<std::string>(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 +1326,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 +1364,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 +1390,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 +1425,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 +1437,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 +1471,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 +1495,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 +1530,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 +1581,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 +1595,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 +1712,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 +1887,12 @@ std::vector<gameFile> Downloader::getExtrasFromJSON(const Json::Value& json, con
     std::vector<gameFile> 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 +2050,11 @@ static int isPresent(std::vector<gameFile>& 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 +2076,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 +2097,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 +2193,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 +2235,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 +2276,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 +2324,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 +2343,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 +2399,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 +2439,7 @@ std::vector<gameDetails> 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<std::string::const_iterator> what;
             if (!boost::regex_search(game.gamename, what, expression)) // Check if name matches the specified regex
                 continue;
@@ -2357,6 +2448,7 @@ std::vector<gameDetails> 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<std::string> nodes;
@@ -2367,13 +2459,9 @@ std::vector<gameDetails> 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 +2487,21 @@ std::vector<gameDetails> 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<gameDetails> dlcs = this->getGameDetailsFromJsonNode(fileDetailsNode, recursion_level + 1);
                         game.dlcs.insert(game.dlcs.end(), dlcs.begin(), dlcs.end());
@@ -2433,18 +2521,17 @@ std::vector<gameDetails> 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 +2660,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 +2718,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 +2738,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 +2821,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 +3022,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 +3095,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 +3109,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 +3206,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 +3243,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 +3258,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 +3269,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 +3279,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 +3292,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 +3321,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 +3331,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 +3351,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 +3360,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 +3389,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 +3414,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 +3481,384 @@ 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;
+
+    std::string sPlatform;
+    unsigned int iPlatform = Globals::globalConfig.dlConf.iGalaxyPlatform;
+    if (iPlatform == GlobalConstants::PLATFORM_LINUX)
+        sPlatform = "linux";
+    else if (iPlatform == GlobalConstants::PLATFORM_MAC)
+        sPlatform = "osx";
+    else
+        sPlatform = "windows";
+
+    std::string sLanguage = "en";
+    unsigned int iLanguage = Globals::globalConfig.dlConf.iGalaxyLanguage;
+    for (unsigned int i = 0; i < GlobalConstants::LANGUAGES.size(); ++i)
+    {
+        if (GlobalConstants::LANGUAGES[i].id == iLanguage)
+        {
+            sLanguage = GlobalConstants::LANGUAGES[i].code;
+            break;
+        }
+    }
+
+    Json::Value json = gogGalaxy->getProductBuilds(product_id, sPlatform);
+
+    // JSON is empty and platform is Linux. Most likely cause is that Galaxy API doesn't have Linux support
+    if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
+    {
+        std::cout << "Galaxy API doesn't have Linux support" << 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::string game_title = json["products"][0]["name"].asString();
+    std::string install_directory = json["installDirectory"].asString();
+    if (install_directory.empty())
+        install_directory = product_id;
+
+    std::vector<galaxyDepotItem> items;
+    for (unsigned int i = 0; i < json["depots"].size(); ++i)
+    {
+        bool bSelectedLanguage = 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 == sLanguage)
+                bSelectedLanguage = true;
+        }
+
+        if (!bSelectedLanguage)
+            continue;
+
+        std::string depotHash = json["depots"][i]["manifest"].asString();
+        std::string depot_product_id = json["depots"][i]["productId"].asString();
+
+        if (depot_product_id.empty())
+            depot_product_id = product_id;
+
+        std::vector<galaxyDepotItem> vec = gogGalaxy->getDepotItemsVector(depotHash);
+
+        // Set product id for items
+        for (auto it = vec.begin(); it != vec.end(); ++it)
+            it->product_id = depot_product_id;
+
+        items.insert(std::end(items), std::begin(vec), std::end(vec));
+    }
+
+    uintmax_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<double>(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 + install_directory + "/" + 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;
+            }
+        }
+
+        unsigned int start_chunk = 0;
+        if (boost::filesystem::exists(path))
+        {
+            std::cout << "File already exists: " << path.string() << std::endl;
+
+            unsigned int resume_chunk = 0;
+            uintmax_t filesize = boost::filesystem::file_size(path);
+            if (filesize == items[i].totalSizeUncompressed)
+            {
+                // File is same size
+                if (Util::getFileHash(path.string(), RHASH_MD5) == items[i].md5)
+                {
+                    std::cout << "\tOK" << std::endl;
+                    continue;
+                }
+                else
+                {
+                    std::cout << "\tMD5 mismatch" << std::endl;
+                    if (!boost::filesystem::remove(path))
+                    {
+                        std::cerr << "\tFailed to delete " << path << std::endl;
+                        continue;
+                    }
+                }
+            }
+            else if (filesize > items[i].totalSizeUncompressed)
+            {
+                // File is bigger than on server, delete old file and start from beginning
+                std::cout << "\tFile is bigger than expected. Deleting old file and starting from beginning." << std::endl;
+                if (!boost::filesystem::remove(path))
+                {
+                    std::cerr << "\tFailed to delete " << path << std::endl;
+                    continue;
+                }
+            }
+            else
+            {
+                // File is smaller than on server, resume
+                for (unsigned int j = 0; j < items[i].chunks.size(); ++j)
+                {
+                    if (items[i].chunks[j].offset_uncompressed == filesize)
+                    {
+                        resume_chunk = j;
+                        break;
+                    }
+                }
+
+                if (resume_chunk > 0)
+                {
+                    std::cout << "\tResume from chunk " << resume_chunk << std::endl;
+                    // Get chunk hash for previous chunk
+                    FILE* f = fopen(path.string().c_str(), "r");
+                    if (!f)
+                    {
+                        std::cerr << "\tFailed to open: " << path << std::endl;
+                        continue;
+                    }
+
+                    unsigned int previous_chunk = resume_chunk - 1;
+                    uintmax_t chunk_size = items[i].chunks[previous_chunk].size_uncompressed;
+                    // use fseeko to support large files on 32 bit platforms
+                    fseeko(f, items[i].chunks[previous_chunk].offset_uncompressed, SEEK_SET);
+                    unsigned char *chunk = (unsigned char *) malloc(chunk_size * sizeof(unsigned char *));
+                    if (chunk == NULL)
+                    {
+                        std::cerr << "Memory error" << std::endl;
+                        fclose(f);
+                        continue;
+                    }
+
+                    uintmax_t fread_size = fread(chunk, 1, chunk_size, f);
+                    fclose(f);
+
+                    if (fread_size != chunk_size)
+                    {
+                        std::cerr << "Read error" << std::endl;
+                        free(chunk);
+                        continue;
+                    }
+                    std::string chunk_hash = Util::getChunkHash(chunk, chunk_size, RHASH_MD5);
+                    free(chunk);
+
+                    if (chunk_hash == items[i].chunks[previous_chunk].md5_uncompressed)
+                    {
+                        // Hash for previous chunk matches, resume at this position
+                        start_chunk = resume_chunk;
+                    }
+                    else
+                    {
+                        // Hash for previous chunk is different, delete old file and start from beginning
+                        std::cout << "\tChunk hash is different. Deleting old file and starting from beginning." << std::endl;
+                        if (!boost::filesystem::remove(path))
+                        {
+                            std::cerr << "\tFailed to delete " << path << std::endl;
+                            continue;
+                        }
+                    }
+                }
+                else
+                {
+                    std::cout << "\tFailed to find valid resume position. Deleting old file and starting from beginning." << std::endl;
+                    if (!boost::filesystem::remove(path))
+                    {
+                        std::cerr << "\tFailed to delete " << path << std::endl;
+                        continue;
+                    }
+                }
+            }
+        }
+
+        for (unsigned int j = start_chunk; j < items[i].chunks.size(); ++j)
+        {
+            ChunkMemoryStruct chunk;
+            chunk.memory = (char *) malloc(1);
+            chunk.size = 0;
+
+            json = gogGalaxy->getSecureLink(items[i].product_id, gogGalaxy->hashToGalaxyPath(items[i].chunks[j].md5_compressed));
+
+            // Prefer edgecast urls
+            bool bPreferEdgecast = true;
+            unsigned int idx = 0;
+            for (unsigned int k = 0; k < json["urls"].size(); ++k)
+            {
+                std::string endpoint_name = json["urls"][k]["endpoint_name"].asString();
+                if (bPreferEdgecast)
+                {
+                    if (endpoint_name == "edgecast")
+                    {
+                        idx = k;
+                        break;
+                    }
+                }
+            }
+
+            // Build url according to url_format
+            std::string link_base_url = json["urls"][idx]["parameters"]["base_url"].asString();
+            std::string link_path = json["urls"][idx]["parameters"]["path"].asString();
+            std::string link_token = json["urls"][idx]["parameters"]["token"].asString();
+            std::string url = json["urls"][idx]["url_format"].asString();
+
+            while(Util::replaceString(url, "{base_url}", link_base_url));
+            while(Util::replaceString(url, "{path}", link_path));
+            while(Util::replaceString(url, "{token}", link_token));
+
+            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<boost::iostreams::output> 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)
+{
+    std::string sPlatform;
+    unsigned int iPlatform = Globals::globalConfig.dlConf.iGalaxyPlatform;
+    if (iPlatform == GlobalConstants::PLATFORM_LINUX)
+        sPlatform = "linux";
+    else if (iPlatform == GlobalConstants::PLATFORM_MAC)
+        sPlatform = "osx";
+    else
+        sPlatform = "windows";
+
+    Json::Value json = gogGalaxy->getProductBuilds(product_id, sPlatform);
+
+    // JSON is empty and platform is Linux. Most likely cause is that Galaxy API doesn't have Linux support
+    if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
+    {
+        std::cout << "Galaxy API doesn't have Linux support" << std::endl;
+        return;
+    }
+
+    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..a06c6b0
--- /dev/null
+++ b/src/galaxyapi.cpp
@@ -0,0 +1,256 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
+#include "galaxyapi.h"
+
+#include <boost/iostreams/filtering_streambuf.hpp>
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/filter/zlib.hpp>
+#include <boost/iostreams/device/back_inserter.hpp>
+
+GalaxyConfig Globals::galaxyConf;
+
+size_t galaxyAPI::writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp) {
+    std::ostringstream *stream = (std::ostringstream*)userp;
+    std::streamsize count = (std::streamsize) size * nmemb;
+    stream->write(ptr, count);
+    return count;
+}
+
+galaxyAPI::galaxyAPI(CurlConfig& conf)
+{
+    this->curlConf = conf;
+
+    curlhandle = curl_easy_init();
+    curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
+    curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+    curl_easy_setopt(curlhandle, CURLOPT_PROGRESSDATA, this);
+    curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
+    curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
+    curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, curlConf.iTimeout);
+    curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
+    curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, curlConf.sCookiePath.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, curlConf.sCookiePath.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, curlConf.bVerifyPeer);
+    curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, curlConf.bVerbose);
+    curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, curlConf.iDownloadRate);
+
+    // Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
+    curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
+    curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
+
+    if (!curlConf.sCACertPath.empty())
+        curl_easy_setopt(curlhandle, CURLOPT_CAINFO, curlConf.sCACertPath.c_str());
+}
+
+galaxyAPI::~galaxyAPI()
+{
+    curl_easy_cleanup(curlhandle);
+}
+
+/* Initialize the API
+    returns 0 if failed
+    returns 1 if successful
+*/
+int galaxyAPI::init()
+{
+    int res = 0;
+
+    if (!this->isTokenExpired())
+    {
+        res = 1;
+    }
+    else
+        res = 0;
+
+    return res;
+}
+
+bool galaxyAPI::refreshLogin()
+{
+    bool res = false;
+    std::string refresh_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+                            + "&client_secret=" + Globals::galaxyConf.getClientSecret()
+                            + "&grant_type=refresh_token"
+                            + "&refresh_token=" + Globals::galaxyConf.getRefreshToken();
+
+    std::string json = this->getResponse(refresh_url);
+    if (!json.empty())
+    {
+        Json::Value token_json;
+        Json::Reader *jsonparser = new Json::Reader;
+        if (jsonparser->parse(json, token_json))
+        {
+            Globals::galaxyConf.setJSON(token_json);
+            res = true;
+        }
+        delete jsonparser;
+    }
+
+    return res;
+}
+
+bool galaxyAPI::isTokenExpired()
+{
+    int res = false;
+
+    if (Globals::galaxyConf.isExpired())
+        res = true;
+
+    return res;
+}
+
+std::string galaxyAPI::getResponse(const std::string& url, const bool& zlib_decompress)
+{
+    std::ostringstream memory;
+
+    struct curl_slist *header = NULL;
+
+    std::string access_token;
+    if (!Globals::galaxyConf.isExpired())
+        access_token = Globals::galaxyConf.getAccessToken();
+    if (!access_token.empty())
+    {
+        std::string bearer = "Authorization: Bearer " + access_token;
+        header = curl_slist_append(header, bearer.c_str());
+    }
+    curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, header);
+
+    curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+    curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, galaxyAPI::writeMemoryCallback);
+    curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+    curl_easy_perform(curlhandle);
+    std::string response = memory.str();
+    memory.str(std::string());
+
+    curl_easy_setopt(curlhandle, CURLOPT_HTTPHEADER, NULL);
+    curl_slist_free_all(header);
+
+    if (zlib_decompress)
+    {
+        std::string response_decompressed;
+        boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
+        in.push(boost::iostreams::zlib_decompressor(GlobalConstants::ZLIB_WINDOW_SIZE));
+        in.push(boost::make_iterator_range(response));
+        boost::iostreams::copy(in, boost::iostreams::back_inserter(response_decompressed));
+        response = response_decompressed;
+    }
+
+    return response;
+}
+
+Json::Value galaxyAPI::getProductBuilds(const std::string& product_id, const std::string& platform, const std::string& generation)
+{
+    Json::Value json;
+
+    std::string url = "https://content-system.gog.com/products/" + product_id + "/os/" + platform + "/builds?generation=" + generation;
+    std::string response = this->getResponse(url);
+
+    Json::Reader *jsonparser = new Json::Reader;
+    jsonparser->parse(response, json);
+    delete jsonparser;
+
+    return json;
+}
+
+Json::Value galaxyAPI::getManifestV1(const std::string& product_id, const std::string& build_id, const std::string& manifest_id, const std::string& platform)
+{
+    Json::Value json;
+
+    std::string url = "https://cdn.gog.com/content-system/v1/manifests/" + product_id + "/" + platform + "/" + build_id + "/" + manifest_id + ".json";
+    std::string response = this->getResponse(url);
+
+    Json::Reader *jsonparser = new Json::Reader;
+    jsonparser->parse(response, json);
+    delete jsonparser;
+
+    return json;
+}
+
+Json::Value galaxyAPI::getManifestV2(std::string manifest_hash)
+{
+    Json::Value json;
+
+    if (!manifest_hash.empty() && manifest_hash.find("/") == std::string::npos)
+        manifest_hash = this->hashToGalaxyPath(manifest_hash);
+
+    std::string url = "https://cdn.gog.com/content-system/v2/meta/" + manifest_hash;
+    std::string response = this->getResponse(url, true);
+
+    Json::Reader *jsonparser = new Json::Reader;
+    jsonparser->parse(response, json);
+    delete jsonparser;
+
+    return json;
+}
+
+Json::Value galaxyAPI::getSecureLink(const std::string& product_id, const std::string& path)
+{
+    Json::Value json;
+
+    std::string url = "https://content-system.gog.com/products/" + product_id + "/secure_link?generation=2&path=" + path + "&_version=2";
+    std::string response = this->getResponse(url);
+
+    Json::Reader *jsonparser = new Json::Reader;
+    jsonparser->parse(response, json);
+    delete jsonparser;
+
+    return json;
+}
+
+std::string galaxyAPI::hashToGalaxyPath(const std::string& hash)
+{
+    std::string galaxy_path = hash;
+    if (galaxy_path.find("/") == std::string::npos)
+        galaxy_path.assign(hash.begin(), hash.begin()+2).append("/").append(hash.begin()+2, hash.begin()+4).append("/").append(hash);
+
+    return galaxy_path;
+}
+
+std::vector<galaxyDepotItem> galaxyAPI::getDepotItemsVector(const std::string& hash)
+{
+    Json::Value json = this->getManifestV2(hash);
+
+    std::vector<galaxyDepotItem> items;
+
+    for (unsigned int i = 0; i < json["depot"]["items"].size(); ++i)
+    {
+        if (!json["depot"]["items"][i]["chunks"].empty())
+        {
+            galaxyDepotItem item;
+            item.totalSizeCompressed = 0;
+            item.totalSizeUncompressed = 0;
+            item.path = json["depot"]["items"][i]["path"].asString();
+
+            while (Util::replaceString(item.path, "\\", "/"));
+            for (unsigned int j = 0; j < json["depot"]["items"][i]["chunks"].size(); ++j)
+            {
+                galaxyDepotItemChunk chunk;
+                chunk.md5_compressed = json["depot"]["items"][i]["chunks"][j]["compressedMd5"].asString();
+                chunk.md5_uncompressed = json["depot"]["items"][i]["chunks"][j]["md5"].asString();
+                chunk.size_compressed = json["depot"]["items"][i]["chunks"][j]["compressedSize"].asLargestUInt();
+                chunk.size_uncompressed = json["depot"]["items"][i]["chunks"][j]["size"].asLargestUInt();
+
+                chunk.offset_compressed = item.totalSizeCompressed;
+                chunk.offset_uncompressed = item.totalSizeUncompressed;
+
+                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<gameFile>& list, const ga
     for (std::vector<gameFile>::iterator fileDetails = list.begin(); fileDetails != list.end(); fileDetails++)
         {
             fileDetails->score = 0;
-            if (!config.vPlatformPriority.empty())
+            if (!config.dlConf.vPlatformPriority.empty())
                 {
-                    for (size_t i = 0; i != config.vPlatformPriority.size(); i++)
-                        if (fileDetails->platform & config.vPlatformPriority[i])
+                    for (size_t i = 0; i != config.dlConf.vPlatformPriority.size(); i++)
+                        if (fileDetails->platform & config.dlConf.vPlatformPriority[i])
                             {
                                 fileDetails->score += i;
                                 break;
                             }
                 }
-            if (!config.vLanguagePriority.empty())
+            if (!config.dlConf.vLanguagePriority.empty())
                 {
-                    for (size_t i = 0; i != config.vLanguagePriority.size(); i++)
-                        if (fileDetails->language & config.vLanguagePriority[i])
+                    for (size_t i = 0; i != config.dlConf.vLanguagePriority.size(); i++)
+                        if (fileDetails->language & config.dlConf.vLanguagePriority[i])
                             {
                                 fileDetails->score += i;
                                 break;
@@ -71,7 +71,7 @@ void gameDetails::filterListWithPriorities(std::vector<gameFile>& list, const ga
         }
 }
 
-void gameDetails::makeFilepaths(const gameSpecificDirectoryConfig& config)
+void gameDetails::makeFilepaths(const DirectoryConfig& config)
 {
     std::string filepath;
     std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
@@ -147,6 +147,7 @@ Json::Value gameDetails::getDetailsAsJson()
     Json::Value json;
 
     json["gamename"] = this->gamename;
+    json["product_id"] = this->product_id;
     json["title"] = this->title;
     json["icon"] = this->icon;
     json["serials"] = this->serials;
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 0803432..307e3cb 100644
--- a/src/website.cpp
+++ b/src/website.cpp
@@ -10,30 +10,29 @@
 #include <htmlcxx/html/ParserDom.h>
 #include <boost/algorithm/string/case_conv.hpp>
 
-Website::Website(Config &conf)
+Website::Website()
 {
-    this->config = conf;
     this->retries = 0;
 
     curlhandle = curl_easy_init();
     curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
-    curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, Globals::globalConfig.sVersionString.c_str());
     curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
     curl_easy_setopt(curlhandle, CURLOPT_NOSIGNAL, 1);
-    curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
+    curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, Globals::globalConfig.curlConf.iTimeout);
     curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
-    curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, config.sCookiePath.c_str());
-    curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, config.sCookiePath.c_str());
-    curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer);
-    curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, config.bVerbose);
-    curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, config.iDownloadRate);
+    curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, Globals::globalConfig.curlConf.sCookiePath.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, Globals::globalConfig.curlConf.sCookiePath.c_str());
+    curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, Globals::globalConfig.curlConf.bVerifyPeer);
+    curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, Globals::globalConfig.curlConf.bVerbose);
+    curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, Globals::globalConfig.curlConf.iDownloadRate);
 
     // Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
     curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
     curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
 
-    if (!config.sCACertPath.empty())
-        curl_easy_setopt(curlhandle, CURLOPT_CAINFO, config.sCACertPath.c_str());
+    if (!Globals::globalConfig.curlConf.sCACertPath.empty())
+        curl_easy_setopt(curlhandle, CURLOPT_CAINFO, Globals::globalConfig.curlConf.sCACertPath.c_str());
 }
 
 Website::~Website()
@@ -61,13 +60,13 @@ std::string Website::getResponse(const std::string& url)
     CURLcode result;
     do
     {
-        if (config.iWait > 0)
-            usleep(config.iWait); // Delay the request by specified time
+        if (Globals::globalConfig.iWait > 0)
+            usleep(Globals::globalConfig.iWait); // Delay the request by specified time
         result = curl_easy_perform(curlhandle);
         response = memory.str();
         memory.str(std::string());
     }
-    while ((result != CURLE_OK) && response.empty() && (this->retries++ < config.iRetries));
+    while ((result != CURLE_OK) && response.empty() && (this->retries++ < Globals::globalConfig.iRetries));
     this->retries = 0; // reset retries counter
 
     if (result != CURLE_OK)
@@ -163,7 +162,31 @@ std::vector<gameItem> Website::getGames()
                 gameItem game;
                 game.name = product["slug"].asString();
                 game.id = product["id"].isInt() ? std::to_string(product["id"].asInt()) : product["id"].asString();
-                game.updates = product["updates"].isInt() ? product["updates"].asInt() : std::stoi(product["updates"].asString());
+
+                if (product.isMember("updates"))
+                {
+                    if (product["updates"].isNull())
+                    {
+                        /* In some cases the value can be null.
+                         * For example when user owns a dlc but not the base game
+                         * https://github.com/Sude-/lgogdownloader/issues/101
+                         * Assume that there are no updates in this case */
+                        game.updates = 0;
+                    }
+                    else if (product["updates"].isInt())
+                        game.updates = product["updates"].asInt();
+                    else
+                    {
+                        try
+                        {
+                            game.updates = std::stoi(product["updates"].asString());
+                        }
+                        catch (std::invalid_argument& e)
+                        {
+                            game.updates = 0; // Assume no updates
+                        }
+                    }
+                }
 
                 unsigned int platform = 0;
                 if (product["worksOn"]["Windows"].asBool())
@@ -174,31 +197,31 @@ std::vector<gameItem> Website::getGames()
                     platform |= GlobalConstants::PLATFORM_LINUX;
 
                 // Skip if platform doesn't match
-                if (config.bPlatformDetection && !(platform & config.iInstallerPlatform))
+                if (Globals::globalConfig.bPlatformDetection && !(platform & Globals::globalConfig.dlConf.iInstallerPlatform))
                     continue;
 
                 // Filter the game list
-                if (!config.sGameRegex.empty())
+                if (!Globals::globalConfig.sGameRegex.empty())
                 {
                     // GameRegex filter aliases
-                    if (config.sGameRegex == "all")
-                        config.sGameRegex = ".*";
+                    if (Globals::globalConfig.sGameRegex == "all")
+                        Globals::globalConfig.sGameRegex = ".*";
 
-                    boost::regex expression(config.sGameRegex);
+                    boost::regex expression(Globals::globalConfig.sGameRegex);
                     boost::match_results<std::string::const_iterator> what;
                     if (!boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
                         continue;
                 }
 
-                if (config.bDLC)
+                if (Globals::globalConfig.dlConf.bDLC)
                 {
                     int dlcCount = product["dlcCount"].asInt();
 
                     bool bDownloadDLCInfo = (dlcCount != 0);
 
-                    if (!bDownloadDLCInfo && !config.sIgnoreDLCCountRegex.empty())
+                    if (!bDownloadDLCInfo && !Globals::globalConfig.sIgnoreDLCCountRegex.empty())
                     {
-                        boost::regex expression(config.sIgnoreDLCCountRegex);
+                        boost::regex expression(Globals::globalConfig.sIgnoreDLCCountRegex);
                         boost::match_results<std::string::const_iterator> what;
                         if (boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
                         {
@@ -206,25 +229,25 @@ std::vector<gameItem> Website::getGames()
                         }
                     }
 
-                    if (!bDownloadDLCInfo && !config.gamehasdlc.empty())
+                    if (!bDownloadDLCInfo && !Globals::globalConfig.gamehasdlc.empty())
                     {
-                        if (config.gamehasdlc.isBlacklisted(game.name))
+                        if (Globals::globalConfig.gamehasdlc.isBlacklisted(game.name))
                             bDownloadDLCInfo = true;
                     }
 
                     // Check game specific config
-                    if (!config.bUpdateCache) // Disable game specific config files for cache update
+                    if (!Globals::globalConfig.bUpdateCache) // Disable game specific config files for cache update
                     {
                         gameSpecificConfig conf;
-                        conf.bIgnoreDLCCount = bDownloadDLCInfo;
+                        conf.dlConf.bIgnoreDLCCount = bDownloadDLCInfo;
                         Util::getGameSpecificConfig(game.name, &conf);
-                        bDownloadDLCInfo = conf.bIgnoreDLCCount;
+                        bDownloadDLCInfo = conf.dlConf.bIgnoreDLCCount;
                     }
 
-                    if (bDownloadDLCInfo && !config.sGameRegex.empty())
+                    if (bDownloadDLCInfo && !Globals::globalConfig.sGameRegex.empty())
                     {
                         // don't download unnecessary info if user is only interested in a subset of his account
-                        boost::regex expression(config.sGameRegex);
+                        boost::regex expression(Globals::globalConfig.sGameRegex);
                         boost::match_results<std::string::const_iterator> what;
                         if (!boost::regex_search(game.name, what, expression))
                         {
@@ -293,98 +316,42 @@ 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;
+
+    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 \"" << Globals::globalConfig.curlConf.sCookiePath << "\"" << std::endl;
+        return res = 0;
+    }
 
-    // Get login token
-    std::string html = this->getResponse("https://www.gog.com/");
     htmlcxx::HTML::ParserDom parser;
-    tree<htmlcxx::HTML::Node> dom = parser.parseTree(html);
-    tree<htmlcxx::HTML::Node>::iterator it = dom.begin();
-    tree<htmlcxx::HTML::Node>::iterator end = dom.end();
-    // Find auth_url
-    bool bFoundAuthUrl = false;
-    for (; it != end; ++it)
+    tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
+    tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
+    tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
+    for (; login_it != login_it_end; ++login_it)
     {
-        if (it->tagName()=="script")
+        if (login_it->tagName()=="input")
         {
-            std::string auth_url;
-            for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
+            login_it->parseAttributes();
+            if (login_it->attribute("id").second == "login__token")
             {
-                tree<htmlcxx::HTML::Node>::iterator script_it = dom.child(it, i);
-                if (!script_it->isTag() && !script_it->isComment())
-                {
-                    if (script_it->text().find("GalaxyAccounts") != std::string::npos)
-                    {
-                        boost::match_results<std::string::const_iterator> what;
-                        boost::regex expression(".*'(https://auth.gog.com/.*?)'.*");
-                        boost::regex_match(script_it->text(), what, expression);
-                        auth_url = what[1];
-                        break;
-                    }
-                }
-            }
-
-            if (!auth_url.empty())
-            {   // Found auth_url, get the necessary info for login
-                bFoundAuthUrl = true;
-                std::string login_form_html = this->getResponse(auth_url);
-                #ifdef DEBUG
-                    std::cerr << "DEBUG INFO (Website::Login)" << std::endl;
-                    std::cerr << login_form_html << std::endl;
-                #endif
-                if (login_form_html.find("google.com/recaptcha") != std::string::npos)
-                {
-                    std::cout   << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
-                                << "Login with browser and export cookies to \"" << config.sCookiePath << "\"" << std::endl;
-                    return res = 0;
-                }
-
-                tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
-                tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
-                tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
-                for (; login_it != login_it_end; ++login_it)
-                {
-                    if (login_it->tagName()=="input")
-                    {
-                        login_it->parseAttributes();
-                        std::string id_login = login_it->attribute("id").second;
-                        if (id_login == "login_username")
-                        {
-                            tagname_username = login_it->attribute("name").second;
-                        }
-                        else if (id_login == "login_password")
-                        {
-                            tagname_password = login_it->attribute("name").second;
-                        }
-                        else if (id_login == "login__token")
-                        {
-                            token = login_it->attribute("value").second; // login token
-                            tagname_token = login_it->attribute("name").second;
-                        }
-                    }
-                    else if (login_it->tagName()=="button")
-                    {
-                        login_it->parseAttributes();
-                        std::string id_login = login_it->attribute("id").second;
-                        if (id_login == "login_login")
-                        {
-                            tagname_login = login_it->attribute("name").second;
-                        }
-                    }
-                }
-                break;
+                token = login_it->attribute("value").second; // login token
+                tagname_token = login_it->attribute("name").second;
             }
         }
     }
 
-    if (!bFoundAuthUrl)
-    {
-        std::cout << "Failed to find url for login form" << std::endl;
-    }
-
     if (token.empty())
     {
         std::cout << "Failed to get login token" << std::endl;
@@ -424,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;
 
@@ -436,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)
@@ -476,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]
@@ -499,6 +449,31 @@ int Website::Login(const std::string& email, const std::string& password)
         curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
     }
 
+    if (!std::string(redirect_url).empty())
+    {
+        long response_code;
+        do
+        {
+            curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
+            result = curl_easy_perform(curlhandle);
+            memory.str(std::string());
+
+            result = curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
+            if ((response_code / 100) == 3)
+                curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
+
+            std::string redir_url = std::string(redirect_url);
+            boost::regex re(".*code=(.*?)([\?&].*|$)", boost::regex_constants::icase);
+            boost::match_results<std::string::const_iterator> what;
+            if (boost::regex_search(redir_url, what, re))
+            {
+                auth_code = what[1];
+                if (!auth_code.empty())
+                    break;
+            }
+        } while (result == CURLE_OK && (response_code / 100) == 3);
+    }
+
     curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
     curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
     curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
@@ -520,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;
 }
 
@@ -653,7 +658,7 @@ std::vector<wishlistItem> Website::getWishlistItems()
                         item.platform |= GlobalConstants::PLATFORM_LINUX;
 
                     // Skip if platform doesn't match
-                    if (config.bPlatformDetection && !(item.platform & config.iInstallerPlatform))
+                    if (Globals::globalConfig.bPlatformDetection && !(item.platform & Globals::globalConfig.dlConf.iInstallerPlatform))
                         continue;
                 }
 
@@ -717,8 +722,3 @@ std::vector<wishlistItem> Website::getWishlistItems()
 
     return wishlistItems;
 }
-
-void Website::setConfig(Config &conf)
-{
-    this->config = conf;
-}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/lgogdownloader.git



More information about the Pkg-games-commits mailing list