Files
nsis-plugin-ns7zip/versions/25.01/CPP/7zip/Compress/ZstdDecoder.cpp
T
Simone d074cc7c07 chore: initial commit (extracted from Launchers monorepo)
Plugin: ns7zip v2.0.0
Architectures: x86-ansi, x86-unicode, amd64-unicode
License: LGPL-2.1-or-later
2026-04-29 14:07:51 +02:00

438 lines
12 KiB
C++

// ZstdDecoder.cpp
#include "StdAfx.h"
// #include <stdio.h>
#include "../../../C/Alloc.h"
#include "../Common/CWrappers.h"
#include "../Common/StreamUtils.h"
#include "ZstdDecoder.h"
namespace NCompress {
namespace NZstd {
static const size_t k_Zstd_BlockSizeMax = 1 << 17;
/*
we set _outStepMask as (k_Zstd_BlockSizeMax - 1), because:
- cycSize in zstd decoder for isCyclicMode is aligned for (1 << 17) only.
So some write sizes will be multiple of ((1 << 17) * n).
- Also it can be optimal to flush data after each block decoding.
*/
CDecoder::CDecoder():
_outStepMask(k_Zstd_BlockSizeMax - 1) // must be = (1 << x) - 1
, _dec(NULL)
, _inProcessed(0)
, _inBufSize(1u << 19) // larger value will reduce the number of memcpy() calls in CZstdDec code
, _inBuf(NULL)
, FinishMode(false)
, DisableHash(False)
// , DisableHash(True) // for debug : fast decoding without hash calculation
{
// ZstdDecInfo_Clear(&ResInfo);
}
CDecoder::~CDecoder()
{
if (_dec)
ZstdDec_Destroy(_dec);
MidFree(_inBuf);
}
Z7_COM7F_IMF(CDecoder::SetInBufSize(UInt32 , UInt32 size))
{ _inBufSize = size; return S_OK; }
Z7_COM7F_IMF(CDecoder::SetOutBufSize(UInt32 , UInt32 size))
{
// we round it down:
size >>= 1;
size |= size >> (1 << 0);
size |= size >> (1 << 1);
size |= size >> (1 << 2);
size |= size >> (1 << 3);
size |= size >> (1 << 4);
_outStepMask = size; // it's (1 << x) - 1 now
return S_OK;
}
Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte * /* prop */, UInt32 /* size */))
{
// if (size != 3 && size != 5) return E_NOTIMPL;
return S_OK;
}
Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode))
{
FinishMode = (finishMode != 0);
// FinishMode = false; // for debug
return S_OK;
}
Z7_COM7F_IMF(CDecoder::ReadUnusedFromInBuf(void *data, UInt32 size, UInt32 *processedSize))
{
size_t cur = ZstdDec_ReadUnusedFromInBuf(_dec, _afterDecoding_tempPos, data, size);
_afterDecoding_tempPos += cur;
size -= (UInt32)cur;
if (size)
{
const size_t rem = _state.inLim - _state.inPos;
if (size > rem)
size = (UInt32)rem;
if (size)
{
memcpy((Byte *)data + cur, _state.inBuf + _state.inPos, size);
_state.inPos += size;
cur += size;
}
}
*processedSize = (UInt32)cur;
return S_OK;
}
HRESULT CDecoder::Prepare(const UInt64 *outSize)
{
_inProcessed = 0;
_afterDecoding_tempPos = 0;
ZstdDecState_Clear(&_state);
ZstdDecInfo_CLEAR(&ResInfo)
// _state.outStep = _outStepMask + 1; // must be = (1 << x)
_state.disableHash = DisableHash;
if (outSize)
{
_state.outSize_Defined = True;
_state.outSize = *outSize;
// _state.outSize = 0; // for debug
}
if (!_dec)
{
_dec = ZstdDec_Create(&g_AlignedAlloc, &g_BigAlloc);
if (!_dec)
return E_OUTOFMEMORY;
}
if (!_inBuf || _inBufSize != _inBufSize_Allocated)
{
MidFree(_inBuf);
_inBuf = NULL;
_inBufSize_Allocated = 0;
_inBuf = (Byte *)MidAlloc(_inBufSize);
if (!_inBuf)
return E_OUTOFMEMORY;
_inBufSize_Allocated = _inBufSize;
}
_state.inBuf = _inBuf;
ZstdDec_Init(_dec);
return S_OK;
}
Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress))
{
RINOK(Prepare(outSize))
UInt64 inPrev = 0;
UInt64 outPrev = 0;
UInt64 writtenSize = 0;
bool readWasFinished = false;
SRes sres = SZ_OK;
HRESULT hres = S_OK;
HRESULT hres_Read = S_OK;
for (;;)
{
if (_state.inPos == _state.inLim && !readWasFinished)
{
_state.inPos = 0;
_state.inLim = _inBufSize;
hres_Read = ReadStream(inStream, _inBuf, &_state.inLim);
// _state.inLim -= 5; readWasFinished = True; // for debug
if (_state.inLim != _inBufSize || hres_Read != S_OK)
{
// hres_Read = 99; // for debug
readWasFinished = True;
}
}
{
const size_t inPos_Start = _state.inPos;
sres = ZstdDec_Decode(_dec, &_state);
_inProcessed += _state.inPos - inPos_Start;
}
/*
if (_state.status == ZSTD_STATUS_FINISHED_FRAME)
printf("\nfinished frame pos=%8x, checksum=%08x\n", (unsigned)_state.outProcessed, (unsigned)_state.info.checksum);
*/
const bool needStop = (sres != SZ_OK)
|| _state.status == ZSTD_STATUS_OUT_REACHED
|| (outSize && *outSize < _state.outProcessed)
|| (readWasFinished && _state.inPos == _state.inLim
&& ZstdDecState_DOES_NEED_MORE_INPUT_OR_FINISHED_FRAME(&_state));
size_t size = _state.winPos - _state.wrPos; // full write size
if (size)
{
if (!needStop)
{
// we try to flush on aligned positions, if possible
size = _state.needWrite_Size; // minimal required write size
const size_t alignedPos = _state.winPos & ~(size_t)_outStepMask;
if (alignedPos > _state.wrPos)
{
const size_t size2 = alignedPos - _state.wrPos; // optimized aligned size
if (size < size2)
size = size2;
}
}
if (size)
{
{
size_t curSize = size;
if (outSize)
{
const UInt64 rem = *outSize - writtenSize;
if (curSize > rem)
curSize = (size_t)rem;
}
if (curSize)
{
// printf("Write wrPos=%8x, size=%8x\n", (unsigned)_state.wrPos, (unsigned)size);
hres = WriteStream(outStream, _state.win + _state.wrPos, curSize);
if (hres != S_OK)
break;
writtenSize += curSize; // it's real size of data that was written to stream
}
}
_state.wrPos += size; // virtual written size, that will be reported to CZstdDec
// _state.needWrite_Size = 0; // optional
}
}
if (needStop)
break;
if (progress)
if (_inProcessed - inPrev >= (1 << 27)
|| _state.outProcessed - outPrev >= (1 << 28))
{
inPrev = _inProcessed;
outPrev = _state.outProcessed;
RINOK(progress->SetRatioInfo(&inPrev, &outPrev))
}
}
if (hres == S_OK)
{
ZstdDec_GetResInfo(_dec, &_state, sres, &ResInfo);
sres = ResInfo.decode_SRes;
/* now (ResInfo.decode_SRes) can contain 2 extra error codes:
- SZ_ERROR_NO_ARCHIVE : if no frames
- SZ_ERROR_INPUT_EOF : if ZSTD_STATUS_NEEDS_MORE_INPUT
*/
_inProcessed -= ResInfo.extraSize;
if (hres_Read != S_OK && _state.inLim == _state.inPos && readWasFinished)
{
/* if (there is stream reading error,
and decoding was stopped because of end of input stream),
then we use reading error as main error code */
if (sres == SZ_OK ||
sres == SZ_ERROR_INPUT_EOF ||
sres == SZ_ERROR_NO_ARCHIVE)
hres = hres_Read;
}
if (sres == SZ_ERROR_INPUT_EOF && !FinishMode)
{
/* SZ_ERROR_INPUT_EOF case is allowed case for (!FinishMode) mode.
So we restore SZ_OK result for that case: */
ResInfo.decode_SRes = sres = SZ_OK;
}
if (hres == S_OK)
{
hres = SResToHRESULT(sres);
if (hres == S_OK && FinishMode)
{
if ((inSize && *inSize != _inProcessed)
|| ResInfo.is_NonFinishedFrame
|| (outSize && (*outSize != writtenSize || writtenSize != _state.outProcessed)))
hres = S_FALSE;
}
}
}
return hres;
}
Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value))
{
*value = _inProcessed;
return S_OK;
}
#ifndef Z7_NO_READ_FROM_CODER_ZSTD
Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize))
{
_inProcessed = 0;
_hres_Read = S_OK;
_hres_Decode = S_OK;
_writtenSize = 0;
_readWasFinished = false;
_wasFinished = false;
return Prepare(outSize);
}
Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream))
{ _inStream = inStream; return S_OK; }
Z7_COM7F_IMF(CDecoder::ReleaseInStream())
{ _inStream.Release(); return S_OK; }
// if SetInStream() mode: the caller must call GetFinishResult() after full decoding
// to check that there decoding was finished correctly
HRESULT CDecoder::GetFinishResult()
{
if (_state.winPos != _state.wrPos || !_wasFinished)
return FinishMode ? S_FALSE : S_OK;
// _state.winPos == _state.wrPos
// _wasFinished == true
if (FinishMode && _hres_Decode == S_OK && _state.outSize_Defined && _state.outSize != _writtenSize)
_hres_Decode = S_FALSE;
return _hres_Decode;
}
Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize))
{
if (processedSize)
*processedSize = 0;
for (;;)
{
if (_state.outSize_Defined)
{
// _writtenSize <= _state.outSize
const UInt64 rem = _state.outSize - _writtenSize;
if (size > rem)
size = (UInt32)rem;
}
{
size_t cur = _state.winPos - _state.wrPos;
if (cur)
{
// _state.winPos != _state.wrPos;
// so there is some decoded data that was not written still
if (size == 0)
{
// if (FinishMode) and we are not allowed to write more, then it's data error
if (FinishMode && _state.outSize_Defined && _state.outSize == _writtenSize)
return S_FALSE;
return S_OK;
}
if (cur > size)
cur = (size_t)size;
// cur != 0
memcpy(data, _state.win + _state.wrPos, cur);
_state.wrPos += cur;
_writtenSize += cur;
data = (void *)((Byte *)data + cur);
if (processedSize)
*processedSize += (UInt32)cur;
size -= (UInt32)cur;
continue;
}
}
// _state.winPos == _state.wrPos
if (_wasFinished)
{
if (_hres_Decode == S_OK && FinishMode
&& _state.outSize_Defined && _state.outSize != _writtenSize)
_hres_Decode = S_FALSE;
return _hres_Decode;
}
// _wasFinished == false
if (size == 0 && _state.outSize_Defined && _state.outSize != _state.outProcessed)
{
/* size == 0 : so the caller don't need more data now.
_state.outSize > _state.outProcessed : so more data will be requested
later by caller for full processing.
So we exit without ZstdDec_Decode() call, because we don't want
ZstdDec_Decode() to start new block decoding
*/
return S_OK;
}
// size != 0 || !_state.outSize_Defined || _state.outSize == _state.outProcessed)
if (_state.inPos == _state.inLim && !_readWasFinished)
{
_state.inPos = 0;
_state.inLim = _inBufSize;
_hres_Read = ReadStream(_inStream, _inBuf, &_state.inLim);
if (_state.inLim != _inBufSize || _hres_Read != S_OK)
{
// _hres_Read = 99; // for debug
_readWasFinished = True;
}
}
SRes sres;
{
const SizeT inPos_Start = _state.inPos;
sres = ZstdDec_Decode(_dec, &_state);
_inProcessed += _state.inPos - inPos_Start;
}
const bool inFinished = (_state.inPos == _state.inLim) && _readWasFinished;
_wasFinished = (sres != SZ_OK)
|| _state.status == ZSTD_STATUS_OUT_REACHED
|| (_state.outSize_Defined && _state.outSize < _state.outProcessed)
|| (inFinished
&& ZstdDecState_DOES_NEED_MORE_INPUT_OR_FINISHED_FRAME(&_state));
if (!_wasFinished)
continue;
// _wasFinished == true
/* (_state.winPos != _state.wrPos) is possible here.
So we still can have some data to flush,
but we must all result variables .
*/
HRESULT hres = S_OK;
ZstdDec_GetResInfo(_dec, &_state, sres, &ResInfo);
sres = ResInfo.decode_SRes;
_inProcessed -= ResInfo.extraSize;
if (_hres_Read != S_OK && inFinished)
{
if (sres == SZ_OK ||
sres == SZ_ERROR_INPUT_EOF ||
sres == SZ_ERROR_NO_ARCHIVE)
hres = _hres_Read;
}
if (sres == SZ_ERROR_INPUT_EOF && !FinishMode)
ResInfo.decode_SRes = sres = SZ_OK;
if (hres == S_OK)
{
hres = SResToHRESULT(sres);
if (hres == S_OK && FinishMode)
if (!inFinished
|| ResInfo.is_NonFinishedFrame
|| (_state.outSize_Defined && _state.outSize != _state.outProcessed))
hres = S_FALSE;
}
_hres_Decode = hres;
}
}
#endif
}}