diff --git a/include/config.h b/include/config.h index 1159cfa..980efe3 100644 --- a/include/config.h +++ b/include/config.h @@ -51,6 +51,7 @@ struct DownloadConfig bool bIgnoreDLCCount; bool bDuplicateHandler; + bool bGalaxyDependencies; }; struct gameSpecificConfig diff --git a/include/galaxyapi.h b/include/galaxyapi.h index 21fafa5..178dafc 100644 --- a/include/galaxyapi.h +++ b/include/galaxyapi.h @@ -37,6 +37,7 @@ struct galaxyDepotItem uintmax_t totalSizeUncompressed; std::string md5; std::string product_id; + bool isDependency = false; }; class galaxyAPI @@ -50,15 +51,18 @@ class galaxyAPI Json::Value getProductBuilds(const std::string& product_id, const std::string& platform = "windows", const std::string& generation = "2"); Json::Value getManifestV1(const std::string& product_id, const std::string& build_id, const std::string& manifest_id = "repository", const std::string& platform = "windows"); Json::Value getManifestV1(const std::string& manifest_url); - Json::Value getManifestV2(std::string manifest_hash); + Json::Value getManifestV2(std::string manifest_hash, const bool& is_dependency = false); Json::Value getSecureLink(const std::string& product_id, const std::string& path); + Json::Value getDependencyLink(const std::string& path); std::string getResponse(const std::string& url, const bool& zlib_decompress = false); Json::Value getResponseJson(const std::string& url, const bool& zlib_decompress = false); std::string hashToGalaxyPath(const std::string& hash); - std::vector getDepotItemsVector(const std::string& hash); + std::vector getDepotItemsVector(const std::string& hash, const bool& is_dependency = false); Json::Value getProductInfo(const std::string& product_id); gameDetails productInfoJsonToGameDetails(const Json::Value& json, const DownloadConfig& dlConf); Json::Value getUserData(); + Json::Value getDependenciesJson(); + std::vector getFilteredDepotItemsVectorFromJson(const Json::Value& depot_json, const std::string& galaxy_language, const std::string& galaxy_arch, const bool& is_dependency = false); protected: private: CurlConfig curlConf; diff --git a/main.cpp b/main.cpp index d60fc02..8b53afb 100644 --- a/main.cpp +++ b/main.cpp @@ -144,6 +144,7 @@ int main(int argc, char *argv[]) bool bNoSubDirectories = false; bool bNoPlatformDetection = false; bool bLogin = false; + bool bNoGalaxyDependencies = false; std::string sInstallerPlatform; std::string sInstallerLanguage; std::string sIncludeOptions; @@ -237,6 +238,7 @@ int main(int argc, char *argv[]) ("galaxy-platform", bpo::value(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str()) ("galaxy-language", bpo::value(&sGalaxyLanguage)->default_value("en"), galaxy_language_text.c_str()) ("galaxy-arch", bpo::value(&sGalaxyArch)->default_value("x64"), galaxy_arch_text.c_str()) + ("galaxy-no-dependencies", bpo::value(&bNoGalaxyDependencies)->zero_tokens()->default_value(false), "Don't download dependencies during --galaxy-install") ; options_cli_all.add(options_cli_no_cfg).add(options_cli_cfg).add(options_cli_experimental); @@ -436,6 +438,7 @@ int main(int argc, char *argv[]) Globals::globalConfig.dlConf.bRemoteXML = !bNoRemoteXML; Globals::globalConfig.dirConf.bSubDirectories = !bNoSubDirectories; Globals::globalConfig.bPlatformDetection = !bNoPlatformDetection; + Globals::globalConfig.dlConf.bGalaxyDependencies = !bNoGalaxyDependencies; 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) diff --git a/src/downloader.cpp b/src/downloader.cpp index 993825b..279721b 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -3323,48 +3323,49 @@ void Downloader::galaxyInstallGame(const std::string& product_id, int build_inde std::vector items; for (unsigned int i = 0; i < json["depots"].size(); ++i) { - bool bSelectedLanguage = false; - bool bSelectedArch = false; - boost::regex language_re("^(" + sLanguageRegex + ")$", boost::regex::perl | boost::regex::icase); - boost::match_results what; - for (unsigned int j = 0; j < json["depots"][i]["languages"].size(); ++j) - { - std::string language = json["depots"][i]["languages"][j].asString(); - if (language == "*" || boost::regex_search(language, what, language_re)) - bSelectedLanguage = true; - } + std::vector vec = gogGalaxy->getFilteredDepotItemsVectorFromJson(json["depots"][i], sLanguageRegex, sGalaxyArch); - if (json["depots"][i].isMember("osBitness")) + if (!vec.empty()) + items.insert(std::end(items), std::begin(vec), std::end(vec)); + } + + // Add dependency ids to vector + std::vector dependencies; + if (json.isMember("dependencies") && Globals::globalConfig.dlConf.bGalaxyDependencies) + { + for (unsigned int i = 0; i < json["dependencies"].size(); ++i) { - for (unsigned int j = 0; j < json["depots"][i]["osBitness"].size(); ++j) + dependencies.push_back(json["dependencies"][i].asString()); + } + } + + // Add dependencies to items vector + if (!dependencies.empty()) + { + Json::Value dependenciesJson = gogGalaxy->getDependenciesJson(); + if (!dependenciesJson.empty() && dependenciesJson.isMember("depots")) + { + for (unsigned int i = 0; i < dependenciesJson["depots"].size(); ++i) { - std::string osBitness = json["depots"][i]["osBitness"][j].asString(); - if (osBitness == "*" || osBitness == sGalaxyArch) - bSelectedArch = true; + std::string dependencyId = dependenciesJson["depots"][i]["dependencyId"].asString(); + if (std::any_of(dependencies.begin(), dependencies.end(), [dependencyId](std::string dependency){return dependency == dependencyId;})) + { + std::vector vec = gogGalaxy->getFilteredDepotItemsVectorFromJson(dependenciesJson["depots"][i], sLanguageRegex, sGalaxyArch, true); + + if (!vec.empty()) + items.insert(std::end(items), std::begin(vec), std::end(vec)); + } } } - else + } + + // Set product id for items + for (auto it = items.begin(); it != items.end(); ++it) + { + if (it->product_id.empty()) { - // No osBitness found, assume that we want to download this depot - bSelectedArch = true; + it->product_id = product_id; } - - if (!bSelectedLanguage || !bSelectedArch) - 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 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; @@ -3626,7 +3627,11 @@ void Downloader::processGalaxyDownloadQueue(const std::string& install_path, Con } } - Json::Value json = galaxy->getSecureLink(item.product_id, galaxy->hashToGalaxyPath(item.chunks[j].md5_compressed)); + Json::Value json; + if (item.isDependency) + json = galaxy->getDependencyLink(galaxy->hashToGalaxyPath(item.chunks[j].md5_compressed)); + else + json = galaxy->getSecureLink(item.product_id, galaxy->hashToGalaxyPath(item.chunks[j].md5_compressed)); // Prefer edgecast urls bool bPreferEdgecast = true; diff --git a/src/galaxyapi.cpp b/src/galaxyapi.cpp index 4759b4f..edf64a5 100644 --- a/src/galaxyapi.cpp +++ b/src/galaxyapi.cpp @@ -176,12 +176,16 @@ Json::Value galaxyAPI::getManifestV1(const std::string& manifest_url) return this->getResponseJson(manifest_url); } -Json::Value galaxyAPI::getManifestV2(std::string manifest_hash) +Json::Value galaxyAPI::getManifestV2(std::string manifest_hash, const bool& is_dependency) { 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 url; + if (is_dependency) + url = "https://cdn.gog.com/content-system/v2/dependencies/meta/" + manifest_hash; + else + url = "https://cdn.gog.com/content-system/v2/meta/" + manifest_hash; return this->getResponseJson(url, true); } @@ -193,6 +197,14 @@ Json::Value galaxyAPI::getSecureLink(const std::string& product_id, const std::s return this->getResponseJson(url); } +Json::Value galaxyAPI::getDependencyLink(const std::string& path) +{ + std::string url = "https://content-system.gog.com/open_link?generation=2&_version=2&path=/dependencies/store/" + path; + + return this->getResponseJson(url); +} + + std::string galaxyAPI::hashToGalaxyPath(const std::string& hash) { std::string galaxy_path = hash; @@ -202,9 +214,9 @@ std::string galaxyAPI::hashToGalaxyPath(const std::string& hash) return galaxy_path; } -std::vector galaxyAPI::getDepotItemsVector(const std::string& hash) +std::vector galaxyAPI::getDepotItemsVector(const std::string& hash, const bool& is_dependency) { - Json::Value json = this->getManifestV2(hash); + Json::Value json = this->getManifestV2(hash, is_dependency); std::vector items; @@ -216,6 +228,7 @@ std::vector galaxyAPI::getDepotItemsVector(const std::string& h item.totalSizeCompressed = 0; item.totalSizeUncompressed = 0; item.path = json["depot"]["items"][i]["path"].asString(); + item.isDependency = is_dependency; while (Util::replaceString(item.path, "\\", "/")); for (unsigned int j = 0; j < json["depot"]["items"][i]["chunks"].size(); ++j) @@ -443,3 +456,71 @@ Json::Value galaxyAPI::getUserData() return this->getResponseJson(url); } + +Json::Value galaxyAPI::getDependenciesJson() +{ + std::string url = "https://content-system.gog.com/dependencies/repository?generation=2"; + Json::Value dependencies; + Json::Value repository = this->getResponseJson(url); + + if (!repository.empty()) + { + if (repository.isMember("repository_manifest")) + { + std::string manifest_url = repository["repository_manifest"].asString(); + dependencies = this->getResponseJson(manifest_url, true); + } + } + + return dependencies; +} + +std::vector galaxyAPI::getFilteredDepotItemsVectorFromJson(const Json::Value& depot_json, const std::string& galaxy_language, const std::string& galaxy_arch, const bool& is_dependency) +{ + std::vector items; + + bool bSelectedLanguage = false; + bool bSelectedArch = false; + boost::regex language_re("^(" + galaxy_language + ")$", boost::regex::perl | boost::regex::icase); + boost::match_results what; + for (unsigned int j = 0; j < depot_json["languages"].size(); ++j) + { + std::string language = depot_json["languages"][j].asString(); + if (language == "*" || boost::regex_search(language, what, language_re)) + bSelectedLanguage = true; + } + + if (depot_json.isMember("osBitness")) + { + for (unsigned int j = 0; j < depot_json["osBitness"].size(); ++j) + { + std::string osBitness = depot_json["osBitness"][j].asString(); + if (osBitness == "*" || osBitness == galaxy_arch) + bSelectedArch = true; + } + } + else + { + // No osBitness found, assume that we want this depot + bSelectedArch = true; + } + + if (bSelectedLanguage && bSelectedArch) + { + std::string depotHash = depot_json["manifest"].asString(); + std::string depot_product_id = depot_json["productId"].asString(); + + items = this->getDepotItemsVector(depotHash, is_dependency); + + // Set product id for items + for (auto it = items.begin(); it != items.end(); ++it) + { + if (!depot_product_id.empty()) + { + it->product_id = depot_product_id; + } + } + } + + return items; +}