iw4x-sp/src/common/utils/compression.cpp
2024-04-26 10:59:05 +02:00

139 lines
3.3 KiB
C++

#include <string>
#include <unordered_map>
#include "memory.hpp"
#include "compression.hpp"
#include <zlib.h>
#include <zip.h>
#include <gsl/gsl>
#include "io.hpp"
namespace utils::compression {
namespace zlib {
namespace {
class zlib_stream {
public:
zlib_stream() {
std::memset(&stream_, 0, sizeof(z_stream));
valid_ = inflateInit(&stream_) == Z_OK;
}
~zlib_stream() {
if (valid_) {
inflateEnd(&stream_);
}
}
zlib_stream(zlib_stream&&) = delete;
zlib_stream(const zlib_stream&) = delete;
zlib_stream& operator=(zlib_stream&&) = delete;
zlib_stream& operator=(const zlib_stream&) = delete;
z_stream& get() { return stream_; }
[[nodiscard]] bool is_valid() const { return valid_; }
private:
bool valid_{false};
z_stream stream_{};
};
} // namespace
std::string decompress(const std::string& data) {
std::string buffer;
zlib_stream stream_container;
if (!stream_container.is_valid()) {
return {};
}
int ret;
std::size_t offset = 0;
static thread_local std::uint8_t dest[CHUNK] = {0};
auto& stream = stream_container.get();
do {
const auto input_size = std::min(sizeof(dest), data.size() - offset);
stream.avail_in = static_cast<uInt>(input_size);
stream.next_in = reinterpret_cast<const Bytef*>(data.data()) + offset;
offset += stream.avail_in;
do {
stream.avail_out = sizeof(dest);
stream.next_out = dest;
ret = inflate(&stream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END) {
return {};
}
buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out);
} while (stream.avail_out == 0);
} while (ret != Z_STREAM_END);
return buffer;
}
std::string compress(const std::string& data) {
std::string result;
auto length = compressBound(static_cast<uLong>(data.size()));
result.resize(length);
if (compress2(reinterpret_cast<Bytef*>(result.data()), &length,
reinterpret_cast<const Bytef*>(data.data()),
static_cast<uLong>(data.size()), Z_BEST_COMPRESSION) != Z_OK) {
return {};
}
result.resize(length);
return result;
}
} // namespace zlib
namespace zip {
namespace {
bool add_file(zipFile& zip_file, const std::string& filename,
const std::string& data) {
if (zipOpenNewFileInZip(zip_file, filename.c_str(), nullptr, nullptr, 0,
nullptr, 0, nullptr, Z_DEFLATED,
Z_BEST_COMPRESSION) != ZIP_OK) {
return false;
}
const auto _ = gsl::finally([&zip_file]() { zipCloseFileInZip(zip_file); });
return ZIP_OK == zipWriteInFileInZip(zip_file, data.data(), data.size());
}
} // namespace
void archive::add(const std::string& filename, const std::string& data) {
this->files_[filename] = data;
}
bool archive::write(const std::string& filename, const std::string& comment) {
// Hack to create the directory :3
io::write_file(filename, {});
io::remove_file(filename);
auto* zip_file = zipOpen(filename.c_str(), 0);
if (zip_file == nullptr) {
return false;
}
const auto _ = gsl::finally([&zip_file, &comment]() {
zipClose(zip_file, comment.empty() ? nullptr : comment.c_str());
});
for (const auto& file : this->files_) {
if (!add_file(zip_file, file.first, file.second)) {
return false;
}
}
return true;
}
} // namespace zip
} // namespace utils::compression