2014-06-03 21:38:10 +04:00
|
|
|
/**
|
|
|
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
|
|
|
* ZGFX (RDP8) Bulk Data Compression
|
|
|
|
*
|
|
|
|
* Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <winpr/crt.h>
|
|
|
|
#include <winpr/print.h>
|
|
|
|
#include <winpr/bitstream.h>
|
|
|
|
|
|
|
|
#include <freerdp/codec/zgfx.h>
|
|
|
|
|
2014-06-06 02:09:37 +04:00
|
|
|
/**
|
|
|
|
* RDP8 Compressor Limits:
|
|
|
|
*
|
|
|
|
* Maximum number of uncompressed bytes in a single segment: 65535
|
|
|
|
* Maximum match distance / minimum history size: 2500000 bytes.
|
|
|
|
* Maximum number of segments: 65535
|
|
|
|
* Maximum expansion of a segment (when compressed size exceeds uncompressed): 1000 bytes
|
|
|
|
* Minimum match length: 3 bytes
|
|
|
|
*/
|
|
|
|
|
2014-06-03 22:29:55 +04:00
|
|
|
struct _ZGFX_TOKEN
|
|
|
|
{
|
|
|
|
int prefixLength;
|
|
|
|
int prefixCode;
|
|
|
|
int valueBits;
|
|
|
|
int tokenType;
|
|
|
|
UINT32 valueBase;
|
|
|
|
};
|
|
|
|
typedef struct _ZGFX_TOKEN ZGFX_TOKEN;
|
|
|
|
|
|
|
|
static const ZGFX_TOKEN ZGFX_TOKEN_TABLE[] =
|
|
|
|
{
|
|
|
|
// len code vbits type vbase
|
|
|
|
{ 1, 0, 8, 0, 0 }, // 0
|
|
|
|
{ 5, 17, 5, 1, 0 }, // 10001
|
|
|
|
{ 5, 18, 7, 1, 32 }, // 10010
|
|
|
|
{ 5, 19, 9, 1, 160 }, // 10011
|
|
|
|
{ 5, 20, 10, 1, 672 }, // 10100
|
|
|
|
{ 5, 21, 12, 1, 1696 }, // 10101
|
|
|
|
{ 5, 24, 0, 0, 0x00 }, // 11000
|
|
|
|
{ 5, 25, 0, 0, 0x01 }, // 11001
|
|
|
|
{ 6, 44, 14, 1, 5792 }, // 101100
|
|
|
|
{ 6, 45, 15, 1, 22176 }, // 101101
|
|
|
|
{ 6, 52, 0, 0, 0x02 }, // 110100
|
|
|
|
{ 6, 53, 0, 0, 0x03 }, // 110101
|
|
|
|
{ 6, 54, 0, 0, 0xFF }, // 110110
|
|
|
|
{ 7, 92, 18, 1, 54944 }, // 1011100
|
|
|
|
{ 7, 93, 20, 1, 317088 }, // 1011101
|
|
|
|
{ 7, 110, 0, 0, 0x04 }, // 1101110
|
|
|
|
{ 7, 111, 0, 0, 0x05 }, // 1101111
|
|
|
|
{ 7, 112, 0, 0, 0x06 }, // 1110000
|
|
|
|
{ 7, 113, 0, 0, 0x07 }, // 1110001
|
|
|
|
{ 7, 114, 0, 0, 0x08 }, // 1110010
|
|
|
|
{ 7, 115, 0, 0, 0x09 }, // 1110011
|
|
|
|
{ 7, 116, 0, 0, 0x0A }, // 1110100
|
|
|
|
{ 7, 117, 0, 0, 0x0B }, // 1110101
|
|
|
|
{ 7, 118, 0, 0, 0x3A }, // 1110110
|
|
|
|
{ 7, 119, 0, 0, 0x3B }, // 1110111
|
|
|
|
{ 7, 120, 0, 0, 0x3C }, // 1111000
|
|
|
|
{ 7, 121, 0, 0, 0x3D }, // 1111001
|
|
|
|
{ 7, 122, 0, 0, 0x3E }, // 1111010
|
|
|
|
{ 7, 123, 0, 0, 0x3F }, // 1111011
|
|
|
|
{ 7, 124, 0, 0, 0x40 }, // 1111100
|
|
|
|
{ 7, 125, 0, 0, 0x80 }, // 1111101
|
|
|
|
{ 8, 188, 20, 1, 1365664 }, // 10111100
|
|
|
|
{ 8, 189, 21, 1, 2414240 }, // 10111101
|
|
|
|
{ 8, 252, 0, 0, 0x0C }, // 11111100
|
|
|
|
{ 8, 253, 0, 0, 0x38 }, // 11111101
|
|
|
|
{ 8, 254, 0, 0, 0x39 }, // 11111110
|
|
|
|
{ 8, 255, 0, 0, 0x66 }, // 11111111
|
|
|
|
{ 9, 380, 22, 1, 4511392 }, // 101111100
|
|
|
|
{ 9, 381, 23, 1, 8705696 }, // 101111101
|
|
|
|
{ 9, 382, 24, 1, 17094304 }, // 101111110
|
|
|
|
{ 0 }
|
|
|
|
};
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
#define zgfx_GetBits(_zgfx, _nbits) \
|
|
|
|
while (_zgfx->cBitsCurrent < _nbits) { \
|
|
|
|
_zgfx->BitsCurrent <<= 8; \
|
|
|
|
if (_zgfx->pbInputCurrent < _zgfx->pbInputEnd) \
|
|
|
|
_zgfx->BitsCurrent += *(_zgfx->pbInputCurrent)++; \
|
|
|
|
_zgfx->cBitsCurrent += 8; \
|
|
|
|
} \
|
|
|
|
_zgfx->cBitsRemaining -= _nbits; \
|
|
|
|
_zgfx->cBitsCurrent -= _nbits; \
|
|
|
|
_zgfx->bits = _zgfx->BitsCurrent >> _zgfx->cBitsCurrent; \
|
|
|
|
_zgfx->BitsCurrent &= ((1 << _zgfx->cBitsCurrent) - 1);
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-06 02:09:37 +04:00
|
|
|
void zgfx_history_buffer_ring_write(ZGFX_CONTEXT* zgfx, BYTE* src, UINT32 count)
|
|
|
|
{
|
|
|
|
UINT32 front;
|
|
|
|
UINT32 residue;
|
|
|
|
|
|
|
|
if (count <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (count > zgfx->HistoryBufferSize)
|
|
|
|
{
|
|
|
|
residue = count - zgfx->HistoryBufferSize;
|
|
|
|
count = zgfx->HistoryBufferSize;
|
|
|
|
src += residue;
|
|
|
|
|
|
|
|
zgfx->HistoryIndex = (zgfx->HistoryIndex + residue) % zgfx->HistoryBufferSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (zgfx->HistoryIndex + count <= zgfx->HistoryBufferSize)
|
|
|
|
{
|
|
|
|
CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, count);
|
|
|
|
|
|
|
|
if ((zgfx->HistoryIndex += count) == zgfx->HistoryBufferSize)
|
|
|
|
zgfx->HistoryIndex = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
front = zgfx->HistoryBufferSize - zgfx->HistoryIndex;
|
|
|
|
CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, front);
|
|
|
|
CopyMemory(zgfx->HistoryBuffer, &src[front], count - front);
|
|
|
|
zgfx->HistoryIndex = count - front;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void zgfx_history_buffer_ring_read(ZGFX_CONTEXT* zgfx, int offset, BYTE* dst, UINT32 count)
|
|
|
|
{
|
|
|
|
UINT32 front;
|
|
|
|
UINT32 index;
|
|
|
|
UINT32 bytes;
|
|
|
|
UINT32 valid;
|
|
|
|
UINT32 bytesLeft;
|
|
|
|
BYTE* dptr = dst;
|
|
|
|
BYTE* origDst = dst;
|
|
|
|
|
|
|
|
if (count <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bytesLeft = count;
|
|
|
|
|
|
|
|
index = (zgfx->HistoryIndex + zgfx->HistoryBufferSize - offset) % zgfx->HistoryBufferSize;
|
|
|
|
|
|
|
|
bytes = MIN(bytesLeft, offset);
|
|
|
|
|
|
|
|
if ((index + bytes) <= zgfx->HistoryBufferSize)
|
|
|
|
{
|
|
|
|
CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), bytes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
front = zgfx->HistoryBufferSize - index;
|
|
|
|
CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), front);
|
|
|
|
CopyMemory(&dptr[front], zgfx->HistoryBuffer, bytes - front);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((bytesLeft -= bytes) == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
dptr += bytes;
|
|
|
|
valid = bytes;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
bytes = valid;
|
|
|
|
|
|
|
|
if (bytes > bytesLeft)
|
|
|
|
bytes = bytesLeft;
|
|
|
|
|
|
|
|
CopyMemory(dptr, origDst, bytes);
|
|
|
|
dptr += bytes;
|
|
|
|
valid <<= 1;
|
|
|
|
}
|
|
|
|
while ((bytesLeft -= bytes) > 0);
|
|
|
|
}
|
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
int zgfx_decompress_segment(ZGFX_CONTEXT* zgfx, BYTE* pbSegment, UINT32 cbSegment)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
|
|
|
BYTE c;
|
2014-06-05 23:24:36 +04:00
|
|
|
BYTE flags;
|
2014-06-03 22:29:55 +04:00
|
|
|
int extra;
|
|
|
|
int opIndex;
|
|
|
|
int haveBits;
|
|
|
|
int inPrefix;
|
|
|
|
UINT32 count;
|
|
|
|
UINT32 distance;
|
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
if (cbSegment < 1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
flags = pbSegment[0]; /* header (1 byte) */
|
|
|
|
|
|
|
|
pbSegment++;
|
|
|
|
cbSegment--;
|
|
|
|
|
2014-06-03 22:29:55 +04:00
|
|
|
zgfx->OutputCount = 0;
|
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
if (!(flags & PACKET_COMPRESSED))
|
|
|
|
{
|
2014-06-06 02:09:37 +04:00
|
|
|
zgfx_history_buffer_ring_write(zgfx, pbSegment, cbSegment);
|
|
|
|
CopyMemory(zgfx->OutputBuffer, pbSegment, cbSegment);
|
|
|
|
zgfx->OutputCount = cbSegment;
|
2014-06-05 23:24:36 +04:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
zgfx->pbInputCurrent = pbSegment;
|
|
|
|
zgfx->pbInputEnd = &pbSegment[cbSegment - 1];
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
/* NumberOfBitsToDecode = ((NumberOfBytesToDecode - 1) * 8) - ValueOfLastByte */
|
2014-06-05 23:24:36 +04:00
|
|
|
zgfx->cBitsRemaining = 8 * (cbSegment - 1) - *zgfx->pbInputEnd;
|
2014-06-03 22:29:55 +04:00
|
|
|
zgfx->cBitsCurrent = 0;
|
|
|
|
zgfx->BitsCurrent = 0;
|
|
|
|
|
|
|
|
while (zgfx->cBitsRemaining)
|
|
|
|
{
|
|
|
|
haveBits = 0;
|
|
|
|
inPrefix = 0;
|
|
|
|
|
|
|
|
for (opIndex = 0; ZGFX_TOKEN_TABLE[opIndex].prefixLength != 0; opIndex++)
|
|
|
|
{
|
|
|
|
while (haveBits < ZGFX_TOKEN_TABLE[opIndex].prefixLength)
|
|
|
|
{
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, 1);
|
|
|
|
inPrefix = (inPrefix << 1) + zgfx->bits;
|
2014-06-03 22:29:55 +04:00
|
|
|
haveBits++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inPrefix == ZGFX_TOKEN_TABLE[opIndex].prefixCode)
|
|
|
|
{
|
|
|
|
if (ZGFX_TOKEN_TABLE[opIndex].tokenType == 0)
|
|
|
|
{
|
2014-06-06 02:09:37 +04:00
|
|
|
/* Literal */
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
|
|
|
|
c = (BYTE) (ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits);
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
zgfx->HistoryBuffer[zgfx->HistoryIndex] = c;
|
|
|
|
|
|
|
|
if (++zgfx->HistoryIndex == zgfx->HistoryBufferSize)
|
|
|
|
zgfx->HistoryIndex = 0;
|
|
|
|
|
|
|
|
zgfx->OutputBuffer[zgfx->OutputCount++] = c;
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
|
|
|
|
distance = ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits;
|
2014-06-03 22:29:55 +04:00
|
|
|
|
|
|
|
if (distance != 0)
|
|
|
|
{
|
2014-06-06 02:09:37 +04:00
|
|
|
/* Match */
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, 1);
|
2014-06-05 22:52:27 +04:00
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
if (zgfx->bits == 0)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
|
|
|
count = 3;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count = 4;
|
|
|
|
extra = 2;
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, 1);
|
2014-06-05 22:52:27 +04:00
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
while (zgfx->bits == 1)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
|
|
|
count *= 2;
|
|
|
|
extra++;
|
2014-06-05 22:52:27 +04:00
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, 1);
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, extra);
|
|
|
|
count += zgfx->bits;
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
|
|
|
|
2014-06-06 02:09:37 +04:00
|
|
|
zgfx_history_buffer_ring_read(zgfx, distance, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
|
|
|
|
zgfx_history_buffer_ring_write(zgfx, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
|
|
|
|
zgfx->OutputCount += count;
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-06-06 02:09:37 +04:00
|
|
|
/* Unencoded */
|
|
|
|
|
2014-06-05 23:41:42 +04:00
|
|
|
zgfx_GetBits(zgfx, 15);
|
|
|
|
count = zgfx->bits;
|
2014-06-03 22:29:55 +04:00
|
|
|
|
|
|
|
zgfx->cBitsRemaining -= zgfx->cBitsCurrent;
|
|
|
|
zgfx->cBitsCurrent = 0;
|
|
|
|
zgfx->BitsCurrent = 0;
|
|
|
|
|
2014-06-06 02:09:37 +04:00
|
|
|
CopyMemory(&(zgfx->OutputBuffer[zgfx->OutputCount]), zgfx->pbInputCurrent, count);
|
|
|
|
zgfx_history_buffer_ring_write(zgfx, zgfx->pbInputCurrent, count);
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-06 02:09:37 +04:00
|
|
|
zgfx->pbInputCurrent += count;
|
|
|
|
zgfx->cBitsRemaining -= (8 * count);
|
|
|
|
zgfx->OutputCount += count;
|
2014-06-05 23:24:36 +04:00
|
|
|
}
|
|
|
|
}
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
break;
|
|
|
|
}
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
int zgfx_decompress(ZGFX_CONTEXT* zgfx, BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
|
|
|
int status;
|
2014-06-05 22:52:27 +04:00
|
|
|
BYTE descriptor;
|
|
|
|
|
|
|
|
if (SrcSize < 1)
|
|
|
|
return -1;
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
descriptor = pSrcData[0]; /* descriptor (1 byte) */
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
if (descriptor == ZGFX_SEGMENTED_SINGLE)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
2014-06-05 23:24:36 +04:00
|
|
|
status = zgfx_decompress_segment(zgfx, &pSrcData[1], SrcSize - 1);
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
*ppDstData = (BYTE*) malloc(zgfx->OutputCount);
|
|
|
|
*pDstSize = zgfx->OutputCount;
|
|
|
|
|
|
|
|
CopyMemory(*ppDstData, zgfx->OutputBuffer, zgfx->OutputCount);
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
2014-06-05 22:52:27 +04:00
|
|
|
else if (descriptor == ZGFX_SEGMENTED_MULTIPART)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
2014-06-05 22:52:27 +04:00
|
|
|
UINT32 segmentSize;
|
2014-06-03 22:29:55 +04:00
|
|
|
UINT16 segmentNumber;
|
2014-06-05 22:52:27 +04:00
|
|
|
UINT16 segmentCount;
|
|
|
|
UINT32 segmentOffset;
|
|
|
|
UINT32 uncompressedSize;
|
|
|
|
BYTE* pConcatenated;
|
|
|
|
|
|
|
|
segmentOffset = 7;
|
|
|
|
segmentCount = *((UINT16*) &pSrcData[1]); /* segmentCount (2 bytes) */
|
|
|
|
uncompressedSize = *((UINT32*) &pSrcData[3]); /* uncompressedSize (4 bytes) */
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
pConcatenated = (BYTE*) malloc(uncompressedSize);
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
*ppDstData = pConcatenated;
|
|
|
|
*pDstSize = uncompressedSize;
|
|
|
|
|
|
|
|
for (segmentNumber = 0; segmentNumber < segmentCount; segmentNumber++)
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
2014-06-05 22:52:27 +04:00
|
|
|
segmentSize = *((UINT32*) &pSrcData[segmentOffset]); /* segmentSize (4 bytes) */
|
|
|
|
segmentOffset += 4;
|
2014-06-03 22:29:55 +04:00
|
|
|
|
2014-06-05 23:24:36 +04:00
|
|
|
status = zgfx_decompress_segment(zgfx, &pSrcData[segmentOffset], segmentSize);
|
2014-06-05 22:52:27 +04:00
|
|
|
segmentOffset += segmentSize;
|
2014-06-03 22:29:55 +04:00
|
|
|
|
|
|
|
CopyMemory(pConcatenated, zgfx->OutputBuffer, zgfx->OutputCount);
|
|
|
|
pConcatenated += zgfx->OutputCount;
|
|
|
|
}
|
|
|
|
}
|
2014-06-05 22:52:27 +04:00
|
|
|
else
|
2014-06-03 22:29:55 +04:00
|
|
|
{
|
2014-06-05 22:52:27 +04:00
|
|
|
return -1;
|
2014-06-03 22:29:55 +04:00
|
|
|
}
|
2014-06-03 21:38:10 +04:00
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
return 1;
|
2014-06-03 21:38:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int zgfx_compress(ZGFX_CONTEXT* zgfx, BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush)
|
|
|
|
{
|
2014-06-03 22:29:55 +04:00
|
|
|
zgfx->HistoryIndex = 0;
|
2014-06-03 21:38:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor)
|
|
|
|
{
|
|
|
|
ZGFX_CONTEXT* zgfx;
|
|
|
|
|
|
|
|
zgfx = (ZGFX_CONTEXT*) calloc(1, sizeof(ZGFX_CONTEXT));
|
|
|
|
|
|
|
|
if (zgfx)
|
|
|
|
{
|
|
|
|
zgfx->Compressor = Compressor;
|
|
|
|
|
2014-06-05 22:52:27 +04:00
|
|
|
zgfx->HistoryBufferSize = sizeof(zgfx->HistoryBuffer);
|
|
|
|
|
2014-06-03 21:38:10 +04:00
|
|
|
zgfx_context_reset(zgfx, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return zgfx;
|
|
|
|
}
|
|
|
|
|
|
|
|
void zgfx_context_free(ZGFX_CONTEXT* zgfx)
|
|
|
|
{
|
|
|
|
if (zgfx)
|
|
|
|
{
|
|
|
|
free(zgfx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|