Galaxy: Add support for downloading dependencies

Adds support for downloading dependencies for a game
Added --galaxy-no-dependencies option to disable downloading dependencies during --galaxy-install
This commit is contained in:
Sude 2018-09-05 17:41:43 +03:00
parent cae5f80698
commit 35b178e674
5 changed files with 136 additions and 42 deletions

View File

@ -51,6 +51,7 @@ struct DownloadConfig
bool bIgnoreDLCCount;
bool bDuplicateHandler;
bool bGalaxyDependencies;
};
struct gameSpecificConfig

View File

@ -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<galaxyDepotItem> getDepotItemsVector(const std::string& hash);
std::vector<galaxyDepotItem> getDepotItemsVector(const std::string& hash, const bool& is_dependency = false);
Json::Value getProductInfo(const std::string& product_id);
gameDetails productInfoJsonToGameDetails(const Json::Value& json, const DownloadConfig& dlConf);
Json::Value getUserData();
Json::Value getDependenciesJson();
std::vector<galaxyDepotItem> 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;

View File

@ -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<std::string>(&sGalaxyPlatform)->default_value("w"), galaxy_platform_text.c_str())
("galaxy-language", bpo::value<std::string>(&sGalaxyLanguage)->default_value("en"), galaxy_language_text.c_str())
("galaxy-arch", bpo::value<std::string>(&sGalaxyArch)->default_value("x64"), galaxy_arch_text.c_str())
("galaxy-no-dependencies", bpo::value<bool>(&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)

View File

@ -3323,48 +3323,49 @@ void Downloader::galaxyInstallGame(const std::string& product_id, int build_inde
std::vector<galaxyDepotItem> 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<std::string::const_iterator> 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<galaxyDepotItem> 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<std::string> 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<galaxyDepotItem> 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<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;
@ -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;

View File

@ -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<galaxyDepotItem> galaxyAPI::getDepotItemsVector(const std::string& hash)
std::vector<galaxyDepotItem> 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<galaxyDepotItem> items;
@ -216,6 +228,7 @@ std::vector<galaxyDepotItem> 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<galaxyDepotItem> galaxyAPI::getFilteredDepotItemsVectorFromJson(const Json::Value& depot_json, const std::string& galaxy_language, const std::string& galaxy_arch, const bool& is_dependency)
{
std::vector<galaxyDepotItem> items;
bool bSelectedLanguage = false;
bool bSelectedArch = false;
boost::regex language_re("^(" + galaxy_language + ")$", boost::regex::perl | boost::regex::icase);
boost::match_results<std::string::const_iterator> 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;
}