mirror of
https://github.com/Sude-/lgogdownloader.git
synced 2024-11-20 11:49:17 +01:00
Galaxy: Add hack for installing Linux games
Try to use Linux installers as replacement for Galaxy repository. Linux installers use MojoSetup which means that the installers are Zip files that have bash script and installer binary prepended. We can get zip file entries from zip central directory and then split the game installer to chunks based on those file entries. Therefore we can download individual files from the installer.
This commit is contained in:
parent
7373d357aa
commit
463a9c386e
@ -35,6 +35,7 @@ find_package(Htmlcxx REQUIRED)
|
|||||||
find_package(Tinyxml2 REQUIRED)
|
find_package(Tinyxml2 REQUIRED)
|
||||||
find_package(Rhash REQUIRED)
|
find_package(Rhash REQUIRED)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
find_package(ZLIB REQUIRED)
|
||||||
|
|
||||||
file(GLOB SRC_FILES
|
file(GLOB SRC_FILES
|
||||||
main.cpp
|
main.cpp
|
||||||
@ -47,6 +48,7 @@ file(GLOB SRC_FILES
|
|||||||
src/gamefile.cpp
|
src/gamefile.cpp
|
||||||
src/gamedetails.cpp
|
src/gamedetails.cpp
|
||||||
src/galaxyapi.cpp
|
src/galaxyapi.cpp
|
||||||
|
src/ziputil.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(GIT_CHECKOUT FALSE)
|
set(GIT_CHECKOUT FALSE)
|
||||||
@ -103,6 +105,7 @@ target_include_directories(${PROJECT_NAME}
|
|||||||
PRIVATE ${Htmlcxx_INCLUDE_DIRS}
|
PRIVATE ${Htmlcxx_INCLUDE_DIRS}
|
||||||
PRIVATE ${Tinyxml2_INCLUDE_DIRS}
|
PRIVATE ${Tinyxml2_INCLUDE_DIRS}
|
||||||
PRIVATE ${Rhash_INCLUDE_DIRS}
|
PRIVATE ${Rhash_INCLUDE_DIRS}
|
||||||
|
PRIVATE ${ZLIB_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(${PROJECT_NAME}
|
||||||
@ -114,6 +117,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
PRIVATE ${Tinyxml2_LIBRARIES}
|
PRIVATE ${Tinyxml2_LIBRARIES}
|
||||||
PRIVATE ${Rhash_LIBRARIES}
|
PRIVATE ${Rhash_LIBRARIES}
|
||||||
PRIVATE ${CMAKE_THREAD_LIBS_INIT}
|
PRIVATE ${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
PRIVATE ${ZLIB_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(LINK_LIBCRYPTO EQUAL 1)
|
if(LINK_LIBCRYPTO EQUAL 1)
|
||||||
|
@ -12,6 +12,7 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
|
|||||||
* [tinyxml2](https://github.com/leethomason/tinyxml2)
|
* [tinyxml2](https://github.com/leethomason/tinyxml2)
|
||||||
* [boost](http://www.boost.org/) (regex, date-time, system, filesystem, program-options, iostreams)
|
* [boost](http://www.boost.org/) (regex, date-time, system, filesystem, program-options, iostreams)
|
||||||
* [libcrypto](https://www.openssl.org/) if libcurl is built with OpenSSL
|
* [libcrypto](https://www.openssl.org/) if libcurl is built with OpenSSL
|
||||||
|
* [zlib](https://www.zlib.net/)
|
||||||
|
|
||||||
## Make dependencies
|
## Make dependencies
|
||||||
* [cmake](https://cmake.org/) >= 3.0.0
|
* [cmake](https://cmake.org/) >= 3.0.0
|
||||||
@ -25,7 +26,7 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
|
|||||||
libjsoncpp-dev liboauth-dev librhash-dev libtinyxml2-dev libhtmlcxx-dev \
|
libjsoncpp-dev liboauth-dev librhash-dev libtinyxml2-dev libhtmlcxx-dev \
|
||||||
libboost-system-dev libboost-filesystem-dev libboost-program-options-dev \
|
libboost-system-dev libboost-filesystem-dev libboost-program-options-dev \
|
||||||
libboost-date-time-dev libboost-iostreams-dev help2man cmake libssl-dev \
|
libboost-date-time-dev libboost-iostreams-dev help2man cmake libssl-dev \
|
||||||
pkg-config
|
pkg-config zlib1g-dev
|
||||||
|
|
||||||
## Build and install
|
## Build and install
|
||||||
|
|
||||||
|
@ -67,6 +67,20 @@ struct ChunkMemoryStruct
|
|||||||
curl_off_t size;
|
curl_off_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
std::string filepath;
|
||||||
|
off_t comp_size;
|
||||||
|
off_t uncomp_size;
|
||||||
|
off_t start_offset_zip;
|
||||||
|
off_t start_offset_mojosetup;
|
||||||
|
off_t end_offset;
|
||||||
|
uint16_t file_attributes;
|
||||||
|
uint32_t crc32;
|
||||||
|
|
||||||
|
std::string installer_url;
|
||||||
|
} zipFileEntry;
|
||||||
|
|
||||||
class Downloader
|
class Downloader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -123,6 +137,9 @@ class Downloader
|
|||||||
|
|
||||||
std::vector<std::string> galaxyGetOrphanedFiles(const std::vector<galaxyDepotItem>& items, const std::string& install_path);
|
std::vector<std::string> galaxyGetOrphanedFiles(const std::vector<galaxyDepotItem>& items, const std::string& install_path);
|
||||||
static void processGalaxyDownloadQueue(const std::string& install_path, Config conf, const unsigned int& tid);
|
static void processGalaxyDownloadQueue(const std::string& install_path, Config conf, const unsigned int& tid);
|
||||||
|
void galaxyInstallGame_MojoSetupHack(const std::string& product_id);
|
||||||
|
static void processGalaxyDownloadQueue_MojoSetupHack(Config conf, const unsigned int& tid);
|
||||||
|
int mojoSetupGetFileVector(const gameFile& gf, std::vector<zipFileEntry>& vFiles);
|
||||||
|
|
||||||
Website *gogWebsite;
|
Website *gogWebsite;
|
||||||
API *gogAPI;
|
API *gogAPI;
|
||||||
|
109
include/ziputil.h
Normal file
109
include/ziputil.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* 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 ZIPUTIL_H
|
||||||
|
#define ZIPUTIL_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#define ZIP_LOCAL_HEADER_SIGNATURE 0x04034b50
|
||||||
|
#define ZIP_CD_HEADER_SIGNATURE 0x02014b50
|
||||||
|
#define ZIP_EOCD_HEADER_SIGNATURE 0x06054b50
|
||||||
|
#define ZIP_EOCD_HEADER_SIGNATURE64 0x06064b50
|
||||||
|
#define ZIP_EXTENSION_ZIP64 0x0001
|
||||||
|
#define ZIP_EXTENDED_TIMESTAMP 0x5455
|
||||||
|
#define ZIP_INFOZIP_UNIX_NEW 0x7875
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t header = 0;
|
||||||
|
uint16_t disk = 0;
|
||||||
|
uint16_t cd_start_disk = 0;
|
||||||
|
uint16_t cd_records = 0;
|
||||||
|
uint16_t total_cd_records = 0;
|
||||||
|
uint32_t cd_size = 0;
|
||||||
|
uint32_t cd_start_offset = 0;
|
||||||
|
uint16_t comment_length = 0;
|
||||||
|
|
||||||
|
std::string comment;
|
||||||
|
} zipEOCD;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t header = 0;
|
||||||
|
uint64_t directory_record_size = 0;
|
||||||
|
uint16_t version_made_by = 0;
|
||||||
|
uint16_t version_needed = 0;
|
||||||
|
uint32_t cd = 0;
|
||||||
|
uint32_t cd_start = 0;
|
||||||
|
uint64_t cd_total_disk = 0;
|
||||||
|
uint64_t cd_total = 0;
|
||||||
|
uint64_t cd_size = 0;
|
||||||
|
uint64_t cd_offset = 0;
|
||||||
|
|
||||||
|
std::string comment;
|
||||||
|
} zip64EOCD;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t header = 0;
|
||||||
|
uint16_t version_made_by = 0;
|
||||||
|
uint16_t version_needed = 0;
|
||||||
|
uint16_t flag = 0;
|
||||||
|
uint16_t compression_method = 0;
|
||||||
|
uint16_t mod_date = 0;
|
||||||
|
uint16_t mod_time = 0;
|
||||||
|
uint32_t crc32 = 0;
|
||||||
|
uint64_t comp_size = 0;
|
||||||
|
uint64_t uncomp_size = 0;
|
||||||
|
uint16_t filename_length = 0;
|
||||||
|
uint16_t extra_length = 0;
|
||||||
|
uint16_t comment_length = 0;
|
||||||
|
uint32_t disk_num = 0;
|
||||||
|
uint16_t internal_file_attr = 0;
|
||||||
|
uint32_t external_file_attr = 0;
|
||||||
|
uint64_t disk_offset = 0;
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
std::string extra;
|
||||||
|
std::string comment;
|
||||||
|
time_t timestamp = 0;
|
||||||
|
bool isLocalCDEntry = false;
|
||||||
|
} zipCDEntry;
|
||||||
|
|
||||||
|
namespace ZipUtil
|
||||||
|
{
|
||||||
|
off_t getMojoSetupScriptSize(std::stringstream *stream);
|
||||||
|
off_t getMojoSetupInstallerSize(std::stringstream *stream);
|
||||||
|
|
||||||
|
struct tm date_time_to_tm(uint64_t date, uint64_t time);
|
||||||
|
bool isValidDate(struct tm timeinfo);
|
||||||
|
|
||||||
|
uint64_t readValue(std::istream *stream, uint32_t len);
|
||||||
|
uint64_t readUInt64(std::istream *stream);
|
||||||
|
uint32_t readUInt32(std::istream *stream);
|
||||||
|
uint16_t readUInt16(std::istream *stream);
|
||||||
|
uint8_t readUInt8(std::istream *stream);
|
||||||
|
|
||||||
|
off_t getZipEOCDOffsetSignature(std::istream *stream, const uint32_t& signature);
|
||||||
|
off_t getZipEOCDOffset(std::istream *stream);
|
||||||
|
off_t getZip64EOCDOffset(std::istream *stream);
|
||||||
|
|
||||||
|
zipEOCD readZipEOCDStruct(std::istream *stream, const off_t& eocd_start_pos = 0);
|
||||||
|
zip64EOCD readZip64EOCDStruct(std::istream *stream, const off_t& eocd_start_pos = 0);
|
||||||
|
zipCDEntry readZipCDEntry(std::istream *stream);
|
||||||
|
|
||||||
|
int extractFile(const std::string& input_file_path, const std::string& output_file_path);
|
||||||
|
int extractStream(std::istream* input_stream, std::ostream* output_stream);
|
||||||
|
boost::filesystem::perms getBoostFilePermission(const uint16_t& attributes);
|
||||||
|
bool isSymlink(const uint16_t& attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ZIPUTIL_H
|
@ -9,6 +9,7 @@
|
|||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "downloadinfo.h"
|
#include "downloadinfo.h"
|
||||||
#include "message.h"
|
#include "message.h"
|
||||||
|
#include "ziputil.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@ -44,6 +45,7 @@ ThreadSafeQueue<gameFile> createXMLQueue;
|
|||||||
ThreadSafeQueue<gameItem> gameItemQueue;
|
ThreadSafeQueue<gameItem> gameItemQueue;
|
||||||
ThreadSafeQueue<gameDetails> gameDetailsQueue;
|
ThreadSafeQueue<gameDetails> gameDetailsQueue;
|
||||||
ThreadSafeQueue<galaxyDepotItem> dlQueueGalaxy;
|
ThreadSafeQueue<galaxyDepotItem> dlQueueGalaxy;
|
||||||
|
ThreadSafeQueue<zipFileEntry> dlQueueGalaxy_MojoSetupHack;
|
||||||
std::mutex mtx_create_directories; // Mutex for creating directories in Downloader::processDownloadQueue
|
std::mutex mtx_create_directories; // Mutex for creating directories in Downloader::processDownloadQueue
|
||||||
|
|
||||||
static curl_off_t WriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp)
|
static curl_off_t WriteChunkMemoryCallback(void *contents, curl_off_t size, curl_off_t nmemb, void *userp)
|
||||||
@ -3307,6 +3309,11 @@ void Downloader::galaxyInstallGame(const std::string& product_id, int build_inde
|
|||||||
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
|
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
|
||||||
{
|
{
|
||||||
std::cout << "Galaxy API doesn't have Linux support" << std::endl;
|
std::cout << "Galaxy API doesn't have Linux support" << std::endl;
|
||||||
|
|
||||||
|
// Galaxy install hack for Linux
|
||||||
|
std::cout << "Trying to use installers as repository" << std::endl;
|
||||||
|
this->galaxyInstallGame_MojoSetupHack(product_id);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3751,6 +3758,42 @@ void Downloader::galaxyShowBuilds(const std::string& product_id, int build_index
|
|||||||
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
|
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
|
||||||
{
|
{
|
||||||
std::cout << "Galaxy API doesn't have Linux support" << std::endl;
|
std::cout << "Galaxy API doesn't have Linux support" << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Checking for installers that can be used as repository" << std::endl;
|
||||||
|
DownloadConfig dlConf = Globals::globalConfig.dlConf;
|
||||||
|
dlConf.bInstallers = true;
|
||||||
|
dlConf.bExtras = false;
|
||||||
|
dlConf.bLanguagePacks = false;
|
||||||
|
dlConf.bPatches = false;
|
||||||
|
dlConf.bDLC = true;
|
||||||
|
dlConf.iInstallerPlatform = dlConf.iGalaxyPlatform;
|
||||||
|
dlConf.iInstallerLanguage = dlConf.iGalaxyLanguage;
|
||||||
|
|
||||||
|
Json::Value product_info = gogGalaxy->getProductInfo(product_id);
|
||||||
|
gameDetails game = gogGalaxy->productInfoJsonToGameDetails(product_info, dlConf);
|
||||||
|
|
||||||
|
std::vector<gameFile> vInstallers;
|
||||||
|
if (!game.installers.empty())
|
||||||
|
{
|
||||||
|
vInstallers.push_back(game.installers[0]);
|
||||||
|
for (unsigned int i = 0; i < game.dlcs.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!game.dlcs[i].installers.empty())
|
||||||
|
vInstallers.push_back(game.dlcs[i].installers[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vInstallers.empty())
|
||||||
|
{
|
||||||
|
std::cout << "No installers found" << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Using these installers" << std::endl;
|
||||||
|
for (unsigned int i = 0; i < vInstallers.size(); ++i)
|
||||||
|
std::cout << "\t" << vInstallers[i].gamename << "/" << vInstallers[i].id << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3859,3 +3902,776 @@ std::vector<std::string> Downloader::galaxyGetOrphanedFiles(const std::vector<ga
|
|||||||
|
|
||||||
return orphans;
|
return orphans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downloader::galaxyInstallGame_MojoSetupHack(const std::string& product_id)
|
||||||
|
{
|
||||||
|
DownloadConfig dlConf = Globals::globalConfig.dlConf;
|
||||||
|
dlConf.bInstallers = true;
|
||||||
|
dlConf.bExtras = false;
|
||||||
|
dlConf.bLanguagePacks = false;
|
||||||
|
dlConf.bPatches = false;
|
||||||
|
dlConf.bDLC = true;
|
||||||
|
dlConf.iInstallerPlatform = dlConf.iGalaxyPlatform;
|
||||||
|
dlConf.iInstallerLanguage = dlConf.iGalaxyLanguage;
|
||||||
|
|
||||||
|
Json::Value product_info = gogGalaxy->getProductInfo(product_id);
|
||||||
|
gameDetails game = gogGalaxy->productInfoJsonToGameDetails(product_info, dlConf);
|
||||||
|
|
||||||
|
std::vector<gameFile> vInstallers;
|
||||||
|
if (!game.installers.empty())
|
||||||
|
{
|
||||||
|
vInstallers.push_back(game.installers[0]);
|
||||||
|
for (unsigned int i = 0; i < game.dlcs.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!game.dlcs[i].installers.empty())
|
||||||
|
vInstallers.push_back(game.dlcs[i].installers[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vInstallers.empty())
|
||||||
|
{
|
||||||
|
std::vector<zipFileEntry> zipFileEntries;
|
||||||
|
for (unsigned int i = 0; i < vInstallers.size(); ++i)
|
||||||
|
{
|
||||||
|
std::vector<zipFileEntry> vFiles;
|
||||||
|
std::cout << "Getting file list for " << vInstallers[i].gamename << "/" << vInstallers[i].id << std::endl;
|
||||||
|
if (this->mojoSetupGetFileVector(vInstallers[i], vFiles))
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get file list" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
zipFileEntries.insert(std::end(zipFileEntries), std::begin(vFiles), std::end(vFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string gamedir = game.title;
|
||||||
|
if (gamedir.empty())
|
||||||
|
gamedir = game.gamename;
|
||||||
|
if (gamedir.empty())
|
||||||
|
gamedir = product_id;
|
||||||
|
|
||||||
|
std::string install_directory = Globals::globalConfig.dirConf.sDirectory + "/" + gamedir + "/";
|
||||||
|
std::vector<zipFileEntry> vZipDirectories;
|
||||||
|
std::vector<zipFileEntry> vZipFiles;
|
||||||
|
std::vector<zipFileEntry> vZipFilesSymlink;
|
||||||
|
for (std::uintmax_t i = 0; i < zipFileEntries.size(); ++i)
|
||||||
|
{
|
||||||
|
// Ignore all files and directories that are not in "data/noarch/" directory
|
||||||
|
std::string noarch = "data/noarch/";
|
||||||
|
if (zipFileEntries[i].filepath.find(noarch) == std::string::npos || zipFileEntries[i].filepath == noarch)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
zipFileEntry zfe = zipFileEntries[i];
|
||||||
|
Util::replaceString(zfe.filepath, noarch, install_directory);
|
||||||
|
while (Util::replaceString(zfe.filepath, "//", "/")); // Replace any double slashes with single slash
|
||||||
|
|
||||||
|
if (zfe.filepath.at(zfe.filepath.length()-1) == '/')
|
||||||
|
vZipDirectories.push_back(zfe);
|
||||||
|
else if (ZipUtil::isSymlink(zfe.file_attributes))
|
||||||
|
vZipFilesSymlink.push_back(zfe);
|
||||||
|
else
|
||||||
|
vZipFiles.push_back(zfe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directories
|
||||||
|
for (std::uintmax_t i = 0; i < vZipDirectories.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::exists(vZipDirectories[i].filepath))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::create_directories(vZipDirectories[i].filepath))
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to create directory " << vZipDirectories[i].filepath << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add files to download queue
|
||||||
|
for (std::uintmax_t i = 0; i < vZipFiles.size(); ++i)
|
||||||
|
dlQueueGalaxy_MojoSetupHack.push(vZipFiles[i]);
|
||||||
|
|
||||||
|
// Add symlinks to download queue
|
||||||
|
for (std::uintmax_t i = 0; i < vZipFilesSymlink.size(); ++i)
|
||||||
|
dlQueueGalaxy_MojoSetupHack.push(vZipFilesSymlink[i]);
|
||||||
|
|
||||||
|
// Limit thread count to number of items in download queue
|
||||||
|
unsigned int iThreads = std::min(Globals::globalConfig.iThreads, static_cast<unsigned int>(dlQueueGalaxy_MojoSetupHack.size()));
|
||||||
|
|
||||||
|
// Create download threads
|
||||||
|
std::vector<std::thread> vThreads;
|
||||||
|
for (unsigned int i = 0; i < iThreads; ++i)
|
||||||
|
{
|
||||||
|
DownloadInfo dlInfo;
|
||||||
|
dlInfo.setStatus(DLSTATUS_NOTSTARTED);
|
||||||
|
vDownloadInfo.push_back(dlInfo);
|
||||||
|
vThreads.push_back(std::thread(Downloader::processGalaxyDownloadQueue_MojoSetupHack, Globals::globalConfig, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->printProgress(dlQueueGalaxy_MojoSetupHack);
|
||||||
|
|
||||||
|
// Join threads
|
||||||
|
for (unsigned int i = 0; i < vThreads.size(); ++i)
|
||||||
|
vThreads[i].join();
|
||||||
|
|
||||||
|
vThreads.clear();
|
||||||
|
vDownloadInfo.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "No installers found" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Downloader::processGalaxyDownloadQueue_MojoSetupHack(Config conf, const unsigned int& tid)
|
||||||
|
{
|
||||||
|
std::string msg_prefix = "[Thread #" + std::to_string(tid) + "]";
|
||||||
|
|
||||||
|
CURL* dlhandle = curl_easy_init();
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_USERAGENT, conf.curlConf.sUserAgent.c_str());
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_NOPROGRESS, 0);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_NOSIGNAL, 1);
|
||||||
|
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_CONNECTTIMEOUT, conf.curlConf.iTimeout);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_FAILONERROR, true);
|
||||||
|
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.curlConf.iDownloadRate);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_FILETIME, 1L);
|
||||||
|
|
||||||
|
// 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, conf.curlConf.iLowSpeedTimeout);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_LOW_SPEED_LIMIT, conf.curlConf.iLowSpeedTimeoutRate);
|
||||||
|
|
||||||
|
if (!conf.curlConf.sCACertPath.empty())
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_CAINFO, conf.curlConf.sCACertPath.c_str());
|
||||||
|
|
||||||
|
xferInfo xferinfo;
|
||||||
|
xferinfo.tid = tid;
|
||||||
|
xferinfo.curlhandle = dlhandle;
|
||||||
|
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_XFERINFOFUNCTION, Downloader::progressCallbackForThread);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_XFERINFODATA, &xferinfo);
|
||||||
|
|
||||||
|
zipFileEntry zfe;
|
||||||
|
while (dlQueueGalaxy_MojoSetupHack.try_pop(zfe))
|
||||||
|
{
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_STARTING);
|
||||||
|
|
||||||
|
boost::filesystem::path path = zfe.filepath;
|
||||||
|
boost::filesystem::path path_tmp = zfe.filepath + ".lgogdltmp";
|
||||||
|
|
||||||
|
// Check that directory exists and create it
|
||||||
|
boost::filesystem::path directory = path.parent_path();
|
||||||
|
mtx_create_directories.lock(); // Use mutex to avoid possible race conditions
|
||||||
|
if (boost::filesystem::exists(directory))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::is_directory(directory))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(directory.string() + " is not directory", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_FINISHED);
|
||||||
|
mtx_create_directories.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::create_directories(directory))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Failed to create directory: " + directory.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_FINISHED);
|
||||||
|
mtx_create_directories.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtx_create_directories.unlock();
|
||||||
|
|
||||||
|
vDownloadInfo[tid].setFilename(path.string());
|
||||||
|
|
||||||
|
if (ZipUtil::isSymlink(zfe.file_attributes))
|
||||||
|
{
|
||||||
|
if (boost::filesystem::is_symlink(path))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Symlink already exists: " + path.string(), MSGTYPE_INFO, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (boost::filesystem::exists(path))
|
||||||
|
{
|
||||||
|
if (conf.bVerbose)
|
||||||
|
msgQueue.push(Message("File already exists: " + path.string(), MSGTYPE_INFO, msg_prefix));
|
||||||
|
|
||||||
|
off_t filesize = static_cast<off_t>(boost::filesystem::file_size(path));
|
||||||
|
if (filesize == zfe.uncomp_size)
|
||||||
|
{
|
||||||
|
// File is same size
|
||||||
|
if (Util::getFileHash(path.string(), RHASH_CRC32) == Util::formattedString("%08x", zfe.crc32))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": OK", MSGTYPE_SUCCESS, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": CRC32 mismatch. Deleting old file.", MSGTYPE_WARNING, msg_prefix));
|
||||||
|
if (!boost::filesystem::remove(path))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// File size mismatch
|
||||||
|
msgQueue.push(Message(path.string() + ": File size mismatch. Deleting old file.", MSGTYPE_INFO, msg_prefix));
|
||||||
|
if (!boost::filesystem::remove(path))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t resume_from = 0;
|
||||||
|
if (boost::filesystem::exists(path_tmp))
|
||||||
|
{
|
||||||
|
off_t filesize = static_cast<off_t>(boost::filesystem::file_size(path_tmp));
|
||||||
|
if (filesize < zfe.comp_size)
|
||||||
|
{
|
||||||
|
// Continue
|
||||||
|
resume_from = filesize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Delete old file
|
||||||
|
msgQueue.push(Message(path_tmp.string() + ": Deleting old file.", MSGTYPE_INFO, msg_prefix));
|
||||||
|
if (!boost::filesystem::remove(path_tmp))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path_tmp.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url = zfe.installer_url;
|
||||||
|
std::string dlrange = std::to_string(zfe.start_offset_mojosetup) + "-" + std::to_string(zfe.end_offset);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_URL, url.c_str());
|
||||||
|
if (ZipUtil::isSymlink(zfe.file_attributes))
|
||||||
|
{
|
||||||
|
// Symlink
|
||||||
|
std::stringstream symlink_compressed;
|
||||||
|
std::stringstream symlink_uncompressed;
|
||||||
|
std::string link_target;
|
||||||
|
|
||||||
|
CURLcode result = CURLE_RECV_ERROR;
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEDATA, &symlink_compressed);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_RANGE, dlrange.c_str());
|
||||||
|
|
||||||
|
vDownloadInfo[tid].setFilename(path.string());
|
||||||
|
|
||||||
|
if (conf.iWait > 0)
|
||||||
|
usleep(conf.iWait); // Delay the request by specified time
|
||||||
|
|
||||||
|
xferinfo.offset = 0;
|
||||||
|
xferinfo.timer.reset();
|
||||||
|
xferinfo.TimeAndSize.clear();
|
||||||
|
|
||||||
|
result = curl_easy_perform(dlhandle);
|
||||||
|
|
||||||
|
if (result != CURLE_OK)
|
||||||
|
{
|
||||||
|
symlink_compressed.str(std::string());
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to download", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = ZipUtil::extractStream(&symlink_compressed, &symlink_uncompressed);
|
||||||
|
symlink_compressed.str(std::string());
|
||||||
|
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
std::string msg = "Extraction failed (";
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
msg += "invalid input stream";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
msg += "unsupported compression method";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
msg += "invalid output stream";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
msg += "zlib error";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg += "unknown error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg += ")";
|
||||||
|
|
||||||
|
msgQueue.push(Message(msg + " " + path.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
symlink_uncompressed.str(std::string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
link_target = symlink_uncompressed.str();
|
||||||
|
symlink_uncompressed.str(std::string());
|
||||||
|
|
||||||
|
if (!link_target.empty())
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::exists(path))
|
||||||
|
{
|
||||||
|
if (conf.bVerbose)
|
||||||
|
msgQueue.push(Message(path.string() + ": Creating symlink to " + link_target, MSGTYPE_INFO, msg_prefix));
|
||||||
|
boost::filesystem::create_symlink(link_target, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Download file
|
||||||
|
CURLcode result = CURLE_RECV_ERROR;
|
||||||
|
|
||||||
|
off_t max_size_memory = 5 << 20; // 5MB
|
||||||
|
if (zfe.comp_size < max_size_memory) // Handle small files in memory
|
||||||
|
{
|
||||||
|
std::ofstream ofs(path.string(), std::ofstream::out | std::ofstream::binary);
|
||||||
|
if (!ofs)
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Failed to create " + path_tmp.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream data_compressed;
|
||||||
|
vDownloadInfo[tid].setFilename(path.string());
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEDATA, &data_compressed);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_RANGE, dlrange.c_str());
|
||||||
|
|
||||||
|
xferinfo.offset = 0;
|
||||||
|
xferinfo.timer.reset();
|
||||||
|
xferinfo.TimeAndSize.clear();
|
||||||
|
|
||||||
|
result = curl_easy_perform(dlhandle);
|
||||||
|
|
||||||
|
if (result != CURLE_OK)
|
||||||
|
{
|
||||||
|
data_compressed.str(std::string());
|
||||||
|
ofs.close();
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to download", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::remove(path))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = ZipUtil::extractStream(&data_compressed, &ofs);
|
||||||
|
data_compressed.str(std::string());
|
||||||
|
ofs.close();
|
||||||
|
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
std::string msg = "Extraction failed (";
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
msg += "invalid input stream";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
msg += "unsupported compression method";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
msg += "invalid output stream";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
msg += "zlib error";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg += "unknown error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg += ")";
|
||||||
|
|
||||||
|
msgQueue.push(Message(msg + " " + path.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
data_compressed.str(std::string());
|
||||||
|
if (boost::filesystem::exists(path) && boost::filesystem::is_regular_file(path))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::remove(path))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file parmission
|
||||||
|
boost::filesystem::perms permissions = ZipUtil::getBoostFilePermission(zfe.file_attributes);
|
||||||
|
if (boost::filesystem::exists(path))
|
||||||
|
Util::setFilePermissions(path, permissions);
|
||||||
|
}
|
||||||
|
else // Use temorary file for bigger files
|
||||||
|
{
|
||||||
|
vDownloadInfo[tid].setFilename(path_tmp.string());
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_READFUNCTION, Downloader::readData);
|
||||||
|
|
||||||
|
int iRetryCount = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (iRetryCount != 0)
|
||||||
|
msgQueue.push(Message("Retry " + std::to_string(iRetryCount) + "/" + std::to_string(conf.iRetries) + ": " + path_tmp.filename().string(), MSGTYPE_INFO, msg_prefix));
|
||||||
|
|
||||||
|
|
||||||
|
FILE* outfile;
|
||||||
|
// File exists, resume
|
||||||
|
if (resume_from > 0)
|
||||||
|
{
|
||||||
|
if ((outfile=fopen(path_tmp.string().c_str(), "r+"))!=NULL)
|
||||||
|
{
|
||||||
|
fseek(outfile, 0, SEEK_END);
|
||||||
|
dlrange = std::to_string(zfe.start_offset_mojosetup + resume_from) + "-" + std::to_string(zfe.end_offset);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEDATA, outfile);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_RANGE, dlrange.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Failed to open " + path_tmp.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // File doesn't exist, create new file
|
||||||
|
{
|
||||||
|
if ((outfile=fopen(path_tmp.string().c_str(), "w"))!=NULL)
|
||||||
|
{
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_WRITEDATA, outfile);
|
||||||
|
curl_easy_setopt(dlhandle, CURLOPT_RANGE, dlrange.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Failed to create " + path_tmp.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.iWait > 0)
|
||||||
|
usleep(conf.iWait); // Delay the request by specified time
|
||||||
|
|
||||||
|
xferinfo.offset = 0;
|
||||||
|
xferinfo.timer.reset();
|
||||||
|
xferinfo.TimeAndSize.clear();
|
||||||
|
result = curl_easy_perform(dlhandle);
|
||||||
|
fclose(outfile);
|
||||||
|
|
||||||
|
if (result == CURLE_PARTIAL_FILE || result == CURLE_OPERATION_TIMEDOUT)
|
||||||
|
{
|
||||||
|
iRetryCount++;
|
||||||
|
if (boost::filesystem::exists(path_tmp) && boost::filesystem::is_regular_file(path_tmp))
|
||||||
|
resume_from = static_cast<off_t>(boost::filesystem::file_size(path_tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
} while ((result == CURLE_PARTIAL_FILE || result == CURLE_OPERATION_TIMEDOUT) && (iRetryCount <= conf.iRetries));
|
||||||
|
|
||||||
|
if (result == CURLE_OK)
|
||||||
|
{
|
||||||
|
// Extract file
|
||||||
|
int res = ZipUtil::extractFile(path_tmp.string(), path.string());
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
std::string msg = "Extraction failed (";
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
msg += "failed to open input file";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
msg += "unsupported compression method";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
msg += "failed to create output file";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
msg += "zlib error";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg += "unknown error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg += ")";
|
||||||
|
|
||||||
|
msgQueue.push(Message(msg + " " + path_tmp.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (boost::filesystem::exists(path_tmp) && boost::filesystem::is_regular_file(path_tmp))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::remove(path_tmp))
|
||||||
|
{
|
||||||
|
msgQueue.push(Message(path_tmp.string() + ": Failed to delete", MSGTYPE_ERROR, msg_prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file parmission
|
||||||
|
boost::filesystem::perms permissions = ZipUtil::getBoostFilePermission(zfe.file_attributes);
|
||||||
|
if (boost::filesystem::exists(path))
|
||||||
|
Util::setFilePermissions(path, permissions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msgQueue.push(Message("Download failed " + path_tmp.string(), MSGTYPE_ERROR, msg_prefix));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msgQueue.push(Message("Download complete: " + path.string(), MSGTYPE_SUCCESS, msg_prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
vDownloadInfo[tid].setStatus(DLSTATUS_FINISHED);
|
||||||
|
curl_easy_cleanup(dlhandle);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Downloader::mojoSetupGetFileVector(const gameFile& gf, std::vector<zipFileEntry>& vFiles)
|
||||||
|
{
|
||||||
|
Json::Value downlinkJson;
|
||||||
|
std::string response = gogGalaxy->getResponse(gf.galaxy_downlink_json_url);
|
||||||
|
|
||||||
|
if (response.empty())
|
||||||
|
{
|
||||||
|
std::cerr << "Found nothing in " << gf.galaxy_downlink_json_url << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::istringstream iss(response);
|
||||||
|
iss >> downlinkJson;
|
||||||
|
}
|
||||||
|
catch (const Json::Exception& exc)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not parse JSON response" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!downlinkJson.isMember("downlink"))
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid JSON response" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string xml_url;
|
||||||
|
if (downlinkJson.isMember("checksum"))
|
||||||
|
{
|
||||||
|
if (!downlinkJson["checksum"].empty())
|
||||||
|
xml_url = downlinkJson["checksum"].asString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid JSON response. Response doesn't contain XML url." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get XML data
|
||||||
|
std::string xml_data = gogGalaxy->getResponse(xml_url);
|
||||||
|
if (xml_data.empty())
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get XML data" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::uintmax_t file_size = 0;
|
||||||
|
tinyxml2::XMLDocument xml;
|
||||||
|
xml.Parse(xml_data.c_str());
|
||||||
|
tinyxml2::XMLElement *fileElem = xml.FirstChildElement("file");
|
||||||
|
|
||||||
|
if (!fileElem)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to parse XML data" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string total_size = fileElem->Attribute("total_size");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
file_size = std::stoull(total_size);
|
||||||
|
}
|
||||||
|
catch (std::invalid_argument& e)
|
||||||
|
{
|
||||||
|
file_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_size == 0)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get file size" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string installer_url = downlinkJson["downlink"].asString();
|
||||||
|
if (installer_url.empty())
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get installer url" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t head_size = 100 << 10; // 100 kB
|
||||||
|
off_t tail_size = 200 << 10; // 200 kB
|
||||||
|
std::string head_range = "0-" + std::to_string(head_size);
|
||||||
|
std::string tail_range = std::to_string(file_size - tail_size) + "-" + std::to_string(file_size);
|
||||||
|
|
||||||
|
CURLcode result;
|
||||||
|
|
||||||
|
// Get head
|
||||||
|
std::stringstream head;
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_URL, installer_url.c_str());
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &head);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_RANGE, head_range.c_str());
|
||||||
|
result = curl_easy_perform(curlhandle);
|
||||||
|
|
||||||
|
if (result != CURLE_OK)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to download data" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get zip start offset in MojoSetup installer
|
||||||
|
off_t mojosetup_zip_offset = 0;
|
||||||
|
off_t mojosetup_script_size = ZipUtil::getMojoSetupScriptSize(&head);
|
||||||
|
head.seekg(0, head.beg);
|
||||||
|
off_t mojosetup_installer_size = ZipUtil::getMojoSetupInstallerSize(&head);
|
||||||
|
head.str(std::string());
|
||||||
|
|
||||||
|
if (mojosetup_script_size == -1 || mojosetup_installer_size == -1)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get Zip offset" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mojosetup_zip_offset = mojosetup_script_size + mojosetup_installer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tail
|
||||||
|
std::stringstream tail;
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &tail);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_RANGE, tail_range.c_str());
|
||||||
|
result = curl_easy_perform(curlhandle);
|
||||||
|
|
||||||
|
if (result != CURLE_OK)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to download data" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t offset_zip_eocd = ZipUtil::getZipEOCDOffset(&tail);
|
||||||
|
off_t offset_zip64_eocd = ZipUtil::getZip64EOCDOffset(&tail);
|
||||||
|
|
||||||
|
if (offset_zip_eocd < 0)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to find Zip EOCD offset" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
zipEOCD eocd = ZipUtil::readZipEOCDStruct(&tail, offset_zip_eocd);
|
||||||
|
|
||||||
|
uint64_t cd_offset = eocd.cd_start_offset;
|
||||||
|
uint64_t cd_total = eocd.total_cd_records;
|
||||||
|
|
||||||
|
if (offset_zip64_eocd >= 0)
|
||||||
|
{
|
||||||
|
zip64EOCD eocd64 = ZipUtil::readZip64EOCDStruct(&tail, offset_zip64_eocd);
|
||||||
|
if (cd_offset == UINT32_MAX)
|
||||||
|
cd_offset = eocd64.cd_offset;
|
||||||
|
|
||||||
|
if (cd_total == UINT16_MAX)
|
||||||
|
cd_total = eocd64.cd_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t cd_offset_in_stream = 0;
|
||||||
|
off_t mojosetup_cd_offset = mojosetup_zip_offset + cd_offset;
|
||||||
|
off_t cd_offset_from_file_end = file_size - mojosetup_cd_offset;
|
||||||
|
|
||||||
|
if (cd_offset_from_file_end > tail_size)
|
||||||
|
{
|
||||||
|
tail.str(std::string());
|
||||||
|
tail_range = std::to_string(mojosetup_cd_offset) + "-" + std::to_string(file_size);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &tail);
|
||||||
|
curl_easy_setopt(curlhandle, CURLOPT_RANGE, tail_range.c_str());
|
||||||
|
result = curl_easy_perform(curlhandle);
|
||||||
|
|
||||||
|
if (result != CURLE_OK)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to download data" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cd_offset_in_stream = tail_size - cd_offset_from_file_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail.seekg(cd_offset_in_stream, tail.beg);
|
||||||
|
uint32_t signature = ZipUtil::readUInt32(&tail);
|
||||||
|
if (signature != ZIP_CD_HEADER_SIGNATURE)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to find Zip Central Directory" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read file entries from Zip Central Directory
|
||||||
|
tail.seekg(cd_offset_in_stream, tail.beg);
|
||||||
|
for (std::uint64_t i = 0; i < cd_total; ++i)
|
||||||
|
{
|
||||||
|
zipCDEntry cd;
|
||||||
|
cd = ZipUtil::readZipCDEntry(&tail);
|
||||||
|
|
||||||
|
zipFileEntry zfe;
|
||||||
|
zfe.filepath = cd.filename;
|
||||||
|
zfe.comp_size = cd.comp_size;
|
||||||
|
zfe.uncomp_size = cd.uncomp_size;
|
||||||
|
zfe.start_offset_zip = cd.disk_offset;
|
||||||
|
zfe.start_offset_mojosetup = zfe.start_offset_zip + mojosetup_zip_offset;
|
||||||
|
zfe.file_attributes = cd.external_file_attr >> 16;
|
||||||
|
zfe.crc32 = cd.crc32;
|
||||||
|
zfe.installer_url = installer_url;
|
||||||
|
|
||||||
|
vFiles.push_back(zfe);
|
||||||
|
}
|
||||||
|
tail.str(std::string());
|
||||||
|
|
||||||
|
// Set end offset for all entries
|
||||||
|
vFiles[vFiles.size() - 1].end_offset = mojosetup_cd_offset - 1;
|
||||||
|
for (std::uintmax_t i = 0; i < (vFiles.size() - 1); i++)
|
||||||
|
{
|
||||||
|
vFiles[i].end_offset = vFiles[i+1].start_offset_mojosetup - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -149,7 +149,17 @@ Json::Value galaxyAPI::getProductBuilds(const std::string& product_id, const std
|
|||||||
std::istringstream response(this->getResponse(url));
|
std::istringstream response(this->getResponse(url));
|
||||||
Json::Value json;
|
Json::Value json;
|
||||||
|
|
||||||
|
if (!response.str().empty())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
response >> json;
|
response >> json;
|
||||||
|
}
|
||||||
|
catch(const Json::Exception& exc)
|
||||||
|
{
|
||||||
|
// Failed to parse json response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
626
src/ziputil.cpp
Normal file
626
src/ziputil.cpp
Normal file
@ -0,0 +1,626 @@
|
|||||||
|
/* 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 "ziputil.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||||
|
#include <boost/iostreams/copy.hpp>
|
||||||
|
#include <boost/iostreams/filter/zlib.hpp>
|
||||||
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
|
off_t ZipUtil::getMojoSetupScriptSize(std::stringstream *stream)
|
||||||
|
{
|
||||||
|
off_t mojosetup_script_size = -1;
|
||||||
|
int script_lines = 0;
|
||||||
|
boost::regex re("offset=`head -n (\\d+?) \"\\$0\"", boost::regex::perl | boost::regex::icase);
|
||||||
|
boost::match_results<std::string::const_iterator> what;
|
||||||
|
|
||||||
|
if (boost::regex_search(stream->str(), what, re))
|
||||||
|
{
|
||||||
|
script_lines = std::stoi(what[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string script;
|
||||||
|
for (int i = 0; i < script_lines; ++i)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
std::getline(*stream, line);
|
||||||
|
script += line + "\n";
|
||||||
|
}
|
||||||
|
mojosetup_script_size = script.size();
|
||||||
|
|
||||||
|
return mojosetup_script_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t ZipUtil::getMojoSetupInstallerSize(std::stringstream *stream)
|
||||||
|
{
|
||||||
|
off_t mojosetup_installer_size = -1;
|
||||||
|
boost::regex re("filesizes=\"(\\d+?)\"", boost::regex::perl | boost::regex::icase);
|
||||||
|
boost::match_results<std::string::const_iterator> what;
|
||||||
|
|
||||||
|
if (boost::regex_search(stream->str(), what, re))
|
||||||
|
{
|
||||||
|
mojosetup_installer_size = std::stoll(what[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mojosetup_installer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tm ZipUtil::date_time_to_tm(uint64_t date, uint64_t time)
|
||||||
|
{
|
||||||
|
/* DOS date time format
|
||||||
|
* Y|Y|Y|Y|Y|Y|Y|M| |M|M|M|D|D|D|D|D| |h|h|h|h|h|m|m|m| |m|m|m|s|s|s|s|s
|
||||||
|
*
|
||||||
|
* second is divided by 2
|
||||||
|
* month starts at 1
|
||||||
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247%28v=vs.85%29.aspx */
|
||||||
|
|
||||||
|
uint64_t local_time_base_year = 1900;
|
||||||
|
uint64_t dos_time_base_year = 1980;
|
||||||
|
|
||||||
|
struct tm timeinfo;
|
||||||
|
timeinfo.tm_year = (uint16_t)(((date & 0xFE00) >> 9) - local_time_base_year + dos_time_base_year);
|
||||||
|
timeinfo.tm_mon = (uint16_t)(((date & 0x1E0) >> 5) - 1);
|
||||||
|
timeinfo.tm_mday = (uint16_t)(date & 0x1F);
|
||||||
|
timeinfo.tm_hour = (uint16_t)((time & 0xF800) >> 11);
|
||||||
|
timeinfo.tm_min = (uint16_t)((time & 0x7E0) >> 5);
|
||||||
|
timeinfo.tm_sec = (uint16_t)(2 * (time & 0x1F));
|
||||||
|
timeinfo.tm_isdst = -1;
|
||||||
|
|
||||||
|
return timeinfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZipUtil::isValidDate(struct tm timeinfo)
|
||||||
|
{
|
||||||
|
if (!(timeinfo.tm_year >= 0 && timeinfo.tm_year <= 207))
|
||||||
|
return false;
|
||||||
|
if (!(timeinfo.tm_mon >= 0 && timeinfo.tm_mon <= 11))
|
||||||
|
return false;
|
||||||
|
if (!(timeinfo.tm_mday >= 1 && timeinfo.tm_mday <= 31))
|
||||||
|
return false;
|
||||||
|
if (!(timeinfo.tm_hour >= 0 && timeinfo.tm_hour <= 23))
|
||||||
|
return false;
|
||||||
|
if (!(timeinfo.tm_min >= 0 && timeinfo.tm_min <= 59))
|
||||||
|
return false;
|
||||||
|
if (!(timeinfo.tm_sec >= 0 && timeinfo.tm_sec <= 59))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ZipUtil::readValue(std::istream *stream, uint32_t len)
|
||||||
|
{
|
||||||
|
uint64_t value = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
value |= ((uint64_t)(stream->get() & 0xFF)) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ZipUtil::readUInt64(std::istream *stream)
|
||||||
|
{
|
||||||
|
uint64_t value = (uint64_t)readValue(stream, sizeof(uint64_t));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ZipUtil::readUInt32(std::istream *stream)
|
||||||
|
{
|
||||||
|
uint32_t value = (uint32_t)readValue(stream, sizeof(uint32_t));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ZipUtil::readUInt16(std::istream *stream)
|
||||||
|
{
|
||||||
|
uint16_t value = (uint16_t)readValue(stream, sizeof(uint16_t));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ZipUtil::readUInt8(std::istream *stream)
|
||||||
|
{
|
||||||
|
uint8_t value = (uint8_t)readValue(stream, sizeof(uint8_t));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t ZipUtil::getZipEOCDOffsetSignature(std::istream *stream, const uint32_t& signature)
|
||||||
|
{
|
||||||
|
off_t offset = -1;
|
||||||
|
stream->seekg(0, stream->end);
|
||||||
|
off_t stream_length = stream->tellg();
|
||||||
|
|
||||||
|
for (off_t i = 4; i <= stream_length; i++)
|
||||||
|
{
|
||||||
|
off_t pos = stream_length - i;
|
||||||
|
stream->seekg(pos, stream->beg);
|
||||||
|
if (readUInt32(stream) == signature)
|
||||||
|
{
|
||||||
|
offset = stream->tellg();
|
||||||
|
offset -= 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t ZipUtil::getZipEOCDOffset(std::istream *stream)
|
||||||
|
{
|
||||||
|
return getZipEOCDOffsetSignature(stream, ZIP_EOCD_HEADER_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t ZipUtil::getZip64EOCDOffset(std::istream *stream)
|
||||||
|
{
|
||||||
|
return getZipEOCDOffsetSignature(stream, ZIP_EOCD_HEADER_SIGNATURE64);
|
||||||
|
}
|
||||||
|
|
||||||
|
zipEOCD ZipUtil::readZipEOCDStruct(std::istream *stream, const off_t& eocd_start_pos)
|
||||||
|
{
|
||||||
|
zipEOCD eocd;
|
||||||
|
|
||||||
|
stream->seekg(eocd_start_pos, stream->beg);
|
||||||
|
|
||||||
|
// end of central dir signature <4 bytes>
|
||||||
|
eocd.header = readUInt32(stream);
|
||||||
|
|
||||||
|
// number of this disk <2 bytes>
|
||||||
|
eocd.disk = readUInt16(stream); // Number of this disk
|
||||||
|
|
||||||
|
// number of the disk with the start of the central directory <2 bytes>
|
||||||
|
eocd.cd_start_disk = readUInt16(stream);
|
||||||
|
|
||||||
|
// total number of entries in the central directory on this disk <2 bytes>
|
||||||
|
eocd.cd_records = readUInt16(stream);
|
||||||
|
|
||||||
|
// total number of entries in the central directory <2 bytes>
|
||||||
|
eocd.total_cd_records = readUInt16(stream);
|
||||||
|
|
||||||
|
// size of the central directory <4 bytes>
|
||||||
|
eocd.cd_size = readUInt32(stream);
|
||||||
|
|
||||||
|
// offset of start of central directory with respect to the starting disk number <4 bytes>
|
||||||
|
eocd.cd_start_offset = readUInt32(stream);
|
||||||
|
|
||||||
|
// .ZIP file comment length <2 bytes>
|
||||||
|
eocd.comment_length = readUInt16(stream);
|
||||||
|
|
||||||
|
// .ZIP file comment <variable size>
|
||||||
|
if (eocd.comment_length > 0)
|
||||||
|
{
|
||||||
|
char *buf = new char[eocd.comment_length + 1];
|
||||||
|
stream->read(buf, eocd.comment_length);
|
||||||
|
eocd.comment = std::string(buf, eocd.comment_length);
|
||||||
|
delete[] buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
return eocd;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip64EOCD ZipUtil::readZip64EOCDStruct(std::istream *stream, const off_t& eocd_start_pos)
|
||||||
|
{
|
||||||
|
zip64EOCD eocd;
|
||||||
|
|
||||||
|
stream->seekg(eocd_start_pos, stream->beg);
|
||||||
|
|
||||||
|
// zip64 end of central dir signature <4 bytes>
|
||||||
|
eocd.header = readUInt32(stream);
|
||||||
|
|
||||||
|
// size of zip64 end of central directory record <8 bytes>
|
||||||
|
eocd.directory_record_size = readUInt64(stream);
|
||||||
|
/* The value stored into the "size of zip64 end of central
|
||||||
|
* directory record" should be the size of the remaining
|
||||||
|
* record and should not include the leading 12 bytes.
|
||||||
|
*
|
||||||
|
* Size = SizeOfFixedFields + SizeOfVariableData - 12 */
|
||||||
|
|
||||||
|
// version made by <2 bytes>
|
||||||
|
eocd.version_made_by = readUInt16(stream);
|
||||||
|
|
||||||
|
// version needed to extract <2 bytes>
|
||||||
|
eocd.version_needed = readUInt16(stream);
|
||||||
|
|
||||||
|
// number of this disk <4 bytes>
|
||||||
|
eocd.cd = readUInt32(stream);
|
||||||
|
|
||||||
|
// number of the disk with the start of the central directory <8 bytes>
|
||||||
|
eocd.cd_start = readUInt32(stream);
|
||||||
|
|
||||||
|
// total number of entries in the central directory on this disk <8 bytes>
|
||||||
|
eocd.cd_total_disk = readUInt64(stream);
|
||||||
|
|
||||||
|
// total number of entries in the central directory <8 bytes>
|
||||||
|
eocd.cd_total = readUInt64(stream);
|
||||||
|
|
||||||
|
// size of the central directory <8 bytes>
|
||||||
|
eocd.cd_size = readUInt64(stream);
|
||||||
|
|
||||||
|
// offset of start of central directory with respect to the starting disk number <8 bytes>
|
||||||
|
eocd.cd_offset = readUInt64(stream);
|
||||||
|
|
||||||
|
// zip64 extensible data sector <variable size>
|
||||||
|
// This is data is not needed for our purposes so we just ignore this data
|
||||||
|
|
||||||
|
return eocd;
|
||||||
|
}
|
||||||
|
|
||||||
|
zipCDEntry ZipUtil::readZipCDEntry(std::istream *stream)
|
||||||
|
{
|
||||||
|
zipCDEntry cd;
|
||||||
|
char *buf;
|
||||||
|
|
||||||
|
// file header signature <4 bytes>
|
||||||
|
cd.header = readUInt32(stream);
|
||||||
|
|
||||||
|
cd.isLocalCDEntry = (cd.header == ZIP_LOCAL_HEADER_SIGNATURE);
|
||||||
|
|
||||||
|
if (!cd.isLocalCDEntry)
|
||||||
|
{
|
||||||
|
// version made by <2 bytes>
|
||||||
|
cd.version_made_by = readUInt16(stream);
|
||||||
|
}
|
||||||
|
// version needed to extract <2 bytes>
|
||||||
|
cd.version_needed = readUInt16(stream);
|
||||||
|
|
||||||
|
// general purpose bit flag <2 bytes>
|
||||||
|
cd.flag = readUInt16(stream);
|
||||||
|
|
||||||
|
// compression method <2 bytes>
|
||||||
|
cd.compression_method = readUInt16(stream);
|
||||||
|
|
||||||
|
// last mod file time <2 bytes>
|
||||||
|
cd.mod_time = readUInt16(stream);
|
||||||
|
|
||||||
|
// last mod file date <2 bytes>
|
||||||
|
cd.mod_date = readUInt16(stream);
|
||||||
|
|
||||||
|
// crc-32 <4 bytes>
|
||||||
|
cd.crc32 = readUInt32(stream);
|
||||||
|
|
||||||
|
// compressed size <4 bytes>
|
||||||
|
cd.comp_size = readUInt32(stream);
|
||||||
|
|
||||||
|
// uncompressed size <4 bytes>
|
||||||
|
cd.uncomp_size = readUInt32(stream);
|
||||||
|
|
||||||
|
// file name length <2 bytes>
|
||||||
|
cd.filename_length = readUInt16(stream);
|
||||||
|
|
||||||
|
// extra field length <2 bytes>
|
||||||
|
cd.extra_length = readUInt16(stream);
|
||||||
|
|
||||||
|
if (!cd.isLocalCDEntry)
|
||||||
|
{
|
||||||
|
// file comment length <2 bytes>
|
||||||
|
cd.comment_length = readUInt16(stream);
|
||||||
|
|
||||||
|
// disk number start <2 bytes>
|
||||||
|
cd.disk_num = readUInt16(stream);
|
||||||
|
|
||||||
|
// internal file attributes <2 bytes>
|
||||||
|
cd.internal_file_attr = readUInt16(stream);
|
||||||
|
|
||||||
|
// external file attributes <4 bytes>
|
||||||
|
cd.external_file_attr = readUInt32(stream);
|
||||||
|
|
||||||
|
// relative offset of local header <4 bytes>
|
||||||
|
cd.disk_offset = readUInt32(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// file name <variable size>
|
||||||
|
buf = new char[cd.filename_length + 1];
|
||||||
|
stream->read(buf, cd.filename_length);
|
||||||
|
cd.filename = std::string(buf, cd.filename_length);
|
||||||
|
delete[] buf;
|
||||||
|
|
||||||
|
// extra field <variable size>
|
||||||
|
buf = new char[cd.extra_length + 1];
|
||||||
|
stream->read(buf, cd.extra_length);
|
||||||
|
cd.extra = std::string(buf, cd.extra_length);
|
||||||
|
delete[] buf;
|
||||||
|
std::stringstream extra_stream(cd.extra);
|
||||||
|
|
||||||
|
cd.timestamp = 0;
|
||||||
|
struct tm timeinfo = date_time_to_tm(cd.mod_date, cd.mod_time);
|
||||||
|
if (isValidDate(timeinfo))
|
||||||
|
cd.timestamp = mktime(&timeinfo);
|
||||||
|
|
||||||
|
// Read extra fields
|
||||||
|
off_t i = 0;
|
||||||
|
while (i < cd.extra_length)
|
||||||
|
{
|
||||||
|
/* Extra field
|
||||||
|
* <2 bytes> signature
|
||||||
|
* <2 bytes> size of extra field data
|
||||||
|
* <variable> extra field data */
|
||||||
|
|
||||||
|
uint16_t header_id = readUInt16(&extra_stream);
|
||||||
|
uint16_t extra_data_size = readUInt16(&extra_stream);
|
||||||
|
|
||||||
|
if (header_id == ZIP_EXTENSION_ZIP64)
|
||||||
|
{
|
||||||
|
/* Zip64 Extended Information Extra Field
|
||||||
|
* <8 bytes> size of uncompressed file
|
||||||
|
* <8 bytes> size of compressed data
|
||||||
|
* <8 bytes> offset of local header record
|
||||||
|
* <4 bytes> number of the disk on which this file starts
|
||||||
|
*
|
||||||
|
* The fields only appear if the corresponding Local or Central
|
||||||
|
* directory record field is set to UINT16_MAX or UINT32_MAX */
|
||||||
|
|
||||||
|
if (cd.uncomp_size == UINT32_MAX)
|
||||||
|
cd.uncomp_size = readUInt64(&extra_stream);
|
||||||
|
if (cd.comp_size == UINT32_MAX)
|
||||||
|
cd.comp_size = readUInt64(&extra_stream);
|
||||||
|
if (cd.disk_offset == UINT32_MAX)
|
||||||
|
cd.disk_offset = readUInt64(&extra_stream);
|
||||||
|
if (cd.disk_num == UINT16_MAX)
|
||||||
|
cd.disk_num = readUInt32(&extra_stream);
|
||||||
|
}
|
||||||
|
else if (header_id == ZIP_EXTENDED_TIMESTAMP)
|
||||||
|
{
|
||||||
|
/* Extended Timestamp Extra Field
|
||||||
|
*
|
||||||
|
* Local header version
|
||||||
|
* <1 byte> info bits
|
||||||
|
* <4 bytes> modification time
|
||||||
|
* <4 bytes> access time
|
||||||
|
* <4 bytes> creation time
|
||||||
|
*
|
||||||
|
* Central header version
|
||||||
|
* <1 byte> info bits
|
||||||
|
* <4 bytes> modification time
|
||||||
|
*
|
||||||
|
* The lower three info bits in both headers indicate
|
||||||
|
* which timestamps are present in the local extra field
|
||||||
|
* bit 0 if set, modification time is present
|
||||||
|
* bit 1 if set, access time is present
|
||||||
|
* bit 2 if set, creation time is present
|
||||||
|
* bits 3-7 reserved for additional timestamps; not set
|
||||||
|
*
|
||||||
|
* If info bits indicate that modification time is present
|
||||||
|
* in the local header field, it must be present in the
|
||||||
|
* central header field.
|
||||||
|
* Those times that are present will appear in the order
|
||||||
|
* indicated, but any combination of times may be omitted. */
|
||||||
|
|
||||||
|
uint32_t modification_time = 0;
|
||||||
|
uint32_t access_time = 0;
|
||||||
|
uint32_t creation_time = 0;
|
||||||
|
|
||||||
|
uint8_t flags = readUInt8(&extra_stream);
|
||||||
|
|
||||||
|
if (flags & 0x1) // modification time is present
|
||||||
|
{
|
||||||
|
modification_time = readUInt32(&extra_stream);
|
||||||
|
cd.timestamp = modification_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd.isLocalCDEntry)
|
||||||
|
{
|
||||||
|
if (flags & 0x2) // access time is present
|
||||||
|
{
|
||||||
|
access_time = readUInt32(&extra_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 0x4) // creation time is present
|
||||||
|
{
|
||||||
|
creation_time = readUInt32(&extra_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// access time and creation time are unused currently
|
||||||
|
// suppress -Wunused-but-set-variable messages by casting these variables to void
|
||||||
|
(void) access_time;
|
||||||
|
(void) creation_time;
|
||||||
|
}
|
||||||
|
else if (header_id == ZIP_INFOZIP_UNIX_NEW)
|
||||||
|
{
|
||||||
|
/* Info-ZIP New Unix Extra Field
|
||||||
|
* <1 byte> version
|
||||||
|
* <1 byte> size of uid
|
||||||
|
* <variable> uid
|
||||||
|
* <1 byte> size of gid
|
||||||
|
* <variable> gid
|
||||||
|
*
|
||||||
|
* Currently Version is set to the number 1. If there is a need
|
||||||
|
* to change this field, the version will be incremented.
|
||||||
|
* UID and GID entries are stored in standard little endian format */
|
||||||
|
|
||||||
|
uint8_t version = readUInt8(&extra_stream);
|
||||||
|
if (version == 1)
|
||||||
|
{
|
||||||
|
uint64_t uid = 0;
|
||||||
|
uint64_t gid = 0;
|
||||||
|
|
||||||
|
uint8_t uid_size = readUInt8(&extra_stream);
|
||||||
|
for (uint8_t i = 0; i < uid_size; i++)
|
||||||
|
{
|
||||||
|
uid |= ((uint64_t)extra_stream.get()) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t gid_size = readUInt8(&extra_stream);
|
||||||
|
for (uint8_t i = 0; i < gid_size; i++)
|
||||||
|
{
|
||||||
|
gid |= ((uint64_t)extra_stream.get()) << (i * 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Unknown version
|
||||||
|
// Skip the rest of this field
|
||||||
|
extra_stream.seekg(extra_data_size - 1, extra_stream.cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Skip over unknown/unimplemented extra field
|
||||||
|
extra_stream.seekg(extra_data_size, extra_stream.cur);
|
||||||
|
}
|
||||||
|
i += 4 + extra_data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// file comment <variable size>
|
||||||
|
buf = new char[cd.comment_length + 1];
|
||||||
|
stream->read(buf, cd.comment_length);
|
||||||
|
cd.comment = std::string(buf, cd.comment_length);
|
||||||
|
delete[] buf;
|
||||||
|
|
||||||
|
return cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract file
|
||||||
|
returns 0 if successful
|
||||||
|
returns 1 if input file could not be opened
|
||||||
|
returns 2 if compression method is unsupported
|
||||||
|
returns 3 if output file could not be created
|
||||||
|
returns 4 if zlib error
|
||||||
|
*/
|
||||||
|
int ZipUtil::extractFile(const std::string& input_file_path, const std::string& output_file_path)
|
||||||
|
{
|
||||||
|
std::ifstream input_file(input_file_path, std::ifstream::in | std::ifstream::binary);
|
||||||
|
|
||||||
|
if (!input_file)
|
||||||
|
{
|
||||||
|
// Could not open input file
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
zipCDEntry cd = readZipCDEntry(&input_file);
|
||||||
|
|
||||||
|
if (!(cd.compression_method == boost::iostreams::zlib::deflated || cd.compression_method == boost::iostreams::zlib::no_compression))
|
||||||
|
{
|
||||||
|
// Unsupported compression method
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::iostreams::zlib_params p;
|
||||||
|
p.window_bits = 15;
|
||||||
|
p.noheader = true; // zlib header and trailing adler-32 checksum is omitted
|
||||||
|
|
||||||
|
std::ofstream output_file(output_file_path, std::ofstream::out | std::ofstream::binary);
|
||||||
|
if (!output_file)
|
||||||
|
{
|
||||||
|
// Failed to create output file
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncompress
|
||||||
|
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
|
||||||
|
|
||||||
|
if (cd.compression_method == boost::iostreams::zlib::deflated)
|
||||||
|
in.push(boost::iostreams::zlib_decompressor(p));
|
||||||
|
|
||||||
|
in.push(input_file);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::iostreams::copy(in, output_file);
|
||||||
|
}
|
||||||
|
catch(boost::iostreams::zlib_error & e)
|
||||||
|
{
|
||||||
|
// zlib error
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_file.close();
|
||||||
|
output_file.close();
|
||||||
|
|
||||||
|
if (cd.timestamp > 0)
|
||||||
|
boost::filesystem::last_write_time(output_file_path, cd.timestamp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract stream to stream
|
||||||
|
returns 0 if successful
|
||||||
|
returns 1 if input stream is not valid
|
||||||
|
returns 2 if compression method is unsupported
|
||||||
|
returns 3 if output stream is not valid
|
||||||
|
returns 4 if zlib error
|
||||||
|
*/
|
||||||
|
int ZipUtil::extractStream(std::istream* input_stream, std::ostream* output_stream)
|
||||||
|
{
|
||||||
|
if (!input_stream)
|
||||||
|
{
|
||||||
|
// Input stream not valid
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
zipCDEntry cd = readZipCDEntry(input_stream);
|
||||||
|
|
||||||
|
if (!(cd.compression_method == boost::iostreams::zlib::deflated || cd.compression_method == boost::iostreams::zlib::no_compression))
|
||||||
|
{
|
||||||
|
// Unsupported compression method
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::iostreams::zlib_params p;
|
||||||
|
p.window_bits = 15;
|
||||||
|
p.noheader = true; // zlib header and trailing adler-32 checksum is omitted
|
||||||
|
|
||||||
|
if (!output_stream)
|
||||||
|
{
|
||||||
|
// Output stream not valid
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncompress
|
||||||
|
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
|
||||||
|
|
||||||
|
if (cd.compression_method == boost::iostreams::zlib::deflated)
|
||||||
|
in.push(boost::iostreams::zlib_decompressor(p));
|
||||||
|
|
||||||
|
//in.push(compressed);
|
||||||
|
in.push(*input_stream);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::iostreams::copy(in, *output_stream);
|
||||||
|
}
|
||||||
|
catch(boost::iostreams::zlib_error & e)
|
||||||
|
{
|
||||||
|
// zlib error
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::perms ZipUtil::getBoostFilePermission(const uint16_t& attributes)
|
||||||
|
{
|
||||||
|
boost::filesystem::perms perms = boost::filesystem::no_perms;
|
||||||
|
if (attributes & S_IRUSR)
|
||||||
|
perms |= boost::filesystem::owner_read;
|
||||||
|
if (attributes & S_IWUSR)
|
||||||
|
perms |= boost::filesystem::owner_write;
|
||||||
|
if (attributes & S_IXUSR)
|
||||||
|
perms |= boost::filesystem::owner_exe;
|
||||||
|
|
||||||
|
if (attributes & S_IRGRP)
|
||||||
|
perms |= boost::filesystem::group_read;
|
||||||
|
if (attributes & S_IWGRP)
|
||||||
|
perms |= boost::filesystem::group_write;
|
||||||
|
if (attributes & S_IXGRP)
|
||||||
|
perms |= boost::filesystem::group_exe;
|
||||||
|
|
||||||
|
if (attributes & S_IROTH)
|
||||||
|
perms |= boost::filesystem::others_read;
|
||||||
|
if (attributes & S_IWOTH)
|
||||||
|
perms |= boost::filesystem::others_write;
|
||||||
|
if (attributes & S_IXOTH)
|
||||||
|
perms |= boost::filesystem::others_exe;
|
||||||
|
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZipUtil::isSymlink(const uint16_t& attributes)
|
||||||
|
{
|
||||||
|
bool bSymlink = ((attributes & S_IFMT) == S_IFLNK);
|
||||||
|
return bSymlink;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user