AuroraRuntime/Source/Compression/BlockDecompressor.cpp

401 lines
12 KiB
C++

/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: BlockDecompressor.cpp
Date: 2021-6-17
Author: Reece
***/
#include <RuntimeInternal.hpp>
#include "Compression.hpp"
#include "BlockDecompressor.hpp"
#include "bzlib.h"
#include "zstd.h"
#include "zlib.h"
#include "lz4.h"
namespace Aurora::Compression
{
bool BaseStream::Read(void * /*opt*/ buffer, AuUInt32 &len, bool ingestUntilError)
{
if (ingestUntilError)
{
while (this->_outbuffer.size() < len)
{
if (Ingest(4096).second == 0)
{
if (this->_outbuffer.size())
{
break;
}
return false;
}
}
}
return !buffer || StreamRead(buffer, len, this->_outbuffer);
}
class ZSTDInflate : public BaseStream
{
public:
~ZSTDInflate()
{
if (auto dctx = std::exchange(dctx_, {}))
{
ZSTD_freeDCtx(dctx);
}
}
bool Init(Aurora::IO::IStreamReader *reader)
{
this->reader_ = reader;
this->dctx_ = ZSTD_createDCtx();
if (!this->dctx_)
{
SysPushErrorGen("Couldn't create decompressor");
return false;
}
this->_outbuffer.reserve(10 * 1024);
return true;
}
std::pair<AuUInt32, AuUInt32> Ingest(AuUInt32 input) override
{
AuUInt32 length = ZSTD_DStreamInSize();
void *din = alloca(length);
auto outFrameLength = ZSTD_DStreamOutSize();
void *dout = alloca(outFrameLength);
AuUInt32 done{}, read{};
while (read != input)
{
AuUInt32 request = std::min(input, length);
if (this->reader_->Read(din, request) != IO::EStreamError::eErrorNone)
{
return std::make_pair(read, done);
}
read += request;
ZSTD_inBuffer input = { din, request, 0 };
while (input.pos < input.size)
{
ZSTD_outBuffer output = { dout, outFrameLength, 0 };
auto ret = ZSTD_decompressStream(this->dctx_, &output, &input);
if (ZSTD_isError(ret))
{
SysPushErrorIO("Compression error: {}", ret);
return std::make_pair(read, 0);
}
done += output.pos;
this->_outbuffer.insert(this->_outbuffer.end(),
reinterpret_cast<const AuUInt8 *>(output.dst),
reinterpret_cast<const AuUInt8 *>(output.dst) + output.pos);
}
}
return std::make_pair(read, done);
}
private:
Aurora::IO::IStreamReader *reader_;
ZSTD_DCtx *dctx_;
};
class ZIPInflate : public BaseStream
{
public:
~ZIPInflate()
{
if (auto ctx = std::exchange(this->init_, {}))
{
inflateEnd(&this->ctx_);
}
}
bool Init(Aurora::IO::IStreamReader *reader)
{
this->reader_ = reader;
auto ret = inflateInit(&this->ctx_);
if (ret != Z_OK)
{
SysPushErrorMem("Error: {}", ret);
return false;
}
this->_outbuffer.reserve(10 * 1024);
this->init_ = true;
return true;
}
std::pair<AuUInt32, AuUInt32> Ingest(AuUInt32 input) override
{
int ret;
AuUInt32 done{}, read{};
while (read != input)
{
AuUInt32 request = std::min(input, AuUInt32(ArraySize(din_)));
if (this->reader_->Read(din_, request) != IO::EStreamError::eErrorNone)
{
return std::make_pair(read, done);
}
read += request;
this->ctx_.avail_in = request;
this->ctx_.next_in = reinterpret_cast<unsigned char *>(din_);
do
{
this->ctx_.avail_out = ArraySize(dout_);
this->ctx_.next_out = dout_;
if (!this->ctx_.avail_out)
{
break;
}
ret = inflate(&this->ctx_, Z_NO_FLUSH);
if (ret != Z_OK)
{
SysPushErrorIO("Error: {}", ret);
return std::make_pair(read, 0);
}
auto have = ArraySize(dout_) - this->ctx_.avail_out;
done += have;
this->_outbuffer.insert(this->_outbuffer.end(),
reinterpret_cast<const AuUInt8 *>(dout_),
reinterpret_cast<const AuUInt8 *>(dout_) + have);
} while (this->ctx_.avail_out == 0);
SysAssert(this->ctx_.avail_in == 0);
}
return std::make_pair(read, done);
}
private:
Aurora::IO::IStreamReader *reader_;
z_stream ctx_ {};
bool init_ {};
unsigned char din_[4096];
unsigned char dout_[4096];
};
class BZIPInflate : public BaseStream
{
public:
~BZIPInflate()
{
if (auto ctx = std::exchange(this->init_, {}))
{
BZ2_bzDecompressEnd(&this->ctx_);
}
}
bool Init(Aurora::IO::IStreamReader *reader)
{
this->reader_ = reader;
auto ret = BZ2_bzDecompressInit(&this->ctx_, 0, 0);
if (ret != Z_OK)
{
SysPushErrorMem("Error: {}", ret);
return false;
}
this->_outbuffer.reserve(10 * 1024);
this->init_ = true;
return true;
}
std::pair<AuUInt32, AuUInt32> Ingest(AuUInt32 input) override
{
int ret;
AuUInt32 done{}, read{};
while (read != input)
{
AuUInt32 request = std::min(input, AuUInt32(ArraySize(din_)));
if (this->reader_->Read(din_, request) != IO::EStreamError::eErrorNone)
{
return std::make_pair(read, done);
}
read += request;
this->ctx_.avail_in = request;
this->ctx_.next_in = reinterpret_cast<char *>(din_);
do
{
this->ctx_.avail_out = ArraySize(dout_);
this->ctx_.next_out = dout_;
ret = BZ2_bzDecompress(&this->ctx_);
if (ret != Z_OK)
{
SysPushErrorIO("Error: {}", ret);
return std::make_pair(read, 0);
}
auto have = ArraySize(dout_) - this->ctx_.avail_out;
done += have;
this->_outbuffer.insert(this->_outbuffer.end(),
reinterpret_cast<const AuUInt8 *>(dout_),
reinterpret_cast<const AuUInt8 *>(dout_) + have);
} while (this->ctx_.avail_out == 0);
SysAssert(this->ctx_.avail_in == 0);
}
return std::make_pair(read, done);
}
private:
Aurora::IO::IStreamReader *reader_;
bz_stream ctx_ {};
bool init_ {};
char dout_[4096];
char din_[4096];
};
class LZ4Inflate : public BaseStream
{
public:
~LZ4Inflate()
{
if (auto ctx = std::exchange(this->lz4Stream_, {}))
{
LZ4_freeStreamDecode(this->lz4Stream_);
}
}
bool Init(Aurora::IO::IStreamReader *reader)
{
this->reader_ = reader;
this->lz4Stream_ = LZ4_createStreamDecode();
if (!this->lz4Stream_)
{
SysPushErrorMem();
return false;
}
this->_outbuffer.reserve(10 * 1024);
return true;
}
std::pair<AuUInt32, AuUInt32> Ingest(AuUInt32 input) override
{
AuUInt32 done {}, read {}, lastFrameSize {};
std::shared_ptr<char> inFrame;
std::shared_ptr<char> outFrames[2];
bool outFrame {};
while (read != input)
{
AuUInt16 frameSize;
AuUInt32 request = sizeof(frameSize);
if (this->reader_->Read(&frameSize, request) != IO::EStreamError::eErrorNone)
{
return std::make_pair(read, done);
}
read += request;
if ((lastFrameSize < frameSize) || (!outFrames[outFrame]))
{
inFrame = std::shared_ptr<char>(new char[frameSize], std::default_delete<char[]>());
outFrames[outFrame] = std::shared_ptr<char>(new char[frameSize], std::default_delete<char[]>());
lastFrameSize = frameSize;
}
request = frameSize;
if (this->reader_->Read(inFrame.get(), request) != IO::EStreamError::eErrorNone)
{
return std::make_pair(read, done);
}
read += request;
auto outPtr = outFrames[outFrame].get();
auto bytes = LZ4_decompress_safe_continue(lz4Stream_, inFrame.get(), outPtr, frameSize, frameSize);
if (bytes <= 0)
{
return std::make_pair(read, 0);
}
done += bytes;
this->_outbuffer.insert(this->_outbuffer.end(),
reinterpret_cast<const AuUInt8 *>(outPtr),
reinterpret_cast<const AuUInt8 *>(outPtr) + bytes);
outFrames[!outFrame] = std::move(outFrames[outFrame]);
outFrame = !outFrame;
}
return std::make_pair(read, done);
}
private:
Aurora::IO::IStreamReader *reader_;
LZ4_streamDecode_t* lz4Stream_ {};
};
AUKN_SYM ICompressionStream *DecompressorNew(IO::IStreamReader *reader, ECompresionType type)
{
BaseStream * ret{};
switch (type)
{
case ECompresionType::eZSTD:
ret = new ZSTDInflate();
break;
case ECompresionType::eBZIP2:
ret = new BZIPInflate();
break;
case ECompresionType::eLZ4:
ret = new LZ4Inflate();
break;
case ECompresionType::eDeflate:
ret = new ZIPInflate();
break;
default:
ret = nullptr;
break;
}
if (ret)
{
if (!ret->Init(reader))
{
delete ret;
ret = nullptr;
}
}
return ret;
}
AUKN_SYM void DecompressorRelease(ICompressionStream * stream)
{
SafeDelete<BaseStream *>(stream);
}
}