diff --git a/include/galaxyapi.h b/include/galaxyapi.h index 7121265..ece4b4e 100644 --- a/include/galaxyapi.h +++ b/include/galaxyapi.h @@ -38,6 +38,10 @@ struct galaxyDepotItem std::string md5; std::string product_id; bool isDependency = false; + bool isSmallFilesContainer = false; + bool isInSFC = false; + uintmax_t sfc_offset; + uintmax_t sfc_size; }; class galaxyAPI diff --git a/src/downloader.cpp b/src/downloader.cpp index de4ba64..7ef8626 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -4013,13 +4013,17 @@ std::vector Downloader::galaxyGetDepotItemVectorFromJson(const } } - // Set product id for items + // Set product id for items and add product id to small files container name for (auto it = items.begin(); it != items.end(); ++it) { if (it->product_id.empty()) { it->product_id = product_id; } + if (it->isSmallFilesContainer) + { + it->path += "_" + it->product_id; + } } return items; @@ -4105,6 +4109,49 @@ void Downloader::galaxyInstallGameById(const std::string& product_id, const std: } } + std::vector items_smallfiles; + std::vector sfc_vector; + bool bUseSmallFilesContainer = true; + for (auto item : items) + { + if (item.isInSFC) + { + std::string item_install_path = install_path + "/" + item.path; + if (boost::filesystem::exists(item_install_path)) + { + bUseSmallFilesContainer = false; + break; + } + } + } + + if (!bUseSmallFilesContainer) + { + for (std::vector::iterator it = items.begin(); it != items.end();) + { + if (it->isSmallFilesContainer) + it = items.erase(it); + else + ++it; + } + } + else + { + for (std::vector::iterator it = items.begin(); it != items.end();) + { + if (it->isSmallFilesContainer) + sfc_vector.push_back(*it); + + if (it->isInSFC) + { + items_smallfiles.push_back(*it); + it = items.erase(it); + } + else + ++it; + } + } + // Check for differences between previously installed build and new build std::vector items_old; @@ -4227,7 +4274,66 @@ void Downloader::galaxyInstallGameById(const std::string& product_id, const std: vThreads.clear(); vDownloadInfo.clear(); + if (bUseSmallFilesContainer) + { + for (auto container : sfc_vector) + { + std::string container_install_path = install_path + "/" + container.path; + if (!boost::filesystem::exists(container_install_path)) + continue; + + std::cout << "Extracting small files container " << container_install_path << std::endl; + + for (auto item : items_smallfiles) + { + if (item.product_id != container.product_id) + continue; + + std::string item_install_path = install_path + "/" + item.path; + std::cout << item_install_path << std::endl; + std::ifstream sfc(container_install_path, std::ifstream::binary); + if (sfc) + { + sfc.seekg(item.sfc_offset, sfc.beg); + char *filecontents = (char *) malloc(item.sfc_size); + sfc.read(filecontents, item.sfc_size); + sfc.close(); + + // Check that directory exists and create it + boost::filesystem::path path = item_install_path; + boost::filesystem::path directory = path.parent_path(); + if (!boost::filesystem::exists(directory)) + { + if (!boost::filesystem::create_directories(directory)) + { + std::cout << "Failed to create directory: " << directory << std::endl; + free(filecontents); + continue; + } + } + + std::ofstream output(item_install_path, std::ofstream::binary); + if (output) + { + output.write(filecontents, item.sfc_size); + output.close(); + } + free(filecontents); + } + } + + std::cout << "Deleting small files container " << container_install_path << std::endl; + if (!boost::filesystem::remove(container_install_path)) + std::cerr << "Failed to delete " << container_install_path << std::endl; + } + } + std::cout << "Checking for orphaned files" << std::endl; + if (bUseSmallFilesContainer) + { + // Add small files back to items vector for ophan checking + items.insert(std::end(items), std::begin(items_smallfiles), std::end(items_smallfiles)); + } std::vector orphans = this->galaxyGetOrphanedFiles(items, install_path); std::cout << "\t" << orphans.size() << " orphaned files" << std::endl; for (unsigned int i = 0; i < orphans.size(); ++i) diff --git a/src/galaxyapi.cpp b/src/galaxyapi.cpp index 716ebdd..b38e90e 100644 --- a/src/galaxyapi.cpp +++ b/src/galaxyapi.cpp @@ -255,6 +255,45 @@ std::vector galaxyAPI::getDepotItemsVector(const std::string& h Json::Value json = this->getManifestV2(hash, is_dependency); std::vector items; + if (json["depot"].isMember("smallFilesContainer")) + { + if (json["depot"]["smallFilesContainer"]["chunks"].isArray()) + { + galaxyDepotItem item; + item.totalSizeCompressed = 0; + item.totalSizeUncompressed = 0; + item.path = "galaxy_smallfilescontainer"; + item.isDependency = is_dependency; + item.isSmallFilesContainer = true; + + for (unsigned int i = 0; i < json["depot"]["smallFilesContainer"]["chunks"].size(); ++i) + { + Json::Value json_chunk = json["depot"]["smallFilesContainer"]["chunks"][i]; + + galaxyDepotItemChunk chunk; + chunk.md5_compressed = json_chunk["compressedMd5"].asString(); + chunk.md5_uncompressed = json_chunk["md5"].asString(); + chunk.size_compressed = json_chunk["compressedSize"].asLargestUInt(); + chunk.size_uncompressed = json_chunk["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"]["smallFilesContainer"].isMember("md5")) + item.md5 = json["depot"]["smallFilesContainer"]["md5"].asString(); + else if (json["depot"]["smallFilesContainer"]["chunks"].size() == 1) + item.md5 = json["depot"]["smallFilesContainer"]["chunks"][0]["md5"].asString(); + else + item.md5 = std::string(); + + items.push_back(item); + } + } for (unsigned int i = 0; i < json["depot"]["items"].size(); ++i) { @@ -266,14 +305,23 @@ std::vector galaxyAPI::getDepotItemsVector(const std::string& h item.path = json["depot"]["items"][i]["path"].asString(); item.isDependency = is_dependency; + if (json["depot"]["items"][i].isMember("sfcRef")) + { + item.isInSFC = true; + item.sfc_offset = json["depot"]["items"][i]["sfcRef"]["offset"].asLargestUInt(); + item.sfc_size = json["depot"]["items"][i]["sfcRef"]["size"].asLargestUInt(); + } + while (Util::replaceString(item.path, "\\", "/")); for (unsigned int j = 0; j < json["depot"]["items"][i]["chunks"].size(); ++j) { + Json::Value json_chunk = json["depot"]["items"][i]["chunks"][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.md5_compressed = json_chunk["compressedMd5"].asString(); + chunk.md5_uncompressed = json_chunk["md5"].asString(); + chunk.size_compressed = json_chunk["compressedSize"].asLargestUInt(); + chunk.size_uncompressed = json_chunk["size"].asLargestUInt(); chunk.offset_compressed = item.totalSizeCompressed; chunk.offset_uncompressed = item.totalSizeUncompressed;