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:
Sude 2018-05-29 08:56:29 +03:00
parent 7373d357aa
commit 463a9c386e
7 changed files with 1585 additions and 2 deletions

View File

@ -35,6 +35,7 @@ find_package(Htmlcxx REQUIRED)
find_package(Tinyxml2 REQUIRED)
find_package(Rhash REQUIRED)
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)
file(GLOB SRC_FILES
main.cpp
@ -47,6 +48,7 @@ file(GLOB SRC_FILES
src/gamefile.cpp
src/gamedetails.cpp
src/galaxyapi.cpp
src/ziputil.cpp
)
set(GIT_CHECKOUT FALSE)
@ -103,6 +105,7 @@ target_include_directories(${PROJECT_NAME}
PRIVATE ${Htmlcxx_INCLUDE_DIRS}
PRIVATE ${Tinyxml2_INCLUDE_DIRS}
PRIVATE ${Rhash_INCLUDE_DIRS}
PRIVATE ${ZLIB_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}
@ -114,6 +117,7 @@ target_link_libraries(${PROJECT_NAME}
PRIVATE ${Tinyxml2_LIBRARIES}
PRIVATE ${Rhash_LIBRARIES}
PRIVATE ${CMAKE_THREAD_LIBS_INIT}
PRIVATE ${ZLIB_LIBRARIES}
)
if(LINK_LIBCRYPTO EQUAL 1)

View File

@ -12,6 +12,7 @@ This repository contains the code of unofficial [GOG](http://www.gog.com/) downl
* [tinyxml2](https://github.com/leethomason/tinyxml2)
* [boost](http://www.boost.org/) (regex, date-time, system, filesystem, program-options, iostreams)
* [libcrypto](https://www.openssl.org/) if libcurl is built with OpenSSL
* [zlib](https://www.zlib.net/)
## Make dependencies
* [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 \
libboost-system-dev libboost-filesystem-dev libboost-program-options-dev \
libboost-date-time-dev libboost-iostreams-dev help2man cmake libssl-dev \
pkg-config
pkg-config zlib1g-dev
## Build and install

View File

@ -67,6 +67,20 @@ struct ChunkMemoryStruct
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
{
public:
@ -123,6 +137,9 @@ class Downloader
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);
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;
API *gogAPI;

109
include/ziputil.h Normal file
View 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

View File

@ -9,6 +9,7 @@
#include "globals.h"
#include "downloadinfo.h"
#include "message.h"
#include "ziputil.h"
#include <cstdio>
#include <cstdlib>
@ -44,6 +45,7 @@ ThreadSafeQueue<gameFile> createXMLQueue;
ThreadSafeQueue<gameItem> gameItemQueue;
ThreadSafeQueue<gameDetails> gameDetailsQueue;
ThreadSafeQueue<galaxyDepotItem> dlQueueGalaxy;
ThreadSafeQueue<zipFileEntry> dlQueueGalaxy_MojoSetupHack;
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)
@ -3307,6 +3309,11 @@ void Downloader::galaxyInstallGame(const std::string& product_id, int build_inde
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
{
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;
}
@ -3751,6 +3758,42 @@ void Downloader::galaxyShowBuilds(const std::string& product_id, int build_index
if (json.empty() && iPlatform == GlobalConstants::PLATFORM_LINUX)
{
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;
}
@ -3859,3 +3902,776 @@ std::vector<std::string> Downloader::galaxyGetOrphanedFiles(const std::vector<ga
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;
}

View File

@ -149,7 +149,17 @@ Json::Value galaxyAPI::getProductBuilds(const std::string& product_id, const std
std::istringstream response(this->getResponse(url));
Json::Value json;
response >> json;
if (!response.str().empty())
{
try
{
response >> json;
}
catch(const Json::Exception& exc)
{
// Failed to parse json response
}
}
return json;
}

626
src/ziputil.cpp Normal file
View 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;
}