summaryrefslogtreecommitdiffstats
path: root/libfreerdp/codec
diff options
context:
space:
mode:
Diffstat (limited to 'libfreerdp/codec')
-rw-r--r--libfreerdp/codec/audio.c298
-rw-r--r--libfreerdp/codec/bitmap.c1088
-rw-r--r--libfreerdp/codec/bulk.c391
-rw-r--r--libfreerdp/codec/bulk.h45
-rw-r--r--libfreerdp/codec/clear.c1188
-rw-r--r--libfreerdp/codec/color.c1681
-rw-r--r--libfreerdp/codec/dsp.c1507
-rw-r--r--libfreerdp/codec/dsp.h34
-rw-r--r--libfreerdp/codec/dsp_ffmpeg.c846
-rw-r--r--libfreerdp/codec/dsp_ffmpeg.h48
-rw-r--r--libfreerdp/codec/h264.c777
-rw-r--r--libfreerdp/codec/h264.h106
-rw-r--r--libfreerdp/codec/h264_ffmpeg.c697
-rw-r--r--libfreerdp/codec/h264_mediacodec.c527
-rw-r--r--libfreerdp/codec/h264_mf.c595
-rw-r--r--libfreerdp/codec/h264_openh264.c632
-rw-r--r--libfreerdp/codec/include/bitmap.c449
-rw-r--r--libfreerdp/codec/interleaved.c750
-rw-r--r--libfreerdp/codec/jpeg.c64
-rw-r--r--libfreerdp/codec/mppc.c857
-rw-r--r--libfreerdp/codec/mppc.h54
-rw-r--r--libfreerdp/codec/ncrush.c3045
-rw-r--r--libfreerdp/codec/ncrush.h53
-rw-r--r--libfreerdp/codec/nsc.c502
-rw-r--r--libfreerdp/codec/nsc_encode.c533
-rw-r--r--libfreerdp/codec/nsc_encode.h29
-rw-r--r--libfreerdp/codec/nsc_sse2.c384
-rw-r--r--libfreerdp/codec/nsc_sse2.h34
-rw-r--r--libfreerdp/codec/nsc_types.h75
-rw-r--r--libfreerdp/codec/planar.c1753
-rw-r--r--libfreerdp/codec/progressive.c2651
-rw-r--r--libfreerdp/codec/progressive.h221
-rw-r--r--libfreerdp/codec/region.c820
-rw-r--r--libfreerdp/codec/rfx.c2411
-rw-r--r--libfreerdp/codec/rfx_bitstream.h107
-rw-r--r--libfreerdp/codec/rfx_constants.h81
-rw-r--r--libfreerdp/codec/rfx_decode.c102
-rw-r--r--libfreerdp/codec/rfx_decode.h36
-rw-r--r--libfreerdp/codec/rfx_differential.h50
-rw-r--r--libfreerdp/codec/rfx_dwt.c218
-rw-r--r--libfreerdp/codec/rfx_dwt.h31
-rw-r--r--libfreerdp/codec/rfx_encode.c313
-rw-r--r--libfreerdp/codec/rfx_encode.h28
-rw-r--r--libfreerdp/codec/rfx_neon.c536
-rw-r--r--libfreerdp/codec/rfx_neon.h34
-rw-r--r--libfreerdp/codec/rfx_quantization.c104
-rw-r--r--libfreerdp/codec/rfx_quantization.h29
-rw-r--r--libfreerdp/codec/rfx_rlgr.c772
-rw-r--r--libfreerdp/codec/rfx_rlgr.h32
-rw-r--r--libfreerdp/codec/rfx_sse2.c484
-rw-r--r--libfreerdp/codec/rfx_sse2.h34
-rw-r--r--libfreerdp/codec/rfx_types.h182
-rw-r--r--libfreerdp/codec/test/CMakeLists.txt37
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecClear.c91
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c219
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecMppc.c1093
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecNCrush.c122
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecPlanar.c5785
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecProgressive.c1146
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c894
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecXCrush.c130
-rw-r--r--libfreerdp/codec/test/TestFreeRDPCodecZGfx.c274
-rw-r--r--libfreerdp/codec/test/TestFreeRDPRegion.c863
-rw-r--r--libfreerdp/codec/test/progressive.bmpbin0 -> 3506618 bytes
-rw-r--r--libfreerdp/codec/test/rfx.bmpbin0 -> 16438 bytes
-rw-r--r--libfreerdp/codec/test/test01.bmpbin0 -> 4150 bytes
-rw-r--r--libfreerdp/codec/xcrush.c1175
-rw-r--r--libfreerdp/codec/xcrush.h51
-rw-r--r--libfreerdp/codec/yuv.c887
-rw-r--r--libfreerdp/codec/zgfx.c603
70 files changed, 41688 insertions, 0 deletions
diff --git a/libfreerdp/codec/audio.c b/libfreerdp/codec/audio.c
new file mode 100644
index 0000000..ff2786d
--- /dev/null
+++ b/libfreerdp/codec/audio.c
@@ -0,0 +1,298 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Formats
+ *
+ * Copyright 2013 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/audio.h>
+
+#define TAG FREERDP_TAG("codec")
+
+UINT32 audio_format_compute_time_length(const AUDIO_FORMAT* format, size_t size)
+{
+ UINT32 mstime = 0;
+ UINT32 wSamples = 0;
+
+ /**
+ * [MSDN-AUDIOFORMAT]:
+ * http://msdn.microsoft.com/en-us/library/ms713497.aspx
+ */
+
+ if (format->wBitsPerSample)
+ {
+ const size_t samples = (size * 8) / format->wBitsPerSample;
+ WINPR_ASSERT(samples <= UINT32_MAX);
+ wSamples = (UINT32)samples;
+ mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
+ }
+ else
+ {
+ mstime = 0;
+
+ if (format->wFormatTag == WAVE_FORMAT_GSM610)
+ {
+ UINT16 nSamplesPerBlock = 0;
+
+ if ((format->cbSize == 2) && (format->data))
+ {
+ nSamplesPerBlock = *((UINT16*)format->data);
+ const size_t samples = (size / format->nBlockAlign) * nSamplesPerBlock;
+ WINPR_ASSERT(samples <= UINT32_MAX);
+ wSamples = (UINT32)samples;
+ mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels);
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "audio_format_compute_time_length: invalid WAVE_FORMAT_GSM610 format");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "audio_format_compute_time_length: unknown format %" PRIu16 "",
+ format->wFormatTag);
+ }
+ }
+
+ return mstime;
+}
+
+char* audio_format_get_tag_string(UINT16 wFormatTag)
+{
+ switch (wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return "WAVE_FORMAT_PCM";
+
+ case WAVE_FORMAT_ADPCM:
+ return "WAVE_FORMAT_ADPCM";
+
+ case WAVE_FORMAT_ALAW:
+ return "WAVE_FORMAT_ALAW";
+
+ case WAVE_FORMAT_MULAW:
+ return "WAVE_FORMAT_MULAW";
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return "WAVE_FORMAT_DVI_ADPCM";
+
+ case WAVE_FORMAT_GSM610:
+ return "WAVE_FORMAT_GSM610";
+
+ case WAVE_FORMAT_MSG723:
+ return "WAVE_FORMAT_MSG723";
+
+ case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
+ return "WAVE_FORMAT_DSPGROUP_TRUESPEECH ";
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return "WAVE_FORMAT_MPEGLAYER3";
+
+ case WAVE_FORMAT_WMAUDIO2:
+ return "WAVE_FORMAT_WMAUDIO2";
+
+ case WAVE_FORMAT_AAC_MS:
+ return "WAVE_FORMAT_AAC_MS";
+ }
+
+ return "WAVE_FORMAT_UNKNOWN";
+}
+
+void audio_format_print(wLog* log, DWORD level, const AUDIO_FORMAT* format)
+{
+ WLog_Print(log, level,
+ "%s:\t wFormatTag: 0x%04" PRIX16 " nChannels: %" PRIu16 " nSamplesPerSec: %" PRIu32
+ " "
+ "nAvgBytesPerSec: %" PRIu32 " nBlockAlign: %" PRIu16 " wBitsPerSample: %" PRIu16
+ " cbSize: %" PRIu16 "",
+ audio_format_get_tag_string(format->wFormatTag), format->wFormatTag,
+ format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec,
+ format->nBlockAlign, format->wBitsPerSample, format->cbSize);
+}
+
+void audio_formats_print(wLog* log, DWORD level, const AUDIO_FORMAT* formats, UINT16 count)
+{
+ if (formats)
+ {
+ WLog_Print(log, level, "AUDIO_FORMATS (%" PRIu16 ") ={", count);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ const AUDIO_FORMAT* format = &formats[index];
+ WLog_Print(log, level, "\t");
+ audio_format_print(log, level, format);
+ }
+
+ WLog_Print(log, level, "}");
+ }
+}
+
+BOOL audio_format_read(wStream* s, AUDIO_FORMAT* format)
+{
+ if (!s || !format)
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 18))
+ return FALSE;
+
+ Stream_Read_UINT16(s, format->wFormatTag);
+ Stream_Read_UINT16(s, format->nChannels);
+ Stream_Read_UINT32(s, format->nSamplesPerSec);
+ Stream_Read_UINT32(s, format->nAvgBytesPerSec);
+ Stream_Read_UINT16(s, format->nBlockAlign);
+ Stream_Read_UINT16(s, format->wBitsPerSample);
+ Stream_Read_UINT16(s, format->cbSize);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, format->cbSize))
+ return FALSE;
+
+ format->data = NULL;
+
+ if (format->cbSize > 0)
+ {
+ format->data = malloc(format->cbSize);
+
+ if (!format->data)
+ return FALSE;
+
+ Stream_Read(s, format->data, format->cbSize);
+ }
+
+ return TRUE;
+}
+
+BOOL audio_format_write(wStream* s, const AUDIO_FORMAT* format)
+{
+ if (!s || !format)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(s, 18 + format->cbSize))
+ return FALSE;
+
+ Stream_Write_UINT16(s, format->wFormatTag); /* wFormatTag (WAVE_FORMAT_PCM) */
+ Stream_Write_UINT16(s, format->nChannels); /* nChannels */
+ Stream_Write_UINT32(s, format->nSamplesPerSec); /* nSamplesPerSec */
+ Stream_Write_UINT32(s, format->nAvgBytesPerSec); /* nAvgBytesPerSec */
+ Stream_Write_UINT16(s, format->nBlockAlign); /* nBlockAlign */
+ Stream_Write_UINT16(s, format->wBitsPerSample); /* wBitsPerSample */
+ Stream_Write_UINT16(s, format->cbSize); /* cbSize */
+
+ if (format->cbSize > 0)
+ Stream_Write(s, format->data, format->cbSize);
+
+ return TRUE;
+}
+
+BOOL audio_format_copy(const AUDIO_FORMAT* srcFormat, AUDIO_FORMAT* dstFormat)
+{
+ if (!srcFormat || !dstFormat)
+ return FALSE;
+
+ *dstFormat = *srcFormat;
+
+ if (srcFormat->cbSize > 0)
+ {
+ dstFormat->data = malloc(srcFormat->cbSize);
+
+ if (!dstFormat->data)
+ return FALSE;
+
+ memcpy(dstFormat->data, srcFormat->data, dstFormat->cbSize);
+ }
+
+ return TRUE;
+}
+
+BOOL audio_format_compatible(const AUDIO_FORMAT* with, const AUDIO_FORMAT* what)
+{
+ if (!with || !what)
+ return FALSE;
+
+ if (with->wFormatTag != WAVE_FORMAT_UNKNOWN)
+ {
+ if (with->wFormatTag != what->wFormatTag)
+ return FALSE;
+ }
+
+ if (with->nChannels != 0)
+ {
+ if (with->nChannels != what->nChannels)
+ return FALSE;
+ }
+
+ if (with->nSamplesPerSec != 0)
+ {
+ if (with->nSamplesPerSec != what->nSamplesPerSec)
+ return FALSE;
+ }
+
+ if (with->wBitsPerSample != 0)
+ {
+ if (with->wBitsPerSample != what->wBitsPerSample)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL audio_format_valid(const AUDIO_FORMAT* format)
+{
+ if (!format)
+ return FALSE;
+
+ if (format->nChannels == 0)
+ return FALSE;
+
+ if (format->nSamplesPerSec == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+AUDIO_FORMAT* audio_format_new(void)
+{
+ return audio_formats_new(1);
+}
+
+AUDIO_FORMAT* audio_formats_new(size_t count)
+{
+ return calloc(count, sizeof(AUDIO_FORMAT));
+}
+
+void audio_format_free(AUDIO_FORMAT* format)
+{
+ if (format)
+ free(format->data);
+}
+
+void audio_formats_free(AUDIO_FORMAT* formats, size_t count)
+{
+ if (formats)
+ {
+ for (size_t index = 0; index < count; index++)
+ {
+ AUDIO_FORMAT* format = &formats[index];
+ audio_format_free(format);
+ }
+
+ free(formats);
+ }
+}
diff --git a/libfreerdp/codec/bitmap.c b/libfreerdp/codec/bitmap.c
new file mode 100644
index 0000000..d51f2f7
--- /dev/null
+++ b/libfreerdp/codec/bitmap.c
@@ -0,0 +1,1088 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bitmap Compression
+ *
+ * Copyright 2004-2012 Jay Sorg <jay.sorg@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+static INLINE UINT16 GETPIXEL16(const void* d, UINT32 x, UINT32 y, UINT32 w)
+{
+ const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT16));
+ return (UINT16)(((UINT16)src[1] << 8) | (UINT16)src[0]);
+}
+
+static INLINE UINT32 GETPIXEL32(const void* d, UINT32 x, UINT32 y, UINT32 w)
+{
+ const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT32));
+ return (((UINT32)src[3]) << 24) | (((UINT32)src[2]) << 16) | (((UINT32)src[1]) << 8) |
+ (src[0] & 0xFF);
+}
+
+/*****************************************************************************/
+static INLINE UINT16 IN_PIXEL16(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w,
+ UINT16 in_last_pixel)
+{
+ if (in_ptr == 0)
+ return 0;
+ else if (in_x < in_w)
+ return GETPIXEL16(in_ptr, in_x, in_y, in_w);
+ else
+ return in_last_pixel;
+}
+
+/*****************************************************************************/
+static INLINE UINT32 IN_PIXEL32(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w,
+ UINT32 in_last_pixel)
+{
+ if (in_ptr == 0)
+ return 0;
+ else if (in_x < in_w)
+ return GETPIXEL32(in_ptr, in_x, in_y, in_w);
+ else
+ return in_last_pixel;
+}
+
+/*****************************************************************************/
+/* color */
+static UINT16 out_color_count_2(UINT16 in_count, wStream* in_s, UINT16 in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x3 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x60);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf3);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write_UINT16(in_s, in_data);
+ }
+
+ return 0;
+}
+#define OUT_COLOR_COUNT2(in_count, in_s, in_data) \
+ in_count = out_color_count_2(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* color */
+static UINT16 out_color_count_3(UINT16 in_count, wStream* in_s, UINT32 in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x3 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x60);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf3);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write_UINT8(in_s, in_data & 0xFF);
+
+ Stream_Write_UINT8(in_s, (in_data >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_data >> 16) & 0xFF);
+ }
+
+ return 0;
+}
+
+#define OUT_COLOR_COUNT3(in_count, in_s, in_data) \
+ in_count = out_color_count_3(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* copy */
+static INLINE UINT16 out_copy_count_2(UINT16 in_count, wStream* in_s, wStream* in_data)
+
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x4 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x80);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf4);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, Stream_Buffer(in_data), in_count * 2);
+ }
+
+ Stream_SetPosition(in_data, 0);
+ return 0;
+}
+#define OUT_COPY_COUNT2(in_count, in_s, in_data) \
+ in_count = out_copy_count_2(in_count, in_s, in_data)
+/*****************************************************************************/
+/* copy */
+static INLINE UINT16 out_copy_count_3(UINT16 in_count, wStream* in_s, wStream* in_data)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x4 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x80);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf4);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, Stream_Pointer(in_data), in_count * 3);
+ }
+
+ Stream_SetPosition(in_data, 0);
+ return 0;
+}
+#define OUT_COPY_COUNT3(in_count, in_s, in_data) \
+ in_count = out_copy_count_3(in_count, in_s, in_data)
+
+/*****************************************************************************/
+/* bicolor */
+static INLINE UINT16 out_bicolor_count_2(UINT16 in_count, wStream* in_s, UINT16 in_color1,
+ UINT16 in_color2)
+{
+ if (in_count > 0)
+ {
+ if (in_count / 2 < 16)
+ {
+ const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count / 2 < 256 + 16)
+ {
+ const BYTE temp = (in_count / 2 - 16) & 0xFF;
+ Stream_Write_UINT8(in_s, 0xe0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf8);
+ Stream_Write_UINT16(in_s, in_count / 2);
+ }
+
+ Stream_Write_UINT16(in_s, in_color1);
+ Stream_Write_UINT16(in_s, in_color2);
+ }
+
+ return 0;
+}
+
+#define OUT_BICOLOR_COUNT2(in_count, in_s, in_color1, in_color2) \
+ in_count = out_bicolor_count_2(in_count, in_s, in_color1, in_color2)
+
+/*****************************************************************************/
+/* bicolor */
+static INLINE UINT16 out_bicolor_count_3(UINT16 in_count, wStream* in_s, UINT32 in_color1,
+ UINT32 in_color2)
+{
+ if (in_count > 0)
+ {
+ if (in_count / 2 < 16)
+ {
+ const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count / 2 < 256 + 16)
+ {
+ const BYTE temp = (in_count / 2 - 16) & 0xFF;
+ Stream_Write_UINT8(in_s, 0xe0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf8);
+ Stream_Write_UINT16(in_s, in_count / 2);
+ }
+
+ Stream_Write_UINT8(in_s, in_color1 & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color1 >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color1 >> 16) & 0xFF);
+ Stream_Write_UINT8(in_s, in_color2 & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color2 >> 8) & 0xFF);
+ Stream_Write_UINT8(in_s, (in_color2 >> 16) & 0xFF);
+ }
+
+ return 0;
+}
+
+#define OUT_BICOLOR_COUNT3(in_count, in_s, in_color1, in_color2) \
+ in_count = out_bicolor_count_3(in_count, in_s, in_color1, in_color2)
+
+/*****************************************************************************/
+/* fill */
+static INLINE UINT16 out_fill_count_2(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ Stream_Write_UINT8(in_s, in_count & 0xFF);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf0);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+
+#define OUT_FILL_COUNT2(in_count, in_s) in_count = out_fill_count_2(in_count, in_s)
+
+/*****************************************************************************/
+/* fill */
+static INLINE UINT16 out_fill_count_3(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ Stream_Write_UINT8(in_s, in_count & 0xFF);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x0);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf0);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+#define OUT_FILL_COUNT3(in_count, in_s) in_count = out_fill_count_3(in_count, in_s)
+
+/*****************************************************************************/
+/* mix */
+static INLINE UINT16 out_mix_count_2(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x1 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x20);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf1);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+#define OUT_MIX_COUNT2(in_count, in_s) in_count = out_mix_count_2(in_count, in_s)
+
+/*****************************************************************************/
+/* mix */
+static INLINE UINT16 out_mix_count_3(UINT16 in_count, wStream* in_s)
+{
+ if (in_count > 0)
+ {
+ if (in_count < 32)
+ {
+ const BYTE temp = ((0x1 << 5) | in_count) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256 + 32)
+ {
+ const BYTE temp = (in_count - 32) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x20);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf1);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+ }
+
+ return 0;
+}
+
+#define OUT_MIX_COUNT3(in_count, in_s) in_count = out_mix_count_3(in_count, in_s)
+
+/*****************************************************************************/
+/* fom */
+static INLINE UINT16 out_from_count_2(UINT16 in_count, wStream* in_s, const char* in_mask,
+ size_t in_mask_len)
+{
+ if (in_count > 0)
+ {
+ if ((in_count % 8) == 0 && in_count < 249)
+ {
+ const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256)
+ {
+ const BYTE temp = (in_count - 1) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x40);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf2);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, in_mask, in_mask_len);
+ }
+
+ return 0;
+}
+#define OUT_FOM_COUNT2(in_count, in_s, in_mask, in_mask_len) \
+ in_count = out_from_count_2(in_count, in_s, in_mask, in_mask_len)
+
+/*****************************************************************************/
+/* fill or mix (fom) */
+static INLINE UINT16 out_from_count_3(UINT16 in_count, wStream* in_s, const char* in_mask,
+ size_t in_mask_len)
+{
+ if (in_count > 0)
+ {
+ if ((in_count % 8) == 0 && in_count < 249)
+ {
+ const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF;
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else if (in_count < 256)
+ {
+ const BYTE temp = (in_count - 1) & 0xFF;
+ Stream_Write_UINT8(in_s, 0x40);
+ Stream_Write_UINT8(in_s, temp);
+ }
+ else
+ {
+ Stream_Write_UINT8(in_s, 0xf2);
+ Stream_Write_UINT16(in_s, in_count);
+ }
+
+ Stream_Write(in_s, in_mask, in_mask_len);
+ }
+
+ return 0;
+}
+#define OUT_FOM_COUNT3(in_count, in_s, in_mask, in_mask_len) \
+ in_count = out_from_count_3(in_count, in_s, in_mask, in_mask_len)
+
+#define TEST_FILL ((last_line == 0 && pixel == 0) || (last_line != 0 && pixel == ypixel))
+#define TEST_MIX ((last_line == 0 && pixel == mix) || (last_line != 0 && pixel == (ypixel ^ mix)))
+#define TEST_FOM TEST_FILL || TEST_MIX
+#define TEST_COLOR pixel == last_pixel
+#define TEST_BICOLOR \
+ ((pixel != last_pixel) && \
+ ((!bicolor_spin && (pixel == bicolor1) && (last_pixel == bicolor2)) || \
+ (bicolor_spin && (pixel == bicolor2) && (last_pixel == bicolor1))))
+#define RESET_COUNTS \
+ do \
+ { \
+ bicolor_count = 0; \
+ fill_count = 0; \
+ color_count = 0; \
+ mix_count = 0; \
+ fom_count = 0; \
+ fom_mask_len = 0; \
+ bicolor_spin = FALSE; \
+ } while (0)
+
+static SSIZE_T freerdp_bitmap_compress_24(const void* srcData, UINT32 width, UINT32 height,
+ wStream* s, UINT32 byte_limit, UINT32 start_line,
+ wStream* temp_s, UINT32 e)
+{
+ char fom_mask[8192]; /* good for up to 64K bitmap */
+ SSIZE_T lines_sent = 0;
+ UINT16 count = 0;
+ UINT16 color_count = 0;
+ UINT32 last_pixel = 0;
+ UINT32 last_ypixel = 0;
+ UINT16 bicolor_count = 0;
+ UINT32 bicolor1 = 0;
+ UINT32 bicolor2 = 0;
+ BOOL bicolor_spin = FALSE;
+ UINT32 end = width + e;
+ UINT32 out_count = end * 3;
+ UINT16 fill_count = 0;
+ UINT16 mix_count = 0;
+ const UINT32 mix = 0xFFFFFF;
+ UINT16 fom_count = 0;
+ size_t fom_mask_len = 0;
+ const char* start = (const char*)srcData;
+ const char* line = start + width * start_line * 4;
+ const char* last_line = NULL;
+
+ while ((line >= start) && (out_count < 32768))
+ {
+ size_t i = Stream_GetPosition(s) + count * 3U;
+
+ if ((i - (color_count * 3) >= byte_limit) && (i - (bicolor_count * 3) >= byte_limit) &&
+ (i - (fill_count * 3) >= byte_limit) && (i - (mix_count * 3) >= byte_limit) &&
+ (i - (fom_count * 3) >= byte_limit))
+ {
+ break;
+ }
+
+ out_count += end * 3;
+
+ for (UINT32 j = 0; j < end; j++)
+ {
+ /* read next pixel */
+ const UINT32 pixel = IN_PIXEL32(line, j, 0, width, last_pixel);
+ const UINT32 ypixel = IN_PIXEL32(last_line, j, 0, width, last_ypixel);
+
+ if (!TEST_FILL)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+ }
+
+ if (!TEST_MIX)
+ {
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+ }
+
+ if (!(TEST_COLOR))
+ {
+ if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count &&
+ color_count >= mix_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_COLOR_COUNT3(color_count, s, last_pixel);
+ RESET_COUNTS;
+ }
+
+ color_count = 0;
+ }
+
+ if (!TEST_BICOLOR)
+ {
+ if (bicolor_count > 3 && bicolor_count >= fill_count &&
+ bicolor_count >= color_count && bicolor_count >= mix_count &&
+ bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1);
+ RESET_COUNTS;
+ }
+
+ bicolor_count = 0;
+ bicolor1 = last_pixel;
+ bicolor2 = pixel;
+ bicolor_spin = FALSE;
+ }
+
+ if (!(TEST_FOM))
+ {
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ if (TEST_FILL)
+ {
+ fill_count++;
+ }
+
+ if (TEST_MIX)
+ {
+ mix_count++;
+ }
+
+ if (TEST_COLOR)
+ {
+ color_count++;
+ }
+
+ if (TEST_BICOLOR)
+ {
+ bicolor_spin = !bicolor_spin;
+ bicolor_count++;
+ }
+
+ if (TEST_FOM)
+ {
+ if ((fom_count % 8) == 0)
+ {
+ fom_mask[fom_mask_len] = 0;
+ fom_mask_len++;
+ }
+
+ if (pixel == (ypixel ^ mix))
+ {
+ fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8));
+ }
+
+ fom_count++;
+ }
+
+ Stream_Write_UINT8(temp_s, pixel & 0xff);
+ Stream_Write_UINT8(temp_s, (pixel >> 8) & 0xff);
+ Stream_Write_UINT8(temp_s, (pixel >> 16) & 0xff);
+ count++;
+ last_pixel = pixel;
+ last_ypixel = ypixel;
+ }
+
+ /* can't take fix, mix, or fom past first line */
+ if (last_line == 0)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ last_line = line;
+ line = line - width * 4;
+ start_line--;
+ lines_sent++;
+ }
+
+ Stream_SetPosition(temp_s, 0);
+
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FILL_COUNT3(fill_count, s);
+ }
+ else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count &&
+ mix_count >= fill_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_MIX_COUNT3(mix_count, s);
+ }
+ else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count &&
+ color_count >= fill_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_COLOR_COUNT3(color_count, s, last_pixel);
+ }
+ else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count &&
+ bicolor_count >= fill_count && bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1);
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor1, bicolor2);
+ }
+ else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count &&
+ fom_count >= fill_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT3(count, s, temp_s);
+ OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len);
+ }
+ else
+ {
+ OUT_COPY_COUNT3(count, s, temp_s);
+ }
+
+ return lines_sent;
+}
+
+static SSIZE_T freerdp_bitmap_compress_16(const void* srcData, UINT32 width, UINT32 height,
+ wStream* s, UINT32 bpp, UINT32 byte_limit,
+ UINT32 start_line, wStream* temp_s, UINT32 e)
+{
+ char fom_mask[8192]; /* good for up to 64K bitmap */
+ SSIZE_T lines_sent = 0;
+ UINT16 count = 0;
+ UINT16 color_count = 0;
+ UINT16 last_pixel = 0;
+ UINT16 last_ypixel = 0;
+ UINT16 bicolor_count = 0;
+ UINT16 bicolor1 = 0;
+ UINT16 bicolor2 = 0;
+ BOOL bicolor_spin = FALSE;
+ UINT32 end = width + e;
+ UINT32 out_count = end * 2;
+ UINT16 fill_count = 0;
+ UINT16 mix_count = 0;
+ const UINT32 mix = (bpp == 15) ? 0xBA1F : 0xFFFF;
+ UINT16 fom_count = 0;
+ size_t fom_mask_len = 0;
+ const char* start = (const char*)srcData;
+ const char* line = start + width * start_line * 2;
+ const char* last_line = NULL;
+
+ while ((line >= start) && (out_count < 32768))
+ {
+ size_t i = Stream_GetPosition(s) + count * 2;
+
+ if ((i - (color_count * 2) >= byte_limit) && (i - (bicolor_count * 2) >= byte_limit) &&
+ (i - (fill_count * 2) >= byte_limit) && (i - (mix_count * 2) >= byte_limit) &&
+ (i - (fom_count * 2) >= byte_limit))
+ {
+ break;
+ }
+
+ out_count += end * 2;
+
+ for (UINT32 j = 0; j < end; j++)
+ {
+ /* read next pixel */
+ const UINT16 pixel = IN_PIXEL16(line, j, 0, width, last_pixel);
+ const UINT16 ypixel = IN_PIXEL16(last_line, j, 0, width, last_ypixel);
+
+ if (!TEST_FILL)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+ }
+
+ if (!TEST_MIX)
+ {
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+ }
+
+ if (!(TEST_COLOR))
+ {
+ if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count &&
+ color_count >= mix_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_COLOR_COUNT2(color_count, s, last_pixel);
+ RESET_COUNTS;
+ }
+
+ color_count = 0;
+ }
+
+ if (!TEST_BICOLOR)
+ {
+ if ((bicolor_count > 3) && (bicolor_count >= fill_count) &&
+ (bicolor_count >= color_count) && (bicolor_count >= mix_count) &&
+ (bicolor_count >= fom_count))
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1);
+ RESET_COUNTS;
+ }
+
+ bicolor_count = 0;
+ bicolor1 = last_pixel;
+ bicolor2 = pixel;
+ bicolor_spin = FALSE;
+ }
+
+ if (!(TEST_FOM))
+ {
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ if (TEST_FILL)
+ {
+ fill_count++;
+ }
+
+ if (TEST_MIX)
+ {
+ mix_count++;
+ }
+
+ if (TEST_COLOR)
+ {
+ color_count++;
+ }
+
+ if (TEST_BICOLOR)
+ {
+ bicolor_spin = !bicolor_spin;
+ bicolor_count++;
+ }
+
+ if (TEST_FOM)
+ {
+ if ((fom_count % 8) == 0)
+ {
+ fom_mask[fom_mask_len] = 0;
+ fom_mask_len++;
+ }
+
+ if (pixel == (ypixel ^ mix))
+ {
+ fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8));
+ }
+
+ fom_count++;
+ }
+
+ Stream_Write_UINT16(temp_s, pixel);
+ count++;
+ last_pixel = pixel;
+ last_ypixel = ypixel;
+ }
+
+ /* can't take fix, mix, or fom past first line */
+ if (last_line == 0)
+ {
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ RESET_COUNTS;
+ }
+
+ fill_count = 0;
+
+ if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count &&
+ mix_count >= color_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ RESET_COUNTS;
+ }
+
+ mix_count = 0;
+
+ if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count &&
+ fom_count >= mix_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ RESET_COUNTS;
+ }
+
+ fom_count = 0;
+ fom_mask_len = 0;
+ }
+
+ last_line = line;
+ line = line - width * 2;
+ start_line--;
+ lines_sent++;
+ }
+
+ Stream_SetPosition(temp_s, 0);
+
+ if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count &&
+ fill_count >= mix_count && fill_count >= fom_count)
+ {
+ if (fill_count > count)
+ return -1;
+
+ count -= fill_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FILL_COUNT2(fill_count, s);
+ }
+ else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count &&
+ mix_count >= fill_count && mix_count >= fom_count)
+ {
+ if (mix_count > count)
+ return -1;
+
+ count -= mix_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_MIX_COUNT2(mix_count, s);
+ }
+ else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count &&
+ color_count >= fill_count && color_count >= fom_count)
+ {
+ if (color_count > count)
+ return -1;
+
+ count -= color_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_COLOR_COUNT2(color_count, s, last_pixel);
+ }
+ else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count &&
+ bicolor_count >= fill_count && bicolor_count >= fom_count)
+ {
+ if ((bicolor_count % 2) != 0)
+ bicolor_count--;
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1);
+
+ if (bicolor_count > count)
+ return -1;
+
+ count -= bicolor_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor1, bicolor2);
+ }
+ else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count &&
+ fom_count >= fill_count && fom_count >= bicolor_count)
+ {
+ if (fom_count > count)
+ return -1;
+
+ count -= fom_count;
+ OUT_COPY_COUNT2(count, s, temp_s);
+ OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len);
+ }
+ else
+ {
+ OUT_COPY_COUNT2(count, s, temp_s);
+ }
+
+ return lines_sent;
+}
+
+SSIZE_T freerdp_bitmap_compress(const void* srcData, UINT32 width, UINT32 height, wStream* s,
+ UINT32 bpp, UINT32 byte_limit, UINT32 start_line, wStream* temp_s,
+ UINT32 e)
+{
+ Stream_SetPosition(temp_s, 0);
+
+ switch (bpp)
+ {
+ case 15:
+ case 16:
+ return freerdp_bitmap_compress_16(srcData, width, height, s, bpp, byte_limit,
+ start_line, temp_s, e);
+
+ case 24:
+ return freerdp_bitmap_compress_24(srcData, width, height, s, byte_limit, start_line,
+ temp_s, e);
+
+ default:
+ return -1;
+ }
+}
diff --git a/libfreerdp/codec/bulk.c b/libfreerdp/codec/bulk.c
new file mode 100644
index 0000000..1f5beb3
--- /dev/null
+++ b/libfreerdp/codec/bulk.c
@@ -0,0 +1,391 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bulk 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.
+ */
+
+#include <math.h>
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include "../core/settings.h"
+#include "bulk.h"
+#include "../codec/mppc.h"
+#include "../codec/ncrush.h"
+#include "../codec/xcrush.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("core")
+
+//#define WITH_BULK_DEBUG 1
+
+struct rdp_bulk
+{
+ ALIGN64 rdpContext* context;
+ ALIGN64 UINT32 CompressionLevel;
+ ALIGN64 UINT32 CompressionMaxSize;
+ ALIGN64 MPPC_CONTEXT* mppcSend;
+ ALIGN64 MPPC_CONTEXT* mppcRecv;
+ ALIGN64 NCRUSH_CONTEXT* ncrushRecv;
+ ALIGN64 NCRUSH_CONTEXT* ncrushSend;
+ ALIGN64 XCRUSH_CONTEXT* xcrushRecv;
+ ALIGN64 XCRUSH_CONTEXT* xcrushSend;
+ ALIGN64 BYTE OutputBuffer[65536];
+};
+
+#if defined(WITH_BULK_DEBUG)
+static INLINE const char* bulk_get_compression_flags_string(UINT32 flags)
+{
+ flags &= BULK_COMPRESSION_FLAGS_MASK;
+
+ if (flags == 0)
+ return "PACKET_UNCOMPRESSED";
+ else if (flags == PACKET_COMPRESSED)
+ return "PACKET_COMPRESSED";
+ else if (flags == PACKET_AT_FRONT)
+ return "PACKET_AT_FRONT";
+ else if (flags == PACKET_FLUSHED)
+ return "PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT";
+ else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_FLUSHED";
+ else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_AT_FRONT | PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED";
+
+ return "PACKET_UNKNOWN";
+}
+#endif
+
+static UINT32 bulk_compression_level(rdpBulk* bulk)
+{
+ rdpSettings* settings = NULL;
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ settings = bulk->context->settings;
+ WINPR_ASSERT(settings);
+ bulk->CompressionLevel = (settings->CompressionLevel >= PACKET_COMPR_TYPE_RDP61)
+ ? PACKET_COMPR_TYPE_RDP61
+ : settings->CompressionLevel;
+ return bulk->CompressionLevel;
+}
+
+UINT32 bulk_compression_max_size(rdpBulk* bulk)
+{
+ WINPR_ASSERT(bulk);
+ bulk_compression_level(bulk);
+ bulk->CompressionMaxSize = (bulk->CompressionLevel < PACKET_COMPR_TYPE_64K) ? 8192 : 65536;
+ return bulk->CompressionMaxSize;
+}
+
+#if defined(WITH_BULK_DEBUG)
+static INLINE int bulk_compress_validate(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE* pDstData, UINT32 DstSize, UINT32 Flags)
+{
+ int status;
+ const BYTE* v_pSrcData = NULL;
+ const BYTE* v_pDstData = NULL;
+ UINT32 v_SrcSize = 0;
+ UINT32 v_DstSize = 0;
+ UINT32 v_Flags = 0;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstData);
+
+ v_pSrcData = pDstData;
+ v_SrcSize = DstSize;
+ v_Flags = Flags | bulk->CompressionLevel;
+ status = bulk_decompress(bulk, v_pSrcData, v_SrcSize, &v_pDstData, &v_DstSize, v_Flags);
+
+ if (status < 0)
+ {
+ WLog_DBG(TAG, "compression/decompression failure");
+ return status;
+ }
+
+ if (v_DstSize != SrcSize)
+ {
+ WLog_DBG(TAG,
+ "compression/decompression size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "",
+ v_DstSize, SrcSize);
+ return -1;
+ }
+
+ if (memcmp(v_pDstData, pSrcData, SrcSize) != 0)
+ {
+ WLog_DBG(TAG, "compression/decompression input/output mismatch! flags: 0x%08" PRIX32 "",
+ v_Flags);
+#if 1
+ WLog_DBG(TAG, "Actual:");
+ winpr_HexDump(TAG, WLOG_DEBUG, v_pDstData, SrcSize);
+ WLog_DBG(TAG, "Expected:");
+ winpr_HexDump(TAG, WLOG_DEBUG, pSrcData, SrcSize);
+#endif
+ return -1;
+ }
+
+ return status;
+}
+#endif
+
+int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData,
+ UINT32* pDstSize, UINT32 flags)
+{
+ UINT32 type = 0;
+ int status = -1;
+ rdpMetrics* metrics = NULL;
+ UINT32 CompressedBytes = 0;
+ UINT32 UncompressedBytes = 0;
+ double CompressionRatio = NAN;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ metrics = bulk->context->metrics;
+ WINPR_ASSERT(metrics);
+
+ bulk_compression_max_size(bulk);
+ type = flags & BULK_COMPRESSION_TYPE_MASK;
+
+ if (flags & BULK_COMPRESSION_FLAGS_MASK)
+ {
+ switch (type)
+ {
+ case PACKET_COMPR_TYPE_8K:
+ mppc_set_compression_level(bulk->mppcRecv, 0);
+ status =
+ mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
+ break;
+
+ case PACKET_COMPR_TYPE_64K:
+ mppc_set_compression_level(bulk->mppcRecv, 1);
+ status =
+ mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP6:
+ status = ncrush_decompress(bulk->ncrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
+ flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP61:
+ status = xcrush_decompress(bulk->xcrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
+ flags);
+ break;
+
+ case PACKET_COMPR_TYPE_RDP8:
+ WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32,
+ bulk->CompressionLevel);
+ status = -1;
+ break;
+ default:
+ WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ }
+ }
+ else
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ status = 0;
+ }
+
+ if (status >= 0)
+ {
+ CompressedBytes = SrcSize;
+ UncompressedBytes = *pDstSize;
+ CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
+#ifdef WITH_BULK_DEBUG
+ {
+ WLog_DBG(TAG,
+ "Decompress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
+ ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
+ " / %" PRIu64 ")",
+ type, bulk_get_compression_flags_string(flags), flags, CompressionRatio,
+ CompressedBytes, UncompressedBytes, metrics->TotalCompressionRatio,
+ metrics->TotalCompressedBytes, metrics->TotalUncompressedBytes);
+ }
+#else
+ WINPR_UNUSED(CompressionRatio);
+#endif
+ }
+ else
+ {
+ WLog_ERR(TAG, "Decompression failure!");
+ }
+
+ return status;
+}
+
+int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData,
+ UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = -1;
+ rdpMetrics* metrics = NULL;
+ UINT32 CompressedBytes = 0;
+ UINT32 UncompressedBytes = 0;
+ double CompressionRatio = NAN;
+
+ WINPR_ASSERT(bulk);
+ WINPR_ASSERT(bulk->context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ metrics = bulk->context->metrics;
+ WINPR_ASSERT(metrics);
+
+ if ((SrcSize <= 50) || (SrcSize >= 16384))
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 0;
+ }
+
+ *pDstSize = sizeof(bulk->OutputBuffer);
+ bulk_compression_level(bulk);
+ bulk_compression_max_size(bulk);
+
+ switch (bulk->CompressionLevel)
+ {
+ case PACKET_COMPR_TYPE_8K:
+ case PACKET_COMPR_TYPE_64K:
+ mppc_set_compression_level(bulk->mppcSend, bulk->CompressionLevel);
+ status = mppc_compress(bulk->mppcSend, pSrcData, SrcSize, bulk->OutputBuffer, ppDstData,
+ pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP6:
+ status = ncrush_compress(bulk->ncrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
+ ppDstData, pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP61:
+ status = xcrush_compress(bulk->xcrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
+ ppDstData, pDstSize, pFlags);
+ break;
+ case PACKET_COMPR_TYPE_RDP8:
+ WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ default:
+ WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel);
+ status = -1;
+ break;
+ }
+
+ if (status >= 0)
+ {
+ CompressedBytes = *pDstSize;
+ UncompressedBytes = SrcSize;
+ CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
+#ifdef WITH_BULK_DEBUG
+ {
+ WLog_DBG(TAG,
+ "Compress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
+ ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
+ " / %" PRIu64 ")",
+ bulk->CompressionLevel, bulk_get_compression_flags_string(*pFlags), *pFlags,
+ CompressionRatio, CompressedBytes, UncompressedBytes,
+ metrics->TotalCompressionRatio, metrics->TotalCompressedBytes,
+ metrics->TotalUncompressedBytes);
+ }
+#else
+ WINPR_UNUSED(CompressionRatio);
+#endif
+ }
+
+#if defined(WITH_BULK_DEBUG)
+
+ if (bulk_compress_validate(bulk, pSrcData, SrcSize, *ppDstData, *pDstSize, *pFlags) < 0)
+ status = -1;
+
+#endif
+ return status;
+}
+
+void bulk_reset(rdpBulk* bulk)
+{
+ WINPR_ASSERT(bulk);
+
+ mppc_context_reset(bulk->mppcSend, FALSE);
+ mppc_context_reset(bulk->mppcRecv, FALSE);
+ ncrush_context_reset(bulk->ncrushRecv, FALSE);
+ ncrush_context_reset(bulk->ncrushSend, FALSE);
+ xcrush_context_reset(bulk->xcrushRecv, FALSE);
+ xcrush_context_reset(bulk->xcrushSend, FALSE);
+}
+
+rdpBulk* bulk_new(rdpContext* context)
+{
+ rdpBulk* bulk = NULL;
+ WINPR_ASSERT(context);
+
+ bulk = (rdpBulk*)calloc(1, sizeof(rdpBulk));
+
+ if (!bulk)
+ goto fail;
+
+ bulk->context = context;
+ bulk->mppcSend = mppc_context_new(1, TRUE);
+ if (!bulk->mppcSend)
+ goto fail;
+ bulk->mppcRecv = mppc_context_new(1, FALSE);
+ if (!bulk->mppcRecv)
+ goto fail;
+ bulk->ncrushRecv = ncrush_context_new(FALSE);
+ if (!bulk->ncrushRecv)
+ goto fail;
+ bulk->ncrushSend = ncrush_context_new(TRUE);
+ if (!bulk->ncrushSend)
+ goto fail;
+ bulk->xcrushRecv = xcrush_context_new(FALSE);
+ if (!bulk->xcrushRecv)
+ goto fail;
+ bulk->xcrushSend = xcrush_context_new(TRUE);
+ if (!bulk->xcrushSend)
+ goto fail;
+ bulk->CompressionLevel = context->settings->CompressionLevel;
+
+ return bulk;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ bulk_free(bulk);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void bulk_free(rdpBulk* bulk)
+{
+ if (!bulk)
+ return;
+
+ mppc_context_free(bulk->mppcSend);
+ mppc_context_free(bulk->mppcRecv);
+ ncrush_context_free(bulk->ncrushRecv);
+ ncrush_context_free(bulk->ncrushSend);
+ xcrush_context_free(bulk->xcrushRecv);
+ xcrush_context_free(bulk->xcrushSend);
+ free(bulk);
+}
diff --git a/libfreerdp/codec/bulk.h b/libfreerdp/codec/bulk.h
new file mode 100644
index 0000000..4c85406
--- /dev/null
+++ b/libfreerdp/codec/bulk.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Bulk 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.
+ */
+
+#ifndef FREERDP_LIB_CORE_BULK_H
+#define FREERDP_LIB_CORE_BULK_H
+
+typedef struct rdp_bulk rdpBulk;
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#define BULK_COMPRESSION_FLAGS_MASK 0xE0
+#define BULK_COMPRESSION_TYPE_MASK 0x0F
+
+FREERDP_LOCAL UINT32 bulk_compression_max_size(rdpBulk* bulk);
+
+FREERDP_LOCAL int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags);
+FREERDP_LOCAL int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags);
+
+FREERDP_LOCAL void bulk_reset(rdpBulk* bulk);
+
+FREERDP_LOCAL void bulk_free(rdpBulk* bulk);
+
+WINPR_ATTR_MALLOC(bulk_free, 1)
+FREERDP_LOCAL rdpBulk* bulk_new(rdpContext* context);
+
+#endif /* FREERDP_LIB_CORE_BULK_H */
diff --git a/libfreerdp/codec/clear.c b/libfreerdp/codec/clear.c
new file mode 100644
index 0000000..5c009d8
--- /dev/null
+++ b/libfreerdp/codec/clear.c
@@ -0,0 +1,1188 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ClearCodec Bitmap Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/clear.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("codec.clear")
+
+#define CLEARCODEC_FLAG_GLYPH_INDEX 0x01
+#define CLEARCODEC_FLAG_GLYPH_HIT 0x02
+#define CLEARCODEC_FLAG_CACHE_RESET 0x04
+
+#define CLEARCODEC_VBAR_SIZE 32768
+#define CLEARCODEC_VBAR_SHORT_SIZE 16384
+
+typedef struct
+{
+ UINT32 size;
+ UINT32 count;
+ UINT32* pixels;
+} CLEAR_GLYPH_ENTRY;
+
+typedef struct
+{
+ UINT32 size;
+ UINT32 count;
+ BYTE* pixels;
+} CLEAR_VBAR_ENTRY;
+
+struct S_CLEAR_CONTEXT
+{
+ BOOL Compressor;
+ NSC_CONTEXT* nsc;
+ UINT32 seqNumber;
+ BYTE* TempBuffer;
+ UINT32 TempSize;
+ UINT32 nTempStep;
+ UINT32 TempFormat;
+ UINT32 format;
+ CLEAR_GLYPH_ENTRY GlyphCache[4000];
+ UINT32 VBarStorageCursor;
+ CLEAR_VBAR_ENTRY VBarStorage[CLEARCODEC_VBAR_SIZE];
+ UINT32 ShortVBarStorageCursor;
+ CLEAR_VBAR_ENTRY ShortVBarStorage[CLEARCODEC_VBAR_SHORT_SIZE];
+};
+
+static const UINT32 CLEAR_LOG2_FLOOR[256] = {
+ 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+};
+
+static const BYTE CLEAR_8BIT_MASKS[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
+
+static void clear_reset_vbar_storage(CLEAR_CONTEXT* clear, BOOL zero)
+{
+ if (zero)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(clear->VBarStorage); i++)
+ winpr_aligned_free(clear->VBarStorage[i].pixels);
+
+ ZeroMemory(clear->VBarStorage, sizeof(clear->VBarStorage));
+ }
+
+ clear->VBarStorageCursor = 0;
+
+ if (zero)
+ {
+ for (size_t i = 0; i < ARRAYSIZE(clear->ShortVBarStorage); i++)
+ winpr_aligned_free(clear->ShortVBarStorage[i].pixels);
+
+ ZeroMemory(clear->ShortVBarStorage, sizeof(clear->ShortVBarStorage));
+ }
+
+ clear->ShortVBarStorageCursor = 0;
+}
+
+static void clear_reset_glyph_cache(CLEAR_CONTEXT* clear)
+{
+ for (size_t i = 0; i < ARRAYSIZE(clear->GlyphCache); i++)
+ winpr_aligned_free(clear->GlyphCache[i].pixels);
+
+ ZeroMemory(clear->GlyphCache, sizeof(clear->GlyphCache));
+}
+
+static BOOL convert_color(BYTE* dst, UINT32 nDstStep, UINT32 DstFormat, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* src, UINT32 nSrcStep,
+ UINT32 SrcFormat, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ if (nWidth + nXDst > nDstWidth)
+ nWidth = nDstWidth - nXDst;
+
+ if (nHeight + nYDst > nDstHeight)
+ nHeight = nDstHeight - nYDst;
+
+ return freerdp_image_copy(dst, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, src,
+ SrcFormat, nSrcStep, 0, 0, palette, FREERDP_KEEP_DST_ALPHA);
+}
+
+static BOOL clear_decompress_nscodec(NSC_CONTEXT* nsc, UINT32 width, UINT32 height, wStream* s,
+ UINT32 bitmapDataByteCount, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel)
+{
+ BOOL rc = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ rc = nsc_process_message(nsc, 32, width, height, Stream_Pointer(s), bitmapDataByteCount,
+ pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel, width, height,
+ FREERDP_FLIP_NONE);
+ Stream_Seek(s, bitmapDataByteCount);
+ return rc;
+}
+
+static BOOL clear_decompress_subcode_rlex(wStream* s, UINT32 bitmapDataByteCount, UINT32 width,
+ UINT32 height, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel,
+ UINT32 nDstWidth, UINT32 nDstHeight)
+{
+ UINT32 x = 0;
+ UINT32 y = 0;
+ UINT32 pixelCount = 0;
+ UINT32 bitmapDataOffset = 0;
+ size_t pixelIndex = 0;
+ UINT32 numBits = 0;
+ BYTE startIndex = 0;
+ BYTE stopIndex = 0;
+ BYTE suiteIndex = 0;
+ BYTE suiteDepth = 0;
+ BYTE paletteCount = 0;
+ UINT32 palette[128] = { 0 };
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+ Stream_Read_UINT8(s, paletteCount);
+ bitmapDataOffset = 1 + (paletteCount * 3);
+
+ if ((paletteCount > 127) || (paletteCount < 1))
+ {
+ WLog_ERR(TAG, "paletteCount %" PRIu8 "", paletteCount);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, paletteCount, 3ull))
+ return FALSE;
+
+ for (UINT32 i = 0; i < paletteCount; i++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ palette[i] = FreeRDPGetColor(DstFormat, r, g, b, 0xFF);
+ }
+
+ pixelIndex = 0;
+ pixelCount = width * height;
+ numBits = CLEAR_LOG2_FLOOR[paletteCount - 1] + 1;
+
+ while (bitmapDataOffset < bitmapDataByteCount)
+ {
+ UINT32 tmp = 0;
+ UINT32 color = 0;
+ UINT32 runLengthFactor = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT8(s, tmp);
+ Stream_Read_UINT8(s, runLengthFactor);
+ bitmapDataOffset += 2;
+ suiteDepth = (tmp >> numBits) & CLEAR_8BIT_MASKS[(8 - numBits)];
+ stopIndex = tmp & CLEAR_8BIT_MASKS[numBits];
+ startIndex = stopIndex - suiteDepth;
+
+ if (runLengthFactor >= 0xFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, runLengthFactor);
+ bitmapDataOffset += 2;
+
+ if (runLengthFactor >= 0xFFFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, runLengthFactor);
+ bitmapDataOffset += 4;
+ }
+ }
+
+ if (startIndex >= paletteCount)
+ {
+ WLog_ERR(TAG, "startIndex %" PRIu8 " > paletteCount %" PRIu8 "]", startIndex,
+ paletteCount);
+ return FALSE;
+ }
+
+ if (stopIndex >= paletteCount)
+ {
+ WLog_ERR(TAG, "stopIndex %" PRIu8 " > paletteCount %" PRIu8 "]", stopIndex,
+ paletteCount);
+ return FALSE;
+ }
+
+ suiteIndex = startIndex;
+
+ if (suiteIndex > 127)
+ {
+ WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127]", suiteIndex);
+ return FALSE;
+ }
+
+ color = palette[suiteIndex];
+
+ if ((pixelIndex + runLengthFactor) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32
+ "",
+ pixelIndex, runLengthFactor, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < runLengthFactor; i++)
+ {
+ BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) +
+ (nYDstRel + y) * nDstStep];
+
+ if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight))
+ FreeRDPWriteColor(pTmpData, DstFormat, color);
+
+ if (++x >= width)
+ {
+ y++;
+ x = 0;
+ }
+ }
+
+ pixelIndex += runLengthFactor;
+
+ if ((pixelIndex + (suiteDepth + 1)) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + suiteDepth %" PRIu8 " + 1 > pixelCount %" PRIu32 "",
+ pixelIndex, suiteDepth, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i <= suiteDepth; i++)
+ {
+ BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) +
+ (nYDstRel + y) * nDstStep];
+ UINT32 ccolor = palette[suiteIndex];
+
+ if (suiteIndex > 127)
+ {
+ WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127", suiteIndex);
+ return FALSE;
+ }
+
+ suiteIndex++;
+
+ if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight))
+ FreeRDPWriteColor(pTmpData, DstFormat, ccolor);
+
+ if (++x >= width)
+ {
+ y++;
+ x = 0;
+ }
+ }
+
+ pixelIndex += (suiteDepth + 1);
+ }
+
+ if (pixelIndex != pixelCount)
+ {
+ WLog_ERR(TAG, "pixelIndex %" PRIdz " != pixelCount %" PRIu32 "", pixelIndex, pixelCount);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_resize_buffer(CLEAR_CONTEXT* clear, UINT32 width, UINT32 height)
+{
+ UINT32 size = 0;
+
+ if (!clear)
+ return FALSE;
+
+ size = ((width + 16) * (height + 16) * FreeRDPGetBytesPerPixel(clear->format));
+
+ if (size > clear->TempSize)
+ {
+ BYTE* tmp = (BYTE*)winpr_aligned_recalloc(clear->TempBuffer, size, sizeof(BYTE), 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "clear->TempBuffer winpr_aligned_recalloc failed for %" PRIu32 " bytes",
+ size);
+ return FALSE;
+ }
+
+ clear->TempSize = size;
+ clear->TempBuffer = tmp;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_residual_data(CLEAR_CONTEXT* clear, wStream* s,
+ UINT32 residualByteCount, UINT32 nWidth, UINT32 nHeight,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, const gdiPalette* palette)
+{
+ UINT32 nSrcStep = 0;
+ UINT32 suboffset = 0;
+ BYTE* dstBuffer = NULL;
+ UINT32 pixelIndex = 0;
+ UINT32 pixelCount = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, residualByteCount))
+ return FALSE;
+
+ suboffset = 0;
+ pixelIndex = 0;
+ pixelCount = nWidth * nHeight;
+
+ if (!clear_resize_buffer(clear, nWidth, nHeight))
+ return FALSE;
+
+ dstBuffer = clear->TempBuffer;
+
+ while (suboffset < residualByteCount)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ UINT32 runLengthFactor = 0;
+ UINT32 color = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ Stream_Read_UINT8(s, runLengthFactor);
+ suboffset += 4;
+ color = FreeRDPGetColor(clear->format, r, g, b, 0xFF);
+
+ if (runLengthFactor >= 0xFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, runLengthFactor);
+ suboffset += 2;
+
+ if (runLengthFactor >= 0xFFFF)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT32(s, runLengthFactor);
+ suboffset += 4;
+ }
+ }
+
+ if ((pixelIndex + runLengthFactor) > pixelCount)
+ {
+ WLog_ERR(TAG,
+ "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32
+ "",
+ pixelIndex, runLengthFactor, pixelCount);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < runLengthFactor; i++)
+ {
+ FreeRDPWriteColor(dstBuffer, clear->format, color);
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ pixelIndex += runLengthFactor;
+ }
+
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format);
+
+ if (pixelIndex != pixelCount)
+ {
+ WLog_ERR(TAG, "pixelIndex %" PRIu32 " != pixelCount %" PRIu32 "", pixelIndex, pixelCount);
+ return FALSE;
+ }
+
+ return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight,
+ clear->TempBuffer, nSrcStep, clear->format, nDstWidth, nDstHeight,
+ palette);
+}
+
+static BOOL clear_decompress_subcodecs_data(CLEAR_CONTEXT* clear, wStream* s,
+ UINT32 subcodecByteCount, UINT32 nWidth, UINT32 nHeight,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, const gdiPalette* palette)
+{
+ UINT16 xStart = 0;
+ UINT16 yStart = 0;
+ UINT16 width = 0;
+ UINT16 height = 0;
+ UINT32 bitmapDataByteCount = 0;
+ BYTE subcodecId = 0;
+ UINT32 suboffset = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, subcodecByteCount))
+ return FALSE;
+
+ suboffset = 0;
+
+ while (suboffset < subcodecByteCount)
+ {
+ UINT32 nXDstRel = 0;
+ UINT32 nYDstRel = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 13))
+ return FALSE;
+
+ Stream_Read_UINT16(s, xStart);
+ Stream_Read_UINT16(s, yStart);
+ Stream_Read_UINT16(s, width);
+ Stream_Read_UINT16(s, height);
+ Stream_Read_UINT32(s, bitmapDataByteCount);
+ Stream_Read_UINT8(s, subcodecId);
+ suboffset += 13;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount))
+ return FALSE;
+
+ nXDstRel = nXDst + xStart;
+ nYDstRel = nYDst + yStart;
+
+ if (width > nWidth)
+ {
+ WLog_ERR(TAG, "width %" PRIu16 " > nWidth %" PRIu32 "", width, nWidth);
+ return FALSE;
+ }
+
+ if (height > nHeight)
+ {
+ WLog_ERR(TAG, "height %" PRIu16 " > nHeight %" PRIu32 "", height, nHeight);
+ return FALSE;
+ }
+
+ if (!clear_resize_buffer(clear, width, height))
+ return FALSE;
+
+ switch (subcodecId)
+ {
+ case 0: /* Uncompressed */
+ {
+ UINT32 nSrcStep = width * FreeRDPGetBytesPerPixel(PIXEL_FORMAT_BGR24);
+ UINT32 nSrcSize = nSrcStep * height;
+
+ if (bitmapDataByteCount != nSrcSize)
+ {
+ WLog_ERR(TAG, "bitmapDataByteCount %" PRIu32 " != nSrcSize %" PRIu32 "",
+ bitmapDataByteCount, nSrcSize);
+ return FALSE;
+ }
+
+ if (!convert_color(pDstData, nDstStep, DstFormat, nXDstRel, nYDstRel, width, height,
+ Stream_Pointer(s), nSrcStep, PIXEL_FORMAT_BGR24, nDstWidth,
+ nDstHeight, palette))
+ return FALSE;
+
+ Stream_Seek(s, bitmapDataByteCount);
+ }
+ break;
+
+ case 1: /* NSCodec */
+ if (!clear_decompress_nscodec(clear->nsc, width, height, s, bitmapDataByteCount,
+ pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel))
+ return FALSE;
+
+ break;
+
+ case 2: /* CLEARCODEC_SUBCODEC_RLEX */
+ if (!clear_decompress_subcode_rlex(s, bitmapDataByteCount, width, height, pDstData,
+ DstFormat, nDstStep, nXDstRel, nYDstRel,
+ nDstWidth, nDstHeight))
+ return FALSE;
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown subcodec ID %" PRIu8 "", subcodecId);
+ return FALSE;
+ }
+
+ suboffset += bitmapDataByteCount;
+ }
+
+ return TRUE;
+}
+
+static BOOL resize_vbar_entry(CLEAR_CONTEXT* clear, CLEAR_VBAR_ENTRY* vBarEntry)
+{
+ if (vBarEntry->count > vBarEntry->size)
+ {
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format);
+ const UINT32 oldPos = vBarEntry->size * bpp;
+ const UINT32 diffSize = (vBarEntry->count - vBarEntry->size) * bpp;
+
+ vBarEntry->size = vBarEntry->count;
+ BYTE* tmp =
+ (BYTE*)winpr_aligned_recalloc(vBarEntry->pixels, vBarEntry->count, 1ull * bpp, 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "vBarEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed",
+ vBarEntry->count * bpp);
+ return FALSE;
+ }
+
+ memset(&tmp[oldPos], 0, diffSize);
+ vBarEntry->pixels = tmp;
+ }
+
+ if (!vBarEntry->pixels && vBarEntry->size)
+ {
+ WLog_ERR(TAG, "vBarEntry->pixels is NULL but vBarEntry->size is %" PRIu32 "",
+ vBarEntry->size);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_bands_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 bandsByteCount,
+ UINT32 nWidth, UINT32 nHeight, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight)
+{
+ UINT32 suboffset = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, bandsByteCount))
+ return FALSE;
+
+ while (suboffset < bandsByteCount)
+ {
+ BYTE cr = 0;
+ BYTE cg = 0;
+ BYTE cb = 0;
+ UINT16 xStart = 0;
+ UINT16 xEnd = 0;
+ UINT16 yStart = 0;
+ UINT16 yEnd = 0;
+ UINT32 colorBkg = 0;
+ UINT16 vBarHeader = 0;
+ UINT16 vBarYOn = 0;
+ UINT16 vBarYOff = 0;
+ UINT32 vBarCount = 0;
+ UINT32 vBarPixelCount = 0;
+ UINT32 vBarShortPixelCount = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 11))
+ return FALSE;
+
+ Stream_Read_UINT16(s, xStart);
+ Stream_Read_UINT16(s, xEnd);
+ Stream_Read_UINT16(s, yStart);
+ Stream_Read_UINT16(s, yEnd);
+ Stream_Read_UINT8(s, cb);
+ Stream_Read_UINT8(s, cg);
+ Stream_Read_UINT8(s, cr);
+ suboffset += 11;
+ colorBkg = FreeRDPGetColor(clear->format, cr, cg, cb, 0xFF);
+
+ if (xEnd < xStart)
+ {
+ WLog_ERR(TAG, "xEnd %" PRIu16 " < xStart %" PRIu16 "", xEnd, xStart);
+ return FALSE;
+ }
+
+ if (yEnd < yStart)
+ {
+ WLog_ERR(TAG, "yEnd %" PRIu16 " < yStart %" PRIu16 "", yEnd, yStart);
+ return FALSE;
+ }
+
+ vBarCount = (xEnd - xStart) + 1;
+
+ for (UINT32 i = 0; i < vBarCount; i++)
+ {
+ UINT32 vBarHeight = 0;
+ CLEAR_VBAR_ENTRY* vBarEntry = NULL;
+ CLEAR_VBAR_ENTRY* vBarShortEntry = NULL;
+ BOOL vBarUpdate = FALSE;
+ const BYTE* cpSrcPixel = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, vBarHeader);
+ suboffset += 2;
+ vBarHeight = (yEnd - yStart + 1);
+
+ if (vBarHeight > 52)
+ {
+ WLog_ERR(TAG, "vBarHeight (%" PRIu32 ") > 52", vBarHeight);
+ return FALSE;
+ }
+
+ if ((vBarHeader & 0xC000) == 0x4000) /* SHORT_VBAR_CACHE_HIT */
+ {
+ const UINT16 vBarIndex = (vBarHeader & 0x3FFF);
+ vBarShortEntry = &(clear->ShortVBarStorage[vBarIndex]);
+
+ if (!vBarShortEntry)
+ {
+ WLog_ERR(TAG, "missing vBarShortEntry %" PRIu16 "", vBarIndex);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, vBarYOn);
+ suboffset += 1;
+ vBarShortPixelCount = vBarShortEntry->count;
+ vBarUpdate = TRUE;
+ }
+ else if ((vBarHeader & 0xC000) == 0x0000) /* SHORT_VBAR_CACHE_MISS */
+ {
+ vBarYOn = (vBarHeader & 0xFF);
+ vBarYOff = ((vBarHeader >> 8) & 0x3F);
+
+ if (vBarYOff < vBarYOn)
+ {
+ WLog_ERR(TAG, "vBarYOff %" PRIu16 " < vBarYOn %" PRIu16 "", vBarYOff, vBarYOn);
+ return FALSE;
+ }
+
+ vBarShortPixelCount = (vBarYOff - vBarYOn);
+
+ if (vBarShortPixelCount > 52)
+ {
+ WLog_ERR(TAG, "vBarShortPixelCount %" PRIu32 " > 52", vBarShortPixelCount);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, vBarShortPixelCount, 3ull))
+ return FALSE;
+
+ if (clear->ShortVBarStorageCursor >= CLEARCODEC_VBAR_SHORT_SIZE)
+ {
+ WLog_ERR(TAG,
+ "clear->ShortVBarStorageCursor %" PRIu32
+ " >= CLEARCODEC_VBAR_SHORT_SIZE (%" PRIu32 ")",
+ clear->ShortVBarStorageCursor, CLEARCODEC_VBAR_SHORT_SIZE);
+ return FALSE;
+ }
+
+ vBarShortEntry = &(clear->ShortVBarStorage[clear->ShortVBarStorageCursor]);
+ vBarShortEntry->count = vBarShortPixelCount;
+
+ if (!resize_vbar_entry(clear, vBarShortEntry))
+ return FALSE;
+
+ for (UINT32 y = 0; y < vBarShortPixelCount; y++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE* dstBuffer =
+ &vBarShortEntry->pixels[y * FreeRDPGetBytesPerPixel(clear->format)];
+ UINT32 color = 0;
+ Stream_Read_UINT8(s, b);
+ Stream_Read_UINT8(s, g);
+ Stream_Read_UINT8(s, r);
+ color = FreeRDPGetColor(clear->format, r, g, b, 0xFF);
+
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, color))
+ return FALSE;
+ }
+
+ suboffset += (vBarShortPixelCount * 3);
+ clear->ShortVBarStorageCursor =
+ (clear->ShortVBarStorageCursor + 1) % CLEARCODEC_VBAR_SHORT_SIZE;
+ vBarUpdate = TRUE;
+ }
+ else if ((vBarHeader & 0x8000) == 0x8000) /* VBAR_CACHE_HIT */
+ {
+ const UINT16 vBarIndex = (vBarHeader & 0x7FFF);
+ vBarEntry = &(clear->VBarStorage[vBarIndex]);
+
+ /* If the cache was reset we need to fill in some dummy data. */
+ if (vBarEntry->size == 0)
+ {
+ WLog_WARN(TAG, "Empty cache index %" PRIu16 ", filling dummy data", vBarIndex);
+ vBarEntry->count = vBarHeight;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "invalid vBarHeader 0x%04" PRIX16 "", vBarHeader);
+ return FALSE; /* invalid vBarHeader */
+ }
+
+ if (vBarUpdate)
+ {
+ BYTE* pSrcPixel = NULL;
+ BYTE* dstBuffer = NULL;
+
+ if (clear->VBarStorageCursor >= CLEARCODEC_VBAR_SIZE)
+ {
+ WLog_ERR(TAG,
+ "clear->VBarStorageCursor %" PRIu32 " >= CLEARCODEC_VBAR_SIZE %" PRIu32
+ "",
+ clear->VBarStorageCursor, CLEARCODEC_VBAR_SIZE);
+ return FALSE;
+ }
+
+ vBarEntry = &(clear->VBarStorage[clear->VBarStorageCursor]);
+ vBarPixelCount = vBarHeight;
+ vBarEntry->count = vBarPixelCount;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+
+ dstBuffer = vBarEntry->pixels;
+ /* if (y < vBarYOn), use colorBkg */
+ UINT32 y = 0;
+ UINT32 count = vBarYOn;
+
+ if ((y + count) > vBarPixelCount)
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ while (count--)
+ {
+ FreeRDPWriteColor(dstBuffer, clear->format, colorBkg);
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ /*
+ * if ((y >= vBarYOn) && (y < (vBarYOn + vBarShortPixelCount))),
+ * use vBarShortPixels at index (y - shortVBarYOn)
+ */
+ y = vBarYOn;
+ count = vBarShortPixelCount;
+
+ if ((y + count) > vBarPixelCount)
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ if (count > 0)
+ pSrcPixel =
+ &vBarShortEntry
+ ->pixels[(y - vBarYOn) * FreeRDPGetBytesPerPixel(clear->format)];
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 color = 0;
+ color = FreeRDPReadColor(&pSrcPixel[x * FreeRDPGetBytesPerPixel(clear->format)],
+ clear->format);
+
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, color))
+ return FALSE;
+
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ /* if (y >= (vBarYOn + vBarShortPixelCount)), use colorBkg */
+ y = vBarYOn + vBarShortPixelCount;
+ count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0;
+
+ while (count--)
+ {
+ if (!FreeRDPWriteColor(dstBuffer, clear->format, colorBkg))
+ return FALSE;
+
+ dstBuffer += FreeRDPGetBytesPerPixel(clear->format);
+ }
+
+ vBarEntry->count = vBarPixelCount;
+ clear->VBarStorageCursor = (clear->VBarStorageCursor + 1) % CLEARCODEC_VBAR_SIZE;
+ }
+
+ if (vBarEntry->count != vBarHeight)
+ {
+ WLog_ERR(TAG, "vBarEntry->count %" PRIu32 " != vBarHeight %" PRIu32 "",
+ vBarEntry->count, vBarHeight);
+ vBarEntry->count = vBarHeight;
+
+ if (!resize_vbar_entry(clear, vBarEntry))
+ return FALSE;
+ }
+
+ const UINT32 nXDstRel = nXDst + xStart;
+ const UINT32 nYDstRel = nYDst + yStart;
+ cpSrcPixel = vBarEntry->pixels;
+
+ if (i < nWidth)
+ {
+ UINT32 count = vBarEntry->count;
+
+ if (count > nHeight)
+ count = nHeight;
+
+ if (nXDstRel + i > nDstWidth)
+ return FALSE;
+
+ for (UINT32 y = 0; y < count; y++)
+ {
+ if (nYDstRel + y > nDstHeight)
+ return FALSE;
+
+ BYTE* pDstPixel8 =
+ &pDstData[((nYDstRel + y) * nDstStep) +
+ ((nXDstRel + i) * FreeRDPGetBytesPerPixel(DstFormat))];
+ UINT32 color = FreeRDPReadColor(cpSrcPixel, clear->format);
+ color = FreeRDPConvertColor(color, clear->format, DstFormat, NULL);
+
+ if (!FreeRDPWriteColor(pDstPixel8, DstFormat, color))
+ return FALSE;
+
+ cpSrcPixel += FreeRDPGetBytesPerPixel(clear->format);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL clear_decompress_glyph_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 glyphFlags,
+ UINT32 nWidth, UINT32 nHeight, BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette, BYTE** ppGlyphData)
+{
+ UINT16 glyphIndex = 0;
+
+ if (ppGlyphData)
+ *ppGlyphData = NULL;
+
+ if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT) && !(glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX))
+ {
+ WLog_ERR(TAG, "Invalid glyph flags %08" PRIX32 "", glyphFlags);
+ return FALSE;
+ }
+
+ if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX) == 0)
+ return TRUE;
+
+ if ((nWidth * nHeight) > (1024 * 1024))
+ {
+ WLog_ERR(TAG, "glyph too large: %" PRIu32 "x%" PRIu32 "", nWidth, nHeight);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return FALSE;
+
+ Stream_Read_UINT16(s, glyphIndex);
+
+ if (glyphIndex >= 4000)
+ {
+ WLog_ERR(TAG, "Invalid glyphIndex %" PRIu16 "", glyphIndex);
+ return FALSE;
+ }
+
+ if (glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT)
+ {
+ UINT32 nSrcStep = 0;
+ CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]);
+ BYTE* glyphData = NULL;
+
+ if (!glyphEntry)
+ {
+ WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]=NULL", glyphIndex);
+ return FALSE;
+ }
+
+ glyphData = (BYTE*)glyphEntry->pixels;
+
+ if (!glyphData)
+ {
+ WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]->pixels=NULL", glyphIndex);
+ return FALSE;
+ }
+
+ if ((nWidth * nHeight) > glyphEntry->count)
+ {
+ WLog_ERR(TAG,
+ "(nWidth %" PRIu32 " * nHeight %" PRIu32 ") > glyphEntry->count %" PRIu32 "",
+ nWidth, nHeight, glyphEntry->count);
+ return FALSE;
+ }
+
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format);
+ return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight,
+ glyphData, nSrcStep, clear->format, nDstWidth, nDstHeight, palette);
+ }
+
+ if (glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX)
+ {
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format);
+ CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]);
+ glyphEntry->count = nWidth * nHeight;
+
+ if (glyphEntry->count > glyphEntry->size)
+ {
+ BYTE* tmp =
+ winpr_aligned_recalloc(glyphEntry->pixels, glyphEntry->count, 1ull * bpp, 32);
+
+ if (!tmp)
+ {
+ WLog_ERR(TAG, "glyphEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed!",
+ glyphEntry->count * bpp);
+ return FALSE;
+ }
+
+ glyphEntry->size = glyphEntry->count;
+ glyphEntry->pixels = (UINT32*)tmp;
+ }
+
+ if (!glyphEntry->pixels)
+ {
+ WLog_ERR(TAG, "glyphEntry->pixels=NULL");
+ return FALSE;
+ }
+
+ if (ppGlyphData)
+ *ppGlyphData = (BYTE*)glyphEntry->pixels;
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL updateContextFormat(CLEAR_CONTEXT* clear, UINT32 DstFormat)
+{
+ if (!clear || !clear->nsc)
+ return FALSE;
+
+ clear->format = DstFormat;
+ return nsc_context_set_parameters(clear->nsc, NSC_COLOR_FORMAT, DstFormat);
+}
+
+INT32 clear_decompress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth,
+ UINT32 nHeight, BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ INT32 rc = -1;
+ BYTE seqNumber = 0;
+ BYTE glyphFlags = 0;
+ UINT32 residualByteCount = 0;
+ UINT32 bandsByteCount = 0;
+ UINT32 subcodecByteCount = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = NULL;
+ BYTE* glyphData = NULL;
+
+ if (!pDstData)
+ return -1002;
+
+ if ((nDstWidth == 0) || (nDstHeight == 0))
+ return -1022;
+
+ if ((nWidth > 0xFFFF) || (nHeight > 0xFFFF))
+ return -1004;
+
+ s = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize);
+
+ if (!s)
+ return -2005;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ goto fail;
+
+ if (!updateContextFormat(clear, DstFormat))
+ goto fail;
+
+ Stream_Read_UINT8(s, glyphFlags);
+ Stream_Read_UINT8(s, seqNumber);
+
+ if (!clear->seqNumber && seqNumber)
+ clear->seqNumber = seqNumber;
+
+ if (seqNumber != clear->seqNumber)
+ {
+ WLog_ERR(TAG, "Sequence number unexpected %" PRIu8 " - %" PRIu32 "", seqNumber,
+ clear->seqNumber);
+ WLog_ERR(TAG, "seqNumber %" PRIu8 " != clear->seqNumber %" PRIu32 "", seqNumber,
+ clear->seqNumber);
+ goto fail;
+ }
+
+ clear->seqNumber = (seqNumber + 1) % 256;
+
+ if (glyphFlags & CLEARCODEC_FLAG_CACHE_RESET)
+ {
+ clear_reset_vbar_storage(clear, FALSE);
+ }
+
+ if (!clear_decompress_glyph_data(clear, s, glyphFlags, nWidth, nHeight, pDstData, DstFormat,
+ nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, palette,
+ &glyphData))
+ {
+ WLog_ERR(TAG, "clear_decompress_glyph_data failed!");
+ goto fail;
+ }
+
+ /* Read composition payload header parameters */
+ if (Stream_GetRemainingLength(s) < 12)
+ {
+ const UINT32 mask = (CLEARCODEC_FLAG_GLYPH_HIT | CLEARCODEC_FLAG_GLYPH_INDEX);
+
+ if ((glyphFlags & mask) == mask)
+ goto finish;
+
+ WLog_ERR(TAG,
+ "invalid glyphFlags, missing flags: 0x%02" PRIx8 " & 0x%02" PRIx32
+ " == 0x%02" PRIx32,
+ glyphFlags, mask, glyphFlags & mask);
+ goto fail;
+ }
+
+ Stream_Read_UINT32(s, residualByteCount);
+ Stream_Read_UINT32(s, bandsByteCount);
+ Stream_Read_UINT32(s, subcodecByteCount);
+
+ if (residualByteCount > 0)
+ {
+ if (!clear_decompress_residual_data(clear, s, residualByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, palette))
+ {
+ WLog_ERR(TAG, "clear_decompress_residual_data failed!");
+ goto fail;
+ }
+ }
+
+ if (bandsByteCount > 0)
+ {
+ if (!clear_decompress_bands_data(clear, s, bandsByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight))
+ {
+ WLog_ERR(TAG, "clear_decompress_bands_data failed!");
+ goto fail;
+ }
+ }
+
+ if (subcodecByteCount > 0)
+ {
+ if (!clear_decompress_subcodecs_data(clear, s, subcodecByteCount, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, palette))
+ {
+ WLog_ERR(TAG, "clear_decompress_subcodecs_data failed!");
+ goto fail;
+ }
+ }
+
+ if (glyphData)
+ {
+ if (!freerdp_image_copy(glyphData, clear->format, 0, 0, 0, nWidth, nHeight, pDstData,
+ DstFormat, nDstStep, nXDst, nYDst, palette, FREERDP_KEEP_DST_ALPHA))
+ goto fail;
+ }
+
+finish:
+ rc = 0;
+fail:
+ return rc;
+}
+
+int clear_compress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize)
+{
+ WLog_ERR(TAG, "TODO: not implemented!");
+ return 1;
+}
+
+BOOL clear_context_reset(CLEAR_CONTEXT* clear)
+{
+ if (!clear)
+ return FALSE;
+
+ /**
+ * The ClearCodec context is not bound to a particular surface,
+ * and its internal caches must NOT be reset on the ResetGraphics PDU.
+ */
+ clear->seqNumber = 0;
+ return TRUE;
+}
+
+CLEAR_CONTEXT* clear_context_new(BOOL Compressor)
+{
+ CLEAR_CONTEXT* clear = (CLEAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(CLEAR_CONTEXT), 32);
+
+ if (!clear)
+ return NULL;
+
+ clear->Compressor = Compressor;
+ clear->nsc = nsc_context_new();
+
+ if (!clear->nsc)
+ goto error_nsc;
+
+ if (!updateContextFormat(clear, PIXEL_FORMAT_BGRX32))
+ goto error_nsc;
+
+ if (!clear_resize_buffer(clear, 512, 512))
+ goto error_nsc;
+
+ if (!clear->TempBuffer)
+ goto error_nsc;
+
+ if (!clear_context_reset(clear))
+ goto error_nsc;
+
+ return clear;
+error_nsc:
+ clear_context_free(clear);
+ return NULL;
+}
+
+void clear_context_free(CLEAR_CONTEXT* clear)
+{
+ if (!clear)
+ return;
+
+ nsc_context_free(clear->nsc);
+ winpr_aligned_free(clear->TempBuffer);
+
+ clear_reset_vbar_storage(clear, TRUE);
+ clear_reset_glyph_cache(clear);
+
+ winpr_aligned_free(clear);
+}
diff --git a/libfreerdp/codec/color.c b/libfreerdp/codec/color.c
new file mode 100644
index 0000000..186d477
--- /dev/null
+++ b/libfreerdp/codec/color.c
@@ -0,0 +1,1681 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Color Conversion Routines
+ *
+ * Copyright 2010 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/primitives.h>
+
+#if defined(WITH_CAIRO)
+#include <cairo.h>
+#endif
+
+#if defined(WITH_SWSCALE)
+#include <libswscale/swscale.h>
+#endif
+
+#define TAG FREERDP_TAG("color")
+
+BYTE* freerdp_glyph_convert(UINT32 width, UINT32 height, const BYTE* data)
+{
+ /*
+ * converts a 1-bit-per-pixel glyph to a one-byte-per-pixel glyph:
+ * this approach uses a little more memory, but provides faster
+ * means of accessing individual pixels in blitting operations
+ */
+ const UINT32 scanline = (width + 7) / 8;
+ BYTE* dstData = (BYTE*)winpr_aligned_malloc(1ull * width * height, 16);
+
+ if (!dstData)
+ return NULL;
+
+ ZeroMemory(dstData, width * height);
+ BYTE* dstp = dstData;
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const BYTE* srcp = &data[1ull * y * scanline];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ if ((*srcp & (0x80 >> (x % 8))) != 0)
+ *dstp = 0xFF;
+
+ dstp++;
+
+ if (((x + 1) % 8 == 0) && x != 0)
+ srcp++;
+ }
+ }
+
+ return dstData;
+}
+
+BOOL freerdp_image_copy_from_monochrome(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth,
+ UINT32 nHeight, const BYTE* WINPR_RESTRICT pSrcData,
+ UINT32 backColor, UINT32 foreColor,
+ const gdiPalette* WINPR_RESTRICT palette)
+{
+ BOOL vFlip = 0;
+ UINT32 monoStep = 0;
+ const UINT32 dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (!pDstData || !pSrcData || !palette)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ vFlip = FALSE;
+ monoStep = (nWidth + 7) / 8;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* monoBits = NULL;
+ BYTE* pDstLine = &pDstData[((nYDst + y) * nDstStep)];
+ UINT32 monoBit = 0x80;
+
+ if (!vFlip)
+ monoBits = &pSrcData[monoStep * y];
+ else
+ monoBits = &pSrcData[monoStep * (nHeight - y - 1)];
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* pDstPixel = &pDstLine[((nXDst + x) * FreeRDPGetBytesPerPixel(DstFormat))];
+ BOOL monoPixel = (*monoBits & monoBit) ? TRUE : FALSE;
+
+ if (!(monoBit >>= 1))
+ {
+ monoBits++;
+ monoBit = 0x80;
+ }
+
+ if (monoPixel)
+ FreeRDPWriteColor(pDstPixel, DstFormat, backColor);
+ else
+ FreeRDPWriteColor(pDstPixel, DstFormat, foreColor);
+ }
+ }
+
+ return TRUE;
+}
+
+static INLINE UINT32 freerdp_image_inverted_pointer_color(UINT32 x, UINT32 y, UINT32 format)
+{
+#if 1
+ /**
+ * Inverted pointer colors (where individual pixels can change their
+ * color to accommodate the background behind them) only seem to be
+ * supported on Windows.
+ * Using a static replacement color for these pixels (e.g. black)
+ * might result in invisible pointers depending on the background.
+ * This function returns either black or white, depending on the
+ * pixel's position.
+ */
+ BYTE fill = (x + y) & 1 ? 0x00 : 0xFF;
+#else
+ BYTE fill = 0x00;
+#endif
+ return FreeRDPGetColor(format, fill, fill, fill, 0xFF);
+}
+
+/*
+ * DIB color palettes are arrays of RGBQUAD structs with colors in BGRX format.
+ * They are used only by 1, 2, 4, and 8-bit bitmaps.
+ */
+static void fill_gdi_palette_for_icon(const BYTE* colorTable, UINT16 cbColorTable,
+ gdiPalette* palette)
+{
+ WINPR_ASSERT(palette);
+
+ palette->format = PIXEL_FORMAT_BGRX32;
+ ZeroMemory(palette->palette, sizeof(palette->palette));
+
+ if (!cbColorTable)
+ return;
+
+ if ((cbColorTable % 4 != 0) || (cbColorTable / 4 > 256))
+ {
+ WLog_WARN(TAG, "weird palette size: %u", cbColorTable);
+ return;
+ }
+
+ for (UINT16 i = 0; i < cbColorTable / 4; i++)
+ {
+ palette->palette[i] = FreeRDPReadColor(&colorTable[4 * i], palette->format);
+ }
+}
+
+static INLINE UINT32 div_ceil(UINT32 a, UINT32 b)
+{
+ return (a + (b - 1)) / b;
+}
+
+static INLINE UINT32 round_up(UINT32 a, UINT32 b)
+{
+ return b * div_ceil(a, b);
+}
+
+BOOL freerdp_image_copy_from_icon_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT16 nWidth,
+ UINT16 nHeight, const BYTE* WINPR_RESTRICT bitsColor,
+ UINT16 cbBitsColor, const BYTE* WINPR_RESTRICT bitsMask,
+ UINT16 cbBitsMask, const BYTE* WINPR_RESTRICT colorTable,
+ UINT16 cbColorTable, UINT32 bpp)
+{
+ DWORD format = 0;
+ gdiPalette palette;
+
+ if (!pDstData || !bitsColor)
+ return FALSE;
+
+ /*
+ * Color formats used by icons are DIB bitmap formats (2-bit format
+ * is not used by MS-RDPERP). Note that 16-bit is RGB555, not RGB565,
+ * and that 32-bit format uses BGRA order.
+ */
+ switch (bpp)
+ {
+ case 1:
+ case 4:
+ /*
+ * These formats are not supported by freerdp_image_copy().
+ * PIXEL_FORMAT_MONO and PIXEL_FORMAT_A4 are *not* correct
+ * color formats for this. Please fix freerdp_image_copy()
+ * if you came here to fix a broken icon of some weird app
+ * that still uses 1 or 4bpp format in the 21st century.
+ */
+ WLog_WARN(TAG, "1bpp and 4bpp icons are not supported");
+ return FALSE;
+
+ case 8:
+ format = PIXEL_FORMAT_RGB8;
+ break;
+
+ case 16:
+ format = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 24:
+ format = PIXEL_FORMAT_RGB24;
+ break;
+
+ case 32:
+ format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ default:
+ WLog_WARN(TAG, "invalid icon bpp: %" PRIu32, bpp);
+ return FALSE;
+ }
+
+ /* Ensure we have enough source data bytes for image copy. */
+ if (cbBitsColor < nWidth * nHeight * FreeRDPGetBytesPerPixel(format))
+ return FALSE;
+
+ fill_gdi_palette_for_icon(colorTable, cbColorTable, &palette);
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, bitsColor,
+ format, 0, 0, 0, &palette, FREERDP_FLIP_VERTICAL))
+ return FALSE;
+
+ /* apply alpha mask */
+ if (FreeRDPColorHasAlpha(DstFormat) && cbBitsMask)
+ {
+ BYTE nextBit = 0;
+ const BYTE* maskByte = NULL;
+ UINT32 stride = 0;
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE* dstBuf = pDstData;
+ UINT32 dstBpp = FreeRDPGetBytesPerPixel(DstFormat);
+
+ /*
+ * Each byte encodes 8 adjacent pixels (with LSB padding as needed).
+ * And due to hysterical raisins, stride of DIB bitmaps must be
+ * a multiple of 4 bytes.
+ */
+ stride = round_up(div_ceil(nWidth, 8), 4);
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ maskByte = &bitsMask[stride * (nHeight - 1 - y)];
+ nextBit = 0x80;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 color = 0;
+ BYTE alpha = (*maskByte & nextBit) ? 0x00 : 0xFF;
+
+ /* read color back, add alpha and write it back */
+ color = FreeRDPReadColor(dstBuf, DstFormat);
+ FreeRDPSplitColor(color, DstFormat, &r, &g, &b, NULL, &palette);
+ color = FreeRDPGetColor(DstFormat, r, g, b, alpha);
+ FreeRDPWriteColor(dstBuf, DstFormat, color);
+
+ nextBit >>= 1;
+ dstBuf += dstBpp;
+ if (!nextBit)
+ {
+ nextBit = 0x80;
+ maskByte++;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_from_pointer_data_1bpp(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp)
+{
+ BOOL vFlip = 0;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 xorBit = 0;
+ UINT32 andBit = 0;
+ UINT32 xorPixel = 0;
+ UINT32 andPixel = 0;
+
+ vFlip = (xorBpp == 1) ? FALSE : TRUE;
+ andStep = (nWidth + 7) / 8;
+ andStep += (andStep % 2);
+
+ if (!xorMask || (xorMaskLength == 0))
+ return FALSE;
+ if (!andMask || (andMaskLength == 0))
+ return FALSE;
+
+ xorStep = (nWidth + 7) / 8;
+ xorStep += (xorStep % 2);
+
+ if (xorStep * nHeight > xorMaskLength)
+ return FALSE;
+
+ if (andStep * nHeight > andMaskLength)
+ return FALSE;
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* andBits = NULL;
+ const BYTE* xorBits = NULL;
+ BYTE* pDstPixel =
+ &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))];
+ xorBit = andBit = 0x80;
+
+ if (!vFlip)
+ {
+ xorBits = &xorMask[xorStep * y];
+ andBits = &andMask[andStep * y];
+ }
+ else
+ {
+ xorBits = &xorMask[xorStep * (nHeight - y - 1)];
+ andBits = &andMask[andStep * (nHeight - y - 1)];
+ }
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 color = 0;
+ xorPixel = (*xorBits & xorBit) ? 1 : 0;
+
+ if (!(xorBit >>= 1))
+ {
+ xorBits++;
+ xorBit = 0x80;
+ }
+
+ andPixel = (*andBits & andBit) ? 1 : 0;
+
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+
+ if (!andPixel && !xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0xFF); /* black */
+ else if (!andPixel && xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0xFF, 0xFF, 0xFF, 0xFF); /* white */
+ else if (andPixel && !xorPixel)
+ color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0); /* transparent */
+ else if (andPixel && xorPixel)
+ color = freerdp_image_inverted_pointer_color(x, y, DstFormat); /* inverted */
+
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_from_pointer_data_xbpp(
+ BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp,
+ const gdiPalette* palette)
+{
+ BOOL vFlip = 0;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 andBit = 0;
+ UINT32 xorPixel = 0;
+ UINT32 andPixel = 0;
+ UINT32 dstBitsPerPixel = 0;
+ UINT32 xorBytesPerPixel = 0;
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+
+ vFlip = (xorBpp == 1) ? FALSE : TRUE;
+ andStep = (nWidth + 7) / 8;
+ andStep += (andStep % 2);
+
+ if (!xorMask || (xorMaskLength == 0))
+ return FALSE;
+
+ xorBytesPerPixel = xorBpp >> 3;
+ xorStep = nWidth * xorBytesPerPixel;
+ xorStep += (xorStep % 2);
+
+ if (xorBpp == 8 && !palette)
+ {
+ WLog_ERR(TAG, "null palette in conversion from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp,
+ dstBitsPerPixel);
+ return FALSE;
+ }
+
+ if (xorStep * nHeight > xorMaskLength)
+ return FALSE;
+
+ if (andMask)
+ {
+ if (andStep * nHeight > andMaskLength)
+ return FALSE;
+ }
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* xorBits = NULL;
+ const BYTE* andBits = NULL;
+ BYTE* pDstPixel =
+ &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))];
+ andBit = 0x80;
+
+ if (!vFlip)
+ {
+ if (andMask)
+ andBits = &andMask[andStep * y];
+
+ xorBits = &xorMask[xorStep * y];
+ }
+ else
+ {
+ if (andMask)
+ andBits = &andMask[andStep * (nHeight - y - 1)];
+
+ xorBits = &xorMask[xorStep * (nHeight - y - 1)];
+ }
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ UINT32 pixelFormat = 0;
+ UINT32 color = 0;
+
+ if (xorBpp == 32)
+ {
+ pixelFormat = PIXEL_FORMAT_BGRA32;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+ else if (xorBpp == 16)
+ {
+ pixelFormat = PIXEL_FORMAT_RGB15;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+ else if (xorBpp == 8)
+ {
+ pixelFormat = palette->format;
+ xorPixel = palette->palette[xorBits[0]];
+ }
+ else
+ {
+ pixelFormat = PIXEL_FORMAT_BGR24;
+ xorPixel = FreeRDPReadColor(xorBits, pixelFormat);
+ }
+
+ xorPixel = FreeRDPConvertColor(xorPixel, pixelFormat, PIXEL_FORMAT_ARGB32, palette);
+ xorBits += xorBytesPerPixel;
+ andPixel = 0;
+
+ if (andMask)
+ {
+ andPixel = (*andBits & andBit) ? 1 : 0;
+
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+ }
+
+ if (andPixel)
+ {
+ if (xorPixel == 0xFF000000) /* black -> transparent */
+ xorPixel = 0x00000000;
+ else if (xorPixel == 0xFFFFFFFF) /* white -> inverted */
+ xorPixel = freerdp_image_inverted_pointer_color(x, y, PIXEL_FORMAT_ARGB32);
+ }
+
+ color = FreeRDPConvertColor(xorPixel, PIXEL_FORMAT_ARGB32, DstFormat, palette);
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Drawing Monochrome Pointers:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556143/
+ *
+ * Drawing Color Pointers:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556138/
+ */
+
+BOOL freerdp_image_copy_from_pointer_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight,
+ const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength,
+ const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength,
+ UINT32 xorBpp, const gdiPalette* palette)
+{
+ UINT32 dstBitsPerPixel = 0;
+ UINT32 dstBytesPerPixel = 0;
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+ dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nDstStep <= 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ for (UINT32 y = nYDst; y < nHeight; y++)
+ {
+ BYTE* WINPR_RESTRICT pDstLine = &pDstData[y * nDstStep + nXDst * dstBytesPerPixel];
+ memset(pDstLine, 0, 1ull * dstBytesPerPixel * (nWidth - nXDst));
+ }
+
+ switch (xorBpp)
+ {
+ case 1:
+ return freerdp_image_copy_from_pointer_data_1bpp(
+ pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask,
+ xorMaskLength, andMask, andMaskLength, xorBpp);
+
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ return freerdp_image_copy_from_pointer_data_xbpp(
+ pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask,
+ xorMaskLength, andMask, andMaskLength, xorBpp, palette);
+
+ default:
+ WLog_ERR(TAG, "failed to convert from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp,
+ dstBitsPerPixel);
+ return FALSE;
+ }
+}
+
+static INLINE BOOL overlapping(const BYTE* pDstData, UINT32 nXDst, UINT32 nYDst, UINT32 nDstStep,
+ UINT32 dstBytesPerPixel, const BYTE* pSrcData, UINT32 nXSrc,
+ UINT32 nYSrc, UINT32 nSrcStep, UINT32 srcBytesPerPixel,
+ UINT32 nWidth, UINT32 nHeight)
+{
+ const BYTE* pDstStart = &pDstData[nXDst * dstBytesPerPixel + nYDst * nDstStep];
+ const BYTE* pDstEnd = pDstStart + nHeight * nDstStep;
+ const BYTE* pSrcStart = &pSrcData[nXSrc * srcBytesPerPixel + nYSrc * nSrcStep];
+ const BYTE* pSrcEnd = pSrcStart + nHeight * nSrcStep;
+
+ WINPR_UNUSED(nWidth);
+
+ if ((pDstStart >= pSrcStart) && (pDstStart <= pSrcEnd))
+ return TRUE;
+
+ if ((pDstEnd >= pSrcStart) && (pDstEnd <= pSrcEnd))
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL freerdp_image_copy_no_overlap(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst,
+ UINT32 nWidth, UINT32 nHeight,
+ const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat,
+ UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc,
+ const gdiPalette* WINPR_RESTRICT palette, UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+ const UINT32 copyDstWidth = nWidth * dstByte;
+ const UINT32 xSrcOffset = nXSrc * srcByte;
+ const UINT32 xDstOffset = nXDst * dstByte;
+ const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE;
+ UINT32 srcVOffset = 0;
+ INT32 srcVMultiplier = 1;
+ UINT32 dstVOffset = 0;
+ INT32 dstVMultiplier = 1;
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if (vSrcVFlip)
+ {
+ srcVOffset = (nHeight - 1) * nSrcStep;
+ srcVMultiplier = -1;
+ }
+
+ if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ }
+ }
+ }
+ else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ else
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* WINPR_RESTRICT srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* WINPR_RESTRICT dstLine =
+ &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_image_copy_overlap(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+ const UINT32 copyDstWidth = nWidth * dstByte;
+ const UINT32 xSrcOffset = nXSrc * srcByte;
+ const UINT32 xDstOffset = nXDst * dstByte;
+ const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE;
+ UINT32 srcVOffset = 0;
+ INT32 srcVMultiplier = 1;
+ UINT32 dstVOffset = 0;
+ INT32 dstVMultiplier = 1;
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if (vSrcVFlip)
+ {
+ srcVOffset = (nHeight - 1) * nSrcStep;
+ srcVMultiplier = -1;
+ }
+
+ if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat))
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat,
+ dstColor);
+ }
+ }
+ }
+ }
+ else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat))
+ {
+ /* Copy down */
+ if (nYDst < nYSrc)
+ {
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy up */
+ else if (nYDst > nYSrc)
+ {
+ for (INT32 y = (INT32)nHeight - 1; y >= 0; y--)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy left */
+ else if (nXSrc > nXDst)
+ {
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Copy right */
+ else if (nXSrc < nXDst)
+ {
+ for (INT32 y = (INT32)nHeight - 1; y >= 0; y--)
+ {
+ const BYTE* srcLine =
+ &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+ memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth);
+ }
+ }
+ /* Source and destination are equal... */
+ else
+ {
+ }
+ }
+ else
+ {
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset];
+ BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset];
+
+ UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat);
+ UINT32 oldColor = color;
+ UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor);
+ for (UINT32 x = 1; x < nWidth; x++)
+ {
+ color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat);
+ if (color == oldColor)
+ {
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ else
+ {
+ oldColor = color;
+ dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette);
+ FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_image_copy(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData,
+ DWORD SrcFormat, UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc,
+ const gdiPalette* palette, UINT32 flags)
+{
+ const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat);
+ const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat);
+
+ if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX))
+ return FALSE;
+
+ if (!pDstData || !pSrcData)
+ return FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+ const BOOL ovl = overlapping(pDstData, nXDst, nYDst, nDstStep, dstByte, pSrcData, nXSrc, nYSrc,
+ nSrcStep, srcByte, nWidth, nHeight);
+ if (ovl)
+ return freerdp_image_copy_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth,
+ nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc,
+ palette, flags);
+ return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth,
+ nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc,
+ palette, flags);
+}
+
+BOOL freerdp_image_fill(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 color)
+{
+ if ((nWidth == 0) || (nHeight == 0))
+ return TRUE;
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat);
+ BYTE* WINPR_RESTRICT pFirstDstLine = NULL;
+ BYTE* WINPR_RESTRICT pFirstDstLineXOffset = NULL;
+
+ if (nDstStep == 0)
+ nDstStep = (nXDst + nWidth) * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pFirstDstLine = &pDstData[nYDst * nDstStep];
+ pFirstDstLineXOffset = &pFirstDstLine[nXDst * bpp];
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ BYTE* pDst = &pFirstDstLine[(x + nXDst) * bpp];
+ FreeRDPWriteColor(pDst, DstFormat, color);
+ }
+
+ for (UINT32 y = 1; y < nHeight; y++)
+ {
+ BYTE* pDstLine = &pDstData[(y + nYDst) * nDstStep + nXDst * bpp];
+ memcpy(pDstLine, pFirstDstLineXOffset, 1ull * nWidth * bpp);
+ }
+
+ return TRUE;
+}
+
+#if defined(WITH_SWSCALE)
+static int av_format_for_buffer(UINT32 format)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ return AV_PIX_FMT_BGRA;
+
+ case PIXEL_FORMAT_XRGB32:
+ return AV_PIX_FMT_BGR0;
+
+ case PIXEL_FORMAT_BGRA32:
+ return AV_PIX_FMT_RGBA;
+
+ case PIXEL_FORMAT_BGRX32:
+ return AV_PIX_FMT_RGB0;
+
+ default:
+ return AV_PIX_FMT_NONE;
+ }
+}
+#endif
+
+BOOL freerdp_image_scale(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep,
+ UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nXSrc, UINT32 nYSrc, UINT32 nSrcWidth, UINT32 nSrcHeight)
+{
+ BOOL rc = FALSE;
+
+ if (nDstStep == 0)
+ nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nSrcStep == 0)
+ nSrcStep = nSrcWidth * FreeRDPGetBytesPerPixel(SrcFormat);
+
+#if defined(WITH_SWSCALE) || defined(WITH_CAIRO)
+ const BYTE* src = &pSrcData[nXSrc * FreeRDPGetBytesPerPixel(SrcFormat) + nYSrc * nSrcStep];
+ BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep];
+#endif
+
+ /* direct copy is much faster than scaling, so check if we can simply copy... */
+ if ((nDstWidth == nSrcWidth) && (nDstHeight == nSrcHeight))
+ {
+ return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth,
+ nDstHeight, pSrcData, SrcFormat, nSrcStep, nXSrc,
+ nYSrc, NULL, FREERDP_FLIP_NONE);
+ }
+ else
+#if defined(WITH_SWSCALE)
+ {
+ int res = 0;
+ struct SwsContext* resize = NULL;
+ int srcFormat = av_format_for_buffer(SrcFormat);
+ int dstFormat = av_format_for_buffer(DstFormat);
+ const int srcStep[1] = { (int)nSrcStep };
+ const int dstStep[1] = { (int)nDstStep };
+
+ if ((srcFormat == AV_PIX_FMT_NONE) || (dstFormat == AV_PIX_FMT_NONE))
+ return FALSE;
+
+ resize = sws_getContext((int)nSrcWidth, (int)nSrcHeight, srcFormat, (int)nDstWidth,
+ (int)nDstHeight, dstFormat, SWS_BILINEAR, NULL, NULL, NULL);
+
+ if (!resize)
+ goto fail;
+
+ res = sws_scale(resize, &src, srcStep, 0, (int)nSrcHeight, &dst, dstStep);
+ rc = (res == ((int)nDstHeight));
+ fail:
+ sws_freeContext(resize);
+ }
+
+#elif defined(WITH_CAIRO)
+ {
+ const double sx = (double)nDstWidth / (double)nSrcWidth;
+ const double sy = (double)nDstHeight / (double)nSrcHeight;
+ cairo_t* cairo_context;
+ cairo_surface_t *csrc, *cdst;
+
+ if ((nSrcWidth > INT_MAX) || (nSrcHeight > INT_MAX) || (nSrcStep > INT_MAX))
+ return FALSE;
+
+ if ((nDstWidth > INT_MAX) || (nDstHeight > INT_MAX) || (nDstStep > INT_MAX))
+ return FALSE;
+
+ csrc = cairo_image_surface_create_for_data((void*)src, CAIRO_FORMAT_ARGB32, (int)nSrcWidth,
+ (int)nSrcHeight, (int)nSrcStep);
+ cdst = cairo_image_surface_create_for_data(dst, CAIRO_FORMAT_ARGB32, (int)nDstWidth,
+ (int)nDstHeight, (int)nDstStep);
+
+ if (!csrc || !cdst)
+ goto fail;
+
+ cairo_context = cairo_create(cdst);
+
+ if (!cairo_context)
+ goto fail2;
+
+ cairo_scale(cairo_context, sx, sy);
+ cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface(cairo_context, csrc, 0, 0);
+ cairo_paint(cairo_context);
+ rc = TRUE;
+ fail2:
+ cairo_destroy(cairo_context);
+ fail:
+ cairo_surface_destroy(csrc);
+ cairo_surface_destroy(cdst);
+ }
+#else
+ {
+ WLog_WARN(TAG, "SmartScaling requested but compiled without libcairo support!");
+ }
+#endif
+ return rc;
+}
+
+DWORD FreeRDPAreColorFormatsEqualNoAlpha(DWORD first, DWORD second)
+{
+ const DWORD mask = (DWORD) ~(8UL << 12UL);
+ return (first & mask) == (second & mask);
+}
+
+const char* FreeRDPGetColorFormatName(UINT32 format)
+{
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ return "PIXEL_FORMAT_ARGB32";
+
+ case PIXEL_FORMAT_XRGB32:
+ return "PIXEL_FORMAT_XRGB32";
+
+ case PIXEL_FORMAT_ABGR32:
+ return "PIXEL_FORMAT_ABGR32";
+
+ case PIXEL_FORMAT_XBGR32:
+ return "PIXEL_FORMAT_XBGR32";
+
+ case PIXEL_FORMAT_BGRA32:
+ return "PIXEL_FORMAT_BGRA32";
+
+ case PIXEL_FORMAT_BGRX32:
+ return "PIXEL_FORMAT_BGRX32";
+
+ case PIXEL_FORMAT_RGBA32:
+ return "PIXEL_FORMAT_RGBA32";
+
+ case PIXEL_FORMAT_RGBX32:
+ return "PIXEL_FORMAT_RGBX32";
+
+ case PIXEL_FORMAT_BGRX32_DEPTH30:
+ return "PIXEL_FORMAT_BGRX32_DEPTH30";
+
+ case PIXEL_FORMAT_RGBX32_DEPTH30:
+ return "PIXEL_FORMAT_RGBX32_DEPTH30";
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ return "PIXEL_FORMAT_RGB24";
+
+ case PIXEL_FORMAT_BGR24:
+ return "PIXEL_FORMAT_BGR24";
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ return "PIXEL_FORMAT_RGB16";
+
+ case PIXEL_FORMAT_BGR16:
+ return "PIXEL_FORMAT_BGR16";
+
+ case PIXEL_FORMAT_ARGB15:
+ return "PIXEL_FORMAT_ARGB15";
+
+ case PIXEL_FORMAT_RGB15:
+ return "PIXEL_FORMAT_RGB15";
+
+ case PIXEL_FORMAT_ABGR15:
+ return "PIXEL_FORMAT_ABGR15";
+
+ case PIXEL_FORMAT_BGR15:
+ return "PIXEL_FORMAT_BGR15";
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+ return "PIXEL_FORMAT_RGB8";
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+ return "PIXEL_FORMAT_A4";
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ return "PIXEL_FORMAT_MONO";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void FreeRDPSplitColor(UINT32 color, UINT32 format, BYTE* _r, BYTE* _g, BYTE* _b, BYTE* _a,
+ const gdiPalette* palette)
+{
+ UINT32 tmp = 0;
+
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ if (_a)
+ *_a = (BYTE)(color >> 24);
+
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_XRGB32:
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_ABGR32:
+ if (_a)
+ *_a = (BYTE)(color >> 24);
+
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_XBGR32:
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ if (_r)
+ *_r = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_b)
+ *_b = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ if (_r)
+ *_r = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_b)
+ *_b = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ if (_b)
+ *_b = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_r)
+ *_r = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = (BYTE)color;
+
+ break;
+
+ case PIXEL_FORMAT_BGRX32:
+ if (_b)
+ *_b = (BYTE)(color >> 24);
+
+ if (_g)
+ *_g = (BYTE)(color >> 16);
+
+ if (_r)
+ *_r = (BYTE)(color >> 8);
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ if (_r)
+ *_r = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_b)
+ *_b = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ if (_b)
+ *_b = (BYTE)(color >> 16);
+
+ if (_g)
+ *_g = (BYTE)(color >> 8);
+
+ if (_r)
+ *_r = (BYTE)color;
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ if (_r)
+ {
+ const UINT32 c = (color >> 11) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x3F;
+ const UINT32 val = (c << 2) + c / 4 / 2;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x3F;
+ const UINT32 val = (c << 2) + c / 4 / 2;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 11) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_ARGB15:
+ if (_r)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = color & 0x8000 ? 0xFF : 0x00;
+
+ break;
+
+ case PIXEL_FORMAT_ABGR15:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = color & 0x8000 ? 0xFF : 0x00;
+
+ break;
+
+ /* 15bpp formats */
+ case PIXEL_FORMAT_RGB15:
+ if (_r)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ case PIXEL_FORMAT_BGR15:
+ if (_r)
+ {
+ const UINT32 c = (color)&0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_r = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_g)
+ {
+ const UINT32 c = (color >> 5) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_g = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_b)
+ {
+ const UINT32 c = (color >> 10) & 0x1F;
+ const UINT32 val = (c << 3) + c / 4;
+ *_b = (BYTE)(val > 255 ? 255 : val);
+ }
+
+ if (_a)
+ *_a = 0xFF;
+
+ break;
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+ if (color <= 0xFF)
+ {
+ tmp = palette->palette[color];
+ FreeRDPSplitColor(tmp, palette->format, _r, _g, _b, _a, NULL);
+ }
+ else
+ {
+ if (_r)
+ *_r = 0x00;
+
+ if (_g)
+ *_g = 0x00;
+
+ if (_b)
+ *_b = 0x00;
+
+ if (_a)
+ *_a = 0x00;
+ }
+
+ break;
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ if (_r)
+ *_r = (color) ? 0xFF : 0x00;
+
+ if (_g)
+ *_g = (color) ? 0xFF : 0x00;
+
+ if (_b)
+ *_b = (color) ? 0xFF : 0x00;
+
+ if (_a)
+ *_a = (color) ? 0xFF : 0x00;
+
+ break;
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+ default:
+ if (_r)
+ *_r = 0x00;
+
+ if (_g)
+ *_g = 0x00;
+
+ if (_b)
+ *_b = 0x00;
+
+ if (_a)
+ *_a = 0x00;
+
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ break;
+ }
+}
+
+BOOL FreeRDPWriteColorIgnoreAlpha(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color)
+{
+ switch (format)
+ {
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_ARGB32:
+ {
+ const UINT32 tmp = ((UINT32)dst[0] << 24ULL) | (color & 0x00FFFFFFULL);
+ return FreeRDPWriteColor(dst, format, tmp);
+ }
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_RGBA32:
+ {
+ const UINT32 tmp = ((UINT32)dst[3]) | (color & 0xFFFFFF00ULL);
+ return FreeRDPWriteColor(dst, format, tmp);
+ }
+ default:
+ return FreeRDPWriteColor(dst, format, color);
+ }
+}
+
+BOOL FreeRDPWriteColor(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color)
+{
+ switch (FreeRDPGetBitsPerPixel(format))
+ {
+ case 32:
+ dst[0] = (BYTE)(color >> 24);
+ dst[1] = (BYTE)(color >> 16);
+ dst[2] = (BYTE)(color >> 8);
+ dst[3] = (BYTE)color;
+ break;
+
+ case 24:
+ dst[0] = (BYTE)(color >> 16);
+ dst[1] = (BYTE)(color >> 8);
+ dst[2] = (BYTE)color;
+ break;
+
+ case 16:
+ dst[1] = (BYTE)(color >> 8);
+ dst[0] = (BYTE)color;
+ break;
+
+ case 15:
+ if (!FreeRDPColorHasAlpha(format))
+ color = color & 0x7FFF;
+
+ dst[1] = (BYTE)(color >> 8);
+ dst[0] = (BYTE)color;
+ break;
+
+ case 8:
+ dst[0] = (BYTE)color;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+UINT32 FreeRDPReadColor(const BYTE* WINPR_RESTRICT src, UINT32 format)
+{
+ UINT32 color = 0;
+
+ switch (FreeRDPGetBitsPerPixel(format))
+ {
+ case 32:
+ color =
+ ((UINT32)src[0] << 24) | ((UINT32)src[1] << 16) | ((UINT32)src[2] << 8) | src[3];
+ break;
+
+ case 24:
+ color = ((UINT32)src[0] << 16) | ((UINT32)src[1] << 8) | src[2];
+ break;
+
+ case 16:
+ color = ((UINT32)src[1] << 8) | src[0];
+ break;
+
+ case 15:
+ color = ((UINT32)src[1] << 8) | src[0];
+
+ if (!FreeRDPColorHasAlpha(format))
+ color = color & 0x7FFF;
+
+ break;
+
+ case 8:
+ case 4:
+ case 1:
+ color = *src;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ color = 0;
+ break;
+ }
+
+ return color;
+}
+
+UINT32 FreeRDPGetColor(UINT32 format, BYTE r, BYTE g, BYTE b, BYTE a)
+{
+ UINT32 _r = r;
+ UINT32 _g = g;
+ UINT32 _b = b;
+ UINT32 _a = a;
+ UINT32 t = 0;
+
+ switch (format)
+ {
+ /* 32bpp formats */
+ case PIXEL_FORMAT_ARGB32:
+ return (_a << 24) | (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_XRGB32:
+ return (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_ABGR32:
+ return (_a << 24) | (_b << 16) | (_g << 8) | _r;
+
+ case PIXEL_FORMAT_XBGR32:
+ return (_b << 16) | (_g << 8) | _r;
+
+ case PIXEL_FORMAT_RGBA32:
+ return (_r << 24) | (_g << 16) | (_b << 8) | _a;
+
+ case PIXEL_FORMAT_RGBX32:
+ return (_r << 24) | (_g << 16) | (_b << 8) | _a;
+
+ case PIXEL_FORMAT_BGRA32:
+ return (_b << 24) | (_g << 16) | (_r << 8) | _a;
+
+ case PIXEL_FORMAT_BGRX32:
+ return (_b << 24) | (_g << 16) | (_r << 8) | _a;
+
+ case PIXEL_FORMAT_RGBX32_DEPTH30:
+ // TODO: Not tested
+ t = (_r << 22) | (_g << 12) | (_b << 2);
+ // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian
+ return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) |
+ (t >> 24);
+
+ case PIXEL_FORMAT_BGRX32_DEPTH30:
+ // NOTE: Swapping b and r channel (unknown reason)
+ t = (_r << 22) | (_g << 12) | (_b << 2);
+ // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian
+ return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) |
+ (t >> 24);
+
+ /* 24bpp formats */
+ case PIXEL_FORMAT_RGB24:
+ return (_r << 16) | (_g << 8) | _b;
+
+ case PIXEL_FORMAT_BGR24:
+ return (_b << 16) | (_g << 8) | _r;
+
+ /* 16bpp formats */
+ case PIXEL_FORMAT_RGB16:
+ return (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_b >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_BGR16:
+ return (((_b >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_r >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_ARGB15:
+ return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F) |
+ (_a ? 0x8000 : 0x0000);
+
+ case PIXEL_FORMAT_ABGR15:
+ return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F) |
+ (_a ? 0x8000 : 0x0000);
+
+ /* 15bpp formats */
+ case PIXEL_FORMAT_RGB15:
+ return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F);
+
+ case PIXEL_FORMAT_BGR15:
+ return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F);
+
+ /* 8bpp formats */
+ case PIXEL_FORMAT_RGB8:
+
+ /* 4 bpp formats */
+ case PIXEL_FORMAT_A4:
+
+ /* 1bpp formats */
+ case PIXEL_FORMAT_MONO:
+ default:
+ WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format));
+ return 0;
+ }
+}
diff --git a/libfreerdp/codec/dsp.c b/libfreerdp/codec/dsp.c
new file mode 100644
index 0000000..68800e4
--- /dev/null
+++ b/libfreerdp/codec/dsp.c
@@ -0,0 +1,1507 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/types.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/dsp.h>
+
+#if !defined(WITH_DSP_FFMPEG)
+#if defined(WITH_GSM)
+#include <gsm/gsm.h>
+#endif
+
+#if defined(WITH_LAME)
+#include <lame/lame.h>
+#endif
+
+#if defined(WITH_OPUS)
+#include <opus/opus.h>
+
+#define OPUS_MAX_FRAMES 5760
+#endif
+
+#if defined(WITH_FAAD2)
+#include <neaacdec.h>
+#endif
+
+#if defined(WITH_FAAC)
+#include <faac.h>
+#endif
+
+#if defined(WITH_SOXR)
+#include <soxr.h>
+#endif
+
+#else
+#include "dsp_ffmpeg.h"
+#endif
+
+#define TAG FREERDP_TAG("dsp")
+
+#if !defined(WITH_DSP_FFMPEG)
+
+typedef union
+{
+ struct
+ {
+ size_t packet_size;
+ INT16 last_sample[2];
+ INT16 last_step[2];
+ } ima;
+ struct
+ {
+ BYTE predictor[2];
+ INT32 delta[2];
+ INT32 sample1[2];
+ INT32 sample2[2];
+ } ms;
+} ADPCM;
+
+struct S_FREERDP_DSP_CONTEXT
+{
+ BOOL encoder;
+
+ ADPCM adpcm;
+ AUDIO_FORMAT format;
+
+ wStream* channelmix;
+ wStream* resample;
+ wStream* buffer;
+
+#if defined(WITH_GSM)
+ gsm gsm;
+#endif
+#if defined(WITH_LAME)
+ lame_t lame;
+ hip_t hip;
+#endif
+#if defined(WITH_OPUS)
+ OpusDecoder* opus_decoder;
+ OpusEncoder* opus_encoder;
+#endif
+#if defined(WITH_FAAD2)
+ NeAACDecHandle faad;
+ BOOL faadSetup;
+#endif
+
+#if defined(WITH_FAAC)
+ faacEncHandle faac;
+ unsigned long faacInputSamples;
+ unsigned long faacMaxOutputBytes;
+#endif
+
+#if defined(WITH_SOXR)
+ soxr_t sox;
+#endif
+};
+
+#if defined(WITH_OPUS)
+static BOOL opus_is_valid_samplerate(const AUDIO_FORMAT* format)
+{
+ WINPR_ASSERT(format);
+
+ switch (format->nSamplesPerSec)
+ {
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 48000:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+#endif
+
+static INT16 read_int16(const BYTE* src)
+{
+ return (INT16)(src[0] | (src[1] << 8));
+}
+
+static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data,
+ size_t* length)
+{
+ UINT32 bpp;
+ size_t samples;
+
+ if (!context || !data || !length)
+ return FALSE;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1;
+ samples = size / bpp / srcFormat->nChannels;
+
+ if (context->format.nChannels == srcFormat->nChannels)
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+ Stream_SetPosition(context->channelmix, 0);
+
+ /* Destination has more channels than source */
+ if (context->format.nChannels > srcFormat->nChannels)
+ {
+ switch (srcFormat->nChannels)
+ {
+ case 1:
+ if (!Stream_EnsureCapacity(context->channelmix, size * 2))
+ return FALSE;
+
+ for (size_t x = 0; x < samples; x++)
+ {
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ return TRUE;
+
+ case 2: /* We only support stereo, so we can not handle this case. */
+ default: /* Unsupported number of channels */
+ return FALSE;
+ }
+ }
+
+ /* Destination has less channels than source */
+ switch (srcFormat->nChannels)
+ {
+ case 2:
+ if (!Stream_EnsureCapacity(context->channelmix, size / 2))
+ return FALSE;
+
+ /* Simply drop second channel.
+ * TODO: Calculate average */
+ for (size_t x = 0; x < samples; x++)
+ {
+ for (size_t y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ return TRUE;
+
+ case 1: /* Invalid, do we want to use a 0 channel sound? */
+ default: /* Unsupported number of channels */
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Microsoft Multimedia Standards Update
+ * http://download.microsoft.com/download/9/8/6/9863C72A-A3AA-4DDB-B1BA-CA8D17EFD2D4/RIFFNEW.pdf
+ */
+
+static BOOL freerdp_dsp_resample(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data, size_t* length)
+{
+#if defined(WITH_SOXR)
+ soxr_error_t error;
+ size_t idone, odone;
+ size_t sframes, rframes;
+ size_t rsize;
+ size_t sbytes, rbytes;
+ size_t dstChannels;
+ size_t srcChannels;
+ size_t srcBytesPerFrame, dstBytesPerFrame;
+#endif
+ AUDIO_FORMAT format;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ {
+ WLog_ERR(TAG, "requires %s for sample input, got %s",
+ audio_format_get_tag_string(WAVE_FORMAT_PCM),
+ audio_format_get_tag_string(srcFormat->wFormatTag));
+ return FALSE;
+ }
+
+ /* We want to ignore differences of source and destination format. */
+ format = *srcFormat;
+ format.wFormatTag = WAVE_FORMAT_UNKNOWN;
+ format.wBitsPerSample = 0;
+
+ if (audio_format_compatible(&format, &context->format))
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+#if defined(WITH_SOXR)
+ srcBytesPerFrame = (srcFormat->wBitsPerSample > 8) ? 2 : 1;
+ dstBytesPerFrame = (context->format.wBitsPerSample > 8) ? 2 : 1;
+ srcChannels = srcFormat->nChannels;
+ dstChannels = context->format.nChannels;
+ sbytes = srcChannels * srcBytesPerFrame;
+ sframes = size / sbytes;
+ rbytes = dstBytesPerFrame * dstChannels;
+ /* Integer rounding correct division */
+ rframes = (sframes * context->format.nSamplesPerSec + (srcFormat->nSamplesPerSec + 1) / 2) /
+ srcFormat->nSamplesPerSec;
+ rsize = rframes * rbytes;
+
+ if (!Stream_EnsureCapacity(context->resample, rsize))
+ return FALSE;
+
+ error = soxr_process(context->sox, src, sframes, &idone, Stream_Buffer(context->resample),
+ Stream_Capacity(context->resample) / rbytes, &odone);
+ Stream_SetLength(context->resample, odone * rbytes);
+ *data = Stream_Buffer(context->resample);
+ *length = Stream_Length(context->resample);
+ return (error == 0) ? TRUE : FALSE;
+#else
+ WLog_ERR(TAG, "Missing resample support, recompile -DWITH_SOXR=ON or -DWITH_DSP_FFMPEG=ON");
+ return FALSE;
+#endif
+}
+
+/**
+ * Microsoft IMA ADPCM specification:
+ *
+ * http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM
+ * http://wiki.multimedia.cx/index.php?title=IMA_ADPCM
+ */
+
+static const INT16 ima_step_index_table[] = {
+ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8
+};
+
+static const INT16 ima_step_size_table[] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23,
+ 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80,
+ 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279,
+ 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963,
+ 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
+ 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487,
+ 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+static UINT16 dsp_decode_ima_adpcm_sample(ADPCM* adpcm, unsigned int channel, BYTE sample)
+{
+ INT32 ss;
+ INT32 d;
+ ss = ima_step_size_table[adpcm->ima.last_step[channel]];
+ d = (ss >> 3);
+
+ if (sample & 1)
+ d += (ss >> 2);
+
+ if (sample & 2)
+ d += (ss >> 1);
+
+ if (sample & 4)
+ d += ss;
+
+ if (sample & 8)
+ d = -d;
+
+ d += adpcm->ima.last_sample[channel];
+
+ if (d < -32768)
+ d = -32768;
+ else if (d > 32767)
+ d = 32767;
+
+ adpcm->ima.last_sample[channel] = (INT16)d;
+ adpcm->ima.last_step[channel] += ima_step_index_table[sample];
+
+ if (adpcm->ima.last_step[channel] < 0)
+ adpcm->ima.last_step[channel] = 0;
+ else if (adpcm->ima.last_step[channel] > 88)
+ adpcm->ima.last_step[channel] = 88;
+
+ return (UINT16)d;
+}
+
+static BOOL freerdp_dsp_decode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ BYTE sample;
+ UINT16 decoded;
+ size_t out_size = size * 4;
+ UINT32 channel;
+ const UINT32 block_size = context->format.nBlockAlign;
+ const UINT32 channels = context->format.nChannels;
+
+ if (!Stream_EnsureCapacity(out, out_size))
+ return FALSE;
+
+ while (size > 0)
+ {
+ if (size % block_size == 0)
+ {
+ context->adpcm.ima.last_sample[0] =
+ (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ context->adpcm.ima.last_step[0] = (INT16)(*(src + 2));
+ src += 4;
+ size -= 4;
+ out_size -= 16;
+
+ if (channels > 1)
+ {
+ context->adpcm.ima.last_sample[1] =
+ (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ context->adpcm.ima.last_step[1] = (INT16)(*(src + 2));
+ src += 4;
+ size -= 4;
+ out_size -= 16;
+ }
+ }
+
+ if (channels > 1)
+ {
+ for (size_t i = 0; i < 8; i++)
+ {
+ BYTE* dst = Stream_Pointer(out);
+
+ channel = (i < 4 ? 0 : 1);
+ sample = ((*src) & 0x0f);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample);
+ dst[((i & 3) << 3) + (channel << 1)] = (decoded & 0xFF);
+ dst[((i & 3) << 3) + (channel << 1) + 1] = (decoded >> 8);
+ sample = ((*src) >> 4);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample);
+ dst[((i & 3) << 3) + (channel << 1) + 4] = (decoded & 0xFF);
+ dst[((i & 3) << 3) + (channel << 1) + 5] = (decoded >> 8);
+ src++;
+ }
+
+ if (!Stream_SafeSeek(out, 32))
+ return FALSE;
+ size -= 8;
+ }
+ else
+ {
+ BYTE* dst = Stream_Pointer(out);
+ if (!Stream_SafeSeek(out, 4))
+ return FALSE;
+
+ sample = ((*src) & 0x0f);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ *dst++ = (decoded & 0xFF);
+ *dst++ = (decoded >> 8);
+ sample = ((*src) >> 4);
+ decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ *dst++ = (decoded & 0xFF);
+ *dst++ = (decoded >> 8);
+ src++;
+ size--;
+ }
+ }
+
+ return TRUE;
+}
+
+#if defined(WITH_GSM)
+static BOOL freerdp_dsp_decode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t offset = 0;
+
+ while (offset < size)
+ {
+ int rc;
+ gsm_signal gsmBlockBuffer[160] = { 0 };
+ rc = gsm_decode(context->gsm, (gsm_byte*)/* API does not modify */ &src[offset],
+ gsmBlockBuffer);
+
+ if (rc < 0)
+ return FALSE;
+
+ if ((offset % 65) == 0)
+ offset += 33;
+ else
+ offset += 32;
+
+ if (!Stream_EnsureRemainingCapacity(out, sizeof(gsmBlockBuffer)))
+ return FALSE;
+
+ Stream_Write(out, (void*)gsmBlockBuffer, sizeof(gsmBlockBuffer));
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t offset = 0;
+
+ while (offset < size)
+ {
+ const gsm_signal* signal = (const gsm_signal*)&src[offset];
+
+ if (!Stream_EnsureRemainingCapacity(out, sizeof(gsm_frame)))
+ return FALSE;
+
+ gsm_encode(context->gsm, (gsm_signal*)/* API does not modify */ signal,
+ Stream_Pointer(out));
+
+ if ((offset % 65) == 0)
+ Stream_Seek(out, 33);
+ else
+ Stream_Seek(out, 32);
+
+ offset += 160;
+ }
+
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_LAME)
+static BOOL freerdp_dsp_decode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ int rc;
+ short* pcm_l;
+ short* pcm_r;
+ size_t buffer_size;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ buffer_size = 2 * context->format.nChannels * context->format.nSamplesPerSec;
+
+ if (!Stream_EnsureCapacity(context->buffer, 2 * buffer_size))
+ return FALSE;
+
+ pcm_l = (short*)Stream_Buffer(context->buffer);
+ pcm_r = (short*)Stream_Buffer(context->buffer) + buffer_size;
+ rc = hip_decode(context->hip, (unsigned char*)/* API is not modifying content */ src, size,
+ pcm_l, pcm_r);
+
+ if (rc <= 0)
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(out, (size_t)rc * context->format.nChannels * 2))
+ return FALSE;
+
+ for (size_t x = 0; x < rc; x++)
+ {
+ Stream_Write_UINT16(out, (UINT16)pcm_l[x]);
+ Stream_Write_UINT16(out, (UINT16)pcm_r[x]);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t samples_per_channel;
+ int rc;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ samples_per_channel = size / context->format.nChannels / context->format.wBitsPerSample / 8;
+
+ /* Ensure worst case buffer size for mp3 stream taken from LAME header */
+ if (!Stream_EnsureRemainingCapacity(out, 5 / 4 * samples_per_channel + 7200))
+ return FALSE;
+
+ samples_per_channel = size / 2 /* size of a sample */ / context->format.nChannels;
+ rc = lame_encode_buffer_interleaved(context->lame, (short*)src, samples_per_channel,
+ Stream_Pointer(out), Stream_GetRemainingCapacity(out));
+
+ if (rc < 0)
+ return FALSE;
+
+ Stream_Seek(out, (size_t)rc);
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_FAAC)
+static BOOL freerdp_dsp_encode_faac(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ const int16_t* inSamples = (const int16_t*)src;
+ unsigned int bpp;
+ size_t nrSamples;
+ int rc;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ bpp = context->format.wBitsPerSample / 8;
+ nrSamples = size / bpp;
+
+ if (!Stream_EnsureRemainingCapacity(context->buffer, nrSamples * sizeof(int16_t)))
+ return FALSE;
+
+ for (size_t x = 0; x < nrSamples; x++)
+ {
+ Stream_Write_INT16(context->buffer, inSamples[x]);
+ if (Stream_GetPosition(context->buffer) / bpp >= context->faacInputSamples)
+ {
+ if (!Stream_EnsureRemainingCapacity(out, context->faacMaxOutputBytes))
+ return FALSE;
+ rc = faacEncEncode(context->faac, (int32_t*)Stream_Buffer(context->buffer),
+ context->faacInputSamples, Stream_Pointer(out),
+ Stream_GetRemainingCapacity(out));
+ if (rc < 0)
+ return FALSE;
+ if (rc > 0)
+ Stream_Seek(out, (size_t)rc);
+ Stream_SetPosition(context->buffer, 0);
+ }
+ }
+
+ return TRUE;
+}
+#endif
+
+#if defined(WITH_OPUS)
+static BOOL freerdp_dsp_decode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t max_size = 5760;
+ int frames;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ /* Max packet duration is 120ms (5760 at 48KHz) */
+ max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t);
+ if (!Stream_EnsureRemainingCapacity(context->buffer, max_size))
+ return FALSE;
+
+ frames = opus_decode(context->opus_decoder, src, size, Stream_Pointer(out), OPUS_MAX_FRAMES, 0);
+ if (frames < 0)
+ return FALSE;
+
+ Stream_Seek(out, frames * context->format.nChannels * sizeof(int16_t));
+
+ return TRUE;
+}
+
+static BOOL freerdp_dsp_encode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ if (!context || !src || !out)
+ return FALSE;
+
+ /* Max packet duration is 120ms (5760 at 48KHz) */
+ const size_t max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t);
+ if (!Stream_EnsureRemainingCapacity(context->buffer, max_size))
+ return FALSE;
+
+ const int src_frames = size / sizeof(opus_int16) / context->format.nChannels;
+ const opus_int16* src_data = (const opus_int16*)src;
+ const int frames =
+ opus_encode(context->opus_encoder, src_data, src_frames, Stream_Pointer(out), max_size);
+ if (frames < 0)
+ return FALSE;
+ return Stream_SafeSeek(out, frames * context->format.nChannels * sizeof(int16_t));
+}
+#endif
+
+#if defined(WITH_FAAD2)
+static BOOL freerdp_dsp_decode_faad(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ NeAACDecFrameInfo info;
+ size_t offset = 0;
+
+ if (!context || !src || !out)
+ return FALSE;
+
+ if (!context->faadSetup)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ unsigned long samplerate;
+ unsigned char channels;
+ long err;
+ cnv.cpv = src;
+ err = NeAACDecInit(context->faad, /* API is not modifying content */ cnv.pv, size,
+ &samplerate, &channels);
+
+ if (err != 0)
+ return FALSE;
+
+ if (channels != context->format.nChannels)
+ return FALSE;
+
+ if (samplerate != context->format.nSamplesPerSec)
+ return FALSE;
+
+ context->faadSetup = TRUE;
+ }
+
+ while (offset < size)
+ {
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ size_t outSize;
+ void* sample_buffer;
+ outSize = context->format.nSamplesPerSec * context->format.nChannels *
+ context->format.wBitsPerSample / 8;
+
+ if (!Stream_EnsureRemainingCapacity(out, outSize))
+ return FALSE;
+
+ sample_buffer = Stream_Pointer(out);
+
+ cnv.cpv = &src[offset];
+ NeAACDecDecode2(context->faad, &info, cnv.pv, size - offset, &sample_buffer,
+ Stream_GetRemainingCapacity(out));
+
+ if (info.error != 0)
+ return FALSE;
+
+ offset += info.bytesconsumed;
+
+ if (info.samples == 0)
+ continue;
+
+ Stream_Seek(out, info.samples * context->format.wBitsPerSample / 8);
+ }
+
+ return TRUE;
+}
+
+#endif
+
+/**
+ * 0 1 2 3
+ * 2 0 6 4 10 8 14 12 <left>
+ *
+ * 4 5 6 7
+ * 3 1 7 5 11 9 15 13 <right>
+ */
+static const struct
+{
+ BYTE byte_num;
+ BYTE byte_shift;
+} ima_stereo_encode_map[] = { { 0, 0 }, { 4, 0 }, { 0, 4 }, { 4, 4 }, { 1, 0 }, { 5, 0 },
+ { 1, 4 }, { 5, 4 }, { 2, 0 }, { 6, 0 }, { 2, 4 }, { 6, 4 },
+ { 3, 0 }, { 7, 0 }, { 3, 4 }, { 7, 4 } };
+
+static BYTE dsp_encode_ima_adpcm_sample(ADPCM* adpcm, int channel, INT16 sample)
+{
+ INT32 e;
+ INT32 d;
+ INT32 ss;
+ BYTE enc;
+ INT32 diff;
+ ss = ima_step_size_table[adpcm->ima.last_step[channel]];
+ d = e = sample - adpcm->ima.last_sample[channel];
+ diff = ss >> 3;
+ enc = 0;
+
+ if (e < 0)
+ {
+ enc = 8;
+ e = -e;
+ }
+
+ if (e >= ss)
+ {
+ enc |= 4;
+ e -= ss;
+ }
+
+ ss >>= 1;
+
+ if (e >= ss)
+ {
+ enc |= 2;
+ e -= ss;
+ }
+
+ ss >>= 1;
+
+ if (e >= ss)
+ {
+ enc |= 1;
+ e -= ss;
+ }
+
+ if (d < 0)
+ diff = d + e - diff;
+ else
+ diff = d - e + diff;
+
+ diff += adpcm->ima.last_sample[channel];
+
+ if (diff < -32768)
+ diff = -32768;
+ else if (diff > 32767)
+ diff = 32767;
+
+ adpcm->ima.last_sample[channel] = (INT16)diff;
+ adpcm->ima.last_step[channel] += ima_step_index_table[enc];
+
+ if (adpcm->ima.last_step[channel] < 0)
+ adpcm->ima.last_step[channel] = 0;
+ else if (adpcm->ima.last_step[channel] > 88)
+ adpcm->ima.last_step[channel] = 88;
+
+ return enc;
+}
+
+static BOOL freerdp_dsp_encode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ INT16 sample;
+ BYTE encoded;
+ size_t align;
+
+ if (!Stream_EnsureRemainingCapacity(out, size))
+ return FALSE;
+
+ align = (context->format.nChannels > 1) ? 32 : 4;
+
+ while (size >= align)
+ {
+ if (Stream_GetPosition(context->buffer) % context->format.nBlockAlign == 0)
+ {
+ Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[0] & 0xFF);
+ Stream_Write_UINT8(context->buffer, (context->adpcm.ima.last_sample[0] >> 8) & 0xFF);
+ Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[0]);
+ Stream_Write_UINT8(context->buffer, 0);
+
+ if (context->format.nChannels > 1)
+ {
+ Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[1] & 0xFF);
+ Stream_Write_UINT8(context->buffer,
+ (context->adpcm.ima.last_sample[1] >> 8) & 0xFF);
+ Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[1]);
+ Stream_Write_UINT8(context->buffer, 0);
+ }
+ }
+
+ if (context->format.nChannels > 1)
+ {
+ BYTE* dst = Stream_Pointer(context->buffer);
+ ZeroMemory(dst, 8);
+
+ for (size_t i = 0; i < 16; i++)
+ {
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, i % 2, sample);
+ dst[ima_stereo_encode_map[i].byte_num] |= encoded
+ << ima_stereo_encode_map[i].byte_shift;
+ }
+
+ if (!Stream_SafeSeek(context->buffer, 8))
+ return FALSE;
+ size -= 32;
+ }
+ else
+ {
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample);
+ sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8));
+ src += 2;
+ encoded |= dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample) << 4;
+ Stream_Write_UINT8(context->buffer, encoded);
+ size -= 4;
+ }
+
+ if (Stream_GetPosition(context->buffer) >= context->adpcm.ima.packet_size)
+ {
+ BYTE* bsrc = Stream_Buffer(context->buffer);
+ Stream_Write(out, bsrc, context->adpcm.ima.packet_size);
+ Stream_SetPosition(context->buffer, 0);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Microsoft ADPCM Specification:
+ *
+ * http://wiki.multimedia.cx/index.php?title=Microsoft_ADPCM
+ */
+
+static const INT32 ms_adpcm_adaptation_table[] = { 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230 };
+
+static const INT32 ms_adpcm_coeffs1[7] = { 256, 512, 0, 192, 240, 460, 392 };
+
+static const INT32 ms_adpcm_coeffs2[7] = { 0, -256, 0, 64, 0, -208, -232 };
+
+static INLINE INT16 freerdp_dsp_decode_ms_adpcm_sample(ADPCM* adpcm, BYTE sample, int channel)
+{
+ INT8 nibble;
+ INT32 presample;
+ nibble = (sample & 0x08 ? (INT8)sample - 16 : (INT8)sample);
+ presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) +
+ (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) /
+ 256;
+ presample += nibble * adpcm->ms.delta[channel];
+
+ if (presample > 32767)
+ presample = 32767;
+ else if (presample < -32768)
+ presample = -32768;
+
+ adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel];
+ adpcm->ms.sample1[channel] = presample;
+ adpcm->ms.delta[channel] = adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[sample] / 256;
+
+ if (adpcm->ms.delta[channel] < 16)
+ adpcm->ms.delta[channel] = 16;
+
+ return (INT16)presample;
+}
+
+static BOOL freerdp_dsp_decode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ BYTE sample;
+ const size_t out_size = size * 4;
+ const UINT32 channels = context->format.nChannels;
+ const UINT32 block_size = context->format.nBlockAlign;
+
+ if (!Stream_EnsureCapacity(out, out_size))
+ return FALSE;
+
+ while (size > 0)
+ {
+ if (size % block_size == 0)
+ {
+ if (channels > 1)
+ {
+ context->adpcm.ms.predictor[0] = *src++;
+ context->adpcm.ms.predictor[1] = *src++;
+ context->adpcm.ms.delta[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.delta[1] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[1] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[1] = read_int16(src);
+ src += 2;
+ size -= 14;
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]);
+ }
+ else
+ {
+ context->adpcm.ms.predictor[0] = *src++;
+ context->adpcm.ms.delta[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample1[0] = read_int16(src);
+ src += 2;
+ context->adpcm.ms.sample2[0] = read_int16(src);
+ src += 2;
+ size -= 7;
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ }
+ }
+
+ if (channels > 1)
+ {
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1));
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1));
+ }
+ else
+ {
+ sample = *src++;
+ size--;
+ Stream_Write_INT16(out,
+ freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0));
+ Stream_Write_INT16(
+ out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 0));
+ }
+ }
+
+ return TRUE;
+}
+
+static BYTE freerdp_dsp_encode_ms_adpcm_sample(ADPCM* adpcm, INT32 sample, int channel)
+{
+ INT32 presample;
+ INT32 errordelta;
+ presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) +
+ (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) /
+ 256;
+ errordelta = (sample - presample) / adpcm->ms.delta[channel];
+
+ if ((sample - presample) % adpcm->ms.delta[channel] > adpcm->ms.delta[channel] / 2)
+ errordelta++;
+
+ if (errordelta > 7)
+ errordelta = 7;
+ else if (errordelta < -8)
+ errordelta = -8;
+
+ presample += adpcm->ms.delta[channel] * errordelta;
+
+ if (presample > 32767)
+ presample = 32767;
+ else if (presample < -32768)
+ presample = -32768;
+
+ adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel];
+ adpcm->ms.sample1[channel] = presample;
+ adpcm->ms.delta[channel] =
+ adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[(((BYTE)errordelta) & 0x0F)] / 256;
+
+ if (adpcm->ms.delta[channel] < 16)
+ adpcm->ms.delta[channel] = 16;
+
+ return ((BYTE)errordelta) & 0x0F;
+}
+
+static BOOL freerdp_dsp_encode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ wStream* out)
+{
+ size_t start;
+ INT32 sample;
+ const size_t step = 8 + ((context->format.nChannels > 1) ? 4 : 0);
+
+ if (!Stream_EnsureRemainingCapacity(out, size))
+ return FALSE;
+
+ start = Stream_GetPosition(out);
+
+ if (context->adpcm.ms.delta[0] < 16)
+ context->adpcm.ms.delta[0] = 16;
+
+ if (context->adpcm.ms.delta[1] < 16)
+ context->adpcm.ms.delta[1] = 16;
+
+ while (size >= step)
+ {
+ BYTE val;
+ if ((Stream_GetPosition(out) - start) % context->format.nBlockAlign == 0)
+ {
+ if (context->format.nChannels > 1)
+ {
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]);
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[1]);
+ Stream_Write_UINT8(out, (context->adpcm.ms.delta[0] & 0xFF));
+ Stream_Write_UINT8(out, ((context->adpcm.ms.delta[0] >> 8) & 0xFF));
+ Stream_Write_UINT8(out, (context->adpcm.ms.delta[1] & 0xFF));
+ Stream_Write_UINT8(out, ((context->adpcm.ms.delta[1] >> 8) & 0xFF));
+
+ context->adpcm.ms.sample1[0] = read_int16(src + 4);
+ context->adpcm.ms.sample1[1] = read_int16(src + 6);
+ context->adpcm.ms.sample2[0] = read_int16(src + 0);
+ context->adpcm.ms.sample2[1] = read_int16(src + 2);
+
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]);
+
+ src += 8;
+ size -= 8;
+ }
+ else
+ {
+ Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]);
+ Stream_Write_UINT8(out, (BYTE)(context->adpcm.ms.delta[0] & 0xFF));
+ Stream_Write_UINT8(out, (BYTE)((context->adpcm.ms.delta[0] >> 8) & 0xFF));
+
+ context->adpcm.ms.sample1[0] = read_int16(src + 2);
+ context->adpcm.ms.sample2[0] = read_int16(src + 0);
+
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]);
+ Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]);
+ src += 4;
+ size -= 4;
+ }
+ }
+
+ sample = read_int16(src);
+ src += 2;
+ Stream_Write_UINT8(
+ out, (freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample, 0) << 4) & 0xFF);
+ sample = read_int16(src);
+ src += 2;
+
+ Stream_Read_UINT8(out, val);
+ val += freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample,
+ context->format.nChannels > 1 ? 1 : 0);
+ Stream_Write_UINT8(out, val);
+ size -= 4;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+FREERDP_DSP_CONTEXT* freerdp_dsp_context_new(BOOL encoder)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_context_new(encoder);
+#else
+ FREERDP_DSP_CONTEXT* context = calloc(1, sizeof(FREERDP_DSP_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->channelmix = Stream_New(NULL, 4096);
+
+ if (!context->channelmix)
+ goto fail;
+
+ context->resample = Stream_New(NULL, 4096);
+
+ if (!context->resample)
+ goto fail;
+
+ context->buffer = Stream_New(NULL, 4096);
+
+ if (!context->buffer)
+ goto fail;
+
+ context->encoder = encoder;
+#if defined(WITH_GSM)
+ context->gsm = gsm_create();
+
+ if (!context->gsm)
+ goto fail;
+
+ {
+ int rc;
+ int val = 1;
+ rc = gsm_option(context->gsm, GSM_OPT_WAV49, &val);
+
+ if (rc < 0)
+ goto fail;
+ }
+#endif
+#if defined(WITH_LAME)
+
+ if (encoder)
+ {
+ context->lame = lame_init();
+
+ if (!context->lame)
+ goto fail;
+ }
+ else
+ {
+ context->hip = hip_decode_init();
+
+ if (!context->hip)
+ goto fail;
+ }
+
+#endif
+#if defined(WITH_FAAD2)
+
+ if (!encoder)
+ {
+ context->faad = NeAACDecOpen();
+
+ if (!context->faad)
+ goto fail;
+ }
+
+#endif
+ return context;
+fail:
+ freerdp_dsp_context_free(context);
+ return NULL;
+#endif
+}
+
+void freerdp_dsp_context_free(FREERDP_DSP_CONTEXT* context)
+{
+#if defined(WITH_DSP_FFMPEG)
+ freerdp_dsp_ffmpeg_context_free(context);
+#else
+
+ if (context)
+ {
+ Stream_Free(context->channelmix, TRUE);
+ Stream_Free(context->resample, TRUE);
+ Stream_Free(context->buffer, TRUE);
+#if defined(WITH_GSM)
+ gsm_destroy(context->gsm);
+#endif
+#if defined(WITH_LAME)
+
+ if (context->encoder)
+ lame_close(context->lame);
+ else
+ hip_decode_exit(context->hip);
+
+#endif
+#if defined(WITH_OPUS)
+
+ if (context->opus_decoder)
+ opus_decoder_destroy(context->opus_decoder);
+ if (context->opus_encoder)
+ opus_encoder_destroy(context->opus_encoder);
+
+#endif
+#if defined(WITH_FAAD2)
+
+ if (!context->encoder)
+ NeAACDecClose(context->faad);
+
+#endif
+#if defined(WITH_FAAC)
+
+ if (context->faac)
+ faacEncClose(context->faac);
+
+#endif
+#if defined(WITH_SOXR)
+ soxr_delete(context->sox);
+#endif
+ free(context);
+ }
+
+#endif
+}
+
+BOOL freerdp_dsp_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_encode(context, srcFormat, data, length, out);
+#else
+ const BYTE* resampleData;
+ size_t resampleLength;
+ AUDIO_FORMAT format;
+
+ if (!context || !context->encoder || !srcFormat || !data || !out)
+ return FALSE;
+
+ format = *srcFormat;
+
+ if (!freerdp_dsp_channel_mix(context, data, length, srcFormat, &resampleData, &resampleLength))
+ return FALSE;
+
+ format.nChannels = context->format.nChannels;
+
+ if (!freerdp_dsp_resample(context, resampleData, resampleLength, &format, &data, &length))
+ return FALSE;
+
+ switch (context->format.wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (!Stream_EnsureRemainingCapacity(out, length))
+ return FALSE;
+
+ Stream_Write(out, data, length);
+ return TRUE;
+
+ case WAVE_FORMAT_ADPCM:
+ return freerdp_dsp_encode_ms_adpcm(context, data, length, out);
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return freerdp_dsp_encode_ima_adpcm(context, data, length, out);
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+ return freerdp_dsp_encode_gsm610(context, data, length, out);
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return freerdp_dsp_encode_mp3(context, data, length, out);
+#endif
+#if defined(WITH_FAAC)
+
+ case WAVE_FORMAT_AAC_MS:
+ return freerdp_dsp_encode_faac(context, data, length, out);
+#endif
+#if defined(WITH_OPUS)
+
+ case WAVE_FORMAT_OPUS:
+ return freerdp_dsp_encode_opus(context, data, length, out);
+#endif
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_decode(context, srcFormat, data, length, out);
+#else
+
+ if (!context || context->encoder || !srcFormat || !data || !out)
+ return FALSE;
+
+ switch (context->format.wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ if (!Stream_EnsureRemainingCapacity(out, length))
+ return FALSE;
+
+ Stream_Write(out, data, length);
+ return TRUE;
+
+ case WAVE_FORMAT_ADPCM:
+ return freerdp_dsp_decode_ms_adpcm(context, data, length, out);
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return freerdp_dsp_decode_ima_adpcm(context, data, length, out);
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+ return freerdp_dsp_decode_gsm610(context, data, length, out);
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ return freerdp_dsp_decode_mp3(context, data, length, out);
+#endif
+#if defined(WITH_FAAD2)
+
+ case WAVE_FORMAT_AAC_MS:
+ return freerdp_dsp_decode_faad(context, data, length, out);
+#endif
+
+#if defined(WITH_OPUS)
+ case WAVE_FORMAT_OPUS:
+ return freerdp_dsp_decode_opus(context, data, length, out);
+#endif
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_supports_format(const AUDIO_FORMAT* format, BOOL encode)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_supports_format(format, encode);
+#else
+
+#if !defined(WITH_DSP_EXPERIMENTAL)
+ WINPR_UNUSED(encode);
+#endif
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ return TRUE;
+#if defined(WITH_DSP_EXPERIMENTAL)
+
+ case WAVE_FORMAT_ADPCM:
+ return FALSE;
+ case WAVE_FORMAT_DVI_ADPCM:
+ return TRUE;
+#endif
+#if defined(WITH_GSM)
+
+ case WAVE_FORMAT_GSM610:
+#if defined(WITH_DSP_EXPERIMENTAL)
+ return TRUE;
+#else
+ return !encode;
+#endif
+#endif
+#if defined(WITH_LAME)
+
+ case WAVE_FORMAT_MPEGLAYER3:
+#if defined(WITH_DSP_EXPERIMENTAL)
+ return TRUE;
+#else
+ return !encode;
+#endif
+#endif
+
+ case WAVE_FORMAT_AAC_MS:
+#if defined(WITH_FAAD2)
+ if (!encode)
+ return TRUE;
+
+#endif
+#if defined(WITH_FAAC)
+
+ if (encode)
+ return TRUE;
+
+#endif
+#if defined(WITH_OPUS)
+ WINPR_FALLTHROUGH
+ case WAVE_FORMAT_OPUS:
+ return opus_is_valid_samplerate(format);
+#endif
+ WINPR_FALLTHROUGH
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+#endif
+}
+
+BOOL freerdp_dsp_context_reset(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* targetFormat,
+ UINT32 FramesPerPacket)
+{
+#if defined(WITH_DSP_FFMPEG)
+ return freerdp_dsp_ffmpeg_context_reset(context, targetFormat);
+#else
+
+ if (!context || !targetFormat)
+ return FALSE;
+
+ context->format = *targetFormat;
+
+ if (context->format.wFormatTag == WAVE_FORMAT_DVI_ADPCM)
+ {
+ size_t min_frame_data =
+ 1ull * context->format.wBitsPerSample * context->format.nChannels * FramesPerPacket;
+ size_t data_per_block = (context->format.nBlockAlign - 4 * context->format.nChannels) * 8;
+ size_t nb_block_per_packet = min_frame_data / data_per_block;
+
+ if (min_frame_data % data_per_block)
+ nb_block_per_packet++;
+
+ context->adpcm.ima.packet_size = nb_block_per_packet * context->format.nBlockAlign;
+ Stream_EnsureCapacity(context->buffer, context->adpcm.ima.packet_size);
+ Stream_SetPosition(context->buffer, 0);
+ }
+
+#if defined(WITH_OPUS)
+
+ if (opus_is_valid_samplerate(&context->format))
+ {
+ if (!context->encoder)
+ {
+ int opus_error = OPUS_OK;
+
+ context->opus_decoder = opus_decoder_create(context->format.nSamplesPerSec,
+ context->format.nChannels, &opus_error);
+ if (opus_error != OPUS_OK)
+ return FALSE;
+ }
+ else
+ {
+ int opus_error = OPUS_OK;
+
+ context->opus_encoder =
+ opus_encoder_create(context->format.nSamplesPerSec, context->format.nChannels,
+ OPUS_APPLICATION_VOIP, &opus_error);
+ if (opus_error != OPUS_OK)
+ return FALSE;
+
+ opus_error = opus_encoder_ctl(context->opus_encoder,
+ OPUS_SET_BITRATE(context->format.nAvgBytesPerSec * 8));
+ if (opus_error != OPUS_OK)
+ return FALSE;
+ }
+ }
+
+#endif
+#if defined(WITH_FAAD2)
+ context->faadSetup = FALSE;
+#endif
+#if defined(WITH_FAAC)
+
+ if (context->encoder)
+ {
+ faacEncConfigurationPtr cfg;
+
+ if (context->faac)
+ faacEncClose(context->faac);
+
+ context->faac = faacEncOpen(targetFormat->nSamplesPerSec, targetFormat->nChannels,
+ &context->faacInputSamples, &context->faacMaxOutputBytes);
+
+ if (!context->faac)
+ return FALSE;
+
+ cfg = faacEncGetCurrentConfiguration(context->faac);
+ cfg->inputFormat = FAAC_INPUT_16BIT;
+ cfg->outputFormat = 0;
+ cfg->mpegVersion = MPEG4;
+ cfg->useTns = 1;
+ cfg->bandWidth = targetFormat->nAvgBytesPerSec;
+ faacEncSetConfiguration(context->faac, cfg);
+ }
+
+#endif
+#if defined(WITH_SOXR)
+ {
+ soxr_io_spec_t iospec = soxr_io_spec(SOXR_INT16, SOXR_INT16);
+ soxr_error_t error;
+ soxr_delete(context->sox);
+ context->sox = soxr_create(context->format.nSamplesPerSec, targetFormat->nSamplesPerSec,
+ targetFormat->nChannels, &error, &iospec, NULL, NULL);
+
+ if (!context->sox || (error != 0))
+ return FALSE;
+ }
+#endif
+ return TRUE;
+#endif
+}
diff --git a/libfreerdp/codec/dsp.h b/libfreerdp/codec/dsp.h
new file mode 100644
index 0000000..1325c31
--- /dev/null
+++ b/libfreerdp/codec/dsp.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - backend
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_DSP_H
+#define FREERDP_LIB_CODEC_DSP_H
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+struct S_FREERDP_DSP_COMMON_CONTEXT
+{
+ wStream* buffer;
+ wStream* resample;
+};
+
+#endif /* FREERDP_LIB_CODEC_DSP_H */
diff --git a/libfreerdp/codec/dsp_ffmpeg.c b/libfreerdp/codec/dsp_ffmpeg.c
new file mode 100644
index 0000000..ff12f07
--- /dev/null
+++ b/libfreerdp/codec/dsp_ffmpeg.c
@@ -0,0 +1,846 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - FFMPEG backend
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libavutil/opt.h>
+#if defined(SWRESAMPLE_FOUND)
+#include <libswresample/swresample.h>
+#elif defined(AVRESAMPLE_FOUND)
+#include <libavresample/avresample.h>
+#else
+#error "libswresample or libavresample required"
+#endif
+
+#include "dsp.h"
+#include "dsp_ffmpeg.h"
+
+#define TAG FREERDP_TAG("dsp.ffmpeg")
+
+struct S_FREERDP_DSP_CONTEXT
+{
+ AUDIO_FORMAT format;
+
+ BOOL isOpen;
+ BOOL encoder;
+
+ UINT32 bufferedSamples;
+
+ enum AVCodecID id;
+ AVCodec* codec;
+ AVCodecContext* context;
+ AVFrame* frame;
+ AVFrame* resampled;
+ AVFrame* buffered;
+ AVPacket* packet;
+#if defined(SWRESAMPLE_FOUND)
+ SwrContext* rcontext;
+#else
+ AVAudioResampleContext* rcontext;
+#endif
+ wStream* channelmix;
+};
+
+static BOOL ffmpeg_codec_is_filtered(enum AVCodecID id, BOOL encoder)
+{
+ switch (id)
+ {
+#if !defined(WITH_DSP_EXPERIMENTAL)
+
+ case AV_CODEC_ID_ADPCM_IMA_OKI:
+ case AV_CODEC_ID_MP3:
+ case AV_CODEC_ID_ADPCM_MS:
+ case AV_CODEC_ID_G723_1:
+ return TRUE;
+#endif
+
+ case AV_CODEC_ID_NONE:
+ return TRUE;
+
+ case AV_CODEC_ID_GSM_MS:
+ case AV_CODEC_ID_AAC:
+ case AV_CODEC_ID_AAC_LATM:
+#if !defined(WITH_DSP_EXPERIMENTAL)
+ if (encoder)
+ return TRUE;
+#endif
+ return FALSE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static enum AVCodecID ffmpeg_get_avcodec(const AUDIO_FORMAT* format)
+{
+ if (!format)
+ return AV_CODEC_ID_NONE;
+
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_UNKNOWN:
+ return AV_CODEC_ID_NONE;
+
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 16:
+ return AV_CODEC_ID_PCM_U16LE;
+
+ case 8:
+ return AV_CODEC_ID_PCM_U8;
+
+ default:
+ return AV_CODEC_ID_NONE;
+ }
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ return AV_CODEC_ID_ADPCM_IMA_OKI;
+
+ case WAVE_FORMAT_ADPCM:
+ return AV_CODEC_ID_ADPCM_MS;
+
+ case WAVE_FORMAT_ALAW:
+ return AV_CODEC_ID_PCM_ALAW;
+
+ case WAVE_FORMAT_MULAW:
+ return AV_CODEC_ID_PCM_MULAW;
+
+ case WAVE_FORMAT_GSM610:
+ return AV_CODEC_ID_GSM_MS;
+
+ case WAVE_FORMAT_MSG723:
+ return AV_CODEC_ID_G723_1;
+
+ case WAVE_FORMAT_AAC_MS:
+ return AV_CODEC_ID_AAC;
+
+ case WAVE_FORMAT_OPUS:
+ return AV_CODEC_ID_OPUS;
+
+ default:
+ return AV_CODEC_ID_NONE;
+ }
+}
+
+static int ffmpeg_sample_format(const AUDIO_FORMAT* format)
+{
+ switch (format->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ switch (format->wBitsPerSample)
+ {
+ case 8:
+ return AV_SAMPLE_FMT_U8;
+
+ case 16:
+ return AV_SAMPLE_FMT_S16;
+
+ default:
+ return FALSE;
+ }
+
+ case WAVE_FORMAT_DVI_ADPCM:
+ case WAVE_FORMAT_ADPCM:
+ return AV_SAMPLE_FMT_S16P;
+
+ case WAVE_FORMAT_MPEGLAYER3:
+ case WAVE_FORMAT_AAC_MS:
+ return AV_SAMPLE_FMT_FLTP;
+
+ case WAVE_FORMAT_OPUS:
+ return AV_SAMPLE_FMT_S16;
+
+ case WAVE_FORMAT_MSG723:
+ case WAVE_FORMAT_GSM610:
+ return AV_SAMPLE_FMT_S16P;
+
+ case WAVE_FORMAT_ALAW:
+ return AV_SAMPLE_FMT_S16;
+
+ default:
+ return FALSE;
+ }
+}
+
+static void ffmpeg_close_context(FREERDP_DSP_CONTEXT* context)
+{
+ if (context)
+ {
+ if (context->context)
+ avcodec_free_context(&context->context);
+
+ if (context->frame)
+ av_frame_free(&context->frame);
+
+ if (context->resampled)
+ av_frame_free(&context->resampled);
+
+ if (context->buffered)
+ av_frame_free(&context->buffered);
+
+ if (context->packet)
+ av_packet_free(&context->packet);
+
+ if (context->rcontext)
+ {
+#if defined(SWRESAMPLE_FOUND)
+ swr_free(&context->rcontext);
+#else
+ avresample_free(&context->rcontext);
+#endif
+ }
+
+ context->id = AV_CODEC_ID_NONE;
+ context->codec = NULL;
+ context->isOpen = FALSE;
+ context->context = NULL;
+ context->frame = NULL;
+ context->resampled = NULL;
+ context->packet = NULL;
+ context->rcontext = NULL;
+ }
+}
+
+static BOOL ffmpeg_open_context(FREERDP_DSP_CONTEXT* context)
+{
+ int ret = 0;
+
+ if (!context || context->isOpen)
+ return FALSE;
+
+ const AUDIO_FORMAT* format = &context->format;
+
+ if (!format)
+ return FALSE;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int layout = av_get_default_channel_layout(format->nChannels);
+#endif
+ context->id = ffmpeg_get_avcodec(format);
+
+ if (ffmpeg_codec_is_filtered(context->id, context->encoder))
+ goto fail;
+
+ if (context->encoder)
+ context->codec = avcodec_find_encoder(context->id);
+ else
+ context->codec = avcodec_find_decoder(context->id);
+
+ if (!context->codec)
+ goto fail;
+
+ context->context = avcodec_alloc_context3(context->codec);
+
+ if (!context->context)
+ goto fail;
+
+ switch (context->id)
+ {
+ /* We need support for multichannel and sample rates != 8000 */
+ case AV_CODEC_ID_GSM_MS:
+ context->context->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
+ break;
+
+ case AV_CODEC_ID_AAC:
+ context->context->profile = FF_PROFILE_AAC_MAIN;
+ break;
+
+ default:
+ break;
+ }
+
+ context->context->max_b_frames = 1;
+ context->context->delay = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->context->channels = format->nChannels;
+ context->context->channel_layout = layout;
+#else
+ av_channel_layout_default(&context->context->ch_layout, format->nChannels);
+#endif
+ context->context->sample_rate = format->nSamplesPerSec;
+ context->context->block_align = format->nBlockAlign;
+ context->context->bit_rate = format->nAvgBytesPerSec * 8;
+ context->context->sample_fmt = ffmpeg_sample_format(format);
+ context->context->time_base = av_make_q(1, context->context->sample_rate);
+
+ if ((ret = avcodec_open2(context->context, context->codec, NULL)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error avcodec_open2 %s [%d]", err, ret);
+ goto fail;
+ }
+
+ context->packet = av_packet_alloc();
+
+ if (!context->packet)
+ goto fail;
+
+ context->frame = av_frame_alloc();
+
+ if (!context->frame)
+ goto fail;
+
+ context->resampled = av_frame_alloc();
+
+ if (!context->resampled)
+ goto fail;
+
+ context->buffered = av_frame_alloc();
+
+ if (!context->buffered)
+ goto fail;
+
+#if defined(SWRESAMPLE_FOUND)
+ context->rcontext = swr_alloc();
+#else
+ context->rcontext = avresample_alloc_context();
+#endif
+
+ if (!context->rcontext)
+ goto fail;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->frame->channel_layout = layout;
+ context->frame->channels = format->nChannels;
+#else
+ av_channel_layout_default(&context->frame->ch_layout, format->nChannels);
+#endif
+ context->frame->sample_rate = format->nSamplesPerSec;
+ context->frame->format = AV_SAMPLE_FMT_S16;
+
+ if (context->encoder)
+ {
+ context->resampled->format = context->context->sample_fmt;
+ context->resampled->sample_rate = context->context->sample_rate;
+ }
+ else
+ {
+ context->resampled->format = AV_SAMPLE_FMT_S16;
+ context->resampled->sample_rate = format->nSamplesPerSec;
+ }
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->resampled->channel_layout = layout;
+ context->resampled->channels = format->nChannels;
+#else
+ av_channel_layout_default(&context->resampled->ch_layout, format->nChannels);
+#endif
+
+ if (context->context->frame_size > 0)
+ {
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ context->buffered->channel_layout = context->resampled->channel_layout;
+ context->buffered->channels = context->resampled->channels;
+#else
+ av_channel_layout_copy(&context->buffered->ch_layout, &context->resampled->ch_layout);
+#endif
+ context->buffered->format = context->resampled->format;
+ context->buffered->nb_samples = context->context->frame_size;
+
+ if ((ret = av_frame_get_buffer(context->buffered, 1)) < 0)
+ goto fail;
+ }
+
+ context->isOpen = TRUE;
+ return TRUE;
+fail:
+ ffmpeg_close_context(context);
+ return FALSE;
+}
+
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_resample_frame(SwrContext* context, AVFrame* in, AVFrame* out)
+{
+ int ret = 0;
+
+ if (!swr_is_initialized(context))
+ {
+ if ((ret = swr_config_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if ((ret = (swr_init(context))) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+ if ((ret = swr_convert_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#else
+static BOOL ffmpeg_resample_frame(AVAudioResampleContext* context, AVFrame* in, AVFrame* out)
+{
+ int ret;
+
+ if (!avresample_is_open(context))
+ {
+ if ((ret = avresample_config(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if ((ret = (avresample_open(context))) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+ if ((ret = avresample_convert_frame(context, out, in)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL ffmpeg_encode_frame(AVCodecContext* context, AVFrame* in, AVPacket* packet,
+ wStream* out)
+{
+ if (in->format == AV_SAMPLE_FMT_FLTP)
+ {
+ uint8_t** pp = in->extended_data;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int nr_channels = in->channels;
+#else
+ const int nr_channels = in->ch_layout.nb_channels;
+#endif
+
+ for (int y = 0; y < nr_channels; y++)
+ {
+ float* data = (float*)pp[y];
+ for (int x = 0; x < in->nb_samples; x++)
+ {
+ const float val1 = data[x];
+ if (isnan(val1))
+ data[x] = 0.0f;
+ else if (isinf(val1))
+ {
+ if (val1 < 0.0f)
+ data[x] = -1.0f;
+ else
+ data[x] = 1.0f;
+ }
+ }
+ }
+ }
+ /* send the packet with the compressed data to the encoder */
+ int ret = avcodec_send_frame(context, in);
+
+ if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error submitting the packet to the encoder %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ /* read all the output frames (in general there may be any number of them */
+ while (ret >= 0)
+ {
+ ret = avcodec_receive_packet(context, packet);
+
+ if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF))
+ return TRUE;
+ else if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during encoding %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(out, packet->size))
+ return FALSE;
+
+ Stream_Write(out, packet->data, packet->size);
+ av_packet_unref(packet);
+ }
+
+ return TRUE;
+}
+
+static BOOL ffmpeg_fill_frame(AVFrame* frame, const AUDIO_FORMAT* inputFormat, const BYTE* data,
+ size_t size)
+{
+ int ret = 0;
+ int bpp = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ frame->channels = inputFormat->nChannels;
+ frame->channel_layout = av_get_default_channel_layout(frame->channels);
+#else
+ av_channel_layout_default(&frame->ch_layout, inputFormat->nChannels);
+#endif
+ frame->sample_rate = inputFormat->nSamplesPerSec;
+ frame->format = ffmpeg_sample_format(inputFormat);
+
+ bpp = av_get_bytes_per_sample(frame->format);
+ frame->nb_samples = size / inputFormat->nChannels / bpp;
+
+ if ((ret = avcodec_fill_audio_frame(frame, inputFormat->nChannels, frame->format, data, size,
+ 1)) < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during audio frame fill %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+ SwrContext* resampleContext, AVFrame* resampled, wStream* out)
+#else
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+ AVAudioResampleContext* resampleContext, AVFrame* resampled, wStream* out)
+#endif
+{
+ int ret = 0;
+ /* send the packet with the compressed data to the decoder */
+ ret = avcodec_send_packet(dec_ctx, pkt);
+
+ if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error submitting the packet to the decoder %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ /* read all the output frames (in general there may be any number of them */
+ while (ret >= 0)
+ {
+ ret = avcodec_receive_frame(dec_ctx, frame);
+
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ return TRUE;
+ else if (ret < 0)
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during decoding %s [%d]", err, ret);
+ return FALSE;
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if (!swr_is_initialized(resampleContext))
+ {
+ if ((ret = swr_config_frame(resampleContext, resampled, frame)) < 0)
+ {
+#else
+ if (!avresample_is_open(resampleContext))
+ {
+ if ((ret = avresample_config(resampleContext, resampled, frame)) < 0)
+ {
+#endif
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if ((ret = (swr_init(resampleContext))) < 0)
+#else
+ if ((ret = (avresample_open(resampleContext))) < 0)
+#endif
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+ }
+
+#if defined(SWRESAMPLE_FOUND)
+ if ((ret = swr_convert_frame(resampleContext, resampled, frame)) < 0)
+#else
+ if ((ret = avresample_convert_frame(resampleContext, resampled, frame)) < 0)
+#endif
+ {
+ const char* err = av_err2str(ret);
+ WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+ return FALSE;
+ }
+
+ {
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const size_t channels = resampled->channels;
+#else
+ const size_t channels = resampled->ch_layout.nb_channels;
+#endif
+ const size_t data_size = channels * resampled->nb_samples * 2;
+ Stream_EnsureRemainingCapacity(out, data_size);
+ Stream_Write(out, resampled->data[0], data_size);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode)
+{
+ enum AVCodecID id = ffmpeg_get_avcodec(format);
+
+ if (ffmpeg_codec_is_filtered(id, encode))
+ return FALSE;
+
+ if (encode)
+ return avcodec_find_encoder(id) != NULL;
+ else
+ return avcodec_find_decoder(id) != NULL;
+}
+
+FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode)
+{
+ FREERDP_DSP_CONTEXT* context = NULL;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+ context = calloc(1, sizeof(FREERDP_DSP_CONTEXT));
+
+ if (!context)
+ return NULL;
+
+ context->channelmix = Stream_New(NULL, 1024);
+ if (!context->channelmix)
+ {
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_dsp_ffmpeg_context_free(context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+ }
+ context->encoder = encode;
+ return context;
+}
+
+void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context)
+{
+ if (context)
+ {
+ ffmpeg_close_context(context);
+ Stream_Free(context->channelmix, TRUE);
+ free(context);
+ }
+}
+
+BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context,
+ const AUDIO_FORMAT* targetFormat)
+{
+ if (!context || !targetFormat)
+ return FALSE;
+
+ ffmpeg_close_context(context);
+ context->format = *targetFormat;
+ return ffmpeg_open_context(context);
+}
+
+static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+ const AUDIO_FORMAT* srcFormat, const BYTE** data,
+ size_t* length, AUDIO_FORMAT* dstFormat)
+{
+ UINT32 bpp = 0;
+ size_t samples = 0;
+
+ if (!context || !data || !length || !dstFormat)
+ return FALSE;
+
+ if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+ return FALSE;
+
+ bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1;
+ samples = size / bpp / srcFormat->nChannels;
+
+ *dstFormat = *srcFormat;
+ if (context->format.nChannels == srcFormat->nChannels)
+ {
+ *data = src;
+ *length = size;
+ return TRUE;
+ }
+
+ Stream_SetPosition(context->channelmix, 0);
+
+ /* Destination has more channels than source */
+ if (context->format.nChannels > srcFormat->nChannels)
+ {
+ switch (srcFormat->nChannels)
+ {
+ case 1:
+ if (!Stream_EnsureCapacity(context->channelmix, size * 2))
+ return FALSE;
+
+ for (UINT32 x = 0; x < samples; x++)
+ {
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ dstFormat->nChannels = 2;
+ return TRUE;
+
+ case 2: /* We only support stereo, so we can not handle this case. */
+ default: /* Unsupported number of channels */
+ WLog_WARN(TAG, "unsupported source channel count %" PRIu16, srcFormat->nChannels);
+ return FALSE;
+ }
+ }
+
+ /* Destination has less channels than source */
+ switch (srcFormat->nChannels)
+ {
+ case 2:
+ if (!Stream_EnsureCapacity(context->channelmix, size / 2))
+ return FALSE;
+
+ /* Simply drop second channel.
+ * TODO: Calculate average */
+ for (UINT32 x = 0; x < samples; x++)
+ {
+ for (UINT32 y = 0; y < bpp; y++)
+ Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]);
+ }
+
+ Stream_SealLength(context->channelmix);
+ *data = Stream_Buffer(context->channelmix);
+ *length = Stream_Length(context->channelmix);
+ dstFormat->nChannels = 1;
+ return TRUE;
+
+ case 1: /* Invalid, do we want to use a 0 channel sound? */
+ default: /* Unsupported number of channels */
+ WLog_WARN(TAG, "unsupported channel count %" PRIu16, srcFormat->nChannels);
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* format,
+ const BYTE* data, size_t length, wStream* out)
+{
+ AUDIO_FORMAT fmt = { 0 };
+
+ if (!context || !format || !data || !out || !context->encoder)
+ return FALSE;
+
+ if (!context || !data || !out)
+ return FALSE;
+
+ /* https://github.com/FreeRDP/FreeRDP/issues/7607
+ *
+ * we get noisy data with channel transformation, so do it ourselves.
+ */
+ if (!freerdp_dsp_channel_mix(context, data, length, format, &data, &length, &fmt))
+ return FALSE;
+
+ /* Create input frame */
+ if (!ffmpeg_fill_frame(context->frame, format, data, length))
+ return FALSE;
+
+ /* Resample to desired format. */
+ if (!ffmpeg_resample_frame(context->rcontext, context->frame, context->resampled))
+ return FALSE;
+
+ if (context->context->frame_size <= 0)
+ {
+ return ffmpeg_encode_frame(context->context, context->resampled, context->packet, out);
+ }
+ else
+ {
+ int copied = 0;
+ int rest = context->resampled->nb_samples;
+
+ do
+ {
+ int inSamples = rest;
+
+ if ((inSamples < 0) || (context->bufferedSamples > (UINT32)(INT_MAX - inSamples)))
+ return FALSE;
+
+ if (inSamples + (int)context->bufferedSamples > context->context->frame_size)
+ inSamples = context->context->frame_size - (int)context->bufferedSamples;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+ const int channels = context->context->channels;
+#else
+ const int channels = context->context->ch_layout.nb_channels;
+#endif
+ const int rc =
+ av_samples_copy(context->buffered->extended_data, context->resampled->extended_data,
+ (int)context->bufferedSamples, copied, inSamples, channels,
+ context->context->sample_fmt);
+ if (rc < 0)
+ return FALSE;
+ rest -= inSamples;
+ copied += inSamples;
+ context->bufferedSamples += (UINT32)inSamples;
+
+ if (context->context->frame_size <= (int)context->bufferedSamples)
+ {
+ /* Encode in desired format. */
+ if (!ffmpeg_encode_frame(context->context, context->buffered, context->packet, out))
+ return FALSE;
+
+ context->bufferedSamples = 0;
+ }
+ } while (rest > 0);
+
+ return TRUE;
+ }
+}
+
+BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out)
+{
+ if (!context || !srcFormat || !data || !out || context->encoder)
+ return FALSE;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_init_packet(context->packet);
+#endif
+ context->packet->data = (uint8_t*)data;
+ context->packet->size = length;
+ return ffmpeg_decode(context->context, context->packet, context->frame, context->rcontext,
+ context->resampled, out);
+}
diff --git a/libfreerdp/codec/dsp_ffmpeg.h b/libfreerdp/codec/dsp_ffmpeg.h
new file mode 100644
index 0000000..973e371
--- /dev/null
+++ b/libfreerdp/codec/dsp_ffmpeg.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - FFMPEG backend
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_DSP_FFMPEG_H
+#define FREERDP_LIB_CODEC_DSP_FFMPEG_H
+
+#include <freerdp/api.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+#include <libavcodec/version.h>
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101)
+#error \
+ "DSP module requires libavcodec version >= 57.48.101. Upgrade or set WITH_DSP_FFMPEG=OFF to continue"
+#endif
+
+void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context);
+
+WINPR_ATTR_MALLOC(freerdp_dsp_ffmpeg_context_free, 1)
+FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode);
+BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode);
+BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+ const BYTE* data, size_t length, wStream* out);
+
+BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context,
+ const AUDIO_FORMAT* targetFormat);
+
+#endif /* FREERDP_LIB_CODEC_DSP_FFMPEG_H */
diff --git a/libfreerdp/codec/h264.c b/libfreerdp/codec/h264.c
new file mode 100644
index 0000000..718bd2c
--- /dev/null
+++ b/libfreerdp/codec/h264.c
@@ -0,0 +1,777 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * Copyright 2017 David Fort <contact@hardening-consulting.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/library.h>
+#include <winpr/bitstream.h>
+#include <winpr/synch.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/yuv.h>
+#include <freerdp/log.h>
+
+#include "h264.h"
+
+#define TAG FREERDP_TAG("codec")
+
+static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight);
+
+BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height)
+{
+ BOOL isNull = FALSE;
+ UINT32 pheight = height;
+
+ if (!h264)
+ return FALSE;
+
+ if (stride == 0)
+ stride = width;
+
+ if (stride % 16 != 0)
+ stride += 16 - stride % 16;
+
+ if (pheight % 16 != 0)
+ pheight += 16 - pheight % 16;
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ if (!h264->pYUVData[x] || !h264->pOldYUVData[x])
+ isNull = TRUE;
+ }
+
+ if (pheight == 0)
+ return FALSE;
+ if (stride == 0)
+ return FALSE;
+
+ if (isNull || (width != h264->width) || (height != h264->height) ||
+ (stride != h264->iStride[0]))
+ {
+ h264->iStride[0] = stride;
+ h264->iStride[1] = (stride + 1) / 2;
+ h264->iStride[2] = (stride + 1) / 2;
+ h264->width = width;
+ h264->height = height;
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ BYTE* tmp1 = winpr_aligned_recalloc(h264->pYUVData[x], h264->iStride[x], pheight, 16);
+ BYTE* tmp2 =
+ winpr_aligned_recalloc(h264->pOldYUVData[x], h264->iStride[x], pheight, 16);
+ if (tmp1)
+ h264->pYUVData[x] = tmp1;
+ if (tmp2)
+ h264->pOldYUVData[x] = tmp2;
+ if (!tmp1 || !tmp2)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData,
+ DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ int status = 0;
+ const BYTE* pYUVData[3];
+
+ if (!h264 || h264->Compressor)
+ return -1001;
+
+ status = h264->subsystem->Decompress(h264, pSrcData, SrcSize);
+
+ if (status == 0)
+ return 1;
+
+ if (status < 0)
+ return status;
+
+ pYUVData[0] = h264->pYUVData[0];
+ pYUVData[1] = h264->pYUVData[1];
+ pYUVData[2] = h264->pYUVData[2];
+ if (!yuv420_context_decode(h264->yuv, pYUVData, h264->iStride, h264->height, DstFormat,
+ pDstData, nDstStep, regionRects, numRegionRects))
+ return -1002;
+
+ return 1;
+}
+
+static BOOL allocate_h264_metablock(UINT32 QP, RECTANGLE_16* rectangles,
+ RDPGFX_H264_METABLOCK* meta, size_t count)
+{
+ /* [MS-RDPEGFX] 2.2.4.4.2 RDPGFX_AVC420_QUANT_QUALITY */
+ if (!meta || (QP > UINT8_MAX))
+ {
+ free(rectangles);
+ return FALSE;
+ }
+
+ meta->regionRects = rectangles;
+ if (count == 0)
+ return TRUE;
+
+ if (count > UINT32_MAX)
+ return FALSE;
+
+ meta->quantQualityVals = calloc(count, sizeof(RDPGFX_H264_QUANT_QUALITY));
+
+ if (!meta->quantQualityVals || !meta->regionRects)
+ return FALSE;
+ meta->numRegionRects = (UINT32)count;
+ for (size_t x = 0; x < count; x++)
+ {
+ RDPGFX_H264_QUANT_QUALITY* cur = &meta->quantQualityVals[x];
+ cur->qp = (UINT8)QP;
+
+ /* qpVal bit 6 and 7 are flags, so mask them out here.
+ * qualityVal is [0-100] so 100 - qpVal [0-64] is always in range */
+ cur->qualityVal = 100 - (QP & 0x3F);
+ }
+ return TRUE;
+}
+
+static INLINE BOOL diff_tile(const RECTANGLE_16* regionRect, BYTE* pYUVData[3],
+ BYTE* pOldYUVData[3], UINT32 const iStride[3])
+{
+ size_t size = 0;
+
+ if (!regionRect || !pYUVData || !pOldYUVData || !iStride)
+ return FALSE;
+ size = regionRect->right - regionRect->left;
+ if (regionRect->right > iStride[0])
+ return FALSE;
+ if (regionRect->right / 2u > iStride[1])
+ return FALSE;
+ if (regionRect->right / 2u > iStride[2])
+ return FALSE;
+
+ for (UINT16 y = regionRect->top; y < regionRect->bottom; y++)
+ {
+ const BYTE* cur0 = &pYUVData[0][y * iStride[0]];
+ const BYTE* cur1 = &pYUVData[1][y * iStride[1]];
+ const BYTE* cur2 = &pYUVData[2][y * iStride[2]];
+ const BYTE* old0 = &pOldYUVData[0][y * iStride[0]];
+ const BYTE* old1 = &pOldYUVData[1][y * iStride[1]];
+ const BYTE* old2 = &pOldYUVData[2][y * iStride[2]];
+
+ if (memcmp(&cur0[regionRect->left], &old0[regionRect->left], size) != 0)
+ return TRUE;
+ if (memcmp(&cur1[regionRect->left / 2], &old1[regionRect->left / 2], size / 2) != 0)
+ return TRUE;
+ if (memcmp(&cur2[regionRect->left / 2], &old2[regionRect->left / 2], size / 2) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static BOOL detect_changes(BOOL firstFrameDone, const UINT32 QP, const RECTANGLE_16* regionRect,
+ BYTE* pYUVData[3], BYTE* pOldYUVData[3], UINT32 const iStride[3],
+ RDPGFX_H264_METABLOCK* meta)
+{
+ size_t count = 0;
+ size_t wc = 0;
+ size_t hc = 0;
+ RECTANGLE_16* rectangles = NULL;
+
+ if (!regionRect || !pYUVData || !pOldYUVData || !iStride || !meta)
+ return FALSE;
+
+ wc = (regionRect->right - regionRect->left) / 64 + 1;
+ hc = (regionRect->bottom - regionRect->top) / 64 + 1;
+ rectangles = calloc(wc * hc, sizeof(RECTANGLE_16));
+ if (!rectangles)
+ return FALSE;
+ if (!firstFrameDone)
+ {
+ rectangles[0] = *regionRect;
+ count = 1;
+ }
+ else
+ {
+ for (size_t y = regionRect->top; y < regionRect->bottom; y += 64)
+ {
+ for (size_t x = regionRect->left; x < regionRect->right; x += 64)
+ {
+ RECTANGLE_16 rect;
+ rect.left = (UINT16)MIN(UINT16_MAX, regionRect->left + x);
+ rect.top = (UINT16)MIN(UINT16_MAX, regionRect->top + y);
+ rect.right =
+ (UINT16)MIN(UINT16_MAX, MIN(regionRect->left + x + 64, regionRect->right));
+ rect.bottom =
+ (UINT16)MIN(UINT16_MAX, MIN(regionRect->top + y + 64, regionRect->bottom));
+ if (diff_tile(&rect, pYUVData, pOldYUVData, iStride))
+ rectangles[count++] = rect;
+ }
+ }
+ }
+ if (!allocate_h264_metablock(QP, rectangles, meta, count))
+ return FALSE;
+ return TRUE;
+}
+
+INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, const RECTANGLE_16* regionRect,
+ BYTE** ppDstData, UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta)
+{
+ INT32 rc = -1;
+ BYTE* pYUVData[3] = { 0 };
+ const BYTE* pcYUVData[3] = { 0 };
+ BYTE* pOldYUVData[3] = { 0 };
+
+ if (!h264 || !regionRect || !meta || !h264->Compressor)
+ return -1;
+
+ if (!h264->subsystem->Compress)
+ return -1;
+
+ if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
+ return -1;
+
+ if (h264->encodingBuffer)
+ {
+ for (size_t x = 0; x < 3; x++)
+ {
+ pYUVData[x] = h264->pYUVData[x];
+ pOldYUVData[x] = h264->pOldYUVData[x];
+ }
+ }
+ else
+ {
+ for (size_t x = 0; x < 3; x++)
+ {
+ pYUVData[x] = h264->pOldYUVData[x];
+ pOldYUVData[x] = h264->pYUVData[x];
+ }
+ }
+ h264->encodingBuffer = !h264->encodingBuffer;
+
+ if (!yuv420_context_encode(h264->yuv, pSrcData, nSrcStep, SrcFormat, h264->iStride, pYUVData,
+ regionRect, 1))
+ goto fail;
+
+ if (!detect_changes(h264->firstLumaFrameDone, h264->QP, regionRect, pYUVData, pOldYUVData,
+ h264->iStride, meta))
+ goto fail;
+
+ if (meta->numRegionRects == 0)
+ {
+ rc = 0;
+ goto fail;
+ }
+
+ for (size_t x = 0; x < 3; x++)
+ pcYUVData[x] = pYUVData[x];
+
+ rc = h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize);
+ if (rc >= 0)
+ h264->firstLumaFrameDone = TRUE;
+
+fail:
+ if (rc < 0)
+ free_h264_metablock(meta);
+ return rc;
+}
+
+INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE version, const RECTANGLE_16* region,
+ BYTE* op, BYTE** ppDstData, UINT32* pDstSize, BYTE** ppAuxDstData,
+ UINT32* pAuxDstSize, RDPGFX_H264_METABLOCK* meta,
+ RDPGFX_H264_METABLOCK* auxMeta)
+{
+ int rc = -1;
+ BYTE* coded = NULL;
+ UINT32 codedSize = 0;
+ BYTE** pYUV444Data = NULL;
+ BYTE** pOldYUV444Data = NULL;
+ BYTE** pYUVData = NULL;
+ BYTE** pOldYUVData = NULL;
+
+ if (!h264 || !h264->Compressor)
+ return -1;
+
+ if (!h264->subsystem->Compress)
+ return -1;
+
+ if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight))
+ return -1;
+
+ if (!avc444_ensure_buffer(h264, nSrcHeight))
+ return -1;
+
+ if (h264->encodingBuffer)
+ {
+ pYUV444Data = h264->pOldYUV444Data;
+ pOldYUV444Data = h264->pYUV444Data;
+ pYUVData = h264->pOldYUVData;
+ pOldYUVData = h264->pYUVData;
+ }
+ else
+ {
+ pYUV444Data = h264->pYUV444Data;
+ pOldYUV444Data = h264->pOldYUV444Data;
+ pYUVData = h264->pYUVData;
+ pOldYUVData = h264->pOldYUVData;
+ }
+ h264->encodingBuffer = !h264->encodingBuffer;
+
+ if (!yuv444_context_encode(h264->yuv, version, pSrcData, nSrcStep, SrcFormat, h264->iStride,
+ pYUV444Data, pYUVData, region, 1))
+ goto fail;
+
+ if (!detect_changes(h264->firstLumaFrameDone, h264->QP, region, pYUV444Data, pOldYUV444Data,
+ h264->iStride, meta))
+ goto fail;
+ if (!detect_changes(h264->firstChromaFrameDone, h264->QP, region, pYUVData, pOldYUVData,
+ h264->iStride, auxMeta))
+ goto fail;
+
+ /* [MS-RDPEGFX] 2.2.4.5 RFX_AVC444_BITMAP_STREAM
+ * LC:
+ * 0 ... Luma & Chroma
+ * 1 ... Luma
+ * 2 ... Chroma
+ */
+ if ((meta->numRegionRects > 0) && (auxMeta->numRegionRects > 0))
+ *op = 0;
+ else if (meta->numRegionRects > 0)
+ *op = 1;
+ else if (auxMeta->numRegionRects > 0)
+ *op = 2;
+ else
+ {
+ WLog_INFO(TAG, "no changes detected for luma or chroma frame");
+ rc = 0;
+ goto fail;
+ }
+
+ if ((*op == 0) || (*op == 1))
+ {
+ const BYTE* pcYUV444Data[3] = { pYUV444Data[0], pYUV444Data[1], pYUV444Data[2] };
+
+ if (h264->subsystem->Compress(h264, pcYUV444Data, h264->iStride, &coded, &codedSize) < 0)
+ goto fail;
+ h264->firstLumaFrameDone = TRUE;
+ memcpy(h264->lumaData, coded, codedSize);
+ *ppDstData = h264->lumaData;
+ *pDstSize = codedSize;
+ }
+
+ if ((*op == 0) || (*op == 2))
+ {
+ const BYTE* pcYUVData[3] = { pYUVData[0], pYUVData[1], pYUVData[2] };
+
+ if (h264->subsystem->Compress(h264, pcYUVData, h264->iStride, &coded, &codedSize) < 0)
+ goto fail;
+ h264->firstChromaFrameDone = TRUE;
+ *ppAuxDstData = coded;
+ *pAuxDstSize = codedSize;
+ }
+
+ rc = 1;
+fail:
+ if (rc < 0)
+ {
+ free_h264_metablock(meta);
+ free_h264_metablock(auxMeta);
+ }
+ return rc;
+}
+
+static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight)
+{
+ WINPR_ASSERT(h264);
+
+ const UINT32* piMainStride = h264->iStride;
+ UINT32* piDstSize = h264->iYUV444Size;
+ UINT32* piDstStride = h264->iYUV444Stride;
+ BYTE** ppYUVDstData = h264->pYUV444Data;
+ BYTE** ppOldYUVDstData = h264->pOldYUV444Data;
+
+ nDstHeight = MAX(h264->height, nDstHeight);
+ const UINT32 pad = nDstHeight % 16;
+ UINT32 padDstHeight = nDstHeight; /* Need alignment to 16x16 blocks */
+
+ if (pad != 0)
+ padDstHeight += 16 - pad;
+
+ if ((piMainStride[0] != piDstStride[0]) ||
+ (piDstSize[0] != 1ull * piMainStride[0] * padDstHeight))
+ {
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ piDstStride[x] = piMainStride[0];
+ piDstSize[x] = piDstStride[x] * padDstHeight;
+
+ if (piDstSize[x] == 0)
+ return FALSE;
+
+ BYTE* tmp1 = winpr_aligned_recalloc(ppYUVDstData[x], piDstSize[x], 1, 16);
+ if (!tmp1)
+ return FALSE;
+ ppYUVDstData[x] = tmp1;
+ BYTE* tmp2 = winpr_aligned_recalloc(ppOldYUVDstData[x], piDstSize[x], 1, 16);
+ if (!tmp2)
+ return FALSE;
+ ppOldYUVDstData[x] = tmp2;
+ }
+
+ {
+ BYTE* tmp = winpr_aligned_recalloc(h264->lumaData, piDstSize[0], 4, 16);
+ if (!tmp)
+ goto fail;
+ h264->lumaData = tmp;
+ }
+ }
+
+ for (UINT32 x = 0; x < 3; x++)
+ {
+ if (!ppOldYUVDstData[x] || !ppYUVDstData[x] || (piDstSize[x] == 0) || (piDstStride[x] == 0))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "YUV buffer not initialized! check your decoder settings");
+ goto fail;
+ }
+ }
+
+ if (!h264->lumaData)
+ goto fail;
+
+ return TRUE;
+fail:
+ return FALSE;
+}
+
+static BOOL avc444_process_rects(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep,
+ UINT32 nDstWidth, UINT32 nDstHeight, const RECTANGLE_16* rects,
+ UINT32 nrRects, avc444_frame_type type)
+{
+ const BYTE* pYUVData[3];
+ BYTE* pYUVDstData[3];
+ UINT32* piDstStride = h264->iYUV444Stride;
+ BYTE** ppYUVDstData = h264->pYUV444Data;
+ const UINT32* piStride = h264->iStride;
+
+ if (h264->subsystem->Decompress(h264, pSrcData, SrcSize) < 0)
+ return FALSE;
+
+ pYUVData[0] = h264->pYUVData[0];
+ pYUVData[1] = h264->pYUVData[1];
+ pYUVData[2] = h264->pYUVData[2];
+ if (!avc444_ensure_buffer(h264, nDstHeight))
+ return FALSE;
+
+ pYUVDstData[0] = ppYUVDstData[0];
+ pYUVDstData[1] = ppYUVDstData[1];
+ pYUVDstData[2] = ppYUVDstData[2];
+ if (!yuv444_context_decode(h264->yuv, (BYTE)type, pYUVData, piStride, h264->height, pYUVDstData,
+ piDstStride, DstFormat, pDstData, nDstStep, rects, nrRects))
+ return FALSE;
+
+ return TRUE;
+}
+
+#if defined(AVC444_FRAME_STAT)
+static UINT64 op1 = 0;
+static double op1sum = 0;
+static UINT64 op2 = 0;
+static double op2sum = 0;
+static UINT64 op3 = 0;
+static double op3sum = 0;
+static double avg(UINT64* count, double old, double size)
+{
+ double tmp = size + *count * old;
+ (*count)++;
+ tmp = tmp / *count;
+ return tmp;
+}
+#endif
+
+INT32 avc444_decompress(H264_CONTEXT* h264, BYTE op, const RECTANGLE_16* regionRects,
+ UINT32 numRegionRects, const BYTE* pSrcData, UINT32 SrcSize,
+ const RECTANGLE_16* auxRegionRects, UINT32 numAuxRegionRect,
+ const BYTE* pAuxSrcData, UINT32 AuxSrcSize, BYTE* pDstData, DWORD DstFormat,
+ UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, UINT32 codecId)
+{
+ INT32 status = -1;
+ avc444_frame_type chroma =
+ (codecId == RDPGFX_CODECID_AVC444) ? AVC444_CHROMAv1 : AVC444_CHROMAv2;
+
+ if (!h264 || !regionRects || !pSrcData || !pDstData || h264->Compressor)
+ return -1001;
+
+ switch (op)
+ {
+ case 0: /* YUV420 in stream 1
+ * Chroma420 in stream 2 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects,
+ AVC444_LUMA))
+ status = -1;
+ else if (!avc444_process_rects(h264, pAuxSrcData, AuxSrcSize, pDstData, DstFormat,
+ nDstStep, nDstWidth, nDstHeight, auxRegionRects,
+ numAuxRegionRect, chroma))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ case 2: /* Chroma420 in stream 1 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects, chroma))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ case 1: /* YUV420 in stream 1 */
+ if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep,
+ nDstWidth, nDstHeight, regionRects, numRegionRects,
+ AVC444_LUMA))
+ status = -1;
+ else
+ status = 0;
+
+ break;
+
+ default: /* WTF? */
+ break;
+ }
+
+#if defined(AVC444_FRAME_STAT)
+
+ switch (op)
+ {
+ case 0:
+ op1sum = avg(&op1, op1sum, SrcSize + AuxSrcSize);
+ break;
+
+ case 1:
+ op2sum = avg(&op2, op2sum, SrcSize);
+ break;
+
+ case 2:
+ op3sum = avg(&op3, op3sum, SrcSize);
+ break;
+
+ default:
+ break;
+ }
+
+ WLog_Print(h264->log, WLOG_INFO,
+ "luma=%" PRIu64 " [avg=%lf] chroma=%" PRIu64 " [avg=%lf] combined=%" PRIu64
+ " [avg=%lf]",
+ op1, op1sum, op2, op2sum, op3, op3sum);
+#endif
+ return status;
+}
+
+#define MAX_SUBSYSTEMS 10
+static INIT_ONCE subsystems_once = INIT_ONCE_STATIC_INIT;
+static const H264_CONTEXT_SUBSYSTEM* subSystems[MAX_SUBSYSTEMS] = { 0 };
+
+static BOOL CALLBACK h264_register_subsystems(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ int i = 0;
+
+#ifdef WITH_MEDIACODEC
+ {
+ subSystems[i] = &g_Subsystem_mediacodec;
+ i++;
+ }
+#endif
+#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION)
+ {
+ subSystems[i] = &g_Subsystem_MF;
+ i++;
+ }
+#endif
+#ifdef WITH_OPENH264
+ {
+ subSystems[i] = &g_Subsystem_OpenH264;
+ i++;
+ }
+#endif
+#ifdef WITH_VIDEO_FFMPEG
+ {
+ subSystems[i] = &g_Subsystem_libavcodec;
+ i++;
+ }
+#endif
+ return i > 0;
+}
+
+static BOOL h264_context_init(H264_CONTEXT* h264)
+{
+ if (!h264)
+ return FALSE;
+
+ h264->log = WLog_Get(TAG);
+
+ if (!h264->log)
+ return FALSE;
+
+ h264->subsystem = NULL;
+ InitOnceExecuteOnce(&subsystems_once, h264_register_subsystems, NULL, NULL);
+
+ for (size_t i = 0; i < MAX_SUBSYSTEMS; i++)
+ {
+ const H264_CONTEXT_SUBSYSTEM* subsystem = subSystems[i];
+
+ if (!subsystem || !subsystem->Init)
+ break;
+
+ if (subsystem->Init(h264))
+ {
+ h264->subsystem = subsystem;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL h264_context_reset(H264_CONTEXT* h264, UINT32 width, UINT32 height)
+{
+ if (!h264)
+ return FALSE;
+
+ h264->width = width;
+ h264->height = height;
+ return yuv_context_reset(h264->yuv, width, height);
+}
+
+H264_CONTEXT* h264_context_new(BOOL Compressor)
+{
+ H264_CONTEXT* h264 = (H264_CONTEXT*)calloc(1, sizeof(H264_CONTEXT));
+ if (!h264)
+ return NULL;
+
+ h264->Compressor = Compressor;
+ if (Compressor)
+
+ {
+ /* Default compressor settings, may be changed by caller */
+ h264->BitRate = 1000000;
+ h264->FrameRate = 30;
+ }
+
+ if (!h264_context_init(h264))
+ goto fail;
+
+ h264->yuv = yuv_context_new(Compressor, 0);
+ if (!h264->yuv)
+ goto fail;
+
+ return h264;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ h264_context_free(h264);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void h264_context_free(H264_CONTEXT* h264)
+{
+ if (h264)
+ {
+ if (h264->subsystem)
+ h264->subsystem->Uninit(h264);
+
+ for (size_t x = 0; x < 3; x++)
+ {
+ if (h264->Compressor)
+ {
+ winpr_aligned_free(h264->pYUVData[x]);
+ winpr_aligned_free(h264->pOldYUVData[x]);
+ }
+ winpr_aligned_free(h264->pYUV444Data[x]);
+ winpr_aligned_free(h264->pOldYUV444Data[x]);
+ }
+ winpr_aligned_free(h264->lumaData);
+
+ yuv_context_free(h264->yuv);
+ free(h264);
+ }
+}
+
+void free_h264_metablock(RDPGFX_H264_METABLOCK* meta)
+{
+ RDPGFX_H264_METABLOCK m = { 0 };
+ if (!meta)
+ return;
+ free(meta->quantQualityVals);
+ free(meta->regionRects);
+ *meta = m;
+}
+
+BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UINT32 value)
+{
+ WINPR_ASSERT(h264);
+ switch (option)
+ {
+ case H264_CONTEXT_OPTION_BITRATE:
+ h264->BitRate = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_FRAMERATE:
+ h264->FrameRate = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_RATECONTROL:
+ h264->RateControlMode = value;
+ return TRUE;
+ case H264_CONTEXT_OPTION_QP:
+ h264->QP = value;
+ return TRUE;
+ default:
+ WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
+ option);
+ return FALSE;
+ }
+}
+
+UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option)
+{
+ WINPR_ASSERT(h264);
+ switch (option)
+ {
+ case H264_CONTEXT_OPTION_BITRATE:
+ return h264->BitRate;
+ case H264_CONTEXT_OPTION_FRAMERATE:
+ return h264->FrameRate;
+ case H264_CONTEXT_OPTION_RATECONTROL:
+ return h264->RateControlMode;
+ case H264_CONTEXT_OPTION_QP:
+ return h264->QP;
+ default:
+ WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]",
+ option);
+ return 0;
+ }
+}
diff --git a/libfreerdp/codec/h264.h b/libfreerdp/codec/h264.h
new file mode 100644
index 0000000..64bda53
--- /dev/null
+++ b/libfreerdp/codec/h264.h
@@ -0,0 +1,106 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 2018 Armin Novak <anovak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_H264_H
+#define FREERDP_LIB_CODEC_H264_H
+
+#include <freerdp/api.h>
+#include <freerdp/config.h>
+#include <freerdp/codec/h264.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef BOOL (*pfnH264SubsystemInit)(H264_CONTEXT* h264);
+ typedef void (*pfnH264SubsystemUninit)(H264_CONTEXT* h264);
+
+ typedef int (*pfnH264SubsystemDecompress)(H264_CONTEXT* h264, const BYTE* pSrcData,
+ UINT32 SrcSize);
+ typedef int (*pfnH264SubsystemCompress)(H264_CONTEXT* h264, const BYTE** pSrcYuv,
+ const UINT32* pStride, BYTE** ppDstData,
+ UINT32* pDstSize);
+
+ struct S_H264_CONTEXT_SUBSYSTEM
+ {
+ const char* name;
+ pfnH264SubsystemInit Init;
+ pfnH264SubsystemUninit Uninit;
+ pfnH264SubsystemDecompress Decompress;
+ pfnH264SubsystemCompress Compress;
+ };
+
+ struct S_H264_CONTEXT
+ {
+ BOOL Compressor;
+
+ UINT32 width;
+ UINT32 height;
+
+ H264_RATECONTROL_MODE RateControlMode;
+ UINT32 BitRate;
+ UINT32 FrameRate;
+ UINT32 QP;
+ UINT32 NumberOfThreads;
+
+ UINT32 iStride[3];
+ BYTE* pOldYUVData[3];
+ BYTE* pYUVData[3];
+
+ UINT32 iYUV444Size[3];
+ UINT32 iYUV444Stride[3];
+ BYTE* pOldYUV444Data[3];
+ BYTE* pYUV444Data[3];
+
+ UINT32 numSystemData;
+ void* pSystemData;
+ const H264_CONTEXT_SUBSYSTEM* subsystem;
+ YUV_CONTEXT* yuv;
+
+ BOOL encodingBuffer;
+ BOOL firstLumaFrameDone;
+ BOOL firstChromaFrameDone;
+
+ void* lumaData;
+ wLog* log;
+ };
+
+ FREERDP_LOCAL BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width,
+ UINT32 height);
+
+#ifdef WITH_MEDIACODEC
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec;
+#endif
+#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION)
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF;
+#endif
+#ifdef WITH_OPENH264
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264;
+#endif
+#ifdef WITH_VIDEO_FFMPEG
+ extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CODEC_H264_H */
diff --git a/libfreerdp/codec/h264_ffmpeg.c b/libfreerdp/codec/h264_ffmpeg.c
new file mode 100644
index 0000000..54492e1
--- /dev/null
+++ b/libfreerdp/codec/h264_ffmpeg.c
@@ -0,0 +1,697 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2015 Marc-André Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * Copyright 2014 erbth <t.erbesdobler@team103.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.
+ */
+#include <freerdp/config.h>
+
+#include <winpr/wlog.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/opt.h>
+
+#include "h264.h"
+
+#ifdef WITH_VAAPI
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 9, 0)
+#include <libavutil/hwcontext.h>
+#else
+#pragma warning You have asked for VA - API decoding, \
+ but your version of libavutil is too old !Disabling.
+#undef WITH_VAAPI
+#endif
+#endif
+
+/* Fallback support for older libavcodec versions */
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100)
+#define AV_CODEC_ID_H264 CODEC_ID_H264
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2)
+#define AV_CODEC_FLAG_LOOP_FILTER CODEC_FLAG_LOOP_FILTER
+#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED
+#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED
+#endif
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#endif
+
+/* Ubuntu 14.04 ships without the functions provided by avutil,
+ * so define error to string methods here. */
+#if !defined(av_err2str)
+static inline char* error_string(char* errbuf, size_t errbuf_size, int errnum)
+{
+ av_strerror(errnum, errbuf, errbuf_size);
+ return errbuf;
+}
+
+#define av_err2str(errnum) error_string((char[64]){ 0 }, 64, errnum)
+#endif
+
+#ifdef WITH_VAAPI
+#define VAAPI_DEVICE "/dev/dri/renderD128"
+#endif
+
+typedef struct
+{
+ const AVCodec* codecDecoder;
+ AVCodecContext* codecDecoderContext;
+ const AVCodec* codecEncoder;
+ AVCodecContext* codecEncoderContext;
+ AVCodecParserContext* codecParser;
+ AVFrame* videoFrame;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ AVPacket bufferpacket;
+#endif
+ AVPacket* packet;
+#ifdef WITH_VAAPI
+ AVBufferRef* hwctx;
+ AVFrame* hwVideoFrame;
+ enum AVPixelFormat hw_pix_fmt;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+ AVBufferRef* hw_frames_ctx;
+#endif
+#endif
+} H264_CONTEXT_LIBAVCODEC;
+
+static void libavcodec_destroy_encoder(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ if (!h264 || !h264->subsystem)
+ return;
+
+ sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+
+ if (sys->codecEncoderContext)
+ {
+ avcodec_close(sys->codecEncoderContext);
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
+ avcodec_free_context(&sys->codecEncoderContext);
+#else
+ av_free(sys->codecEncoderContext);
+#endif
+ }
+
+ sys->codecEncoder = NULL;
+ sys->codecEncoderContext = NULL;
+}
+
+static BOOL libavcodec_create_encoder(H264_CONTEXT* h264)
+{
+ BOOL recreate = FALSE;
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ if (!h264 || !h264->subsystem)
+ return FALSE;
+
+ if ((h264->width > INT_MAX) || (h264->height > INT_MAX))
+ return FALSE;
+
+ sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ if (!sys)
+ return FALSE;
+ recreate = !sys->codecEncoder || !sys->codecEncoderContext;
+
+ if (sys->codecEncoderContext)
+ {
+ if ((sys->codecEncoderContext->width != (int)h264->width) ||
+ (sys->codecEncoderContext->height != (int)h264->height))
+ recreate = TRUE;
+ }
+
+ if (!recreate)
+ return TRUE;
+
+ libavcodec_destroy_encoder(h264);
+ sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
+
+ if (!sys->codecEncoder)
+ goto EXCEPTION;
+
+ sys->codecEncoderContext = avcodec_alloc_context3(sys->codecEncoder);
+
+ if (!sys->codecEncoderContext)
+ goto EXCEPTION;
+
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ sys->codecEncoderContext->bit_rate = h264->BitRate;
+ break;
+
+ case H264_RATECONTROL_CQP:
+ /* TODO: sys->codecEncoderContext-> = h264->QP; */
+ break;
+
+ default:
+ break;
+ }
+
+ sys->codecEncoderContext->width = (int)MIN(INT32_MAX, h264->width);
+ sys->codecEncoderContext->height = (int)MIN(INT32_MAX, h264->height);
+ sys->codecEncoderContext->delay = 0;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 13, 100)
+ sys->codecEncoderContext->framerate = (AVRational){ h264->FrameRate, 1 };
+#endif
+ sys->codecEncoderContext->time_base = (AVRational){ 1, h264->FrameRate };
+ av_opt_set(sys->codecEncoderContext, "preset", "medium", AV_OPT_SEARCH_CHILDREN);
+ av_opt_set(sys->codecEncoderContext, "tune", "zerolatency", AV_OPT_SEARCH_CHILDREN);
+ sys->codecEncoderContext->flags |= AV_CODEC_FLAG_LOOP_FILTER;
+ sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P;
+
+ if (avcodec_open2(sys->codecEncoderContext, sys->codecEncoder, NULL) < 0)
+ goto EXCEPTION;
+
+ return TRUE;
+EXCEPTION:
+ libavcodec_destroy_encoder(h264);
+ return FALSE;
+}
+
+static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ union
+ {
+ const BYTE* cpv;
+ BYTE* pv;
+ } cnv;
+ int rc = -1;
+ int status = 0;
+ int gotFrame = 0;
+ AVPacket* packet = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData || (SrcSize == 0));
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ BYTE** pYUVData = h264->pYUVData;
+ UINT32* iStride = h264->iStride;
+
+ WINPR_ASSERT(sys);
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ packet = &sys->bufferpacket;
+ WINPR_ASSERT(packet);
+ av_init_packet(packet);
+#else
+ packet = av_packet_alloc();
+#endif
+ if (!packet)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket");
+ goto fail;
+ }
+
+ cnv.cpv = pSrcData;
+ packet->data = cnv.pv;
+ packet->size = (int)MIN(SrcSize, INT32_MAX);
+
+ WINPR_ASSERT(sys->codecDecoderContext);
+ /* avcodec_decode_video2 is deprecated with libavcodec 57.48.101 */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ status = avcodec_send_packet(sys->codecDecoderContext, packet);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status);
+ goto fail;
+ }
+
+ sys->videoFrame->format = AV_PIX_FMT_YUV420P;
+
+ do
+ {
+#ifdef WITH_VAAPI
+ status = avcodec_receive_frame(sys->codecDecoderContext,
+ sys->hwctx ? sys->hwVideoFrame : sys->videoFrame);
+#else
+ status = avcodec_receive_frame(sys->codecDecoderContext, sys->videoFrame);
+#endif
+ } while (status == AVERROR(EAGAIN));
+
+ gotFrame = (status == 0);
+#else
+#ifdef WITH_VAAPI
+ status =
+ avcodec_decode_video2(sys->codecDecoderContext,
+ sys->hwctx ? sys->hwVideoFrame : sys->videoFrame, &gotFrame, packet);
+#else
+ status = avcodec_decode_video2(sys->codecDecoderContext, sys->videoFrame, &gotFrame, packet);
+#endif
+#endif
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status);
+ goto fail;
+ }
+
+#ifdef WITH_VAAPI
+
+ if (sys->hwctx)
+ {
+ if (sys->hwVideoFrame->format == sys->hw_pix_fmt)
+ {
+ sys->videoFrame->width = sys->hwVideoFrame->width;
+ sys->videoFrame->height = sys->hwVideoFrame->height;
+ status = av_hwframe_transfer_data(sys->videoFrame, sys->hwVideoFrame, 0);
+ }
+ else
+ {
+ status = av_frame_copy(sys->videoFrame, sys->hwVideoFrame);
+ }
+ }
+
+ gotFrame = (status == 0);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to transfer video frame (status=%d) (%s)", status,
+ av_err2str(status));
+ goto fail;
+ }
+
+#endif
+#if 0
+ WLog_Print(h264->log, WLOG_INFO,
+ "libavcodec_decompress: frame decoded (status=%d, gotFrame=%d, width=%d, height=%d, Y=[%p,%d], U=[%p,%d], V=[%p,%d])",
+ status, gotFrame, sys->videoFrame->width, sys->videoFrame->height,
+ (void*) sys->videoFrame->data[0], sys->videoFrame->linesize[0],
+ (void*) sys->videoFrame->data[1], sys->videoFrame->linesize[1],
+ (void*) sys->videoFrame->data[2], sys->videoFrame->linesize[2]);
+#endif
+
+ if (gotFrame)
+ {
+ WINPR_ASSERT(sys->videoFrame);
+
+ pYUVData[0] = sys->videoFrame->data[0];
+ pYUVData[1] = sys->videoFrame->data[1];
+ pYUVData[2] = sys->videoFrame->data[2];
+ iStride[0] = (UINT32)MAX(0, sys->videoFrame->linesize[0]);
+ iStride[1] = (UINT32)MAX(0, sys->videoFrame->linesize[1]);
+ iStride[2] = (UINT32)MAX(0, sys->videoFrame->linesize[2]);
+
+ rc = 1;
+ }
+ else
+ rc = -2;
+
+fail:
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_packet_unref(packet);
+#else
+ av_packet_free(&packet);
+#endif
+
+ return rc;
+}
+
+static int libavcodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ union
+ {
+ const BYTE* cpv;
+ uint8_t* pv;
+ } cnv;
+ int rc = -1;
+ int status = 0;
+ int gotFrame = 0;
+
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (!libavcodec_create_encoder(h264))
+ return -1;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ sys->packet = &sys->bufferpacket;
+ av_packet_unref(sys->packet);
+ av_init_packet(sys->packet);
+#else
+ av_packet_free(&sys->packet);
+ sys->packet = av_packet_alloc();
+#endif
+ if (!sys->packet)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket");
+ goto fail;
+ }
+
+ WINPR_ASSERT(sys->packet);
+ sys->packet->data = NULL;
+ sys->packet->size = 0;
+
+ WINPR_ASSERT(sys->videoFrame);
+ WINPR_ASSERT(sys->codecEncoderContext);
+ sys->videoFrame->format = sys->codecEncoderContext->pix_fmt;
+ sys->videoFrame->width = sys->codecEncoderContext->width;
+ sys->videoFrame->height = sys->codecEncoderContext->height;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 48, 100)
+ sys->videoFrame->colorspace = AVCOL_SPC_BT709;
+#endif
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 92, 100)
+ sys->videoFrame->chroma_location = AVCHROMA_LOC_LEFT;
+#endif
+ cnv.cpv = pSrcYuv[0];
+ sys->videoFrame->data[0] = cnv.pv;
+
+ cnv.cpv = pSrcYuv[1];
+ sys->videoFrame->data[1] = cnv.pv;
+
+ cnv.cpv = pSrcYuv[2];
+ sys->videoFrame->data[2] = cnv.pv;
+
+ sys->videoFrame->linesize[0] = (int)pStride[0];
+ sys->videoFrame->linesize[1] = (int)pStride[1];
+ sys->videoFrame->linesize[2] = (int)pStride[2];
+ sys->videoFrame->pts++;
+ /* avcodec_encode_video2 is deprecated with libavcodec 57.48.101 */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ status = avcodec_send_frame(sys->codecEncoderContext, sys->videoFrame);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ status = avcodec_receive_packet(sys->codecEncoderContext, sys->packet);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ gotFrame = (status == 0);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+
+ do
+ {
+ status = avcodec_encode_video2(sys->codecEncoderContext, sys->packet, sys->videoFrame,
+ &gotFrame);
+ } while ((status >= 0) && (gotFrame == 0));
+
+#else
+ sys->packet->size =
+ avpicture_get_size(sys->codecDecoderContext->pix_fmt, sys->codecDecoderContext->width,
+ sys->codecDecoderContext->height);
+ sys->packet->data = av_malloc(sys->packet->size);
+
+ if (!sys->packet->data)
+ status = -1;
+ else
+ {
+ status = avcodec_encode_video(sys->codecDecoderContext, sys->packet->data,
+ sys->packet->size, sys->videoFrame);
+ }
+
+#endif
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])",
+ av_err2str(status), status);
+ goto fail;
+ }
+
+ WINPR_ASSERT(sys->packet);
+ *ppDstData = sys->packet->data;
+ *pDstSize = (UINT32)MAX(0, sys->packet->size);
+
+ if (!gotFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Did not get frame! (%s [%d])", av_err2str(status),
+ status);
+ rc = -2;
+ }
+ else
+ rc = 1;
+fail:
+ return rc;
+}
+
+static void libavcodec_uninit(H264_CONTEXT* h264)
+{
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+
+ if (!sys)
+ return;
+
+ if (sys->packet)
+ {
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+ av_packet_unref(sys->packet);
+#else
+ av_packet_free(&sys->packet);
+#endif
+ }
+
+ if (sys->videoFrame)
+ {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ av_frame_free(&sys->videoFrame);
+#else
+ av_free(sys->videoFrame);
+#endif
+ }
+
+#ifdef WITH_VAAPI
+
+ if (sys->hwVideoFrame)
+ {
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ av_frame_free(&sys->hwVideoFrame);
+#else
+ av_free(sys->hwVideoFrame);
+#endif
+ }
+
+ if (sys->hwctx)
+ av_buffer_unref(&sys->hwctx);
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+
+ if (sys->hw_frames_ctx)
+ av_buffer_unref(&sys->hw_frames_ctx);
+
+#endif
+#endif
+
+ if (sys->codecParser)
+ av_parser_close(sys->codecParser);
+
+ if (sys->codecDecoderContext)
+ {
+ avcodec_close(sys->codecDecoderContext);
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
+ avcodec_free_context(&sys->codecDecoderContext);
+#else
+ av_free(sys->codecDecoderContext);
+#endif
+ }
+
+ libavcodec_destroy_encoder(h264);
+ free(sys);
+ h264->pSystemData = NULL;
+}
+
+#ifdef WITH_VAAPI
+static enum AVPixelFormat libavcodec_get_format(struct AVCodecContext* ctx,
+ const enum AVPixelFormat* fmts)
+{
+ WINPR_ASSERT(ctx);
+
+ H264_CONTEXT* h264 = (H264_CONTEXT*)ctx->opaque;
+ WINPR_ASSERT(h264);
+
+ H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ for (const enum AVPixelFormat* p = fmts; *p != AV_PIX_FMT_NONE; p++)
+ {
+ if (*p == sys->hw_pix_fmt)
+ {
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100)
+ sys->hw_frames_ctx = av_hwframe_ctx_alloc(sys->hwctx);
+
+ if (!sys->hw_frames_ctx)
+ {
+ return AV_PIX_FMT_NONE;
+ }
+
+ sys->codecDecoderContext->pix_fmt = *p;
+ AVHWFramesContext* frames = (AVHWFramesContext*)sys->hw_frames_ctx->data;
+ frames->format = *p;
+ frames->height = sys->codecDecoderContext->coded_height;
+ frames->width = sys->codecDecoderContext->coded_width;
+ frames->sw_format =
+ (sys->codecDecoderContext->sw_pix_fmt == AV_PIX_FMT_YUV420P10 ? AV_PIX_FMT_P010
+ : AV_PIX_FMT_NV12);
+ frames->initial_pool_size = 20;
+
+ if (sys->codecDecoderContext->active_thread_type & FF_THREAD_FRAME)
+ frames->initial_pool_size += sys->codecDecoderContext->thread_count;
+
+ int err = av_hwframe_ctx_init(sys->hw_frames_ctx);
+
+ if (err < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Could not init hwframes context: %s",
+ av_err2str(err));
+ return AV_PIX_FMT_NONE;
+ }
+
+ sys->codecDecoderContext->hw_frames_ctx = av_buffer_ref(sys->hw_frames_ctx);
+#endif
+ return *p;
+ }
+ }
+
+ return AV_PIX_FMT_NONE;
+}
+#endif
+
+static BOOL libavcodec_init(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_LIBAVCODEC* sys = NULL;
+
+ WINPR_ASSERT(h264);
+ sys = (H264_CONTEXT_LIBAVCODEC*)calloc(1, sizeof(H264_CONTEXT_LIBAVCODEC));
+
+ if (!sys)
+ {
+ goto EXCEPTION;
+ }
+
+ h264->pSystemData = (void*)sys;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+ avcodec_register_all();
+#endif
+
+ if (!h264->Compressor)
+ {
+ sys->codecDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
+
+ if (!sys->codecDecoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to find libav H.264 codec");
+ goto EXCEPTION;
+ }
+
+ sys->codecDecoderContext = avcodec_alloc_context3(sys->codecDecoder);
+
+ if (!sys->codecDecoderContext)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav codec context");
+ goto EXCEPTION;
+ }
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100)
+ if (sys->codecDecoder->capabilities & AV_CODEC_CAP_TRUNCATED)
+ {
+ sys->codecDecoderContext->flags |= AV_CODEC_FLAG_TRUNCATED;
+ }
+#endif
+
+#ifdef WITH_VAAPI
+
+ if (!sys->hwctx)
+ {
+ int ret =
+ av_hwdevice_ctx_create(&sys->hwctx, AV_HWDEVICE_TYPE_VAAPI, VAAPI_DEVICE, NULL, 0);
+
+ if (ret < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Could not initialize hardware decoder, falling back to software: %s",
+ av_err2str(ret));
+ sys->hwctx = NULL;
+ goto fail_hwdevice_create;
+ }
+ }
+
+ sys->codecDecoderContext->get_format = libavcodec_get_format;
+ sys->hw_pix_fmt = AV_PIX_FMT_VAAPI;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
+ sys->codecDecoderContext->hw_device_ctx = av_buffer_ref(sys->hwctx);
+#endif
+ sys->codecDecoderContext->opaque = (void*)h264;
+ fail_hwdevice_create:
+#endif
+
+ if (avcodec_open2(sys->codecDecoderContext, sys->codecDecoder, NULL) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to open libav codec");
+ goto EXCEPTION;
+ }
+
+ sys->codecParser = av_parser_init(AV_CODEC_ID_H264);
+
+ if (!sys->codecParser)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize libav parser");
+ goto EXCEPTION;
+ }
+ }
+
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
+ sys->videoFrame = av_frame_alloc();
+#ifdef WITH_VAAPI
+ sys->hwVideoFrame = av_frame_alloc();
+#endif
+#else
+ sys->videoFrame = avcodec_alloc_frame();
+#endif
+
+ if (!sys->videoFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav frame");
+ goto EXCEPTION;
+ }
+
+#ifdef WITH_VAAPI
+
+ if (!sys->hwVideoFrame)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav hw frame");
+ goto EXCEPTION;
+ }
+
+#endif
+ sys->videoFrame->pts = 0;
+ return TRUE;
+EXCEPTION:
+ libavcodec_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec = { "libavcodec", libavcodec_init,
+ libavcodec_uninit, libavcodec_decompress,
+ libavcodec_compress };
diff --git a/libfreerdp/codec/h264_mediacodec.c b/libfreerdp/codec/h264_mediacodec.c
new file mode 100644
index 0000000..ae6d76a
--- /dev/null
+++ b/libfreerdp/codec/h264_mediacodec.c
@@ -0,0 +1,527 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2022 Ely Ronnen <elyronnen@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.
+ */
+
+#include <winpr/wlog.h>
+#include <winpr/assert.h>
+#include <winpr/library.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaFormat.h>
+
+#include "h264.h"
+
+static const char* CODEC_NAME = "video/avc";
+
+static const int COLOR_FormatYUV420Planar = 19;
+static const int COLOR_FormatYUV420Flexible = 0x7f420888;
+
+/* https://developer.android.com/reference/android/media/MediaCodec#qualityFloor */
+static const int MEDIACODEC_MINIMUM_WIDTH = 320;
+static const int MEDIACODEC_MINIMUM_HEIGHT = 240;
+
+typedef struct
+{
+ AMediaCodec* decoder;
+ AMediaFormat* inputFormat;
+ AMediaFormat* outputFormat;
+ int32_t width;
+ int32_t height;
+ int32_t outputWidth;
+ int32_t outputHeight;
+ ssize_t currentOutputBufferIndex;
+} H264_CONTEXT_MEDIACODEC;
+
+static AMediaFormat* mediacodec_format_new(wLog* log, int width, int height)
+{
+ const char* media_format;
+ AMediaFormat* format = AMediaFormat_new();
+ if (format == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "AMediaFormat_new failed");
+ return NULL;
+ }
+
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Planar);
+
+ media_format = AMediaFormat_toString(format);
+ if (media_format == NULL)
+ {
+ WLog_Print(log, WLOG_ERROR, "AMediaFormat_toString failed");
+ AMediaFormat_delete(format);
+ return NULL;
+ }
+
+ WLog_Print(log, WLOG_DEBUG, "MediaCodec configuring with desired output format [%s]",
+ media_format);
+
+ return format;
+}
+
+static void set_mediacodec_format(H264_CONTEXT* h264, AMediaFormat** formatVariable,
+ AMediaFormat* newFormat)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(formatVariable);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (*formatVariable == newFormat)
+ return;
+
+ if (*formatVariable != NULL)
+ {
+ status = AMediaFormat_delete(*formatVariable);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaFormat_delete %d", status);
+ }
+ }
+
+ *formatVariable = newFormat;
+}
+
+static int update_mediacodec_inputformat(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ AMediaFormat* inputFormat;
+ const char* mediaFormatName;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+#if __ANDROID__ >= 21
+ inputFormat = AMediaCodec_getInputFormat(sys->decoder);
+ if (inputFormat == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputFormat failed");
+ return -1;
+ }
+#else
+ inputFormat = sys->inputFormat;
+#endif
+ set_mediacodec_format(h264, &sys->inputFormat, inputFormat);
+
+ mediaFormatName = AMediaFormat_toString(sys->inputFormat);
+ if (mediaFormatName == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
+ return -1;
+ }
+ WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with input MediaFormat [%s]",
+ mediaFormatName);
+
+ return 1;
+}
+
+static int update_mediacodec_outputformat(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ AMediaFormat* outputFormat;
+ const char* mediaFormatName;
+ int32_t outputWidth, outputHeight;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ outputFormat = AMediaCodec_getOutputFormat(sys->decoder);
+ if (outputFormat == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getOutputFormat failed");
+ return -1;
+ }
+ set_mediacodec_format(h264, &sys->outputFormat, outputFormat);
+
+ mediaFormatName = AMediaFormat_toString(sys->outputFormat);
+ if (mediaFormatName == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
+ return -1;
+ }
+ WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with output MediaFormat [%s]",
+ mediaFormatName);
+
+ if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_WIDTH, &outputWidth))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting width");
+ return -1;
+ }
+
+ if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_HEIGHT, &outputHeight))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting height");
+ return -1;
+ }
+
+ sys->outputWidth = outputWidth;
+ sys->outputHeight = outputHeight;
+
+ return 1;
+}
+
+static void release_current_outputbuffer(H264_CONTEXT* h264)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ if (sys->currentOutputBufferIndex < 0)
+ {
+ return;
+ }
+
+ status = AMediaCodec_releaseOutputBuffer(sys->decoder, sys->currentOutputBufferIndex, FALSE);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_releaseOutputBuffer %d", status);
+ }
+
+ sys->currentOutputBufferIndex = -1;
+}
+
+static int mediacodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcYuv);
+ WINPR_ASSERT(pStride);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
+ return -1;
+}
+
+static int mediacodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ ssize_t inputBufferId = -1;
+ size_t inputBufferSize, outputBufferSize;
+ uint8_t* inputBuffer;
+ media_status_t status;
+ BYTE** pYUVData;
+ UINT32* iStride;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ pYUVData = h264->pYUVData;
+ WINPR_ASSERT(pYUVData);
+
+ iStride = h264->iStride;
+ WINPR_ASSERT(iStride);
+
+ release_current_outputbuffer(h264);
+
+ if (sys->width != h264->width || sys->height != h264->height)
+ {
+ sys->width = h264->width;
+ sys->height = h264->height;
+
+ if (sys->width < MEDIACODEC_MINIMUM_WIDTH || sys->height < MEDIACODEC_MINIMUM_HEIGHT)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "MediaCodec got width or height smaller than minimum [%d,%d]", sys->width,
+ sys->height);
+ return -1;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec setting new input width and height [%d,%d]",
+ sys->width, sys->height);
+
+#if __ANDROID__ >= 26
+ AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_WIDTH, sys->width);
+ AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_HEIGHT, sys->height);
+ status = AMediaCodec_setParameters(sys->decoder, sys->inputFormat);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_setParameters failed: %d", status);
+ return -1;
+ }
+#else
+ set_mediacodec_format(h264, &sys->inputFormat,
+ mediacodec_format_new(h264->log, sys->width, sys->height));
+#endif
+
+ // The codec can change output width and height
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
+ return -1;
+ }
+ }
+
+ while (true)
+ {
+ UINT32 inputBufferCurrnetOffset = 0;
+ while (inputBufferCurrnetOffset < SrcSize)
+ {
+ UINT32 numberOfBytesToCopy = SrcSize - inputBufferCurrnetOffset;
+ inputBufferId = AMediaCodec_dequeueInputBuffer(sys->decoder, -1);
+ if (inputBufferId < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_dequeueInputBuffer failed [%d]",
+ inputBufferId);
+ // TODO: sleep?
+ continue;
+ }
+
+ inputBuffer = AMediaCodec_getInputBuffer(sys->decoder, inputBufferId, &inputBufferSize);
+ if (inputBuffer == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputBuffer failed");
+ return -1;
+ }
+
+ if (numberOfBytesToCopy > inputBufferSize)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "MediaCodec inputBufferSize: got [%d] but wanted [%d]", inputBufferSize,
+ numberOfBytesToCopy);
+ numberOfBytesToCopy = inputBufferSize;
+ }
+
+ memcpy(inputBuffer, pSrcData + inputBufferCurrnetOffset, numberOfBytesToCopy);
+ inputBufferCurrnetOffset += numberOfBytesToCopy;
+
+ status = AMediaCodec_queueInputBuffer(sys->decoder, inputBufferId, 0,
+ numberOfBytesToCopy, 0, 0);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_queueInputBuffer %d", status);
+ return -1;
+ }
+ }
+
+ while (true)
+ {
+ AMediaCodecBufferInfo bufferInfo;
+ ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(sys->decoder, &bufferInfo, -1);
+ if (outputBufferId >= 0)
+ {
+ sys->currentOutputBufferIndex = outputBufferId;
+
+ uint8_t* outputBuffer;
+ outputBuffer =
+ AMediaCodec_getOutputBuffer(sys->decoder, outputBufferId, &outputBufferSize);
+ sys->currentOutputBufferIndex = outputBufferId;
+
+ if (outputBufferSize !=
+ (sys->outputWidth * sys->outputHeight +
+ ((sys->outputWidth + 1) / 2) * ((sys->outputHeight + 1) / 2) * 2))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Error MediaCodec unexpected output buffer size %d",
+ outputBufferSize);
+ return -1;
+ }
+
+ // TODO: work with AImageReader and get AImage object instead of
+ // COLOR_FormatYUV420Planar buffer.
+ iStride[0] = sys->outputWidth;
+ iStride[1] = (sys->outputWidth + 1) / 2;
+ iStride[2] = (sys->outputWidth + 1) / 2;
+ pYUVData[0] = outputBuffer;
+ pYUVData[1] = outputBuffer + iStride[0] * sys->outputHeight;
+ pYUVData[2] = outputBuffer + iStride[0] * sys->outputHeight +
+ iStride[1] * ((sys->outputHeight + 1) / 2);
+ break;
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
+ {
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "MediaCodec failed updating output format in decompress");
+ return -1;
+ }
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "AMediaCodec_dequeueOutputBuffer need to try again later");
+ // TODO: sleep?
+ }
+ else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
+ {
+ WLog_Print(h264->log, WLOG_WARN,
+ "AMediaCodec_dequeueOutputBuffer returned deprecated value "
+ "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED, ignoring");
+ }
+ else
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "AMediaCodec_dequeueOutputBuffer returned unknown value [%d]",
+ outputBufferId);
+ return -1;
+ }
+ }
+
+ break;
+ }
+
+ return 1;
+}
+
+static void mediacodec_uninit(H264_CONTEXT* h264)
+{
+ media_status_t status = AMEDIA_OK;
+ H264_CONTEXT_MEDIACODEC* sys;
+
+ WINPR_ASSERT(h264);
+
+ sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Uninitializing MediaCodec");
+
+ if (!sys)
+ return;
+
+ if (sys->decoder != NULL)
+ {
+ release_current_outputbuffer(h264);
+ status = AMediaCodec_stop(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_stop %d", status);
+ }
+
+ status = AMediaCodec_delete(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_delete %d", status);
+ }
+
+ sys->decoder = NULL;
+ }
+
+ set_mediacodec_format(h264, &sys->inputFormat, NULL);
+ set_mediacodec_format(h264, &sys->outputFormat, NULL);
+
+ free(sys);
+ h264->pSystemData = NULL;
+}
+
+static BOOL mediacodec_init(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MEDIACODEC* sys;
+ media_status_t status;
+
+ WINPR_ASSERT(h264);
+
+ if (h264->Compressor)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Initializing MediaCodec");
+
+ sys = (H264_CONTEXT_MEDIACODEC*)calloc(1, sizeof(H264_CONTEXT_MEDIACODEC));
+
+ if (!sys)
+ {
+ goto EXCEPTION;
+ }
+
+ h264->pSystemData = (void*)sys;
+
+ sys->currentOutputBufferIndex = -1;
+
+ // updated when we're given the height and width for the first time
+ sys->width = sys->outputWidth = MEDIACODEC_MINIMUM_WIDTH;
+ sys->height = sys->outputHeight = MEDIACODEC_MINIMUM_HEIGHT;
+ sys->decoder = AMediaCodec_createDecoderByType(CODEC_NAME);
+ if (sys->decoder == NULL)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createCodecByName failed");
+ goto EXCEPTION;
+ }
+
+#if __ANDROID_API__ >= 28
+ char* codec_name;
+ status = AMediaCodec_getName(sys->decoder, &codec_name);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getName failed: %d", status);
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec using %s codec [%s]", CODEC_NAME, codec_name);
+ AMediaCodec_releaseName(sys->decoder, codec_name);
+#endif
+
+ set_mediacodec_format(h264, &sys->inputFormat,
+ mediacodec_format_new(h264->log, sys->width, sys->height));
+
+ status = AMediaCodec_configure(sys->decoder, sys->inputFormat, NULL, NULL, 0);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure failed: %d", status);
+ goto EXCEPTION;
+ }
+
+ if (update_mediacodec_inputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
+ goto EXCEPTION;
+ }
+
+ if (update_mediacodec_outputformat(h264) < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating output format");
+ goto EXCEPTION;
+ }
+
+ WLog_Print(h264->log, WLOG_DEBUG, "Starting MediaCodec");
+ status = AMediaCodec_start(sys->decoder);
+ if (status != AMEDIA_OK)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start failed %d", status);
+ goto EXCEPTION;
+ }
+
+ return TRUE;
+EXCEPTION:
+ mediacodec_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec = { "MediaCodec", mediacodec_init,
+ mediacodec_uninit, mediacodec_decompress,
+ mediacodec_compress };
diff --git a/libfreerdp/codec/h264_mf.c b/libfreerdp/codec/h264_mf.c
new file mode 100644
index 0000000..b5e65f5
--- /dev/null
+++ b/libfreerdp/codec/h264_mf.c
@@ -0,0 +1,595 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.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.
+ */
+
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <ks.h>
+#include <codecapi.h>
+
+#include <mfapi.h>
+#include <mferror.h>
+#include <wmcodecdsp.h>
+#include <mftransform.h>
+
+#include "h264.h"
+
+#define TAG FREERDP_TAG("codec")
+
+static const GUID sCLSID_CMSH264DecoderMFT = {
+ 0x62CE7E72, 0x4C71, 0x4d20, { 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D }
+};
+static const GUID sIID_IMFTransform = {
+ 0xbf94c121, 0x5b05, 0x4e6f, { 0x80, 0x00, 0xba, 0x59, 0x89, 0x61, 0x41, 0x4d }
+};
+static const GUID sMF_MT_MAJOR_TYPE = {
+ 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f }
+};
+static const GUID sMF_MT_FRAME_SIZE = {
+ 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d }
+};
+static const GUID sMF_MT_DEFAULT_STRIDE = {
+ 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 }
+};
+static const GUID sMF_MT_SUBTYPE = {
+ 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 }
+};
+static const GUID sMFMediaType_Video = {
+ 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }
+};
+static const GUID sMFVideoFormat_H264 = {
+ 0x34363248, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+};
+static const GUID sMFVideoFormat_IYUV = {
+ 0x56555949, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+};
+static const GUID sIID_ICodecAPI = {
+ 0x901db4c7, 0x31ce, 0x41a2, { 0x85, 0xdc, 0x8f, 0xa0, 0xbf, 0x41, 0xb8, 0xda }
+};
+static const GUID sCODECAPI_AVLowLatencyMode = {
+ 0x9c27891a, 0xed7a, 0x40e1, { 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee }
+};
+
+typedef HRESULT(__stdcall* pfnMFStartup)(ULONG Version, DWORD dwFlags);
+typedef HRESULT(__stdcall* pfnMFShutdown)(void);
+typedef HRESULT(__stdcall* pfnMFCreateSample)(IMFSample** ppIMFSample);
+typedef HRESULT(__stdcall* pfnMFCreateMemoryBuffer)(DWORD cbMaxLength, IMFMediaBuffer** ppBuffer);
+typedef HRESULT(__stdcall* pfnMFCreateMediaType)(IMFMediaType** ppMFType);
+
+typedef struct
+{
+ ICodecAPI* codecApi;
+ IMFTransform* transform;
+ IMFMediaType* inputType;
+ IMFMediaType* outputType;
+ IMFSample* sample;
+ UINT32 frameWidth;
+ UINT32 frameHeight;
+ IMFSample* outputSample;
+ IMFMediaBuffer* outputBuffer;
+ HMODULE mfplat;
+ pfnMFStartup MFStartup;
+ pfnMFShutdown MFShutdown;
+ pfnMFCreateSample MFCreateSample;
+ pfnMFCreateMemoryBuffer MFCreateMemoryBuffer;
+ pfnMFCreateMediaType MFCreateMediaType;
+} H264_CONTEXT_MF;
+
+static HRESULT mf_find_output_type(H264_CONTEXT_MF* sys, const GUID* guid,
+ IMFMediaType** ppMediaType)
+{
+ DWORD idx = 0;
+ GUID mediaGuid;
+ HRESULT hr = S_OK;
+ IMFMediaType* pMediaType = NULL;
+
+ while (1)
+ {
+ hr = sys->transform->lpVtbl->GetOutputAvailableType(sys->transform, 0, idx, &pMediaType);
+
+ if (FAILED(hr))
+ break;
+
+ pMediaType->lpVtbl->GetGUID(pMediaType, &sMF_MT_SUBTYPE, &mediaGuid);
+
+ if (IsEqualGUID(&mediaGuid, guid))
+ {
+ *ppMediaType = pMediaType;
+ return S_OK;
+ }
+
+ pMediaType->lpVtbl->Release(pMediaType);
+ idx++;
+ }
+
+ return hr;
+}
+
+static HRESULT mf_create_output_sample(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
+{
+ HRESULT hr = S_OK;
+ MFT_OUTPUT_STREAM_INFO streamInfo;
+
+ if (sys->outputSample)
+ {
+ sys->outputSample->lpVtbl->Release(sys->outputSample);
+ sys->outputSample = NULL;
+ }
+
+ hr = sys->MFCreateSample(&sys->outputSample);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetOutputStreamInfo failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateMemoryBuffer(streamInfo.cbSize, &sys->outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, sys->outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->outputBuffer->lpVtbl->Release(sys->outputBuffer);
+error:
+ return hr;
+}
+
+static int mf_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ HRESULT hr;
+ BYTE* pbBuffer = NULL;
+ DWORD cbMaxLength = 0;
+ DWORD cbCurrentLength = 0;
+ DWORD outputStatus = 0;
+ IMFSample* inputSample = NULL;
+ IMFMediaBuffer* inputBuffer = NULL;
+ IMFMediaBuffer* outputBuffer = NULL;
+ MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+ UINT32* iStride = h264->iStride;
+ BYTE** pYUVData = h264->pYUVData;
+ hr = sys->MFCreateMemoryBuffer(SrcSize, &inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = inputBuffer->lpVtbl->Lock(inputBuffer, &pbBuffer, &cbMaxLength, &cbCurrentLength);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ CopyMemory(pbBuffer, pSrcData, SrcSize);
+ hr = inputBuffer->lpVtbl->SetCurrentLength(inputBuffer, SrcSize);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetCurrentLength failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = inputBuffer->lpVtbl->Unlock(inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateSample(&inputSample);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ inputSample->lpVtbl->AddBuffer(inputSample, inputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ inputBuffer->lpVtbl->Release(inputBuffer);
+ hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, inputSample, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "ProcessInput failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ outputDataBuffer.dwStreamID = 0;
+ outputDataBuffer.dwStatus = 0;
+ outputDataBuffer.pEvents = NULL;
+ outputDataBuffer.pSample = sys->outputSample;
+ hr = sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &outputDataBuffer,
+ &outputStatus);
+
+ if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
+ {
+ UINT32 stride = 0;
+ UINT64 frameSize = 0;
+
+ if (sys->outputType)
+ {
+ sys->outputType->lpVtbl->Release(sys->outputType);
+ sys->outputType = NULL;
+ }
+
+ hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->outputType->lpVtbl->GetUINT64(sys->outputType, &sMF_MT_FRAME_SIZE, &frameSize);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "GetUINT64(MF_MT_FRAME_SIZE) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ sys->frameWidth = (UINT32)(frameSize >> 32);
+ sys->frameHeight = (UINT32)frameSize;
+ hr = sys->outputType->lpVtbl->GetUINT32(sys->outputType, &sMF_MT_DEFAULT_STRIDE, &stride);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "GetUINT32(MF_MT_DEFAULT_STRIDE) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ if (!avc420_ensure_buffer(h264, stride, sys->frameWidth, sys->frameHeight))
+ goto error;
+ }
+ else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
+ {
+ }
+ else if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "ProcessOutput failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+ else
+ {
+ int offset = 0;
+ BYTE* buffer = NULL;
+ DWORD bufferCount = 0;
+ DWORD cbMaxLength = 0;
+ DWORD cbCurrentLength = 0;
+ hr = sys->outputSample->lpVtbl->GetBufferCount(sys->outputSample, &bufferCount);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetBufferCount failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "GetBufferByIndex failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = outputBuffer->lpVtbl->Lock(outputBuffer, &buffer, &cbMaxLength, &cbCurrentLength);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ CopyMemory(pYUVData[0], &buffer[offset], iStride[0] * sys->frameHeight);
+ offset += iStride[0] * sys->frameHeight;
+ CopyMemory(pYUVData[1], &buffer[offset], iStride[1] * (sys->frameHeight / 2));
+ offset += iStride[1] * (sys->frameHeight / 2);
+ CopyMemory(pYUVData[2], &buffer[offset], iStride[2] * (sys->frameHeight / 2));
+ offset += iStride[2] * (sys->frameHeight / 2);
+ hr = outputBuffer->lpVtbl->Unlock(outputBuffer);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ outputBuffer->lpVtbl->Release(outputBuffer);
+ }
+
+ inputSample->lpVtbl->Release(inputSample);
+ return 1;
+error:
+ fprintf(stderr, "mf_decompress error\n");
+ return -1;
+}
+
+static int mf_compress(H264_CONTEXT* h264, const BYTE** ppSrcYuv, const UINT32* pStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+ return 1;
+}
+
+static BOOL mf_plat_loaded(H264_CONTEXT_MF* sys)
+{
+ return sys->MFStartup && sys->MFShutdown && sys->MFCreateSample && sys->MFCreateMemoryBuffer &&
+ sys->MFCreateMediaType;
+}
+
+static void mf_uninit(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
+
+ if (sys)
+ {
+ if (sys->transform)
+ {
+ sys->transform->lpVtbl->Release(sys->transform);
+ sys->transform = NULL;
+ }
+
+ if (sys->codecApi)
+ {
+ sys->codecApi->lpVtbl->Release(sys->codecApi);
+ sys->codecApi = NULL;
+ }
+
+ if (sys->inputType)
+ {
+ sys->inputType->lpVtbl->Release(sys->inputType);
+ sys->inputType = NULL;
+ }
+
+ if (sys->outputType)
+ {
+ sys->outputType->lpVtbl->Release(sys->outputType);
+ sys->outputType = NULL;
+ }
+
+ if (sys->outputSample)
+ {
+ sys->outputSample->lpVtbl->Release(sys->outputSample);
+ sys->outputSample = NULL;
+ }
+
+ if (sys->mfplat)
+ {
+ if (mf_plat_loaded(sys))
+ sys->MFShutdown();
+
+ FreeLibrary(sys->mfplat);
+ sys->mfplat = NULL;
+
+ if (mf_plat_loaded(sys))
+ CoUninitialize();
+ }
+
+ for (size_t x = 0; x < sizeof(h264->pYUVData) / sizeof(h264->pYUVData[0]); x++)
+ winpr_aligned_free(h264->pYUVData[x]);
+
+ memset(h264->pYUVData, 0, sizeof(h264->pYUVData));
+ memset(h264->iStride, 0, sizeof(h264->iStride));
+
+ free(sys);
+ h264->pSystemData = NULL;
+ }
+}
+
+static BOOL mf_init(H264_CONTEXT* h264)
+{
+ HRESULT hr;
+ H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)calloc(1, sizeof(H264_CONTEXT_MF));
+
+ if (!sys)
+ goto error;
+
+ h264->pSystemData = (void*)sys;
+ /* http://decklink-sdk-delphi.googlecode.com/svn/trunk/Blackmagic%20DeckLink%20SDK%209.7/Win/Samples/Streaming/StreamingPreview/DecoderMF.cpp
+ */
+ sys->mfplat = LoadLibraryA("mfplat.dll");
+
+ if (!sys->mfplat)
+ goto error;
+
+ sys->MFStartup = (pfnMFStartup)GetProcAddress(sys->mfplat, "MFStartup");
+ sys->MFShutdown = (pfnMFShutdown)GetProcAddress(sys->mfplat, "MFShutdown");
+ sys->MFCreateSample = (pfnMFCreateSample)GetProcAddress(sys->mfplat, "MFCreateSample");
+ sys->MFCreateMemoryBuffer =
+ (pfnMFCreateMemoryBuffer)GetProcAddress(sys->mfplat, "MFCreateMemoryBuffer");
+ sys->MFCreateMediaType = (pfnMFCreateMediaType)GetProcAddress(sys->mfplat, "MFCreateMediaType");
+
+ if (!mf_plat_loaded(sys))
+ goto error;
+
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ if (h264->Compressor)
+ {
+ }
+ else
+ {
+ VARIANT var = { 0 };
+ hr = sys->MFStartup(MF_VERSION, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = CoCreateInstance(&sCLSID_CMSH264DecoderMFT, NULL, CLSCTX_INPROC_SERVER,
+ &sIID_IMFTransform, (void**)&sys->transform);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "CoCreateInstance(CLSID_CMSH264DecoderMFT) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI,
+ (void**)&sys->codecApi);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "QueryInterface(IID_ICodecAPI) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ var.vt = VT_UI4;
+ var.ulVal = 1;
+ hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, &sCODECAPI_AVLowLatencyMode, &var);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "SetValue(CODECAPI_AVLowLatencyMode) failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->MFCreateMediaType(&sys->inputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "MFCreateMediaType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_MAJOR_TYPE,
+ &sMFMediaType_Video);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_MAJOR_TYPE) failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_SUBTYPE, &sMFVideoFormat_H264);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_SUBTYPE) failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetInputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
+ goto error;
+ }
+
+ hr = mf_create_output_sample(h264, sys);
+
+ if (FAILED(hr))
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
+ hr);
+ goto error;
+ }
+ }
+
+ return TRUE;
+error:
+ WLog_Print(h264->log, WLOG_ERROR, "mf_init failure");
+ mf_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF = { "MediaFoundation", mf_init, mf_uninit,
+ mf_decompress, mf_compress };
diff --git a/libfreerdp/codec/h264_openh264.c b/libfreerdp/codec/h264_openh264.c
new file mode 100644
index 0000000..2fa6a03
--- /dev/null
+++ b/libfreerdp/codec/h264_openh264.c
@@ -0,0 +1,632 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * H.264 Bitmap Compression
+ *
+ * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com>
+ * Copyright 2015 Vic Lee <llyzs.vic@gmail.com>
+ * Copyright 2014 Armin Novak <armin.novak@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.
+ */
+#include <freerdp/config.h>
+
+#include <winpr/library.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/h264.h>
+
+#include <wels/codec_def.h>
+#include <wels/codec_api.h>
+#include <wels/codec_ver.h>
+
+#include "h264.h"
+
+typedef void (*pWelsGetCodecVersionEx)(OpenH264Version* pVersion);
+
+typedef long (*pWelsCreateDecoder)(ISVCDecoder** ppDecoder);
+typedef void (*pWelsDestroyDecoder)(ISVCDecoder* pDecoder);
+
+typedef int (*pWelsCreateSVCEncoder)(ISVCEncoder** ppEncoder);
+typedef void (*pWelsDestroySVCEncoder)(ISVCEncoder* pEncoder);
+
+typedef struct
+{
+#if defined(WITH_OPENH264_LOADING)
+ HMODULE lib;
+ OpenH264Version version;
+#endif
+ pWelsGetCodecVersionEx WelsGetCodecVersionEx;
+ pWelsCreateDecoder WelsCreateDecoder;
+ pWelsDestroyDecoder WelsDestroyDecoder;
+ pWelsCreateSVCEncoder WelsCreateSVCEncoder;
+ pWelsDestroySVCEncoder WelsDestroySVCEncoder;
+ ISVCDecoder* pDecoder;
+ ISVCEncoder* pEncoder;
+ SEncParamExt EncParamExt;
+} H264_CONTEXT_OPENH264;
+
+#if defined(WITH_OPENH264_LOADING)
+static const char* openh264_library_names[] = {
+#if defined(_WIN32)
+ "openh264.dll"
+#elif defined(__APPLE__)
+ "libopenh264.dylib"
+#else
+ "libopenh264.so"
+#endif
+};
+#endif
+
+static void openh264_trace_callback(H264_CONTEXT* h264, int level, const char* message)
+{
+ if (h264)
+ WLog_Print(h264->log, WLOG_TRACE, "%d - %s", level, message);
+}
+
+static int openh264_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
+{
+ DECODING_STATE state = dsInvalidArgument;
+ SBufferInfo sBufferInfo = { 0 };
+ SSysMEMBuffer* pSystemBuffer = NULL;
+ H264_CONTEXT_OPENH264* sys = NULL;
+ UINT32* iStride = NULL;
+ BYTE** pYUVData = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pSrcData || (SrcSize == 0));
+
+ sys = (H264_CONTEXT_OPENH264*)h264->pSystemData;
+ WINPR_ASSERT(sys);
+
+ iStride = h264->iStride;
+ WINPR_ASSERT(iStride);
+
+ pYUVData = h264->pYUVData;
+ WINPR_ASSERT(pYUVData);
+
+ if (!sys->pDecoder)
+ return -2001;
+
+ /*
+ * Decompress the image. The RDP host only seems to send I420 format.
+ */
+ pYUVData[0] = NULL;
+ pYUVData[1] = NULL;
+ pYUVData[2] = NULL;
+
+ WINPR_ASSERT(sys->pDecoder);
+ state =
+ (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, pSrcData, SrcSize, pYUVData, &sBufferInfo);
+
+ if (sBufferInfo.iBufferStatus != 1)
+ {
+ if (state == dsNoParamSets)
+ {
+ /* this happens on the first frame due to missing parameter sets */
+ state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo);
+ }
+ else if (state == dsErrorFree)
+ {
+ /* call DecodeFrame2 again to decode without delay */
+ state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo);
+ }
+ else
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%04X iBufferStatus: %d", state,
+ sBufferInfo.iBufferStatus);
+ return -2002;
+ }
+ }
+
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state);
+ return -2003;
+ }
+
+#if OPENH264_MAJOR >= 2
+ state = (*sys->pDecoder)->FlushFrame(sys->pDecoder, pYUVData, &sBufferInfo);
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "FlushFrame state: 0x%02X", state);
+ return -2003;
+ }
+#endif
+
+ pSystemBuffer = &sBufferInfo.UsrData.sSystemBuffer;
+ iStride[0] = pSystemBuffer->iStride[0];
+ iStride[1] = pSystemBuffer->iStride[1];
+ iStride[2] = pSystemBuffer->iStride[1];
+
+ if (sBufferInfo.iBufferStatus != 1)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 iBufferStatus: %d",
+ sBufferInfo.iBufferStatus);
+ return 0;
+ }
+
+ if (state != dsErrorFree)
+ {
+ WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state);
+ return -2003;
+ }
+
+#if 0
+ WLog_Print(h264->log, WLOG_INFO,
+ "h264_decompress: state=%u, pYUVData=[%p,%p,%p], bufferStatus=%d, width=%d, height=%d, format=%d, stride=[%d,%d]",
+ state, (void*) pYUVData[0], (void*) pYUVData[1], (void*) pYUVData[2], sBufferInfo.iBufferStatus,
+ pSystemBuffer->iWidth, pSystemBuffer->iHeight, pSystemBuffer->iFormat,
+ pSystemBuffer->iStride[0], pSystemBuffer->iStride[1]);
+#endif
+
+ if (pSystemBuffer->iFormat != videoFormatI420)
+ return -2004;
+
+ if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2])
+ return -2005;
+
+ return 1;
+}
+
+static int openh264_compress(H264_CONTEXT* h264, const BYTE** pYUVData, const UINT32* iStride,
+ BYTE** ppDstData, UINT32* pDstSize)
+{
+ int status = 0;
+ SFrameBSInfo info = { 0 };
+ SSourcePicture pic = { 0 };
+
+ H264_CONTEXT_OPENH264* sys = NULL;
+
+ WINPR_ASSERT(h264);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ sys = &((H264_CONTEXT_OPENH264*)h264->pSystemData)[0];
+ WINPR_ASSERT(sys);
+
+ if (!sys->pEncoder)
+ return -1;
+
+ if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2])
+ return -1;
+
+ if ((h264->width > INT_MAX) || (h264->height > INT_MAX))
+ return -1;
+
+ if ((h264->FrameRate > INT_MAX) || (h264->NumberOfThreads > INT_MAX) ||
+ (h264->BitRate > INT_MAX) || (h264->QP > INT_MAX))
+ return -1;
+
+ WINPR_ASSERT(sys->pEncoder);
+ if ((sys->EncParamExt.iPicWidth != (int)h264->width) ||
+ (sys->EncParamExt.iPicHeight != (int)h264->height))
+ {
+ WINPR_ASSERT((*sys->pEncoder)->GetDefaultParams);
+ status = (*sys->pEncoder)->GetDefaultParams(sys->pEncoder, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to get OpenH264 default parameters (status=%d)", status);
+ return status;
+ }
+
+ sys->EncParamExt.iUsageType = SCREEN_CONTENT_REAL_TIME;
+ sys->EncParamExt.iPicWidth = (int)h264->width;
+ sys->EncParamExt.iPicHeight = (int)h264->height;
+ sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate;
+ sys->EncParamExt.iMaxBitrate = UNSPECIFIED_BIT_RATE;
+ sys->EncParamExt.bEnableDenoise = 0;
+ sys->EncParamExt.bEnableLongTermReference = 0;
+ sys->EncParamExt.bEnableFrameSkip = 0;
+ sys->EncParamExt.iSpatialLayerNum = 1;
+ sys->EncParamExt.iMultipleThreadIdc = (int)h264->NumberOfThreads;
+ sys->EncParamExt.sSpatialLayers[0].fFrameRate = h264->FrameRate;
+ sys->EncParamExt.sSpatialLayers[0].iVideoWidth = sys->EncParamExt.iPicWidth;
+ sys->EncParamExt.sSpatialLayers[0].iVideoHeight = sys->EncParamExt.iPicHeight;
+ sys->EncParamExt.sSpatialLayers[0].iMaxSpatialBitrate = sys->EncParamExt.iMaxBitrate;
+
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ sys->EncParamExt.iRCMode = RC_BITRATE_MODE;
+ sys->EncParamExt.iTargetBitrate = (int)h264->BitRate;
+ sys->EncParamExt.sSpatialLayers[0].iSpatialBitrate =
+ sys->EncParamExt.iTargetBitrate;
+ break;
+
+ case H264_RATECONTROL_CQP:
+ sys->EncParamExt.iRCMode = RC_OFF_MODE;
+ sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP;
+ break;
+ }
+
+ if (sys->EncParamExt.iMultipleThreadIdc > 1)
+ {
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ sys->EncParamExt.sSpatialLayers[0].sSliceCfg.uiSliceMode = SM_AUTO_SLICE;
+#else
+ sys->EncParamExt.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_FIXEDSLCNUM_SLICE;
+#endif
+ }
+
+ WINPR_ASSERT((*sys->pEncoder)->InitializeExt);
+ status = (*sys->pEncoder)->InitializeExt(sys->pEncoder, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize OpenH264 encoder (status=%d)",
+ status);
+ return status;
+ }
+
+ WINPR_ASSERT((*sys->pEncoder)->GetOption);
+ status =
+ (*sys->pEncoder)
+ ->GetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to get initial OpenH264 encoder parameters (status=%d)", status);
+ return status;
+ }
+ }
+ else
+ {
+ switch (h264->RateControlMode)
+ {
+ case H264_RATECONTROL_VBR:
+ if (sys->EncParamExt.iTargetBitrate != (int)h264->BitRate)
+ {
+ SBitrateInfo bitrate = { 0 };
+
+ sys->EncParamExt.iTargetBitrate = (int)h264->BitRate;
+ bitrate.iLayer = SPATIAL_LAYER_ALL;
+ bitrate.iBitrate = (int)h264->BitRate;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_BITRATE, &bitrate);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder bitrate (status=%d)", status);
+ return status;
+ }
+ }
+
+ if (sys->EncParamExt.fMaxFrameRate != (int)h264->FrameRate)
+ {
+ sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_FRAME_RATE,
+ &sys->EncParamExt.fMaxFrameRate);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder framerate (status=%d)", status);
+ return status;
+ }
+ }
+
+ break;
+
+ case H264_RATECONTROL_CQP:
+ if (sys->EncParamExt.sSpatialLayers[0].iDLayerQp != (int)h264->QP)
+ {
+ sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP;
+
+ WINPR_ASSERT((*sys->pEncoder)->SetOption);
+ status = (*sys->pEncoder)
+ ->SetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT,
+ &sys->EncParamExt);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set encoder parameters (status=%d)", status);
+ return status;
+ }
+ }
+
+ break;
+ }
+ }
+
+ pic.iPicWidth = (int)h264->width;
+ pic.iPicHeight = (int)h264->height;
+ pic.iColorFormat = videoFormatI420;
+ pic.iStride[0] = (int)iStride[0];
+ pic.iStride[1] = (int)iStride[1];
+ pic.iStride[2] = (int)iStride[2];
+ pic.pData[0] = (unsigned char*)pYUVData[0];
+ pic.pData[1] = (unsigned char*)pYUVData[1];
+ pic.pData[2] = (unsigned char*)pYUVData[2];
+
+ WINPR_ASSERT((*sys->pEncoder)->EncodeFrame);
+ status = (*sys->pEncoder)->EncodeFrame(sys->pEncoder, &pic, &info);
+
+ if (status < 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to encode frame (status=%d)", status);
+ return status;
+ }
+
+ *ppDstData = info.sLayerInfo[0].pBsBuf;
+ *pDstSize = 0;
+
+ for (int i = 0; i < info.iLayerNum; i++)
+ {
+ for (int j = 0; j < info.sLayerInfo[i].iNalCount; j++)
+ {
+ *pDstSize += info.sLayerInfo[i].pNalLengthInByte[j];
+ }
+ }
+
+ return 1;
+}
+
+static void openh264_uninit(H264_CONTEXT* h264)
+{
+ H264_CONTEXT_OPENH264* sysContexts = NULL;
+
+ WINPR_ASSERT(h264);
+
+ sysContexts = (H264_CONTEXT_OPENH264*)h264->pSystemData;
+
+ if (sysContexts)
+ {
+ for (UINT32 x = 0; x < h264->numSystemData; x++)
+ {
+ H264_CONTEXT_OPENH264* sys = &sysContexts[x];
+
+ if (sys->pDecoder)
+ {
+ (*sys->pDecoder)->Uninitialize(sys->pDecoder);
+ sysContexts->WelsDestroyDecoder(sys->pDecoder);
+ sys->pDecoder = NULL;
+ }
+
+ if (sys->pEncoder)
+ {
+ (*sys->pEncoder)->Uninitialize(sys->pEncoder);
+ sysContexts->WelsDestroySVCEncoder(sys->pEncoder);
+ sys->pEncoder = NULL;
+ }
+ }
+
+#if defined(WITH_OPENH264_LOADING)
+ if (sysContexts->lib)
+ FreeLibrary(sysContexts->lib);
+#endif
+ free(h264->pSystemData);
+ h264->pSystemData = NULL;
+ }
+}
+
+#if defined(WITH_OPENH264_LOADING)
+static BOOL openh264_load_functionpointers(H264_CONTEXT* h264, const char* name)
+{
+ H264_CONTEXT_OPENH264* sysContexts;
+
+ WINPR_ASSERT(name);
+
+ if (!h264)
+ return FALSE;
+
+ sysContexts = h264->pSystemData;
+
+ if (!sysContexts)
+ return FALSE;
+
+ sysContexts->lib = LoadLibraryA(name);
+
+ if (!sysContexts->lib)
+ return FALSE;
+
+ sysContexts->WelsGetCodecVersionEx =
+ (pWelsGetCodecVersionEx)GetProcAddress(sysContexts->lib, "WelsGetCodecVersionEx");
+ sysContexts->WelsCreateDecoder =
+ (pWelsCreateDecoder)GetProcAddress(sysContexts->lib, "WelsCreateDecoder");
+ sysContexts->WelsDestroyDecoder =
+ (pWelsDestroyDecoder)GetProcAddress(sysContexts->lib, "WelsDestroyDecoder");
+ sysContexts->WelsCreateSVCEncoder =
+ (pWelsCreateSVCEncoder)GetProcAddress(sysContexts->lib, "WelsCreateSVCEncoder");
+ sysContexts->WelsDestroySVCEncoder =
+ (pWelsDestroySVCEncoder)GetProcAddress(sysContexts->lib, "WelsDestroySVCEncoder");
+
+ if (!sysContexts->WelsCreateDecoder || !sysContexts->WelsDestroyDecoder ||
+ !sysContexts->WelsCreateSVCEncoder || !sysContexts->WelsDestroySVCEncoder ||
+ !sysContexts->WelsGetCodecVersionEx)
+ {
+ FreeLibrary(sysContexts->lib);
+ sysContexts->lib = NULL;
+ return FALSE;
+ }
+
+ sysContexts->WelsGetCodecVersionEx(&sysContexts->version);
+ WLog_Print(h264->log, WLOG_INFO, "loaded %s %d.%d.%d", name, sysContexts->version.uMajor,
+ sysContexts->version.uMinor, sysContexts->version.uRevision);
+
+ if ((sysContexts->version.uMajor < 1) ||
+ ((sysContexts->version.uMajor == 1) && (sysContexts->version.uMinor < 6)))
+ {
+ WLog_Print(
+ h264->log, WLOG_ERROR,
+ "OpenH264 %s %d.%d.%d is too old, need at least version 1.6.0 for dynamic loading",
+ name, sysContexts->version.uMajor, sysContexts->version.uMinor,
+ sysContexts->version.uRevision);
+ FreeLibrary(sysContexts->lib);
+ sysContexts->lib = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+static BOOL openh264_init(H264_CONTEXT* h264)
+{
+#if defined(WITH_OPENH264_LOADING)
+ BOOL success = FALSE;
+#endif
+ long status = 0;
+ H264_CONTEXT_OPENH264* sysContexts = NULL;
+ static int traceLevel = WELS_LOG_DEBUG;
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ static EVideoFormatType videoFormat = videoFormatI420;
+#endif
+ static WelsTraceCallback traceCallback = (WelsTraceCallback)openh264_trace_callback;
+
+ WINPR_ASSERT(h264);
+
+ h264->numSystemData = 1;
+ sysContexts =
+ (H264_CONTEXT_OPENH264*)calloc(h264->numSystemData, sizeof(H264_CONTEXT_OPENH264));
+
+ if (!sysContexts)
+ goto EXCEPTION;
+
+ h264->pSystemData = (void*)sysContexts;
+#if defined(WITH_OPENH264_LOADING)
+
+ for (size_t i = 0; i < ARRAYSIZE(openh264_library_names); i++)
+ {
+ const char* current = openh264_library_names[i];
+ success = openh264_load_functionpointers(h264, current);
+
+ if (success)
+ break;
+ }
+
+ if (!success)
+ goto EXCEPTION;
+
+#else
+ sysContexts->WelsGetCodecVersionEx = WelsGetCodecVersionEx;
+ sysContexts->WelsCreateDecoder = WelsCreateDecoder;
+ sysContexts->WelsDestroyDecoder = WelsDestroyDecoder;
+ sysContexts->WelsCreateSVCEncoder = WelsCreateSVCEncoder;
+ sysContexts->WelsDestroySVCEncoder = WelsDestroySVCEncoder;
+#endif
+
+ for (UINT32 x = 0; x < h264->numSystemData; x++)
+ {
+ SDecodingParam sDecParam = { 0 };
+ H264_CONTEXT_OPENH264* sys = &sysContexts[x];
+
+ if (h264->Compressor)
+ {
+ sysContexts->WelsCreateSVCEncoder(&sys->pEncoder);
+
+ if (!sys->pEncoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 encoder");
+ goto EXCEPTION;
+ }
+ }
+ else
+ {
+ sysContexts->WelsCreateDecoder(&sys->pDecoder);
+
+ if (!sys->pDecoder)
+ {
+ WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 decoder");
+ goto EXCEPTION;
+ }
+
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ sDecParam.eOutputColorFormat = videoFormatI420;
+#endif
+ sDecParam.eEcActiveIdc = ERROR_CON_FRAME_COPY;
+ sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
+ status = (*sys->pDecoder)->Initialize(sys->pDecoder, &sDecParam);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to initialize OpenH264 decoder (status=%ld)", status);
+ goto EXCEPTION;
+ }
+
+#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5)
+ status =
+ (*sys->pDecoder)->SetOption(sys->pDecoder, DECODER_OPTION_DATAFORMAT, &videoFormat);
+#endif
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set data format option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ if (WLog_GetLogLevel(h264->log) == WLOG_TRACE)
+ {
+ status = (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_LEVEL, &traceLevel);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set trace level option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ status =
+ (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK_CONTEXT, &h264);
+
+ if (status != 0)
+ {
+ WLog_Print(h264->log, WLOG_ERROR,
+ "Failed to set trace callback context option on OpenH264 decoder "
+ "(status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+
+ status =
+ (*sys->pDecoder)
+ ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK, &traceCallback);
+
+ if (status != 0)
+ {
+ WLog_Print(
+ h264->log, WLOG_ERROR,
+ "Failed to set trace callback option on OpenH264 decoder (status=%ld)",
+ status);
+ goto EXCEPTION;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+EXCEPTION:
+ openh264_uninit(h264);
+ return FALSE;
+}
+
+const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264 = { "OpenH264", openh264_init, openh264_uninit,
+ openh264_decompress, openh264_compress };
diff --git a/libfreerdp/codec/include/bitmap.c b/libfreerdp/codec/include/bitmap.c
new file mode 100644
index 0000000..af83387
--- /dev/null
+++ b/libfreerdp/codec/include/bitmap.c
@@ -0,0 +1,449 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RLE Compressed Bitmap Stream
+ *
+ * Copyright 2011 Jay Sorg <jay.sorg@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+/* do not compile the file directly */
+
+/**
+ * Write a foreground/background image to a destination buffer.
+ */
+static INLINE BYTE* WRITEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, UINT32 rowDelta,
+ BYTE bitmask, PIXEL fgPel, INT32 cBits)
+{
+ PIXEL xorPixel;
+ BYTE mask = 0x01;
+
+ if (cBits > 8)
+ {
+ WLog_ERR(TAG, "cBits %d > 8", cBits);
+ return NULL;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits))
+ return NULL;
+
+ UNROLL(cBits, {
+ UINT32 data;
+ DESTREADPIXEL(xorPixel, pbDest - rowDelta);
+
+ if (bitmask & mask)
+ data = xorPixel ^ fgPel;
+ else
+ data = xorPixel;
+
+ DESTWRITEPIXEL(pbDest, data);
+ mask = mask << 1;
+ });
+ return pbDest;
+}
+
+/**
+ * Write a foreground/background image to a destination buffer
+ * for the first line of compressed data.
+ */
+static INLINE BYTE* WRITEFIRSTLINEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, BYTE bitmask,
+ PIXEL fgPel, UINT32 cBits)
+{
+ BYTE mask = 0x01;
+
+ if (cBits > 8)
+ {
+ WLog_ERR(TAG, "cBits %d > 8", cBits);
+ return NULL;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits))
+ return NULL;
+
+ UNROLL(cBits, {
+ UINT32 data;
+
+ if (bitmask & mask)
+ data = fgPel;
+ else
+ data = BLACK_PIXEL;
+
+ DESTWRITEPIXEL(pbDest, data);
+ mask = mask << 1;
+ });
+ return pbDest;
+}
+
+/**
+ * Decompress an RLE compressed bitmap.
+ */
+static INLINE BOOL RLEDECOMPRESS(const BYTE* pbSrcBuffer, UINT32 cbSrcBuffer, BYTE* pbDestBuffer,
+ UINT32 rowDelta, UINT32 width, UINT32 height)
+{
+#if defined(WITH_DEBUG_CODECS)
+ char sbuffer[128] = { 0 };
+#endif
+ const BYTE* pbSrc = pbSrcBuffer;
+ const BYTE* pbEnd;
+ const BYTE* pbDestEnd;
+ BYTE* pbDest = pbDestBuffer;
+ PIXEL temp;
+ PIXEL fgPel = WHITE_PIXEL;
+ BOOL fInsertFgPel = FALSE;
+ BOOL fFirstLine = TRUE;
+ BYTE bitmask;
+ PIXEL pixelA, pixelB;
+ UINT32 runLength;
+ UINT32 code;
+ UINT32 advance = 0;
+ RLEEXTRA
+
+ if ((rowDelta == 0) || (rowDelta < width))
+ {
+ WLog_ERR(TAG, "Invalid arguments: rowDelta=%" PRIu32 " == 0 || < width=%" PRIu32, rowDelta,
+ width);
+ return FALSE;
+ }
+
+ if (!pbSrcBuffer || !pbDestBuffer)
+ {
+ WLog_ERR(TAG, "Invalid arguments: pbSrcBuffer=%p, pbDestBuffer=%p", pbSrcBuffer,
+ pbDestBuffer);
+ return FALSE;
+ }
+
+ pbEnd = pbSrcBuffer + cbSrcBuffer;
+ pbDestEnd = pbDestBuffer + rowDelta * height;
+
+ while (pbSrc < pbEnd)
+ {
+ /* Watch out for the end of the first scanline. */
+ if (fFirstLine)
+ {
+ if ((UINT32)(pbDest - pbDestBuffer) >= rowDelta)
+ {
+ fFirstLine = FALSE;
+ fInsertFgPel = FALSE;
+ }
+ }
+
+ /*
+ Extract the compression order code ID from the compression
+ order header.
+ */
+ code = ExtractCodeId(*pbSrc);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_VRB(TAG, "pbSrc=%p code=%s, rem=%" PRIuz, pbSrc,
+ rle_code_str_buffer(code, sbuffer, sizeof(sbuffer)), pbEnd - pbSrc);
+#endif
+
+ /* Handle Background Run Orders. */
+ if ((code == REGULAR_BG_RUN) || (code == MEGA_MEGA_BG_RUN))
+ {
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (fFirstLine)
+ {
+ if (fInsertFgPel)
+ {
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, fgPel);
+ runLength = runLength - 1;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, BLACK_PIXEL); });
+ }
+ else
+ {
+ if (fInsertFgPel)
+ {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, temp ^ fgPel);
+ runLength--;
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+ DESTWRITEPIXEL(pbDest, temp);
+ });
+ }
+
+ /* A follow-on background run order will need a foreground pel inserted. */
+ fInsertFgPel = TRUE;
+ continue;
+ }
+
+ /* For any of the other run-types a follow-on background run
+ order does not need a foreground pel inserted. */
+ fInsertFgPel = FALSE;
+
+ switch (code)
+ {
+ /* Handle Foreground Run Orders. */
+ case REGULAR_FG_RUN:
+ case MEGA_MEGA_FG_RUN:
+ case LITE_SET_FG_FG_RUN:
+ case MEGA_MEGA_SET_FG_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (code == LITE_SET_FG_FG_RUN || code == MEGA_MEGA_SET_FG_RUN)
+ {
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(fgPel, pbSrc);
+ }
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ if (fFirstLine)
+ {
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, fgPel); });
+ }
+ else
+ {
+ UNROLL(runLength, {
+ DESTREADPIXEL(temp, pbDest - rowDelta);
+ DESTWRITEPIXEL(pbDest, temp ^ fgPel);
+ });
+ }
+
+ break;
+
+ /* Handle Dithered Run Orders. */
+ case LITE_DITHERED_RUN:
+ case MEGA_MEGA_DITHERED_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelA, pbSrc);
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelB, pbSrc);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength * 2))
+ return FALSE;
+
+ UNROLL(runLength, {
+ DESTWRITEPIXEL(pbDest, pixelA);
+ DESTWRITEPIXEL(pbDest, pixelB);
+ });
+ break;
+
+ /* Handle Color Run Orders. */
+ case REGULAR_COLOR_RUN:
+ case MEGA_MEGA_COLOR_RUN:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(pixelA, pbSrc);
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, { DESTWRITEPIXEL(pbDest, pixelA); });
+ break;
+
+ /* Handle Foreground/Background Image Orders. */
+ case REGULAR_FGBG_IMAGE:
+ case MEGA_MEGA_FGBG_IMAGE:
+ case LITE_SET_FG_FGBG_IMAGE:
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+
+ if (code == LITE_SET_FG_FGBG_IMAGE || code == MEGA_MEGA_SET_FGBG_IMAGE)
+ {
+ if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd))
+ return FALSE;
+ SRCREADPIXEL(fgPel, pbSrc);
+ }
+
+ if (!buffer_within_range(pbSrc, runLength / 8, pbEnd))
+ return FALSE;
+ if (fFirstLine)
+ {
+ while (runLength > 8)
+ {
+ bitmask = *pbSrc;
+ pbSrc = pbSrc + 1;
+ pbDest = WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, 8);
+
+ if (!pbDest)
+ return FALSE;
+
+ runLength = runLength - 8;
+ }
+ }
+ else
+ {
+ while (runLength > 8)
+ {
+ bitmask = *pbSrc++;
+
+ pbDest = WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, 8);
+
+ if (!pbDest)
+ return FALSE;
+
+ runLength = runLength - 8;
+ }
+ }
+
+ if (runLength > 0)
+ {
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ bitmask = *pbSrc++;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, runLength);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, runLength);
+ }
+
+ if (!pbDest)
+ return FALSE;
+ }
+
+ break;
+
+ /* Handle Color Image Orders. */
+ case REGULAR_COLOR_IMAGE:
+ case MEGA_MEGA_COLOR_IMAGE:
+ runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance);
+ if (advance == 0)
+ return FALSE;
+ pbSrc = pbSrc + advance;
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength))
+ return FALSE;
+ if (!ENSURE_CAPACITY(pbSrc, pbEnd, runLength))
+ return FALSE;
+
+ UNROLL(runLength, {
+ SRCREADPIXEL(temp, pbSrc);
+ DESTWRITEPIXEL(pbDest, temp);
+ });
+ break;
+
+ /* Handle Special Order 1. */
+ case SPECIAL_FGBG_1:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg1, fgPel, 8);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg1, fgPel, 8);
+ }
+
+ if (!pbDest)
+ return FALSE;
+
+ break;
+
+ /* Handle Special Order 2. */
+ case SPECIAL_FGBG_2:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (fFirstLine)
+ {
+ pbDest =
+ WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg2, fgPel, 8);
+ }
+ else
+ {
+ pbDest =
+ WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg2, fgPel, 8);
+ }
+
+ if (!pbDest)
+ return FALSE;
+
+ break;
+
+ /* Handle White Order. */
+ case SPECIAL_WHITE:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, WHITE_PIXEL);
+ break;
+
+ /* Handle Black Order. */
+ case SPECIAL_BLACK:
+ if (!buffer_within_range(pbSrc, 1, pbEnd))
+ return FALSE;
+ pbSrc = pbSrc + 1;
+
+ if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1))
+ return FALSE;
+
+ DESTWRITEPIXEL(pbDest, BLACK_PIXEL);
+ break;
+
+ default:
+ WLog_ERR(TAG, "invalid code 0x%08" PRIx32 ", pbSrcBuffer=%p, pbSrc=%p, pbEnd=%p",
+ code, pbSrcBuffer, pbSrc, pbEnd);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/interleaved.c b/libfreerdp/codec/interleaved.c
new file mode 100644
index 0000000..75b2e27
--- /dev/null
+++ b/libfreerdp/codec/interleaved.c
@@ -0,0 +1,750 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Interleaved RLE Bitmap Codec
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <freerdp/codec/interleaved.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define UNROLL_BODY(_exp, _count) \
+ do \
+ { \
+ for (size_t x = 0; x < (_count); x++) \
+ { \
+ do \
+ { \
+ _exp \
+ } while (FALSE); \
+ } \
+ } while (FALSE)
+
+#define UNROLL_MULTIPLE(_condition, _exp, _count) \
+ do \
+ { \
+ while ((_condition) >= _count) \
+ { \
+ UNROLL_BODY(_exp, _count); \
+ (_condition) -= _count; \
+ } \
+ } while (FALSE)
+
+#define UNROLL(_condition, _exp) \
+ do \
+ { \
+ UNROLL_MULTIPLE(_condition, _exp, 16); \
+ UNROLL_MULTIPLE(_condition, _exp, 4); \
+ UNROLL_MULTIPLE(_condition, _exp, 1); \
+ } while (FALSE)
+
+/*
+ RLE Compressed Bitmap Stream (RLE_BITMAP_STREAM)
+ http://msdn.microsoft.com/en-us/library/cc240895%28v=prot.10%29.aspx
+ pseudo-code
+ http://msdn.microsoft.com/en-us/library/dd240593%28v=prot.10%29.aspx
+*/
+
+#define REGULAR_BG_RUN 0x00
+#define MEGA_MEGA_BG_RUN 0xF0
+#define REGULAR_FG_RUN 0x01
+#define MEGA_MEGA_FG_RUN 0xF1
+#define LITE_SET_FG_FG_RUN 0x0C
+#define MEGA_MEGA_SET_FG_RUN 0xF6
+#define LITE_DITHERED_RUN 0x0E
+#define MEGA_MEGA_DITHERED_RUN 0xF8
+#define REGULAR_COLOR_RUN 0x03
+#define MEGA_MEGA_COLOR_RUN 0xF3
+#define REGULAR_FGBG_IMAGE 0x02
+#define MEGA_MEGA_FGBG_IMAGE 0xF2
+#define LITE_SET_FG_FGBG_IMAGE 0x0D
+#define MEGA_MEGA_SET_FGBG_IMAGE 0xF7
+#define REGULAR_COLOR_IMAGE 0x04
+#define MEGA_MEGA_COLOR_IMAGE 0xF4
+#define SPECIAL_FGBG_1 0xF9
+#define SPECIAL_FGBG_2 0xFA
+#define SPECIAL_WHITE 0xFD
+#define SPECIAL_BLACK 0xFE
+
+#define BLACK_PIXEL 0x000000
+
+typedef UINT32 PIXEL;
+
+static const BYTE g_MaskSpecialFgBg1 = 0x03;
+static const BYTE g_MaskSpecialFgBg2 = 0x05;
+
+static const BYTE g_MaskRegularRunLength = 0x1F;
+static const BYTE g_MaskLiteRunLength = 0x0F;
+
+static const char* rle_code_str(UINT32 code)
+{
+ switch (code)
+ {
+ case REGULAR_BG_RUN:
+ return "REGULAR_BG_RUN";
+ case MEGA_MEGA_BG_RUN:
+ return "MEGA_MEGA_BG_RUN";
+ case REGULAR_FG_RUN:
+ return "REGULAR_FG_RUN";
+ case MEGA_MEGA_FG_RUN:
+ return "MEGA_MEGA_FG_RUN";
+ case LITE_SET_FG_FG_RUN:
+ return "LITE_SET_FG_FG_RUN";
+ case MEGA_MEGA_SET_FG_RUN:
+ return "MEGA_MEGA_SET_FG_RUN";
+ case LITE_DITHERED_RUN:
+ return "LITE_DITHERED_RUN";
+ case MEGA_MEGA_DITHERED_RUN:
+ return "MEGA_MEGA_DITHERED_RUN";
+ case REGULAR_COLOR_RUN:
+ return "REGULAR_COLOR_RUN";
+ case MEGA_MEGA_COLOR_RUN:
+ return "MEGA_MEGA_COLOR_RUN";
+ case REGULAR_FGBG_IMAGE:
+ return "REGULAR_FGBG_IMAGE";
+ case MEGA_MEGA_FGBG_IMAGE:
+ return "MEGA_MEGA_FGBG_IMAGE";
+ case LITE_SET_FG_FGBG_IMAGE:
+ return "LITE_SET_FG_FGBG_IMAGE";
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ return "MEGA_MEGA_SET_FGBG_IMAGE";
+ case REGULAR_COLOR_IMAGE:
+ return "REGULAR_COLOR_IMAGE";
+ case MEGA_MEGA_COLOR_IMAGE:
+ return "MEGA_MEGA_COLOR_IMAGE";
+ case SPECIAL_FGBG_1:
+ return "SPECIAL_FGBG_1";
+ case SPECIAL_FGBG_2:
+ return "SPECIAL_FGBG_2";
+ case SPECIAL_WHITE:
+ return "SPECIAL_WHITE";
+ case SPECIAL_BLACK:
+ return "SPECIAL_BLACK";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char* rle_code_str_buffer(UINT32 code, char* buffer, size_t size)
+{
+ const char* str = rle_code_str(code);
+ _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, code);
+ return buffer;
+}
+
+#define buffer_within_range(pbSrc, size, pbEnd) \
+ buffer_within_range_((pbSrc), (size), (pbEnd), __func__, __FILE__, __LINE__)
+static INLINE BOOL buffer_within_range_(const void* pbSrc, size_t size, const void* pbEnd,
+ const char* fkt, const char* file, size_t line)
+{
+ WINPR_UNUSED(file);
+ WINPR_ASSERT(pbSrc);
+ WINPR_ASSERT(pbEnd);
+
+ if ((const char*)pbSrc + size > (const char*)pbEnd)
+ {
+ WLog_ERR(TAG, "[%s:%" PRIuz "] pbSrc=%p + %" PRIuz " > pbEnd=%p", fkt, line, pbSrc, size,
+ pbEnd);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Reads the supplied order header and extracts the compression
+ * order code ID.
+ */
+static INLINE UINT32 ExtractCodeId(BYTE bOrderHdr)
+{
+ if ((bOrderHdr & 0xC0U) != 0xC0U)
+ {
+ /* REGULAR orders
+ * (000x xxxx, 001x xxxx, 010x xxxx, 011x xxxx, 100x xxxx)
+ */
+ return bOrderHdr >> 5;
+ }
+ else if ((bOrderHdr & 0xF0U) == 0xF0U)
+ {
+ /* MEGA and SPECIAL orders (0xF*) */
+ return bOrderHdr;
+ }
+ else
+ {
+ /* LITE orders
+ * 1100 xxxx, 1101 xxxx, 1110 xxxx)
+ */
+ return bOrderHdr >> 4;
+ }
+}
+
+/**
+ * Extract the run length of a compression order.
+ */
+static UINT ExtractRunLengthRegularFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = (*pbOrderHdr) & g_MaskRegularRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 1;
+ (*advance)++;
+ }
+ else
+ runLength = runLength * 8;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthLiteFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskLiteRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 1;
+ (*advance)++;
+ }
+ else
+ runLength = runLength * 8;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthRegular(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskRegularRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 32;
+ (*advance)++;
+ }
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthMegaMega(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ if (!buffer_within_range(pbOrderHdr, 2, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+
+ runLength = ((UINT16)pbOrderHdr[1]) | (((UINT16)pbOrderHdr[2]) << 8);
+ (*advance) += 2;
+
+ return runLength;
+}
+
+static UINT ExtractRunLengthLite(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance)
+{
+ UINT runLength = 0;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ runLength = *pbOrderHdr & g_MaskLiteRunLength;
+ if (runLength == 0)
+ {
+ if (!buffer_within_range(pbOrderHdr, 1, pbEnd))
+ {
+ *advance = 0;
+ return 0;
+ }
+ runLength = *(pbOrderHdr + 1) + 16;
+ (*advance)++;
+ }
+ return runLength;
+}
+
+static INLINE UINT32 ExtractRunLength(UINT32 code, const BYTE* pbOrderHdr, const BYTE* pbEnd,
+ UINT32* advance)
+{
+ UINT32 runLength = 0;
+ UINT32 ladvance = 1;
+
+ WINPR_ASSERT(pbOrderHdr);
+ WINPR_ASSERT(pbEnd);
+ WINPR_ASSERT(advance);
+
+ *advance = 0;
+ if (!buffer_within_range(pbOrderHdr, 0, pbEnd))
+ return 0;
+
+ switch (code)
+ {
+ case REGULAR_FGBG_IMAGE:
+ runLength = ExtractRunLengthRegularFgBg(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case LITE_SET_FG_FGBG_IMAGE:
+ runLength = ExtractRunLengthLiteFgBg(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case REGULAR_BG_RUN:
+ case REGULAR_FG_RUN:
+ case REGULAR_COLOR_RUN:
+ case REGULAR_COLOR_IMAGE:
+ runLength = ExtractRunLengthRegular(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case LITE_SET_FG_FG_RUN:
+ case LITE_DITHERED_RUN:
+ runLength = ExtractRunLengthLite(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ case MEGA_MEGA_BG_RUN:
+ case MEGA_MEGA_FG_RUN:
+ case MEGA_MEGA_SET_FG_RUN:
+ case MEGA_MEGA_DITHERED_RUN:
+ case MEGA_MEGA_COLOR_RUN:
+ case MEGA_MEGA_FGBG_IMAGE:
+ case MEGA_MEGA_SET_FGBG_IMAGE:
+ case MEGA_MEGA_COLOR_IMAGE:
+ runLength = ExtractRunLengthMegaMega(pbOrderHdr, pbEnd, &ladvance);
+ break;
+
+ default:
+ runLength = 0;
+ ladvance = 0;
+ break;
+ }
+
+ *advance = ladvance;
+ return runLength;
+}
+
+#define ensure_capacity(start, end, size, base) \
+ ensure_capacity_((start), (end), (size), (base), __func__, __FILE__, __LINE__)
+static INLINE BOOL ensure_capacity_(const BYTE* start, const BYTE* end, size_t size, size_t base,
+ const char* fkt, const char* file, size_t line)
+{
+ const size_t available = (uintptr_t)end - (uintptr_t)start;
+ const BOOL rc = available >= size * base;
+ const BOOL res = rc && (start <= end);
+
+ if (!res)
+ WLog_ERR(TAG,
+ "[%s:%" PRIuz "] failed: start=%p <= end=%p, available=%" PRIuz " >= size=%" PRIuz
+ " * base=%" PRIuz,
+ fkt, line, start, end, available, size, base);
+ return res;
+}
+
+static INLINE void write_pixel_8(BYTE* _buf, BYTE _pix)
+{
+ WINPR_ASSERT(_buf);
+ *_buf = _pix;
+}
+
+static INLINE void write_pixel_24(BYTE* _buf, UINT32 _pix)
+{
+ WINPR_ASSERT(_buf);
+ (_buf)[0] = (BYTE)(_pix);
+ (_buf)[1] = (BYTE)((_pix) >> 8);
+ (_buf)[2] = (BYTE)((_pix) >> 16);
+}
+
+static INLINE void write_pixel_16(BYTE* _buf, UINT16 _pix)
+{
+ WINPR_ASSERT(_buf);
+ _buf[0] = _pix & 0xFF;
+ _buf[1] = (_pix >> 8) & 0xFF;
+}
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 1
+#define PIXEL BYTE
+#define WHITE_PIXEL 0xFF
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_8(_buf, _pix); \
+ _buf += 1; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0]
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0]; \
+ _buf += 1; \
+ } while (0)
+
+#define WRITEFGBGIMAGE WriteFgBgImage8to8
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage8to8
+#define RLEDECOMPRESS RleDecompress8to8
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 1)
+#include "include/bitmap.c"
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 2
+#define PIXEL UINT16
+#define WHITE_PIXEL 0xFFFF
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_16(_buf, _pix); \
+ _buf += 2; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = ((UINT16*)(_buf))[0]
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0] | ((_buf)[1] << 8); \
+ _buf += 2; \
+ } while (0)
+#define WRITEFGBGIMAGE WriteFgBgImage16to16
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage16to16
+#define RLEDECOMPRESS RleDecompress16to16
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 2)
+#include "include/bitmap.c"
+
+#undef DESTWRITEPIXEL
+#undef DESTREADPIXEL
+#undef SRCREADPIXEL
+#undef WRITEFGBGIMAGE
+#undef WRITEFIRSTLINEFGBGIMAGE
+#undef RLEDECOMPRESS
+#undef RLEEXTRA
+#undef WHITE_PIXEL
+#undef PIXEL_SIZE
+#undef PIXEL
+#define PIXEL_SIZE 3
+#define PIXEL UINT32
+#define WHITE_PIXEL 0xffffff
+#define DESTWRITEPIXEL(_buf, _pix) \
+ do \
+ { \
+ write_pixel_24(_buf, _pix); \
+ _buf += 3; \
+ } while (0)
+#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16)
+#define SRCREADPIXEL(_pix, _buf) \
+ do \
+ { \
+ _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16); \
+ _buf += 3; \
+ } while (0)
+
+#define WRITEFGBGIMAGE WriteFgBgImage24to24
+#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage24to24
+#define RLEDECOMPRESS RleDecompress24to24
+#define RLEEXTRA
+#undef ENSURE_CAPACITY
+#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 3)
+#include "include/bitmap.c"
+
+struct S_BITMAP_INTERLEAVED_CONTEXT
+{
+ BOOL Compressor;
+
+ UINT32 TempSize;
+ BYTE* TempBuffer;
+
+ wStream* bts;
+};
+
+BOOL interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32 nSrcWidth, UINT32 nSrcHeight, UINT32 bpp,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight,
+ const gdiPalette* palette)
+{
+ UINT32 scanline = 0;
+ UINT32 SrcFormat = 0;
+ UINT32 BufferSize = 0;
+
+ if (!interleaved || !pSrcData || !pDstData)
+ {
+ WLog_ERR(TAG, "invalid arguments: interleaved=%p, pSrcData=%p, pDstData=%p", interleaved,
+ pSrcData, pDstData);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ scanline = nSrcWidth * 3;
+ SrcFormat = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ scanline = nSrcWidth * 2;
+ SrcFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ scanline = nSrcWidth * 2;
+ SrcFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ scanline = nSrcWidth;
+ SrcFormat = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp);
+ return FALSE;
+ }
+
+ BufferSize = scanline * nSrcHeight;
+
+ if (BufferSize > interleaved->TempSize)
+ {
+ interleaved->TempBuffer =
+ winpr_aligned_recalloc(interleaved->TempBuffer, BufferSize, sizeof(BYTE), 16);
+ interleaved->TempSize = BufferSize;
+ }
+
+ if (!interleaved->TempBuffer)
+ {
+ WLog_ERR(TAG, "interleaved->TempBuffer=%p", interleaved->TempBuffer);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ if (!RleDecompress24to24(pSrcData, SrcSize, interleaved->TempBuffer, scanline,
+ nSrcWidth, nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress24to24 failed");
+ return FALSE;
+ }
+
+ break;
+
+ case 16:
+ case 15:
+ if (!RleDecompress16to16(pSrcData, SrcSize, interleaved->TempBuffer, scanline,
+ nSrcWidth, nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress16to16 failed");
+ return FALSE;
+ }
+
+ break;
+
+ case 8:
+ if (!RleDecompress8to8(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nSrcWidth,
+ nSrcHeight))
+ {
+ WLog_ERR(TAG, "RleDecompress8to8 failed");
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp);
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight,
+ interleaved->TempBuffer, SrcFormat, scanline, 0, 0, palette,
+ FREERDP_FLIP_VERTICAL | FREERDP_KEEP_DST_ALPHA))
+ {
+ WLog_ERR(TAG, "freerdp_image_copy failed");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL interleaved_compress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pDstData, UINT32* pDstSize,
+ UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData, UINT32 SrcFormat,
+ UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette,
+ UINT32 bpp)
+{
+ BOOL status = 0;
+ wStream* s = NULL;
+ UINT32 DstFormat = 0;
+ const UINT32 maxSize = 64 * 64 * 4;
+
+ if (!interleaved || !pDstData || !pSrcData)
+ return FALSE;
+
+ if ((nWidth == 0) || (nHeight == 0))
+ return FALSE;
+
+ if (nWidth % 4)
+ {
+ WLog_ERR(TAG, "interleaved_compress: width is not a multiple of 4");
+ return FALSE;
+ }
+
+ if ((nWidth > 64) || (nHeight > 64))
+ {
+ WLog_ERR(TAG,
+ "interleaved_compress: width (%" PRIu32 ") or height (%" PRIu32
+ ") is greater than 64",
+ nWidth, nHeight);
+ return FALSE;
+ }
+
+ switch (bpp)
+ {
+ case 24:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+
+ case 16:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(interleaved->TempBuffer, DstFormat, 0, 0, 0, nWidth, nHeight, pSrcData,
+ SrcFormat, nSrcStep, nXSrc, nYSrc, palette, FREERDP_KEEP_DST_ALPHA))
+ return FALSE;
+
+ s = Stream_New(pDstData, *pDstSize);
+
+ if (!s)
+ return FALSE;
+
+ Stream_SetPosition(interleaved->bts, 0);
+
+ if (freerdp_bitmap_compress(interleaved->TempBuffer, nWidth, nHeight, s, bpp, maxSize,
+ nHeight - 1, interleaved->bts, 0) < 0)
+ status = FALSE;
+ else
+ status = TRUE;
+
+ Stream_SealLength(s);
+ *pDstSize = (UINT32)Stream_Length(s);
+ Stream_Free(s, FALSE);
+ return status;
+}
+
+BOOL bitmap_interleaved_context_reset(BITMAP_INTERLEAVED_CONTEXT* interleaved)
+{
+ if (!interleaved)
+ return FALSE;
+
+ return TRUE;
+}
+
+BITMAP_INTERLEAVED_CONTEXT* bitmap_interleaved_context_new(BOOL Compressor)
+{
+ BITMAP_INTERLEAVED_CONTEXT* interleaved = NULL;
+ interleaved = (BITMAP_INTERLEAVED_CONTEXT*)winpr_aligned_recalloc(
+ NULL, 1, sizeof(BITMAP_INTERLEAVED_CONTEXT), 32);
+
+ if (interleaved)
+ {
+ interleaved->TempSize = 64 * 64 * 4;
+ interleaved->TempBuffer = winpr_aligned_calloc(interleaved->TempSize, sizeof(BYTE), 16);
+
+ if (!interleaved->TempBuffer)
+ goto fail;
+
+ interleaved->bts = Stream_New(NULL, interleaved->TempSize);
+
+ if (!interleaved->bts)
+ goto fail;
+ }
+
+ return interleaved;
+
+fail:
+ bitmap_interleaved_context_free(interleaved);
+ return NULL;
+}
+
+void bitmap_interleaved_context_free(BITMAP_INTERLEAVED_CONTEXT* interleaved)
+{
+ if (!interleaved)
+ return;
+
+ winpr_aligned_free(interleaved->TempBuffer);
+ Stream_Free(interleaved->bts, TRUE);
+ winpr_aligned_free(interleaved);
+}
diff --git a/libfreerdp/codec/jpeg.c b/libfreerdp/codec/jpeg.c
new file mode 100644
index 0000000..65c8d28
--- /dev/null
+++ b/libfreerdp/codec/jpeg.c
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Compressed jpeg
+ *
+ * Copyright 2012 Jay Sorg <jay.sorg@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/stream.h>
+#include <winpr/image.h>
+
+#include <freerdp/codec/color.h>
+
+#include <freerdp/codec/jpeg.h>
+
+#ifdef WITH_JPEG
+
+/* jpeg decompress */
+BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp)
+{
+ BOOL rc = FALSE;
+
+ if (bpp != 24)
+ return FALSE;
+
+ wImage* image = winpr_image_new();
+ if (!image)
+ goto fail;
+
+ if (winpr_image_read_buffer(image, input, size) <= 0)
+ goto fail;
+
+ if ((image->width != width) || (image->height != height) || (image->bitsPerPixel != bpp))
+ goto fail;
+
+ memcpy(output, image->data, 1ull * image->scanline * image->height);
+ rc = TRUE;
+
+fail:
+ winpr_image_free(image, TRUE);
+ return rc;
+}
+
+#else
+
+BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp)
+{
+ return 0;
+}
+
+#endif
diff --git a/libfreerdp/codec/mppc.c b/libfreerdp/codec/mppc.c
new file mode 100644
index 0000000..e4f57a7
--- /dev/null
+++ b/libfreerdp/codec/mppc.c
@@ -0,0 +1,857 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * MPPC 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.
+ */
+
+#include <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include "mppc.h"
+
+#define TAG FREERDP_TAG("codec.mppc")
+
+//#define DEBUG_MPPC 1
+
+#define MPPC_MATCH_INDEX(_sym1, _sym2, _sym3) \
+ ((((MPPC_MATCH_TABLE[_sym3] << 16) + (MPPC_MATCH_TABLE[_sym2] << 8) + \
+ MPPC_MATCH_TABLE[_sym1]) & \
+ 0x07FFF000) >> \
+ 12)
+
+struct s_MPPC_CONTEXT
+{
+ ALIGN64 wBitStream* bs;
+ ALIGN64 BOOL Compressor;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[65536];
+ ALIGN64 UINT16 MatchBuffer[32768];
+ ALIGN64 UINT32 CompressionLevel;
+};
+
+static const UINT32 MPPC_MATCH_TABLE[256] = {
+ 0x00000000, 0x009CCF93, 0x01399F26, 0x01D66EB9, 0x02733E4C, 0x03100DDF, 0x03ACDD72, 0x0449AD05,
+ 0x04E67C98, 0x05834C2B, 0x06201BBE, 0x06BCEB51, 0x0759BAE4, 0x07F68A77, 0x08935A0A, 0x0930299D,
+ 0x09CCF930, 0x0A69C8C3, 0x0B069856, 0x0BA367E9, 0x0C40377C, 0x0CDD070F, 0x0D79D6A2, 0x0E16A635,
+ 0x0EB375C8, 0x0F50455B, 0x0FED14EE, 0x1089E481, 0x1126B414, 0x11C383A7, 0x1260533A, 0x12FD22CD,
+ 0x1399F260, 0x1436C1F3, 0x14D39186, 0x15706119, 0x160D30AC, 0x16AA003F, 0x1746CFD2, 0x17E39F65,
+ 0x18806EF8, 0x191D3E8B, 0x19BA0E1E, 0x1A56DDB1, 0x1AF3AD44, 0x1B907CD7, 0x1C2D4C6A, 0x1CCA1BFD,
+ 0x1D66EB90, 0x1E03BB23, 0x1EA08AB6, 0x1F3D5A49, 0x1FDA29DC, 0x2076F96F, 0x2113C902, 0x21B09895,
+ 0x224D6828, 0x22EA37BB, 0x2387074E, 0x2423D6E1, 0x24C0A674, 0x255D7607, 0x25FA459A, 0x2697152D,
+ 0x2733E4C0, 0x27D0B453, 0x286D83E6, 0x290A5379, 0x29A7230C, 0x2A43F29F, 0x2AE0C232, 0x2B7D91C5,
+ 0x2C1A6158, 0x2CB730EB, 0x2D54007E, 0x2DF0D011, 0x2E8D9FA4, 0x2F2A6F37, 0x2FC73ECA, 0x30640E5D,
+ 0x3100DDF0, 0x319DAD83, 0x323A7D16, 0x32D74CA9, 0x33741C3C, 0x3410EBCF, 0x34ADBB62, 0x354A8AF5,
+ 0x35E75A88, 0x36842A1B, 0x3720F9AE, 0x37BDC941, 0x385A98D4, 0x38F76867, 0x399437FA, 0x3A31078D,
+ 0x3ACDD720, 0x3B6AA6B3, 0x3C077646, 0x3CA445D9, 0x3D41156C, 0x3DDDE4FF, 0x3E7AB492, 0x3F178425,
+ 0x3FB453B8, 0x4051234B, 0x40EDF2DE, 0x418AC271, 0x42279204, 0x42C46197, 0x4361312A, 0x43FE00BD,
+ 0x449AD050, 0x45379FE3, 0x45D46F76, 0x46713F09, 0x470E0E9C, 0x47AADE2F, 0x4847ADC2, 0x48E47D55,
+ 0x49814CE8, 0x4A1E1C7B, 0x4ABAEC0E, 0x4B57BBA1, 0x4BF48B34, 0x4C915AC7, 0x4D2E2A5A, 0x4DCAF9ED,
+ 0x4E67C980, 0x4F049913, 0x4FA168A6, 0x503E3839, 0x50DB07CC, 0x5177D75F, 0x5214A6F2, 0x52B17685,
+ 0x534E4618, 0x53EB15AB, 0x5487E53E, 0x5524B4D1, 0x55C18464, 0x565E53F7, 0x56FB238A, 0x5797F31D,
+ 0x5834C2B0, 0x58D19243, 0x596E61D6, 0x5A0B3169, 0x5AA800FC, 0x5B44D08F, 0x5BE1A022, 0x5C7E6FB5,
+ 0x5D1B3F48, 0x5DB80EDB, 0x5E54DE6E, 0x5EF1AE01, 0x5F8E7D94, 0x602B4D27, 0x60C81CBA, 0x6164EC4D,
+ 0x6201BBE0, 0x629E8B73, 0x633B5B06, 0x63D82A99, 0x6474FA2C, 0x6511C9BF, 0x65AE9952, 0x664B68E5,
+ 0x66E83878, 0x6785080B, 0x6821D79E, 0x68BEA731, 0x695B76C4, 0x69F84657, 0x6A9515EA, 0x6B31E57D,
+ 0x6BCEB510, 0x6C6B84A3, 0x6D085436, 0x6DA523C9, 0x6E41F35C, 0x6EDEC2EF, 0x6F7B9282, 0x70186215,
+ 0x70B531A8, 0x7152013B, 0x71EED0CE, 0x728BA061, 0x73286FF4, 0x73C53F87, 0x74620F1A, 0x74FEDEAD,
+ 0x759BAE40, 0x76387DD3, 0x76D54D66, 0x77721CF9, 0x780EEC8C, 0x78ABBC1F, 0x79488BB2, 0x79E55B45,
+ 0x7A822AD8, 0x7B1EFA6B, 0x7BBBC9FE, 0x7C589991, 0x7CF56924, 0x7D9238B7, 0x7E2F084A, 0x7ECBD7DD,
+ 0x7F68A770, 0x80057703, 0x80A24696, 0x813F1629, 0x81DBE5BC, 0x8278B54F, 0x831584E2, 0x83B25475,
+ 0x844F2408, 0x84EBF39B, 0x8588C32E, 0x862592C1, 0x86C26254, 0x875F31E7, 0x87FC017A, 0x8898D10D,
+ 0x8935A0A0, 0x89D27033, 0x8A6F3FC6, 0x8B0C0F59, 0x8BA8DEEC, 0x8C45AE7F, 0x8CE27E12, 0x8D7F4DA5,
+ 0x8E1C1D38, 0x8EB8ECCB, 0x8F55BC5E, 0x8FF28BF1, 0x908F5B84, 0x912C2B17, 0x91C8FAAA, 0x9265CA3D,
+ 0x930299D0, 0x939F6963, 0x943C38F6, 0x94D90889, 0x9575D81C, 0x9612A7AF, 0x96AF7742, 0x974C46D5,
+ 0x97E91668, 0x9885E5FB, 0x9922B58E, 0x99BF8521, 0x9A5C54B4, 0x9AF92447, 0x9B95F3DA, 0x9C32C36D
+};
+
+int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ BYTE Literal = 0;
+ BYTE* SrcPtr = NULL;
+ UINT32 CopyOffset = 0;
+ UINT32 LengthOfMatch = 0;
+ UINT32 accumulator = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 HistoryBufferSize = 0;
+ UINT32 CompressionLevel = 0;
+ wBitStream* bs = NULL;
+
+ WINPR_ASSERT(mppc);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ bs = mppc->bs;
+ WINPR_ASSERT(bs);
+
+ HistoryBuffer = mppc->HistoryBuffer;
+ WINPR_ASSERT(HistoryBuffer);
+
+ HistoryBufferSize = mppc->HistoryBufferSize;
+ HistoryBufferEnd = &HistoryBuffer[HistoryBufferSize - 1];
+ CompressionLevel = mppc->CompressionLevel;
+ BitStream_Attach(bs, pSrcData, SrcSize);
+ BitStream_Fetch(bs);
+
+ if (flags & PACKET_AT_FRONT)
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = HistoryBuffer;
+ }
+
+ if (flags & PACKET_FLUSHED)
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = HistoryBuffer;
+ ZeroMemory(HistoryBuffer, mppc->HistoryBufferSize);
+ }
+
+ HistoryPtr = mppc->HistoryPtr;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ *pDstSize = SrcSize;
+ *ppDstData = pSrcData;
+ return 1;
+ }
+
+ while ((bs->length - bs->position) >= 8)
+ {
+ accumulator = bs->accumulator;
+
+ /**
+ * Literal Encoding
+ */
+
+ if (HistoryPtr > HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "history buffer index out of range");
+ return -1004;
+ }
+
+ if ((accumulator & 0x80000000) == 0x00000000)
+ {
+ /**
+ * Literal, less than 0x80
+ * bit 0 followed by the lower 7 bits of the literal
+ */
+ Literal = ((accumulator & 0x7F000000) >> 24);
+ *(HistoryPtr) = Literal;
+ HistoryPtr++;
+ BitStream_Shift(bs, 8);
+ continue;
+ }
+ else if ((accumulator & 0xC0000000) == 0x80000000)
+ {
+ /**
+ * Literal, greater than 0x7F
+ * bits 10 followed by the lower 7 bits of the literal
+ */
+ Literal = ((accumulator & 0x3F800000) >> 23) + 0x80;
+ *(HistoryPtr) = Literal;
+ HistoryPtr++;
+ BitStream_Shift(bs, 9);
+ continue;
+ }
+
+ /**
+ * CopyOffset Encoding
+ */
+ if (CompressionLevel) /* RDP5 */
+ {
+ if ((accumulator & 0xF8000000) == 0xF8000000)
+ {
+ /**
+ * CopyOffset, range [0, 63]
+ * bits 11111 + lower 6 bits of CopyOffset
+ */
+ CopyOffset = ((accumulator >> 21) & 0x3F);
+ BitStream_Shift(bs, 11);
+ }
+ else if ((accumulator & 0xF8000000) == 0xF0000000)
+ {
+ /**
+ * CopyOffset, range [64, 319]
+ * bits 11110 + lower 8 bits of (CopyOffset - 64)
+ */
+ CopyOffset = ((accumulator >> 19) & 0xFF) + 64;
+ BitStream_Shift(bs, 13);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * CopyOffset, range [320, 2367]
+ * bits 1110 + lower 11 bits of (CopyOffset - 320)
+ */
+ CopyOffset = ((accumulator >> 17) & 0x7FF) + 320;
+ BitStream_Shift(bs, 15);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * CopyOffset, range [2368, ]
+ * bits 110 + lower 16 bits of (CopyOffset - 2368)
+ */
+ CopyOffset = ((accumulator >> 13) & 0xFFFF) + 2368;
+ BitStream_Shift(bs, 19);
+ }
+ else
+ {
+ /* Invalid CopyOffset Encoding */
+ return -1001;
+ }
+ }
+ else /* RDP4 */
+ {
+ if ((accumulator & 0xF0000000) == 0xF0000000)
+ {
+ /**
+ * CopyOffset, range [0, 63]
+ * bits 1111 + lower 6 bits of CopyOffset
+ */
+ CopyOffset = ((accumulator >> 22) & 0x3F);
+ BitStream_Shift(bs, 10);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * CopyOffset, range [64, 319]
+ * bits 1110 + lower 8 bits of (CopyOffset - 64)
+ */
+ CopyOffset = ((accumulator >> 20) & 0xFF) + 64;
+ BitStream_Shift(bs, 12);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * CopyOffset, range [320, 8191]
+ * bits 110 + lower 13 bits of (CopyOffset - 320)
+ */
+ CopyOffset = ((accumulator >> 16) & 0x1FFF) + 320;
+ BitStream_Shift(bs, 16);
+ }
+ else
+ {
+ /* Invalid CopyOffset Encoding */
+ return -1002;
+ }
+ }
+
+ /**
+ * LengthOfMatch Encoding
+ */
+ accumulator = bs->accumulator;
+
+ if ((accumulator & 0x80000000) == 0x00000000)
+ {
+ /**
+ * LengthOfMatch [3]
+ * bit 0 + 0 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = 3;
+ BitStream_Shift(bs, 1);
+ }
+ else if ((accumulator & 0xC0000000) == 0x80000000)
+ {
+ /**
+ * LengthOfMatch [4, 7]
+ * bits 10 + 2 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 28) & 0x0003) + 0x0004;
+ BitStream_Shift(bs, 4);
+ }
+ else if ((accumulator & 0xE0000000) == 0xC0000000)
+ {
+ /**
+ * LengthOfMatch [8, 15]
+ * bits 110 + 3 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 26) & 0x0007) + 0x0008;
+ BitStream_Shift(bs, 6);
+ }
+ else if ((accumulator & 0xF0000000) == 0xE0000000)
+ {
+ /**
+ * LengthOfMatch [16, 31]
+ * bits 1110 + 4 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 24) & 0x000F) + 0x0010;
+ BitStream_Shift(bs, 8);
+ }
+ else if ((accumulator & 0xF8000000) == 0xF0000000)
+ {
+ /**
+ * LengthOfMatch [32, 63]
+ * bits 11110 + 5 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 22) & 0x001F) + 0x0020;
+ BitStream_Shift(bs, 10);
+ }
+ else if ((accumulator & 0xFC000000) == 0xF8000000)
+ {
+ /**
+ * LengthOfMatch [64, 127]
+ * bits 111110 + 6 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 20) & 0x003F) + 0x0040;
+ BitStream_Shift(bs, 12);
+ }
+ else if ((accumulator & 0xFE000000) == 0xFC000000)
+ {
+ /**
+ * LengthOfMatch [128, 255]
+ * bits 1111110 + 7 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 18) & 0x007F) + 0x0080;
+ BitStream_Shift(bs, 14);
+ }
+ else if ((accumulator & 0xFF000000) == 0xFE000000)
+ {
+ /**
+ * LengthOfMatch [256, 511]
+ * bits 11111110 + 8 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 16) & 0x00FF) + 0x0100;
+ BitStream_Shift(bs, 16);
+ }
+ else if ((accumulator & 0xFF800000) == 0xFF000000)
+ {
+ /**
+ * LengthOfMatch [512, 1023]
+ * bits 111111110 + 9 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 14) & 0x01FF) + 0x0200;
+ BitStream_Shift(bs, 18);
+ }
+ else if ((accumulator & 0xFFC00000) == 0xFF800000)
+ {
+ /**
+ * LengthOfMatch [1024, 2047]
+ * bits 1111111110 + 10 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 12) & 0x03FF) + 0x0400;
+ BitStream_Shift(bs, 20);
+ }
+ else if ((accumulator & 0xFFE00000) == 0xFFC00000)
+ {
+ /**
+ * LengthOfMatch [2048, 4095]
+ * bits 11111111110 + 11 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 10) & 0x07FF) + 0x0800;
+ BitStream_Shift(bs, 22);
+ }
+ else if ((accumulator & 0xFFF00000) == 0xFFE00000)
+ {
+ /**
+ * LengthOfMatch [4096, 8191]
+ * bits 111111111110 + 12 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 8) & 0x0FFF) + 0x1000;
+ BitStream_Shift(bs, 24);
+ }
+ else if (((accumulator & 0xFFF80000) == 0xFFF00000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [8192, 16383]
+ * bits 1111111111110 + 13 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 6) & 0x1FFF) + 0x2000;
+ BitStream_Shift(bs, 26);
+ }
+ else if (((accumulator & 0xFFFC0000) == 0xFFF80000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [16384, 32767]
+ * bits 11111111111110 + 14 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 4) & 0x3FFF) + 0x4000;
+ BitStream_Shift(bs, 28);
+ }
+ else if (((accumulator & 0xFFFE0000) == 0xFFFC0000) && CompressionLevel) /* RDP5 */
+ {
+ /**
+ * LengthOfMatch [32768, 65535]
+ * bits 111111111111110 + 15 lower bits of LengthOfMatch
+ */
+ LengthOfMatch = ((accumulator >> 2) & 0x7FFF) + 0x8000;
+ BitStream_Shift(bs, 30);
+ }
+ else
+ {
+ /* Invalid LengthOfMatch Encoding */
+ return -1003;
+ }
+
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch);
+#endif
+
+ if ((HistoryPtr + LengthOfMatch - 1) > HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "history buffer overflow");
+ return -1005;
+ }
+
+ SrcPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) &
+ (CompressionLevel ? 0xFFFF : 0x1FFF)];
+
+ do
+ {
+ *HistoryPtr++ = *SrcPtr++;
+ } while (--LengthOfMatch);
+ }
+
+ *pDstSize = (UINT32)(HistoryPtr - mppc->HistoryPtr);
+ *ppDstData = mppc->HistoryPtr;
+ mppc->HistoryPtr = HistoryPtr;
+ return 1;
+}
+
+int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ const BYTE* pSrcPtr = NULL;
+ const BYTE* pSrcEnd = NULL;
+ BYTE* MatchPtr = NULL;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ UINT32 MatchIndex = 0;
+ UINT32 accumulator = 0;
+ BOOL PacketFlushed = 0;
+ BOOL PacketAtFront = 0;
+ DWORD CopyOffset = 0;
+ DWORD LengthOfMatch = 0;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryPtr = NULL;
+ UINT32 HistoryOffset = 0;
+ UINT32 HistoryBufferSize = 0;
+ BYTE Sym1 = 0;
+ BYTE Sym2 = 0;
+ BYTE Sym3 = 0;
+ UINT32 CompressionLevel = 0;
+ wBitStream* bs = NULL;
+
+ WINPR_ASSERT(mppc);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstBuffer);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ bs = mppc->bs;
+ WINPR_ASSERT(bs);
+
+ HistoryBuffer = mppc->HistoryBuffer;
+ WINPR_ASSERT(HistoryBuffer);
+
+ HistoryBufferSize = mppc->HistoryBufferSize;
+ CompressionLevel = mppc->CompressionLevel;
+ HistoryOffset = mppc->HistoryOffset;
+ *pFlags = 0;
+ PacketFlushed = FALSE;
+
+ if (((HistoryOffset + SrcSize) < (HistoryBufferSize - 3)) && HistoryOffset)
+ {
+ PacketAtFront = FALSE;
+ }
+ else
+ {
+ if (HistoryOffset == (HistoryBufferSize + 1))
+ PacketFlushed = TRUE;
+
+ HistoryOffset = 0;
+ PacketAtFront = TRUE;
+ }
+
+ HistoryPtr = &(HistoryBuffer[HistoryOffset]);
+ pDstData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+
+ if (!pDstData)
+ return -1;
+
+ if (*pDstSize > SrcSize)
+ DstSize = SrcSize;
+ else
+ DstSize = *pDstSize;
+
+ BitStream_Attach(bs, pDstData, DstSize);
+ pSrcPtr = pSrcData;
+ pSrcEnd = &(pSrcData[SrcSize - 1]);
+
+ while (pSrcPtr < (pSrcEnd - 2))
+ {
+ Sym1 = pSrcPtr[0];
+ Sym2 = pSrcPtr[1];
+ Sym3 = pSrcPtr[2];
+ *HistoryPtr++ = *pSrcPtr++;
+ MatchIndex = MPPC_MATCH_INDEX(Sym1, Sym2, Sym3);
+ MatchPtr = &(HistoryBuffer[mppc->MatchBuffer[MatchIndex]]);
+
+ if (MatchPtr != (HistoryPtr - 1))
+ mppc->MatchBuffer[MatchIndex] = (UINT16)(HistoryPtr - HistoryBuffer);
+
+ if (mppc->HistoryPtr < HistoryPtr)
+ mppc->HistoryPtr = HistoryPtr;
+
+ if ((Sym1 != *(MatchPtr - 1)) || (Sym2 != MatchPtr[0]) || (Sym3 != MatchPtr[1]) ||
+ (&MatchPtr[1] > mppc->HistoryPtr) || (MatchPtr == HistoryBuffer) ||
+ (MatchPtr == (HistoryPtr - 1)) || (MatchPtr == HistoryPtr))
+ {
+ if (((bs->position / 8) + 2) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ accumulator = Sym1;
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "%" PRIu32 "", accumulator);
+#endif
+
+ if (accumulator < 0x80)
+ {
+ /* 8 bits of literal are encoded as-is */
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else
+ {
+ /* bits 10 followed by lower 7 bits of literal */
+ accumulator = 0x100 | (accumulator & 0x7F);
+ BitStream_Write_Bits(bs, accumulator, 9);
+ }
+ }
+ else
+ {
+ CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - MatchPtr);
+ *HistoryPtr++ = Sym2;
+ *HistoryPtr++ = Sym3;
+ pSrcPtr += 2;
+ LengthOfMatch = 3;
+ MatchPtr += 2;
+
+ while ((*pSrcPtr == *MatchPtr) && (pSrcPtr < pSrcEnd) && (MatchPtr <= mppc->HistoryPtr))
+ {
+ MatchPtr++;
+ *HistoryPtr++ = *pSrcPtr++;
+ LengthOfMatch++;
+ }
+
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch);
+#endif
+
+ /* Encode CopyOffset */
+
+ if (((bs->position / 8) + 7) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ if (CompressionLevel) /* RDP5 */
+ {
+ if (CopyOffset < 64)
+ {
+ /* bits 11111 + lower 6 bits of CopyOffset */
+ accumulator = 0x07C0 | (CopyOffset & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 11);
+ }
+ else if ((CopyOffset >= 64) && (CopyOffset < 320))
+ {
+ /* bits 11110 + lower 8 bits of (CopyOffset - 64) */
+ accumulator = 0x1E00 | ((CopyOffset - 64) & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 13);
+ }
+ else if ((CopyOffset >= 320) && (CopyOffset < 2368))
+ {
+ /* bits 1110 + lower 11 bits of (CopyOffset - 320) */
+ accumulator = 0x7000 | ((CopyOffset - 320) & 0x07FF);
+ BitStream_Write_Bits(bs, accumulator, 15);
+ }
+ else
+ {
+ /* bits 110 + lower 16 bits of (CopyOffset - 2368) */
+ accumulator = 0x060000 | ((CopyOffset - 2368) & 0xFFFF);
+ BitStream_Write_Bits(bs, accumulator, 19);
+ }
+ }
+ else /* RDP4 */
+ {
+ if (CopyOffset < 64)
+ {
+ /* bits 1111 + lower 6 bits of CopyOffset */
+ accumulator = 0x03C0 | (CopyOffset & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 10);
+ }
+ else if ((CopyOffset >= 64) && (CopyOffset < 320))
+ {
+ /* bits 1110 + lower 8 bits of (CopyOffset - 64) */
+ accumulator = 0x0E00 | ((CopyOffset - 64) & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 12);
+ }
+ else if ((CopyOffset >= 320) && (CopyOffset < 8192))
+ {
+ /* bits 110 + lower 13 bits of (CopyOffset - 320) */
+ accumulator = 0xC000 | ((CopyOffset - 320) & 0x1FFF);
+ BitStream_Write_Bits(bs, accumulator, 16);
+ }
+ }
+
+ /* Encode LengthOfMatch */
+
+ if (LengthOfMatch == 3)
+ {
+ /* 0 + 0 lower bits of LengthOfMatch */
+ BitStream_Write_Bits(bs, 0, 1);
+ }
+ else if ((LengthOfMatch >= 4) && (LengthOfMatch < 8))
+ {
+ /* 10 + 2 lower bits of LengthOfMatch */
+ accumulator = 0x0008 | (LengthOfMatch & 0x0003);
+ BitStream_Write_Bits(bs, accumulator, 4);
+ }
+ else if ((LengthOfMatch >= 8) && (LengthOfMatch < 16))
+ {
+ /* 110 + 3 lower bits of LengthOfMatch */
+ accumulator = 0x0030 | (LengthOfMatch & 0x0007);
+ BitStream_Write_Bits(bs, accumulator, 6);
+ }
+ else if ((LengthOfMatch >= 16) && (LengthOfMatch < 32))
+ {
+ /* 1110 + 4 lower bits of LengthOfMatch */
+ accumulator = 0x00E0 | (LengthOfMatch & 0x000F);
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else if ((LengthOfMatch >= 32) && (LengthOfMatch < 64))
+ {
+ /* 11110 + 5 lower bits of LengthOfMatch */
+ accumulator = 0x03C0 | (LengthOfMatch & 0x001F);
+ BitStream_Write_Bits(bs, accumulator, 10);
+ }
+ else if ((LengthOfMatch >= 64) && (LengthOfMatch < 128))
+ {
+ /* 111110 + 6 lower bits of LengthOfMatch */
+ accumulator = 0x0F80 | (LengthOfMatch & 0x003F);
+ BitStream_Write_Bits(bs, accumulator, 12);
+ }
+ else if ((LengthOfMatch >= 128) && (LengthOfMatch < 256))
+ {
+ /* 1111110 + 7 lower bits of LengthOfMatch */
+ accumulator = 0x3F00 | (LengthOfMatch & 0x007F);
+ BitStream_Write_Bits(bs, accumulator, 14);
+ }
+ else if ((LengthOfMatch >= 256) && (LengthOfMatch < 512))
+ {
+ /* 11111110 + 8 lower bits of LengthOfMatch */
+ accumulator = 0xFE00 | (LengthOfMatch & 0x00FF);
+ BitStream_Write_Bits(bs, accumulator, 16);
+ }
+ else if ((LengthOfMatch >= 512) && (LengthOfMatch < 1024))
+ {
+ /* 111111110 + 9 lower bits of LengthOfMatch */
+ accumulator = 0x3FC00 | (LengthOfMatch & 0x01FF);
+ BitStream_Write_Bits(bs, accumulator, 18);
+ }
+ else if ((LengthOfMatch >= 1024) && (LengthOfMatch < 2048))
+ {
+ /* 1111111110 + 10 lower bits of LengthOfMatch */
+ accumulator = 0xFF800 | (LengthOfMatch & 0x03FF);
+ BitStream_Write_Bits(bs, accumulator, 20);
+ }
+ else if ((LengthOfMatch >= 2048) && (LengthOfMatch < 4096))
+ {
+ /* 11111111110 + 11 lower bits of LengthOfMatch */
+ accumulator = 0x3FF000 | (LengthOfMatch & 0x07FF);
+ BitStream_Write_Bits(bs, accumulator, 22);
+ }
+ else if ((LengthOfMatch >= 4096) && (LengthOfMatch < 8192))
+ {
+ /* 111111111110 + 12 lower bits of LengthOfMatch */
+ accumulator = 0xFFE000 | (LengthOfMatch & 0x0FFF);
+ BitStream_Write_Bits(bs, accumulator, 24);
+ }
+ else if (((LengthOfMatch >= 8192) && (LengthOfMatch < 16384)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 1111111111110 + 13 lower bits of LengthOfMatch */
+ accumulator = 0x3FFC000 | (LengthOfMatch & 0x1FFF);
+ BitStream_Write_Bits(bs, accumulator, 26);
+ }
+ else if (((LengthOfMatch >= 16384) && (LengthOfMatch < 32768)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 11111111111110 + 14 lower bits of LengthOfMatch */
+ accumulator = 0xFFF8000 | (LengthOfMatch & 0x3FFF);
+ BitStream_Write_Bits(bs, accumulator, 28);
+ }
+ else if (((LengthOfMatch >= 32768) && (LengthOfMatch < 65536)) &&
+ CompressionLevel) /* RDP5 */
+ {
+ /* 111111111111110 + 15 lower bits of LengthOfMatch */
+ accumulator = 0x3FFF0000 | (LengthOfMatch & 0x7FFF);
+ BitStream_Write_Bits(bs, accumulator, 30);
+ }
+ }
+ }
+
+ /* Encode trailing symbols as literals */
+
+ while (pSrcPtr <= pSrcEnd)
+ {
+ if (((bs->position / 8) + 2) > (DstSize - 1))
+ {
+ mppc_context_reset(mppc, TRUE);
+ *pFlags |= PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ accumulator = *pSrcPtr;
+#if defined(DEBUG_MPPC)
+ WLog_DBG(TAG, "%" PRIu32 "", accumulator);
+#endif
+
+ if (accumulator < 0x80)
+ {
+ /* 8 bits of literal are encoded as-is */
+ BitStream_Write_Bits(bs, accumulator, 8);
+ }
+ else
+ {
+ /* bits 10 followed by lower 7 bits of literal */
+ accumulator = 0x100 | (accumulator & 0x7F);
+ BitStream_Write_Bits(bs, accumulator, 9);
+ }
+
+ *HistoryPtr++ = *pSrcPtr++;
+ }
+
+ BitStream_Flush(bs);
+ *pFlags |= PACKET_COMPRESSED;
+ *pFlags |= CompressionLevel;
+
+ if (PacketAtFront)
+ *pFlags |= PACKET_AT_FRONT;
+
+ if (PacketFlushed)
+ *pFlags |= PACKET_FLUSHED;
+
+ *pDstSize = ((bs->position + 7) / 8);
+ mppc->HistoryPtr = HistoryPtr;
+ mppc->HistoryOffset = HistoryPtr - HistoryBuffer;
+ return 1;
+}
+
+void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel)
+{
+ WINPR_ASSERT(mppc);
+
+ if (CompressionLevel < 1)
+ {
+ mppc->CompressionLevel = 0;
+ mppc->HistoryBufferSize = 8192;
+ }
+ else
+ {
+ mppc->CompressionLevel = 1;
+ mppc->HistoryBufferSize = 65536;
+ }
+}
+
+void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush)
+{
+ WINPR_ASSERT(mppc);
+
+ ZeroMemory(&(mppc->HistoryBuffer), sizeof(mppc->HistoryBuffer));
+ ZeroMemory(&(mppc->MatchBuffer), sizeof(mppc->MatchBuffer));
+
+ if (flush)
+ {
+ mppc->HistoryOffset = mppc->HistoryBufferSize + 1;
+ mppc->HistoryPtr = mppc->HistoryBuffer;
+ }
+ else
+ {
+ mppc->HistoryOffset = 0;
+ mppc->HistoryPtr = &(mppc->HistoryBuffer[mppc->HistoryOffset]);
+ }
+}
+
+MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor)
+{
+ MPPC_CONTEXT* mppc = calloc(1, sizeof(MPPC_CONTEXT));
+
+ if (!mppc)
+ goto fail;
+
+ mppc->Compressor = Compressor;
+
+ if (CompressionLevel < 1)
+ {
+ mppc->CompressionLevel = 0;
+ mppc->HistoryBufferSize = 8192;
+ }
+ else
+ {
+ mppc->CompressionLevel = 1;
+ mppc->HistoryBufferSize = 65536;
+ }
+
+ mppc->bs = BitStream_New();
+
+ if (!mppc->bs)
+ goto fail;
+
+ mppc_context_reset(mppc, FALSE);
+
+ return mppc;
+
+fail:
+ mppc_context_free(mppc);
+ return NULL;
+}
+
+void mppc_context_free(MPPC_CONTEXT* mppc)
+{
+ if (mppc)
+ {
+ BitStream_Free(mppc->bs);
+ free(mppc);
+ }
+}
diff --git a/libfreerdp/codec/mppc.h b/libfreerdp/codec/mppc.h
new file mode 100644
index 0000000..a4b3c48
--- /dev/null
+++ b/libfreerdp/codec/mppc.h
@@ -0,0 +1,54 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * MPPC 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.
+ */
+
+#ifndef FREERDP_MPPC_H
+#define FREERDP_MPPC_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include <winpr/bitstream.h>
+
+#include <freerdp/codec/bulk.h>
+
+typedef struct s_MPPC_CONTEXT MPPC_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags);
+
+ FREERDP_LOCAL void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel);
+
+ FREERDP_LOCAL void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush);
+
+ FREERDP_LOCAL MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor);
+ FREERDP_LOCAL void mppc_context_free(MPPC_CONTEXT* mppc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_MPPC_H */
diff --git a/libfreerdp/codec/ncrush.c b/libfreerdp/codec/ncrush.c
new file mode 100644
index 0000000..4a7162c
--- /dev/null
+++ b/libfreerdp/codec/ncrush.c
@@ -0,0 +1,3045 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NCrush (RDP6) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/types.h>
+
+#include "ncrush.h"
+
+#define TAG FREERDP_TAG("codec")
+
+struct s_NCRUSH_CONTEXT
+{
+ ALIGN64 BOOL Compressor;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryEndOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[65536];
+ ALIGN64 UINT32 HistoryBufferFence;
+ ALIGN64 UINT32 OffsetCache[4];
+ ALIGN64 UINT16 HashTable[65536];
+ ALIGN64 UINT16 MatchTable[65536];
+ ALIGN64 BYTE HuffTableCopyOffset[1024];
+ ALIGN64 BYTE HuffTableLOM[4096];
+};
+
+static const UINT16 HuffTableLEC[8192] = {
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD0AB, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A,
+ 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099,
+ 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F,
+ 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5,
+ 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052,
+ 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C,
+ 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093,
+ 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058,
+ 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D,
+ 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC,
+ 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A,
+ 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F,
+ 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066,
+ 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2,
+ 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D,
+ 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089,
+ 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096,
+ 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C,
+ 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032,
+ 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF,
+ 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045,
+ 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C,
+ 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063,
+ 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8,
+ 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055,
+ 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F,
+ 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xD101, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A,
+ 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099,
+ 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F,
+ 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5,
+ 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052,
+ 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C,
+ 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093,
+ 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058,
+ 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D,
+ 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC,
+ 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A,
+ 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F,
+ 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066,
+ 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2,
+ 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D,
+ 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089,
+ 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096,
+ 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C,
+ 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032,
+ 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF,
+ 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045,
+ 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C,
+ 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063,
+ 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8,
+ 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055,
+ 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F,
+ 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD100,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091,
+ 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056,
+ 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023,
+ 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9,
+ 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046,
+ 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D,
+ 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064,
+ 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3,
+ 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E,
+ 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A,
+ 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097,
+ 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D,
+ 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034,
+ 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0,
+ 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027,
+ 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4,
+ 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B,
+ 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B,
+ 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070,
+ 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9,
+ 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0,
+ 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A,
+ 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C,
+ 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF,
+ 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061,
+ 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D,
+ 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044,
+ 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC,
+ 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC,
+ 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6,
+ 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031,
+ 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1,
+ 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053,
+ 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D,
+ 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088,
+ 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB,
+ 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6,
+ 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094,
+ 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037,
+ 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8,
+ 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A,
+ 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074,
+ 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050,
+ 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3,
+ 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E,
+ 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD,
+ 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A,
+ 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA,
+ 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B,
+ 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086,
+ 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081,
+ 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3,
+ 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2,
+ 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0,
+ 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041,
+ 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6,
+ 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067,
+ 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068,
+ 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042,
+ 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7,
+ 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4,
+ 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1,
+ 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C,
+ 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB,
+ 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C,
+ 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087,
+ 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082,
+ 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5,
+ 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC,
+ 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095,
+ 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038,
+ 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9,
+ 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B,
+ 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075,
+ 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051,
+ 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4,
+ 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F,
+ 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE,
+ 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028,
+ 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5,
+ 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D,
+ 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C,
+ 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078,
+ 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA,
+ 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE,
+ 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B,
+ 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E,
+ 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1,
+ 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062,
+ 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E,
+ 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048,
+ 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD,
+ 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD,
+ 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7,
+ 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033,
+ 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2,
+ 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054,
+ 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E,
+ 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090,
+ 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104,
+ 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0,
+ 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092,
+ 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035,
+ 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5,
+ 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057,
+ 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072,
+ 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049,
+ 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1,
+ 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B,
+ 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA,
+ 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029,
+ 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8,
+ 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047,
+ 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083,
+ 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F,
+ 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED,
+ 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1,
+ 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E,
+ 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F,
+ 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4,
+ 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065,
+ 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B,
+ 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043,
+ 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA,
+ 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8,
+ 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4,
+ 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030,
+ 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE,
+ 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F,
+ 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B,
+ 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084,
+ 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9,
+ 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0,
+ 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098,
+ 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039,
+ 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD,
+ 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E,
+ 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079,
+ 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059,
+ 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7,
+ 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036,
+ 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1,
+ 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD125
+};
+
+static const UINT16 HuffTableLOM[512] = {
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x9018, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9013,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901C, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901A,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9016, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x8011,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x901E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9019, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x9014, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901D,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009,
+ 0x2001, 0x4006, 0x3004, 0x901B, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A,
+ 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008,
+ 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9017,
+ 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007,
+ 0x2001, 0x4006, 0x3004, 0x8011, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C,
+ 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901F
+};
+
+static const BYTE HuffTableMask[39] = {
+ 0x11, /* 0 */
+ 0x9E, /* 1 */
+ 0xA1, /* 2 */
+ 0x00, /* 3 */
+ 0x00, /* 4 */
+ 0x01, /* 5 */
+ 0x00, /* 6 */
+ 0x03, /* 7 */
+ 0x00, /* 8 */
+ 0x07, /* 9 */
+ 0x00, /* 10 */
+ 0x0F, /* 11 */
+ 0x00, /* 12 */
+ 0x1F, /* 13 */
+ 0x00, /* 14 */
+ 0x3F, /* 15 */
+ 0x00, /* 16 */
+ 0x7F, /* 17 */
+ 0x00, /* 18 */
+ 0xFF, /* 19 */
+ 0x00, /* 20 */
+ 0xFF, /* 21 */
+ 0x01, /* 22 */
+ 0xFF, /* 23 */
+ 0x03, /* 24 */
+ 0xFF, /* 25 */
+ 0x07, /* 26 */
+ 0xFF, /* 27 */
+ 0x0F, /* 28 */
+ 0xFF, /* 29 */
+ 0x1F, /* 30 */
+ 0xFF, /* 31 */
+ 0x3F, /* 32 */
+ 0xFF, /* 33 */
+ 0x7F, /* 34 */
+ 0xFF, /* 35 */
+ 0xFF, /* 36 */
+ 0x00, /* 37 */
+ 0x00 /* 38 */
+};
+
+static const BYTE HuffLengthLEC[294] = {
+ 6, /* 0 */
+ 6, /* 1 */
+ 6, /* 2 */
+ 7, /* 3 */
+ 7, /* 4 */
+ 7, /* 5 */
+ 7, /* 6 */
+ 7, /* 7 */
+ 7, /* 8 */
+ 7, /* 9 */
+ 7, /* 10 */
+ 8, /* 11 */
+ 8, /* 12 */
+ 8, /* 13 */
+ 8, /* 14 */
+ 8, /* 15 */
+ 8, /* 16 */
+ 8, /* 17 */
+ 9, /* 18 */
+ 8, /* 19 */
+ 9, /* 20 */
+ 9, /* 21 */
+ 9, /* 22 */
+ 9, /* 23 */
+ 8, /* 24 */
+ 8, /* 25 */
+ 9, /* 26 */
+ 9, /* 27 */
+ 9, /* 28 */
+ 9, /* 29 */
+ 9, /* 30 */
+ 9, /* 31 */
+ 8, /* 32 */
+ 9, /* 33 */
+ 9, /* 34 */
+ 10, /* 35 */
+ 9, /* 36 */
+ 9, /* 37 */
+ 9, /* 38 */
+ 9, /* 39 */
+ 9, /* 40 */
+ 9, /* 41 */
+ 9, /* 42 */
+ 10, /* 43 */
+ 9, /* 44 */
+ 10, /* 45 */
+ 10, /* 46 */
+ 10, /* 47 */
+ 9, /* 48 */
+ 9, /* 49 */
+ 10, /* 50 */
+ 9, /* 51 */
+ 10, /* 52 */
+ 9, /* 53 */
+ 10, /* 54 */
+ 9, /* 55 */
+ 9, /* 56 */
+ 9, /* 57 */
+ 10, /* 58 */
+ 10, /* 59 */
+ 9, /* 60 */
+ 10, /* 61 */
+ 9, /* 62 */
+ 9, /* 63 */
+ 8, /* 64 */
+ 9, /* 65 */
+ 9, /* 66 */
+ 9, /* 67 */
+ 9, /* 68 */
+ 10, /* 69 */
+ 10, /* 70 */
+ 10, /* 71 */
+ 9, /* 72 */
+ 9, /* 73 */
+ 10, /* 74 */
+ 10, /* 75 */
+ 10, /* 76 */
+ 10, /* 77 */
+ 10, /* 78 */
+ 10, /* 79 */
+ 9, /* 80 */
+ 9, /* 81 */
+ 10, /* 82 */
+ 10, /* 83 */
+ 10, /* 84 */
+ 10, /* 85 */
+ 10, /* 86 */
+ 10, /* 87 */
+ 10, /* 88 */
+ 9, /* 89 */
+ 10, /* 90 */
+ 10, /* 91 */
+ 10, /* 92 */
+ 10, /* 93 */
+ 10, /* 94 */
+ 10, /* 95 */
+ 8, /* 96 */
+ 10, /* 97 */
+ 10, /* 98 */
+ 10, /* 99 */
+ 10, /* 100 */
+ 10, /* 101 */
+ 10, /* 102 */
+ 10, /* 103 */
+ 10, /* 104 */
+ 10, /* 105 */
+ 10, /* 106 */
+ 10, /* 107 */
+ 10, /* 108 */
+ 10, /* 109 */
+ 10, /* 110 */
+ 10, /* 111 */
+ 9, /* 112 */
+ 10, /* 113 */
+ 10, /* 114 */
+ 10, /* 115 */
+ 10, /* 116 */
+ 10, /* 117 */
+ 10, /* 118 */
+ 10, /* 119 */
+ 9, /* 120 */
+ 10, /* 121 */
+ 10, /* 122 */
+ 10, /* 123 */
+ 10, /* 124 */
+ 10, /* 125 */
+ 10, /* 126 */
+ 9, /* 127 */
+ 7, /* 128 */
+ 9, /* 129 */
+ 9, /* 130 */
+ 10, /* 131 */
+ 9, /* 132 */
+ 10, /* 133 */
+ 10, /* 134 */
+ 10, /* 135 */
+ 9, /* 136 */
+ 10, /* 137 */
+ 10, /* 138 */
+ 10, /* 139 */
+ 10, /* 140 */
+ 10, /* 141 */
+ 10, /* 142 */
+ 10, /* 143 */
+ 9, /* 144 */
+ 10, /* 145 */
+ 10, /* 146 */
+ 10, /* 147 */
+ 10, /* 148 */
+ 10, /* 149 */
+ 10, /* 150 */
+ 10, /* 151 */
+ 10, /* 152 */
+ 10, /* 153 */
+ 10, /* 154 */
+ 10, /* 155 */
+ 10, /* 156 */
+ 10, /* 157 */
+ 10, /* 158 */
+ 10, /* 159 */
+ 10, /* 160 */
+ 10, /* 161 */
+ 10, /* 162 */
+ 10, /* 163 */
+ 10, /* 164 */
+ 10, /* 165 */
+ 10, /* 166 */
+ 10, /* 167 */
+ 10, /* 168 */
+ 10, /* 169 */
+ 10, /* 170 */
+ 13, /* 171 */
+ 10, /* 172 */
+ 10, /* 173 */
+ 10, /* 174 */
+ 10, /* 175 */
+ 10, /* 176 */
+ 10, /* 177 */
+ 11, /* 178 */
+ 10, /* 179 */
+ 10, /* 180 */
+ 10, /* 181 */
+ 10, /* 182 */
+ 10, /* 183 */
+ 10, /* 184 */
+ 10, /* 185 */
+ 10, /* 186 */
+ 10, /* 187 */
+ 10, /* 188 */
+ 10, /* 189 */
+ 10, /* 190 */
+ 10, /* 191 */
+ 9, /* 192 */
+ 10, /* 193 */
+ 10, /* 194 */
+ 10, /* 195 */
+ 10, /* 196 */
+ 10, /* 197 */
+ 9, /* 198 */
+ 10, /* 199 */
+ 10, /* 200 */
+ 10, /* 201 */
+ 10, /* 202 */
+ 10, /* 203 */
+ 9, /* 204 */
+ 10, /* 205 */
+ 10, /* 206 */
+ 10, /* 207 */
+ 9, /* 208 */
+ 10, /* 209 */
+ 10, /* 210 */
+ 10, /* 211 */
+ 10, /* 212 */
+ 10, /* 213 */
+ 10, /* 214 */
+ 10, /* 215 */
+ 10, /* 216 */
+ 10, /* 217 */
+ 10, /* 218 */
+ 10, /* 219 */
+ 10, /* 220 */
+ 10, /* 221 */
+ 10, /* 222 */
+ 10, /* 223 */
+ 9, /* 224 */
+ 10, /* 225 */
+ 10, /* 226 */
+ 10, /* 227 */
+ 10, /* 228 */
+ 10, /* 229 */
+ 10, /* 230 */
+ 10, /* 231 */
+ 10, /* 232 */
+ 10, /* 233 */
+ 10, /* 234 */
+ 10, /* 235 */
+ 10, /* 236 */
+ 10, /* 237 */
+ 9, /* 238 */
+ 10, /* 239 */
+ 8, /* 240 */
+ 9, /* 241 */
+ 9, /* 242 */
+ 10, /* 243 */
+ 9, /* 244 */
+ 10, /* 245 */
+ 10, /* 246 */
+ 10, /* 247 */
+ 9, /* 248 */
+ 10, /* 249 */
+ 10, /* 250 */
+ 10, /* 251 */
+ 9, /* 252 */
+ 9, /* 253 */
+ 8, /* 254 */
+ 7, /* 255 */
+ 13, /* 256 */
+ 13, /* 257 */
+ 7, /* 258 */
+ 7, /* 259 */
+ 10, /* 260 */
+ 7, /* 261 */
+ 7, /* 262 */
+ 6, /* 263 */
+ 6, /* 264 */
+ 6, /* 265 */
+ 6, /* 266 */
+ 5, /* 267 */
+ 6, /* 268 */
+ 6, /* 269 */
+ 6, /* 270 */
+ 5, /* 271 */
+ 6, /* 272 */
+ 5, /* 273 */
+ 6, /* 274 */
+ 6, /* 275 */
+ 6, /* 276 */
+ 6, /* 277 */
+ 6, /* 278 */
+ 6, /* 279 */
+ 6, /* 280 */
+ 6, /* 281 */
+ 6, /* 282 */
+ 6, /* 283 */
+ 6, /* 284 */
+ 6, /* 285 */
+ 6, /* 286 */
+ 6, /* 287 */
+ 8, /* 288 */
+ 5, /* 289 */
+ 6, /* 290 */
+ 7, /* 291 */
+ 7, /* 292 */
+ 13 /* 293 */
+};
+
+static const BYTE HuffCodeLEC[588] = {
+ 0x04, /* 0 */
+ 0x00, /* 1 */
+ 0x24, /* 2 */
+ 0x00, /* 3 */
+ 0x14, /* 4 */
+ 0x00, /* 5 */
+ 0x11, /* 6 */
+ 0x00, /* 7 */
+ 0x51, /* 8 */
+ 0x00, /* 9 */
+ 0x31, /* 10 */
+ 0x00, /* 11 */
+ 0x71, /* 12 */
+ 0x00, /* 13 */
+ 0x09, /* 14 */
+ 0x00, /* 15 */
+ 0x49, /* 16 */
+ 0x00, /* 17 */
+ 0x29, /* 18 */
+ 0x00, /* 19 */
+ 0x69, /* 20 */
+ 0x00, /* 21 */
+ 0x15, /* 22 */
+ 0x00, /* 23 */
+ 0x95, /* 24 */
+ 0x00, /* 25 */
+ 0x55, /* 26 */
+ 0x00, /* 27 */
+ 0xD5, /* 28 */
+ 0x00, /* 29 */
+ 0x35, /* 30 */
+ 0x00, /* 31 */
+ 0xB5, /* 32 */
+ 0x00, /* 33 */
+ 0x75, /* 34 */
+ 0x00, /* 35 */
+ 0x1D, /* 36 */
+ 0x00, /* 37 */
+ 0xF5, /* 38 */
+ 0x00, /* 39 */
+ 0x1D, /* 40 */
+ 0x01, /* 41 */
+ 0x9D, /* 42 */
+ 0x00, /* 43 */
+ 0x9D, /* 44 */
+ 0x01, /* 45 */
+ 0x5D, /* 46 */
+ 0x00, /* 47 */
+ 0x0D, /* 48 */
+ 0x00, /* 49 */
+ 0x8D, /* 50 */
+ 0x00, /* 51 */
+ 0x5D, /* 52 */
+ 0x01, /* 53 */
+ 0xDD, /* 54 */
+ 0x00, /* 55 */
+ 0xDD, /* 56 */
+ 0x01, /* 57 */
+ 0x3D, /* 58 */
+ 0x00, /* 59 */
+ 0x3D, /* 60 */
+ 0x01, /* 61 */
+ 0xBD, /* 62 */
+ 0x00, /* 63 */
+ 0x4D, /* 64 */
+ 0x00, /* 65 */
+ 0xBD, /* 66 */
+ 0x01, /* 67 */
+ 0x7D, /* 68 */
+ 0x00, /* 69 */
+ 0x6B, /* 70 */
+ 0x00, /* 71 */
+ 0x7D, /* 72 */
+ 0x01, /* 73 */
+ 0xFD, /* 74 */
+ 0x00, /* 75 */
+ 0xFD, /* 76 */
+ 0x01, /* 77 */
+ 0x03, /* 78 */
+ 0x00, /* 79 */
+ 0x03, /* 80 */
+ 0x01, /* 81 */
+ 0x83, /* 82 */
+ 0x00, /* 83 */
+ 0x83, /* 84 */
+ 0x01, /* 85 */
+ 0x6B, /* 86 */
+ 0x02, /* 87 */
+ 0x43, /* 88 */
+ 0x00, /* 89 */
+ 0x6B, /* 90 */
+ 0x01, /* 91 */
+ 0x6B, /* 92 */
+ 0x03, /* 93 */
+ 0xEB, /* 94 */
+ 0x00, /* 95 */
+ 0x43, /* 96 */
+ 0x01, /* 97 */
+ 0xC3, /* 98 */
+ 0x00, /* 99 */
+ 0xEB, /* 100 */
+ 0x02, /* 101 */
+ 0xC3, /* 102 */
+ 0x01, /* 103 */
+ 0xEB, /* 104 */
+ 0x01, /* 105 */
+ 0x23, /* 106 */
+ 0x00, /* 107 */
+ 0xEB, /* 108 */
+ 0x03, /* 109 */
+ 0x23, /* 110 */
+ 0x01, /* 111 */
+ 0xA3, /* 112 */
+ 0x00, /* 113 */
+ 0xA3, /* 114 */
+ 0x01, /* 115 */
+ 0x1B, /* 116 */
+ 0x00, /* 117 */
+ 0x1B, /* 118 */
+ 0x02, /* 119 */
+ 0x63, /* 120 */
+ 0x00, /* 121 */
+ 0x1B, /* 122 */
+ 0x01, /* 123 */
+ 0x63, /* 124 */
+ 0x01, /* 125 */
+ 0xE3, /* 126 */
+ 0x00, /* 127 */
+ 0xCD, /* 128 */
+ 0x00, /* 129 */
+ 0xE3, /* 130 */
+ 0x01, /* 131 */
+ 0x13, /* 132 */
+ 0x00, /* 133 */
+ 0x13, /* 134 */
+ 0x01, /* 135 */
+ 0x93, /* 136 */
+ 0x00, /* 137 */
+ 0x1B, /* 138 */
+ 0x03, /* 139 */
+ 0x9B, /* 140 */
+ 0x00, /* 141 */
+ 0x9B, /* 142 */
+ 0x02, /* 143 */
+ 0x93, /* 144 */
+ 0x01, /* 145 */
+ 0x53, /* 146 */
+ 0x00, /* 147 */
+ 0x9B, /* 148 */
+ 0x01, /* 149 */
+ 0x9B, /* 150 */
+ 0x03, /* 151 */
+ 0x5B, /* 152 */
+ 0x00, /* 153 */
+ 0x5B, /* 154 */
+ 0x02, /* 155 */
+ 0x5B, /* 156 */
+ 0x01, /* 157 */
+ 0x5B, /* 158 */
+ 0x03, /* 159 */
+ 0x53, /* 160 */
+ 0x01, /* 161 */
+ 0xD3, /* 162 */
+ 0x00, /* 163 */
+ 0xDB, /* 164 */
+ 0x00, /* 165 */
+ 0xDB, /* 166 */
+ 0x02, /* 167 */
+ 0xDB, /* 168 */
+ 0x01, /* 169 */
+ 0xDB, /* 170 */
+ 0x03, /* 171 */
+ 0x3B, /* 172 */
+ 0x00, /* 173 */
+ 0x3B, /* 174 */
+ 0x02, /* 175 */
+ 0x3B, /* 176 */
+ 0x01, /* 177 */
+ 0xD3, /* 178 */
+ 0x01, /* 179 */
+ 0x3B, /* 180 */
+ 0x03, /* 181 */
+ 0xBB, /* 182 */
+ 0x00, /* 183 */
+ 0xBB, /* 184 */
+ 0x02, /* 185 */
+ 0xBB, /* 186 */
+ 0x01, /* 187 */
+ 0xBB, /* 188 */
+ 0x03, /* 189 */
+ 0x7B, /* 190 */
+ 0x00, /* 191 */
+ 0x2D, /* 192 */
+ 0x00, /* 193 */
+ 0x7B, /* 194 */
+ 0x02, /* 195 */
+ 0x7B, /* 196 */
+ 0x01, /* 197 */
+ 0x7B, /* 198 */
+ 0x03, /* 199 */
+ 0xFB, /* 200 */
+ 0x00, /* 201 */
+ 0xFB, /* 202 */
+ 0x02, /* 203 */
+ 0xFB, /* 204 */
+ 0x01, /* 205 */
+ 0xFB, /* 206 */
+ 0x03, /* 207 */
+ 0x07, /* 208 */
+ 0x00, /* 209 */
+ 0x07, /* 210 */
+ 0x02, /* 211 */
+ 0x07, /* 212 */
+ 0x01, /* 213 */
+ 0x07, /* 214 */
+ 0x03, /* 215 */
+ 0x87, /* 216 */
+ 0x00, /* 217 */
+ 0x87, /* 218 */
+ 0x02, /* 219 */
+ 0x87, /* 220 */
+ 0x01, /* 221 */
+ 0x87, /* 222 */
+ 0x03, /* 223 */
+ 0x33, /* 224 */
+ 0x00, /* 225 */
+ 0x47, /* 226 */
+ 0x00, /* 227 */
+ 0x47, /* 228 */
+ 0x02, /* 229 */
+ 0x47, /* 230 */
+ 0x01, /* 231 */
+ 0x47, /* 232 */
+ 0x03, /* 233 */
+ 0xC7, /* 234 */
+ 0x00, /* 235 */
+ 0xC7, /* 236 */
+ 0x02, /* 237 */
+ 0xC7, /* 238 */
+ 0x01, /* 239 */
+ 0x33, /* 240 */
+ 0x01, /* 241 */
+ 0xC7, /* 242 */
+ 0x03, /* 243 */
+ 0x27, /* 244 */
+ 0x00, /* 245 */
+ 0x27, /* 246 */
+ 0x02, /* 247 */
+ 0x27, /* 248 */
+ 0x01, /* 249 */
+ 0x27, /* 250 */
+ 0x03, /* 251 */
+ 0xA7, /* 252 */
+ 0x00, /* 253 */
+ 0xB3, /* 254 */
+ 0x00, /* 255 */
+ 0x19, /* 256 */
+ 0x00, /* 257 */
+ 0xB3, /* 258 */
+ 0x01, /* 259 */
+ 0x73, /* 260 */
+ 0x00, /* 261 */
+ 0xA7, /* 262 */
+ 0x02, /* 263 */
+ 0x73, /* 264 */
+ 0x01, /* 265 */
+ 0xA7, /* 266 */
+ 0x01, /* 267 */
+ 0xA7, /* 268 */
+ 0x03, /* 269 */
+ 0x67, /* 270 */
+ 0x00, /* 271 */
+ 0xF3, /* 272 */
+ 0x00, /* 273 */
+ 0x67, /* 274 */
+ 0x02, /* 275 */
+ 0x67, /* 276 */
+ 0x01, /* 277 */
+ 0x67, /* 278 */
+ 0x03, /* 279 */
+ 0xE7, /* 280 */
+ 0x00, /* 281 */
+ 0xE7, /* 282 */
+ 0x02, /* 283 */
+ 0xE7, /* 284 */
+ 0x01, /* 285 */
+ 0xE7, /* 286 */
+ 0x03, /* 287 */
+ 0xF3, /* 288 */
+ 0x01, /* 289 */
+ 0x17, /* 290 */
+ 0x00, /* 291 */
+ 0x17, /* 292 */
+ 0x02, /* 293 */
+ 0x17, /* 294 */
+ 0x01, /* 295 */
+ 0x17, /* 296 */
+ 0x03, /* 297 */
+ 0x97, /* 298 */
+ 0x00, /* 299 */
+ 0x97, /* 300 */
+ 0x02, /* 301 */
+ 0x97, /* 302 */
+ 0x01, /* 303 */
+ 0x97, /* 304 */
+ 0x03, /* 305 */
+ 0x57, /* 306 */
+ 0x00, /* 307 */
+ 0x57, /* 308 */
+ 0x02, /* 309 */
+ 0x57, /* 310 */
+ 0x01, /* 311 */
+ 0x57, /* 312 */
+ 0x03, /* 313 */
+ 0xD7, /* 314 */
+ 0x00, /* 315 */
+ 0xD7, /* 316 */
+ 0x02, /* 317 */
+ 0xD7, /* 318 */
+ 0x01, /* 319 */
+ 0xD7, /* 320 */
+ 0x03, /* 321 */
+ 0x37, /* 322 */
+ 0x00, /* 323 */
+ 0x37, /* 324 */
+ 0x02, /* 325 */
+ 0x37, /* 326 */
+ 0x01, /* 327 */
+ 0x37, /* 328 */
+ 0x03, /* 329 */
+ 0xB7, /* 330 */
+ 0x00, /* 331 */
+ 0xB7, /* 332 */
+ 0x02, /* 333 */
+ 0xB7, /* 334 */
+ 0x01, /* 335 */
+ 0xB7, /* 336 */
+ 0x03, /* 337 */
+ 0x77, /* 338 */
+ 0x00, /* 339 */
+ 0x77, /* 340 */
+ 0x02, /* 341 */
+ 0xFF, /* 342 */
+ 0x07, /* 343 */
+ 0x77, /* 344 */
+ 0x01, /* 345 */
+ 0x77, /* 346 */
+ 0x03, /* 347 */
+ 0xF7, /* 348 */
+ 0x00, /* 349 */
+ 0xF7, /* 350 */
+ 0x02, /* 351 */
+ 0xF7, /* 352 */
+ 0x01, /* 353 */
+ 0xF7, /* 354 */
+ 0x03, /* 355 */
+ 0xFF, /* 356 */
+ 0x03, /* 357 */
+ 0x0F, /* 358 */
+ 0x00, /* 359 */
+ 0x0F, /* 360 */
+ 0x02, /* 361 */
+ 0x0F, /* 362 */
+ 0x01, /* 363 */
+ 0x0F, /* 364 */
+ 0x03, /* 365 */
+ 0x8F, /* 366 */
+ 0x00, /* 367 */
+ 0x8F, /* 368 */
+ 0x02, /* 369 */
+ 0x8F, /* 370 */
+ 0x01, /* 371 */
+ 0x8F, /* 372 */
+ 0x03, /* 373 */
+ 0x4F, /* 374 */
+ 0x00, /* 375 */
+ 0x4F, /* 376 */
+ 0x02, /* 377 */
+ 0x4F, /* 378 */
+ 0x01, /* 379 */
+ 0x4F, /* 380 */
+ 0x03, /* 381 */
+ 0xCF, /* 382 */
+ 0x00, /* 383 */
+ 0x0B, /* 384 */
+ 0x00, /* 385 */
+ 0xCF, /* 386 */
+ 0x02, /* 387 */
+ 0xCF, /* 388 */
+ 0x01, /* 389 */
+ 0xCF, /* 390 */
+ 0x03, /* 391 */
+ 0x2F, /* 392 */
+ 0x00, /* 393 */
+ 0x2F, /* 394 */
+ 0x02, /* 395 */
+ 0x0B, /* 396 */
+ 0x01, /* 397 */
+ 0x2F, /* 398 */
+ 0x01, /* 399 */
+ 0x2F, /* 400 */
+ 0x03, /* 401 */
+ 0xAF, /* 402 */
+ 0x00, /* 403 */
+ 0xAF, /* 404 */
+ 0x02, /* 405 */
+ 0xAF, /* 406 */
+ 0x01, /* 407 */
+ 0x8B, /* 408 */
+ 0x00, /* 409 */
+ 0xAF, /* 410 */
+ 0x03, /* 411 */
+ 0x6F, /* 412 */
+ 0x00, /* 413 */
+ 0x6F, /* 414 */
+ 0x02, /* 415 */
+ 0x8B, /* 416 */
+ 0x01, /* 417 */
+ 0x6F, /* 418 */
+ 0x01, /* 419 */
+ 0x6F, /* 420 */
+ 0x03, /* 421 */
+ 0xEF, /* 422 */
+ 0x00, /* 423 */
+ 0xEF, /* 424 */
+ 0x02, /* 425 */
+ 0xEF, /* 426 */
+ 0x01, /* 427 */
+ 0xEF, /* 428 */
+ 0x03, /* 429 */
+ 0x1F, /* 430 */
+ 0x00, /* 431 */
+ 0x1F, /* 432 */
+ 0x02, /* 433 */
+ 0x1F, /* 434 */
+ 0x01, /* 435 */
+ 0x1F, /* 436 */
+ 0x03, /* 437 */
+ 0x9F, /* 438 */
+ 0x00, /* 439 */
+ 0x9F, /* 440 */
+ 0x02, /* 441 */
+ 0x9F, /* 442 */
+ 0x01, /* 443 */
+ 0x9F, /* 444 */
+ 0x03, /* 445 */
+ 0x5F, /* 446 */
+ 0x00, /* 447 */
+ 0x4B, /* 448 */
+ 0x00, /* 449 */
+ 0x5F, /* 450 */
+ 0x02, /* 451 */
+ 0x5F, /* 452 */
+ 0x01, /* 453 */
+ 0x5F, /* 454 */
+ 0x03, /* 455 */
+ 0xDF, /* 456 */
+ 0x00, /* 457 */
+ 0xDF, /* 458 */
+ 0x02, /* 459 */
+ 0xDF, /* 460 */
+ 0x01, /* 461 */
+ 0xDF, /* 462 */
+ 0x03, /* 463 */
+ 0x3F, /* 464 */
+ 0x00, /* 465 */
+ 0x3F, /* 466 */
+ 0x02, /* 467 */
+ 0x3F, /* 468 */
+ 0x01, /* 469 */
+ 0x3F, /* 470 */
+ 0x03, /* 471 */
+ 0xBF, /* 472 */
+ 0x00, /* 473 */
+ 0xBF, /* 474 */
+ 0x02, /* 475 */
+ 0x4B, /* 476 */
+ 0x01, /* 477 */
+ 0xBF, /* 478 */
+ 0x01, /* 479 */
+ 0xAD, /* 480 */
+ 0x00, /* 481 */
+ 0xCB, /* 482 */
+ 0x00, /* 483 */
+ 0xCB, /* 484 */
+ 0x01, /* 485 */
+ 0xBF, /* 486 */
+ 0x03, /* 487 */
+ 0x2B, /* 488 */
+ 0x00, /* 489 */
+ 0x7F, /* 490 */
+ 0x00, /* 491 */
+ 0x7F, /* 492 */
+ 0x02, /* 493 */
+ 0x7F, /* 494 */
+ 0x01, /* 495 */
+ 0x2B, /* 496 */
+ 0x01, /* 497 */
+ 0x7F, /* 498 */
+ 0x03, /* 499 */
+ 0xFF, /* 500 */
+ 0x00, /* 501 */
+ 0xFF, /* 502 */
+ 0x02, /* 503 */
+ 0xAB, /* 504 */
+ 0x00, /* 505 */
+ 0xAB, /* 506 */
+ 0x01, /* 507 */
+ 0x6D, /* 508 */
+ 0x00, /* 509 */
+ 0x59, /* 510 */
+ 0x00, /* 511 */
+ 0xFF, /* 512 */
+ 0x17, /* 513 */
+ 0xFF, /* 514 */
+ 0x0F, /* 515 */
+ 0x39, /* 516 */
+ 0x00, /* 517 */
+ 0x79, /* 518 */
+ 0x00, /* 519 */
+ 0xFF, /* 520 */
+ 0x01, /* 521 */
+ 0x05, /* 522 */
+ 0x00, /* 523 */
+ 0x45, /* 524 */
+ 0x00, /* 525 */
+ 0x34, /* 526 */
+ 0x00, /* 527 */
+ 0x0C, /* 528 */
+ 0x00, /* 529 */
+ 0x2C, /* 530 */
+ 0x00, /* 531 */
+ 0x1C, /* 532 */
+ 0x00, /* 533 */
+ 0x00, /* 534 */
+ 0x00, /* 535 */
+ 0x3C, /* 536 */
+ 0x00, /* 537 */
+ 0x02, /* 538 */
+ 0x00, /* 539 */
+ 0x22, /* 540 */
+ 0x00, /* 541 */
+ 0x10, /* 542 */
+ 0x00, /* 543 */
+ 0x12, /* 544 */
+ 0x00, /* 545 */
+ 0x08, /* 546 */
+ 0x00, /* 547 */
+ 0x32, /* 548 */
+ 0x00, /* 549 */
+ 0x0A, /* 550 */
+ 0x00, /* 551 */
+ 0x2A, /* 552 */
+ 0x00, /* 553 */
+ 0x1A, /* 554 */
+ 0x00, /* 555 */
+ 0x3A, /* 556 */
+ 0x00, /* 557 */
+ 0x06, /* 558 */
+ 0x00, /* 559 */
+ 0x26, /* 560 */
+ 0x00, /* 561 */
+ 0x16, /* 562 */
+ 0x00, /* 563 */
+ 0x36, /* 564 */
+ 0x00, /* 565 */
+ 0x0E, /* 566 */
+ 0x00, /* 567 */
+ 0x2E, /* 568 */
+ 0x00, /* 569 */
+ 0x1E, /* 570 */
+ 0x00, /* 571 */
+ 0x3E, /* 572 */
+ 0x00, /* 573 */
+ 0x01, /* 574 */
+ 0x00, /* 575 */
+ 0xED, /* 576 */
+ 0x00, /* 577 */
+ 0x18, /* 578 */
+ 0x00, /* 579 */
+ 0x21, /* 580 */
+ 0x00, /* 581 */
+ 0x25, /* 582 */
+ 0x00, /* 583 */
+ 0x65, /* 584 */
+ 0x00, /* 585 */
+ 0xFF, /* 586 */
+ 0x1F /* 587 */
+};
+
+static const BYTE HuffLengthLOM[32] = {
+ 4, /* 0 */
+ 2, /* 1 */
+ 3, /* 2 */
+ 4, /* 3 */
+ 3, /* 4 */
+ 4, /* 5 */
+ 4, /* 6 */
+ 5, /* 7 */
+ 4, /* 8 */
+ 5, /* 9 */
+ 5, /* 10 */
+ 6, /* 11 */
+ 6, /* 12 */
+ 7, /* 13 */
+ 7, /* 14 */
+ 8, /* 15 */
+ 7, /* 16 */
+ 8, /* 17 */
+ 8, /* 18 */
+ 9, /* 19 */
+ 9, /* 20 */
+ 8, /* 21 */
+ 9, /* 22 */
+ 9, /* 23 */
+ 9, /* 24 */
+ 9, /* 25 */
+ 9, /* 26 */
+ 9, /* 27 */
+ 9, /* 28 */
+ 9, /* 29 */
+ 9, /* 30 */
+ 9 /* 31 */
+};
+
+static const UINT16 HuffCodeLOM[32] = {
+ 0x0001, /* 0 */
+ 0x0000, /* 1 */
+ 0x0002, /* 2 */
+ 0x0009, /* 3 */
+ 0x0006, /* 4 */
+ 0x0005, /* 5 */
+ 0x000D, /* 6 */
+ 0x000B, /* 7 */
+ 0x0003, /* 8 */
+ 0x001B, /* 9 */
+ 0x0007, /* 10 */
+ 0x0017, /* 11 */
+ 0x0037, /* 12 */
+ 0x000F, /* 13 */
+ 0x004F, /* 14 */
+ 0x006F, /* 15 */
+ 0x002F, /* 16 */
+ 0x00EF, /* 17 */
+ 0x001F, /* 18 */
+ 0x005F, /* 19 */
+ 0x015F, /* 20 */
+ 0x009F, /* 21 */
+ 0x00DF, /* 22 */
+ 0x01DF, /* 23 */
+ 0x003F, /* 24 */
+ 0x013F, /* 25 */
+ 0x00BF, /* 26 */
+ 0x01BF, /* 27 */
+ 0x007F, /* 28 */
+ 0x017F, /* 29 */
+ 0x00FF, /* 30 */
+ 0x01FF /* 31 */
+};
+
+static const UINT32 CopyOffsetBitsLUT[32] = {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x1, /* 4 */
+ 0x1, /* 5 */
+ 0x2, /* 6 */
+ 0x2, /* 7 */
+ 0x3, /* 8 */
+ 0x3, /* 9 */
+ 0x4, /* 10 */
+ 0x4, /* 11 */
+ 0x5, /* 12 */
+ 0x5, /* 13 */
+ 0x6, /* 14 */
+ 0x6, /* 15 */
+ 0x7, /* 16 */
+ 0x7, /* 17 */
+ 0x8, /* 18 */
+ 0x8, /* 19 */
+ 0x9, /* 20 */
+ 0x9, /* 21 */
+ 0xA, /* 22 */
+ 0xA, /* 23 */
+ 0xB, /* 24 */
+ 0xB, /* 25 */
+ 0xC, /* 26 */
+ 0xC, /* 27 */
+ 0xD, /* 28 */
+ 0xD, /* 29 */
+ 0xE, /* 30 */
+ 0xE /* 31 */
+};
+
+static const UINT32 CopyOffsetBaseLUT[32] = {
+ 0x1, /* 0 */
+ 0x2, /* 1 */
+ 0x3, /* 2 */
+ 0x4, /* 3 */
+ 0x5, /* 4 */
+ 0x7, /* 5 */
+ 0x9, /* 6 */
+ 0xD, /* 7 */
+ 0x11, /* 8 */
+ 0x19, /* 9 */
+ 0x21, /* 10 */
+ 0x31, /* 11 */
+ 0x41, /* 12 */
+ 0x61, /* 13 */
+ 0x81, /* 14 */
+ 0xC1, /* 15 */
+ 0x101, /* 16 */
+ 0x181, /* 17 */
+ 0x201, /* 18 */
+ 0x301, /* 19 */
+ 0x401, /* 20 */
+ 0x601, /* 21 */
+ 0x801, /* 22 */
+ 0xC01, /* 23 */
+ 0x1001, /* 24 */
+ 0x1801, /* 25 */
+ 0x2001, /* 26 */
+ 0x3001, /* 27 */
+ 0x4001, /* 28 */
+ 0x6001, /* 29 */
+ 0x8001, /* 30 */
+ 0xC001 /* 31 */
+};
+
+static const UINT32 LOMBitsLUT[30] = {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x0, /* 4 */
+ 0x0, /* 5 */
+ 0x0, /* 6 */
+ 0x0, /* 7 */
+ 0x1, /* 8 */
+ 0x1, /* 9 */
+ 0x1, /* 10 */
+ 0x1, /* 11 */
+ 0x2, /* 12 */
+ 0x2, /* 13 */
+ 0x2, /* 14 */
+ 0x2, /* 15 */
+ 0x3, /* 16 */
+ 0x3, /* 17 */
+ 0x3, /* 18 */
+ 0x3, /* 19 */
+ 0x4, /* 20 */
+ 0x4, /* 21 */
+ 0x4, /* 22 */
+ 0x4, /* 23 */
+ 0x6, /* 24 */
+ 0x6, /* 25 */
+ 0x8, /* 26 */
+ 0x8, /* 27 */
+ 0xE, /* 28 */
+ 0xE /* 29 */
+};
+
+static const UINT32 LOMBaseLUT[30] = {
+ 0x2, /* 0 */
+ 0x3, /* 1 */
+ 0x4, /* 2 */
+ 0x5, /* 3 */
+ 0x6, /* 4 */
+ 0x7, /* 5 */
+ 0x8, /* 6 */
+ 0x9, /* 7 */
+ 0xA, /* 8 */
+ 0xC, /* 9 */
+ 0xE, /* 10 */
+ 0x10, /* 11 */
+ 0x12, /* 12 */
+ 0x16, /* 13 */
+ 0x1A, /* 14 */
+ 0x1E, /* 15 */
+ 0x22, /* 16 */
+ 0x2A, /* 17 */
+ 0x32, /* 18 */
+ 0x3A, /* 19 */
+ 0x42, /* 20 */
+ 0x52, /* 21 */
+ 0x62, /* 22 */
+ 0x72, /* 23 */
+ 0x82, /* 24 */
+ 0xC2, /* 25 */
+ 0x102, /* 26 */
+ 0x202, /* 27 */
+ 0x2, /* 28 */
+ 0x2 /* 29 */
+};
+
+static INLINE UINT16 get_word(const BYTE* data)
+{
+ UINT16 tmp = 0;
+
+ WINPR_ASSERT(data);
+ tmp = *data++;
+ tmp |= *data << 8;
+ return tmp;
+}
+
+static INLINE UINT32 get_dword(const BYTE* data)
+{
+ UINT32 tmp = 0;
+ WINPR_ASSERT(data);
+ tmp = *data++;
+ tmp |= (UINT32)*data++ << 8U;
+ tmp |= (UINT32)*data++ << 16U;
+ tmp |= (UINT32)*data++ << 24U;
+ return tmp;
+}
+
+static INLINE BOOL NCrushFetchBits(const BYTE** SrcPtr, const BYTE** SrcEnd, INT32* nbits,
+ UINT32* bits)
+{
+ WINPR_ASSERT(SrcPtr);
+ WINPR_ASSERT(SrcEnd);
+ WINPR_ASSERT(nbits);
+ WINPR_ASSERT(bits);
+
+ if (*nbits < 16)
+ {
+ if ((*SrcPtr + 1) >= *SrcEnd)
+ {
+ if (*SrcPtr >= *SrcEnd)
+ {
+ if (*nbits < 0)
+ return FALSE;
+ }
+ else
+ {
+ *bits += *(*SrcPtr)++ << *nbits;
+ *nbits += 8;
+ }
+ }
+ else
+ {
+ UINT16 tmp = *(*SrcPtr)++;
+ tmp |= (*(*SrcPtr)++) << 8;
+ *bits += tmp << *nbits;
+ *nbits += 16;
+ }
+ }
+
+ return TRUE;
+}
+
+static INLINE void NCrushWriteStart(UINT32* bits, UINT32* offset, UINT32* accumulator)
+{
+ WINPR_ASSERT(bits);
+ WINPR_ASSERT(offset);
+ WINPR_ASSERT(accumulator);
+
+ *bits = 0;
+ *offset = 0;
+ *accumulator = 0;
+}
+
+static INLINE void NCrushWriteBits(BYTE** DstPtr, UINT32* accumulator, UINT32* offset, UINT32 _bits,
+ UINT32 _nbits)
+{
+ WINPR_ASSERT(DstPtr);
+ WINPR_ASSERT(accumulator);
+ WINPR_ASSERT(offset);
+
+ *accumulator |= _bits << *offset;
+ *offset += _nbits;
+
+ if (*offset >= 16)
+ {
+ *(*DstPtr)++ = (*accumulator & 0xFF);
+ *(*DstPtr)++ = ((*accumulator >> 8) & 0xFF);
+ *accumulator >>= 16;
+ *offset -= 16;
+ }
+}
+
+static INLINE void NCrushWriteFinish(BYTE** DstPtr, UINT32 accumulator)
+{
+ WINPR_ASSERT(DstPtr);
+
+ *(*DstPtr)++ = accumulator & 0xFF;
+ *(*DstPtr)++ = (accumulator >> 8) & 0xFF;
+}
+
+int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ UINT32 index = 0;
+ BYTE Literal = 0;
+ UINT32 IndexLEC = 0;
+ UINT32 BitLength = 0;
+ UINT32 CopyOffset = 0;
+ UINT32 CopyLength = 0;
+ UINT32 OldCopyOffset = 0;
+ const BYTE* CopyOffsetPtr = NULL;
+ UINT32 LengthOfMatch = 0;
+ UINT32 CopyOffsetIndex = 0;
+ UINT32 OffsetCacheIndex = 0;
+ UINT32 CopyOffsetBits = 0;
+ UINT32 CopyOffsetBase = 0;
+ UINT32 LengthOfMatchBits = 0;
+ UINT32 LengthOfMatchBase = 0;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ if (ncrush->HistoryEndOffset != 65535)
+ return -1001;
+
+ BYTE* HistoryBuffer = ncrush->HistoryBuffer;
+ const BYTE* HistoryBufferEnd = &HistoryBuffer[ncrush->HistoryEndOffset];
+
+ if (flags & PACKET_AT_FRONT)
+ {
+ if ((ncrush->HistoryPtr - 32768) <= HistoryBuffer)
+ return -1002;
+
+ MoveMemory(HistoryBuffer, (ncrush->HistoryPtr - 32768), 32768);
+ ncrush->HistoryPtr = &(HistoryBuffer[32768]);
+ ZeroMemory(&HistoryBuffer[32768], 32768);
+ }
+
+ if (flags & PACKET_FLUSHED)
+ {
+ ncrush->HistoryPtr = HistoryBuffer;
+ ZeroMemory(HistoryBuffer, sizeof(ncrush->HistoryBuffer));
+ ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache));
+ }
+
+ BYTE* HistoryPtr = ncrush->HistoryPtr;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ const BYTE* SrcEnd = &pSrcData[SrcSize];
+ const BYTE* SrcPtr = pSrcData + 4;
+
+ INT32 nbits = 32;
+ UINT32 bits = get_dword(pSrcData);
+ while (1)
+ {
+ while (1)
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[29]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits >= ARRAYSIZE(HuffTableLEC))
+ return -1;
+ IndexLEC = HuffTableLEC[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLEC[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (IndexLEC >= 256)
+ break;
+
+ if (HistoryPtr >= HistoryBufferEnd)
+ {
+ WLog_ERR(TAG, "ncrush_decompress error: HistoryPtr (%p) >= HistoryBufferEnd (%p)",
+ (const void*)HistoryPtr, (const void*)HistoryBufferEnd);
+ return -1003;
+ }
+
+ Literal = (HuffTableLEC[MaskedBits] & 0xFF);
+ *HistoryPtr++ = Literal;
+ }
+
+ if (IndexLEC == 256)
+ break; /* EOS */
+
+ CopyOffsetIndex = IndexLEC - 257;
+
+ if (CopyOffsetIndex >= 32)
+ {
+ OffsetCacheIndex = IndexLEC - 289;
+
+ if (OffsetCacheIndex >= 4)
+ return -1004;
+
+ {
+ CopyOffset = ncrush->OffsetCache[OffsetCacheIndex];
+ const UINT16 Mask = get_word(&HuffTableMask[21]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits > ARRAYSIZE(HuffTableLOM))
+ return -1;
+ LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLOM[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+ }
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+
+ LengthOfMatchBits = LOMBitsLUT[LengthOfMatch];
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ LengthOfMatchBase = LOMBaseLUT[LengthOfMatch];
+
+ if (LengthOfMatchBits)
+ {
+ const size_t idx = (2ull * LengthOfMatchBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ bits >>= LengthOfMatchBits;
+ nbits -= LengthOfMatchBits;
+ LengthOfMatchBase += MaskedBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+
+ OldCopyOffset = ncrush->OffsetCache[OffsetCacheIndex];
+ ncrush->OffsetCache[OffsetCacheIndex] = ncrush->OffsetCache[0];
+ ncrush->OffsetCache[0] = OldCopyOffset;
+ }
+ else
+ {
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT))
+ return -1;
+
+ CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex];
+
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBaseLUT))
+ return -1;
+ CopyOffsetBase = CopyOffsetBaseLUT[CopyOffsetIndex];
+ CopyOffset = CopyOffsetBase - 1;
+
+ if (CopyOffsetBits)
+ {
+ const size_t idx = (2ull * CopyOffsetBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ const UINT32 tmp = CopyOffsetBase + MaskedBits;
+ if (tmp < 1)
+ return -1;
+ CopyOffset = tmp - 1;
+ }
+ bits >>= CopyOffsetBits;
+ nbits -= CopyOffsetBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+ {
+ const UINT16 Mask = get_word(&HuffTableMask[21]);
+ const UINT32 MaskedBits = bits & Mask;
+ if (MaskedBits >= ARRAYSIZE(HuffTableLOM))
+ return -1;
+
+ LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF;
+ BitLength = HuffTableLOM[MaskedBits] >> 12;
+ bits >>= BitLength;
+ nbits -= BitLength;
+ }
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+
+ LengthOfMatchBits = LOMBitsLUT[LengthOfMatch];
+
+ if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ LengthOfMatchBase = LOMBaseLUT[LengthOfMatch];
+
+ if (LengthOfMatchBits)
+ {
+ const size_t idx = (2ull * LengthOfMatchBits) + 3ull;
+ if (idx >= ARRAYSIZE(HuffTableMask))
+ return -1;
+
+ const UINT16 Mask = get_word(&HuffTableMask[idx]);
+ const UINT32 MaskedBits = bits & Mask;
+ bits >>= LengthOfMatchBits;
+ nbits -= LengthOfMatchBits;
+ LengthOfMatchBase += MaskedBits;
+
+ if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits))
+ return -1;
+ }
+
+ ncrush->OffsetCache[3] = ncrush->OffsetCache[2];
+ ncrush->OffsetCache[2] = ncrush->OffsetCache[1];
+ ncrush->OffsetCache[1] = ncrush->OffsetCache[0];
+ ncrush->OffsetCache[0] = CopyOffset;
+ }
+
+ CopyOffsetPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) & 0xFFFF];
+ LengthOfMatch = LengthOfMatchBase;
+
+ if (LengthOfMatch < 2)
+ return -1005;
+
+ if ((CopyOffsetPtr >= (HistoryBufferEnd - LengthOfMatch)) ||
+ (HistoryPtr >= (HistoryBufferEnd - LengthOfMatch)))
+ return -1006;
+
+ CopyOffsetPtr = HistoryPtr - CopyOffset;
+ index = 0;
+ CopyLength = (LengthOfMatch > CopyOffset) ? CopyOffset : LengthOfMatch;
+
+ if (CopyOffsetPtr >= HistoryBuffer)
+ {
+ while (CopyLength > 0)
+ {
+ *HistoryPtr++ = *CopyOffsetPtr++;
+ CopyLength--;
+ }
+
+ while (LengthOfMatch > CopyOffset)
+ {
+ index = ((index >= CopyOffset)) ? 0 : index;
+ *HistoryPtr++ = *(CopyOffsetPtr + index++);
+ LengthOfMatch--;
+ }
+ }
+ else
+ {
+ CopyOffsetPtr = HistoryBufferEnd - (CopyOffset - (HistoryPtr - HistoryBuffer));
+ CopyOffsetPtr++;
+
+ while (CopyLength && (CopyOffsetPtr <= HistoryBufferEnd))
+ {
+ *HistoryPtr++ = *CopyOffsetPtr++;
+ CopyLength--;
+ }
+
+ CopyOffsetPtr = HistoryBuffer;
+
+ while (LengthOfMatch > CopyOffset)
+ {
+ index = ((index >= CopyOffset)) ? 0 : index;
+ *HistoryPtr++ = *(CopyOffsetPtr + index++);
+ LengthOfMatch--;
+ }
+ }
+
+ LengthOfMatch = LengthOfMatchBase;
+
+ if (LengthOfMatch == 2)
+ continue;
+ }
+
+ if (IndexLEC != 256)
+ return -1;
+
+ if (ncrush->HistoryBufferFence != 0xABABABAB)
+ {
+ WLog_ERR(TAG, "NCrushDecompress: history buffer fence was overwritten, potential buffer "
+ "overflow detected!");
+ return -1007;
+ }
+
+ const intptr_t hsize = HistoryPtr - ncrush->HistoryPtr;
+ WINPR_ASSERT(hsize >= 0);
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ *pDstSize = (UINT32)hsize;
+ *ppDstData = ncrush->HistoryPtr;
+ ncrush->HistoryPtr = HistoryPtr;
+ return 1;
+}
+
+static int ncrush_hash_table_add(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 HistoryOffset)
+{
+ const BYTE* SrcPtr = pSrcData;
+ UINT32 Hash = 0;
+ UINT32 Offset = HistoryOffset;
+ UINT32 EndOffset = Offset + SrcSize - 8;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(Offset + SrcSize >= 8);
+
+ while (Offset < EndOffset)
+ {
+ Hash = ncrush->HashTable[get_word(SrcPtr)];
+ ncrush->HashTable[get_word(SrcPtr)] = Offset;
+ ncrush->MatchTable[Offset] = Hash;
+ SrcPtr++;
+ Offset++;
+ }
+
+ return 1;
+}
+
+static int ncrush_find_match_length(const BYTE* Ptr1, const BYTE* Ptr2, BYTE* HistoryPtr)
+{
+ BYTE val1 = 0;
+ BYTE val2 = 0;
+ const BYTE* Ptr = Ptr1;
+
+ WINPR_ASSERT(Ptr1);
+ WINPR_ASSERT(Ptr2);
+ WINPR_ASSERT(HistoryPtr);
+
+ do
+ {
+ if (Ptr1 > HistoryPtr)
+ break;
+
+ val1 = *Ptr1++;
+ val2 = *Ptr2++;
+ } while (val1 == val2);
+
+ const intptr_t psize = Ptr1 - (Ptr + 1);
+ WINPR_ASSERT(psize <= INT_MAX);
+ WINPR_ASSERT(psize >= -INT_MAX);
+ return (int)psize;
+}
+
+static int ncrush_find_best_match(NCRUSH_CONTEXT* ncrush, UINT16 HistoryOffset,
+ UINT32* pMatchOffset)
+{
+ int Length = 0;
+ int MatchLength = 0;
+ BYTE* MatchPtr = NULL;
+ UINT16 Offset = 0;
+ UINT16 NextOffset = 0;
+ UINT16 MatchOffset = 0;
+ BYTE* HistoryBuffer = NULL;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pMatchOffset);
+
+ if (!ncrush->MatchTable[HistoryOffset])
+ return -1;
+
+ MatchLength = 2;
+ Offset = HistoryOffset;
+ HistoryBuffer = (BYTE*)ncrush->HistoryBuffer;
+ ncrush->MatchTable[0] = HistoryOffset;
+ MatchOffset = ncrush->MatchTable[HistoryOffset];
+ NextOffset = ncrush->MatchTable[Offset];
+ MatchPtr = &HistoryBuffer[MatchLength];
+
+ for (int i = 0; i < 4; i++)
+ {
+ int j = -1;
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 0;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 1;
+ }
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 2;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 3;
+ }
+
+ if (j < 0)
+ {
+ Offset = ncrush->MatchTable[NextOffset];
+
+ if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 4;
+ }
+
+ if (j < 0)
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+
+ if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength])
+ j = 5;
+ }
+
+ if (j >= 0)
+ {
+ if ((j % 2) == 0)
+ Offset = NextOffset;
+
+ if ((Offset != HistoryOffset) && Offset)
+ {
+ Length = ncrush_find_match_length(&HistoryBuffer[HistoryOffset + 2],
+ &HistoryBuffer[Offset + 2], ncrush->HistoryPtr) +
+ 2;
+
+ if (Length < 2)
+ return -1;
+
+ if (Length > 16)
+ break;
+
+ if (Length > MatchLength)
+ {
+ MatchLength = Length;
+ MatchOffset = Offset;
+ }
+
+ if ((Length <= MatchLength) ||
+ (&HistoryBuffer[HistoryOffset + 2] < ncrush->HistoryPtr))
+ {
+ NextOffset = ncrush->MatchTable[Offset];
+ MatchPtr = &HistoryBuffer[MatchLength];
+ continue;
+ }
+ }
+
+ break;
+ }
+ }
+
+ ncrush->MatchTable[0] = 0;
+ *pMatchOffset = MatchOffset;
+ return MatchLength;
+}
+
+static int ncrush_move_encoder_windows(NCRUSH_CONTEXT* ncrush, BYTE* HistoryPtr)
+{
+ int NewHash = 0;
+ int NewMatch = 0;
+ UINT32 HistoryOffset = 0;
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(HistoryPtr);
+
+ if (HistoryPtr < &ncrush->HistoryBuffer[32768])
+ return -1;
+
+ if (HistoryPtr > &ncrush->HistoryBuffer[65536])
+ return -1;
+
+ MoveMemory(ncrush->HistoryBuffer, HistoryPtr - 32768, 32768);
+ const intptr_t hsize = HistoryPtr - 32768 - ncrush->HistoryBuffer;
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ WINPR_ASSERT(hsize >= 0);
+ HistoryOffset = (UINT32)hsize;
+
+ for (int i = 0; i < 65536; i += 4)
+ {
+ NewHash = ncrush->HashTable[i + 0] - HistoryOffset;
+ ncrush->HashTable[i + 0] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 1] - HistoryOffset;
+ ncrush->HashTable[i + 1] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 2] - HistoryOffset;
+ ncrush->HashTable[i + 2] = (NewHash <= 0) ? 0 : NewHash;
+ NewHash = ncrush->HashTable[i + 3] - HistoryOffset;
+ ncrush->HashTable[i + 3] = (NewHash <= 0) ? 0 : NewHash;
+ }
+
+ for (int j = 0; j < 32768; j += 4)
+ {
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 0] - HistoryOffset;
+ ncrush->MatchTable[j + 0] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 1] - HistoryOffset;
+ ncrush->MatchTable[j + 1] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 2] - HistoryOffset;
+ ncrush->MatchTable[j + 2] = (NewMatch <= 0) ? 0 : NewMatch;
+ NewMatch = ncrush->MatchTable[HistoryOffset + j + 3] - HistoryOffset;
+ ncrush->MatchTable[j + 3] = (NewMatch <= 0) ? 0 : NewMatch;
+ }
+
+ ZeroMemory(&ncrush->MatchTable[32768], 65536);
+ return 1;
+}
+
+int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ BYTE Literal = 0;
+ const BYTE* SrcPtr = NULL;
+ BYTE* DstPtr = NULL;
+ UINT32 bits = 0;
+ UINT32 offset = 0;
+ UINT16 Mask = 0;
+ UINT32 MaskedBits = 0;
+ UINT32 accumulator = 0;
+ const BYTE* SrcEndPtr = NULL;
+ BYTE* DstEndPtr = NULL;
+ BYTE* HistoryPtr = NULL;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize = 0;
+ BOOL PacketAtFront = FALSE;
+ BOOL PacketFlushed = FALSE;
+ UINT32 MatchLength = 0;
+ UINT32 IndexLEC = 0;
+ UINT32 IndexLOM = 0;
+ UINT32 IndexCO = 0;
+ UINT32 CodeLEC = 0;
+ UINT32 BitLength = 0;
+ UINT32 CopyOffset = 0;
+ UINT32 MatchOffset = 0;
+ UINT32 OldCopyOffset = 0;
+ UINT32* OffsetCache = NULL;
+ UINT32 OffsetCacheIndex = 0;
+ UINT32 HistoryOffset = 0;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 HistoryBufferSize = 0;
+ BYTE* HistoryBufferEndPtr = NULL;
+ UINT32 CopyOffsetIndex = 0;
+ UINT32 CopyOffsetBits = 0;
+ UINT32 CompressionLevel = 2;
+
+ WINPR_ASSERT(ncrush);
+
+ WINPR_ASSERT(ncrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(pDstBuffer);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ HistoryBuffer = ncrush->HistoryBuffer;
+ *pFlags = 0;
+
+ if ((SrcSize + ncrush->HistoryOffset) >= 65529)
+ {
+ if (ncrush->HistoryOffset == (ncrush->HistoryBufferSize + 1))
+ {
+ ncrush->HistoryOffset = 0;
+ ncrush->HistoryPtr = HistoryBuffer;
+ PacketFlushed = TRUE;
+ }
+ else
+ {
+ if (ncrush_move_encoder_windows(ncrush, &(HistoryBuffer[ncrush->HistoryOffset])) < 0)
+ return -1001;
+
+ ncrush->HistoryPtr = &HistoryBuffer[32768];
+ ncrush->HistoryOffset = 32768;
+ PacketAtFront = TRUE;
+ }
+ }
+ else
+ {
+ *pFlags = 0;
+ }
+
+ pDstData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+
+ if (!pDstData)
+ return -1002;
+
+ DstSize = *pDstSize;
+
+ if (DstSize < SrcSize)
+ return -1003;
+
+ DstSize = SrcSize;
+ NCrushWriteStart(&bits, &offset, &accumulator);
+ DstPtr = pDstData;
+ SrcPtr = pSrcData;
+ SrcEndPtr = &pSrcData[SrcSize];
+ DstEndPtr = &pDstData[DstSize - 1];
+ OffsetCache = ncrush->OffsetCache;
+ HistoryPtr = &HistoryBuffer[ncrush->HistoryOffset];
+ HistoryBufferEndPtr = &HistoryBuffer[65536];
+ HistoryBufferSize = ncrush->HistoryBufferSize;
+ CopyOffset = 0;
+ MatchOffset = 0;
+ const intptr_t thsize = HistoryPtr - HistoryBuffer;
+ WINPR_ASSERT(thsize >= 0);
+ WINPR_ASSERT(thsize <= UINT32_MAX);
+ ncrush_hash_table_add(ncrush, pSrcData, SrcSize, (UINT32)thsize);
+ CopyMemory(HistoryPtr, pSrcData, SrcSize);
+ ncrush->HistoryPtr = &HistoryPtr[SrcSize];
+
+ while (SrcPtr < (SrcEndPtr - 2))
+ {
+ MatchLength = 0;
+ const intptr_t hsize = HistoryPtr - HistoryBuffer;
+ WINPR_ASSERT(hsize <= UINT32_MAX);
+ WINPR_ASSERT(hsize >= 0);
+ HistoryOffset = (UINT32)hsize;
+
+ if (ncrush->HistoryPtr && (HistoryPtr > ncrush->HistoryPtr))
+ return -1;
+
+ if (HistoryOffset >= 65536)
+ return -1004;
+
+ if (ncrush->MatchTable[HistoryOffset])
+ {
+ int rc = 0;
+
+ MatchOffset = 0;
+ rc = ncrush_find_best_match(ncrush, HistoryOffset, &MatchOffset);
+
+ if (rc < 0)
+ return -1005;
+ MatchLength = (UINT32)rc;
+ }
+
+ if (MatchLength)
+ CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - &HistoryBuffer[MatchOffset]);
+
+ if ((MatchLength == 2) && (CopyOffset >= 64))
+ MatchLength = 0;
+
+ if (MatchLength == 0)
+ {
+ /* Literal */
+ Literal = *SrcPtr++;
+ HistoryPtr++;
+
+ if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #1 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ IndexLEC = Literal;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1006;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ }
+ else
+ {
+ HistoryPtr += MatchLength;
+ SrcPtr += MatchLength;
+
+ if (!MatchLength)
+ return -1007;
+
+ if ((DstPtr + 8) > DstEndPtr) /* PACKET_FLUSH #2 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ OffsetCacheIndex = 5;
+
+ if ((CopyOffset == OffsetCache[0]) || (CopyOffset == OffsetCache[1]) ||
+ (CopyOffset == OffsetCache[2]) || (CopyOffset == OffsetCache[3]))
+ {
+ if (CopyOffset == OffsetCache[3])
+ {
+ OldCopyOffset = OffsetCache[3];
+ OffsetCache[3] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 3;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[2])
+ {
+ OldCopyOffset = OffsetCache[2];
+ OffsetCache[2] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 2;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[1])
+ {
+ OldCopyOffset = OffsetCache[1];
+ OffsetCache[1] = OffsetCache[0];
+ OffsetCache[0] = OldCopyOffset;
+ OffsetCacheIndex = 1;
+ }
+ else
+ {
+ if (CopyOffset == OffsetCache[0])
+ {
+ OffsetCacheIndex = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ OffsetCache[3] = OffsetCache[2];
+ OffsetCache[2] = OffsetCache[1];
+ OffsetCache[1] = OffsetCache[0];
+ OffsetCache[0] = CopyOffset;
+ }
+
+ if (OffsetCacheIndex >= 4)
+ {
+ /* CopyOffset not in OffsetCache */
+ if (CopyOffset >= 256)
+ bits = (CopyOffset >> 7) + 256;
+ else
+ bits = CopyOffset;
+
+ CopyOffsetIndex = ncrush->HuffTableCopyOffset[bits + 2];
+
+ if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT))
+ return -1;
+
+ CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex];
+ IndexLEC = 257 + CopyOffsetIndex;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1008;
+
+ if (CopyOffsetBits > 18)
+ return -1009;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ Mask = ((1 << CopyOffsetBits) - 1);
+ MaskedBits = CopyOffset & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, CopyOffsetBits);
+
+ if ((MatchLength - 2) >= 768)
+ IndexCO = 28;
+ else
+ IndexCO = ncrush->HuffTableLOM[MatchLength];
+
+ if (IndexCO >= ARRAYSIZE(HuffLengthLOM))
+ return -1;
+ BitLength = HuffLengthLOM[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ IndexLOM = LOMBitsLUT[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(HuffCodeLOM))
+ return -1;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength);
+ Mask = ((1 << IndexLOM) - 1);
+ MaskedBits = (MatchLength - 2) & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM);
+
+ if (IndexCO >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength)
+ return -1010;
+ }
+ else
+ {
+ /* CopyOffset in OffsetCache */
+ IndexLEC = 289 + OffsetCacheIndex;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength >= 15)
+ return -1011;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+
+ if ((MatchLength - 2) >= 768)
+ IndexCO = 28;
+ else
+ IndexCO = ncrush->HuffTableLOM[MatchLength];
+
+ if (IndexCO >= ARRAYSIZE(HuffLengthLOM))
+ return -1;
+
+ BitLength = HuffLengthLOM[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ IndexLOM = LOMBitsLUT[IndexCO];
+
+ if (IndexCO >= ARRAYSIZE(HuffCodeLOM))
+ return -1;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength);
+ Mask = ((1 << IndexLOM) - 1);
+ MaskedBits = (MatchLength - 2) & Mask;
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM);
+
+ if (IndexCO >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+ if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength)
+ return -1012;
+ }
+ }
+
+ if (HistoryPtr >= HistoryBufferEndPtr)
+ return -1013;
+ }
+
+ while (SrcPtr < SrcEndPtr)
+ {
+ if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #3 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ Literal = *SrcPtr++;
+ HistoryPtr++;
+ IndexLEC = Literal;
+ if (IndexLEC >= ARRAYSIZE(HuffLengthLEC))
+ return -1;
+ if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC))
+ return -1;
+ BitLength = HuffLengthLEC[IndexLEC];
+ CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]);
+
+ if (BitLength > 15)
+ return -1014;
+
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength);
+ }
+
+ if ((DstPtr + 4) >= DstEndPtr) /* PACKET_FLUSH #4 */
+ {
+ ncrush_context_reset(ncrush, TRUE);
+ *pFlags = PACKET_FLUSHED;
+ *pFlags |= CompressionLevel;
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ return 1;
+ }
+
+ IndexLEC = 256;
+ BitLength = HuffLengthLEC[IndexLEC];
+
+ if (BitLength > 15)
+ return -1015;
+
+ bits = get_word(&HuffCodeLEC[IndexLEC * 2]);
+ NCrushWriteBits(&DstPtr, &accumulator, &offset, bits, BitLength);
+ NCrushWriteFinish(&DstPtr, accumulator);
+ const intptr_t dsize = DstPtr - pDstData;
+ WINPR_ASSERT(dsize <= UINT32_MAX);
+ WINPR_ASSERT(dsize >= 0);
+ *pDstSize = (UINT32)dsize;
+
+ if (*pDstSize > SrcSize)
+ return -1016;
+
+ *pFlags |= PACKET_COMPRESSED;
+ *pFlags |= CompressionLevel;
+
+ if (PacketAtFront)
+ *pFlags |= PACKET_AT_FRONT;
+
+ if (PacketFlushed)
+ *pFlags |= PACKET_FLUSHED;
+
+ ncrush->HistoryOffset = HistoryPtr - HistoryBuffer;
+
+ if (ncrush->HistoryOffset >= ncrush->HistoryBufferSize)
+ return -1;
+
+ return 1;
+}
+
+static int ncrush_generate_tables(NCRUSH_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(28 < ARRAYSIZE(LOMBitsLUT));
+
+ UINT32 k = 0;
+ for (int i = 0; i < 28; i++)
+ {
+ for (int j = 0; j < 1 << LOMBitsLUT[i]; j++)
+ {
+ size_t l = (k++) + 2ull;
+ context->HuffTableLOM[l] = (int)i;
+ }
+ }
+
+ for (UINT32 k = 2; k < 4096; k++)
+ {
+ size_t i = 28;
+ if ((k - 2) < 768)
+ i = context->HuffTableLOM[k];
+
+ if (i >= ARRAYSIZE(LOMBitsLUT))
+ return -1;
+ if (i >= ARRAYSIZE(LOMBaseLUT))
+ return -1;
+
+ if (((((1 << LOMBitsLUT[i]) - 1) & (k - 2)) + LOMBaseLUT[i]) != k)
+ return -1;
+ }
+
+ k = 0;
+
+ for (int i = 0; i < 16; i++)
+ {
+ for (int j = 0; j < 1 << CopyOffsetBitsLUT[i]; j++)
+ {
+ size_t l = k++ + 2ull;
+ context->HuffTableCopyOffset[l] = i;
+ }
+ }
+
+ k /= 128;
+
+ for (int i = 16; i < 32; i++)
+ {
+ for (int j = 0; j < 1 << (CopyOffsetBitsLUT[i] - 7); j++)
+ {
+ size_t l = k++ + 2 + 256ull;
+ context->HuffTableCopyOffset[l] = i;
+ }
+ }
+
+ if ((k + 256) > 1024)
+ return -1;
+
+ return 1;
+}
+
+void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush)
+{
+ WINPR_ASSERT(ncrush);
+
+ ZeroMemory(&(ncrush->HistoryBuffer), sizeof(ncrush->HistoryBuffer));
+ ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache));
+ ZeroMemory(&(ncrush->MatchTable), sizeof(ncrush->MatchTable));
+ ZeroMemory(&(ncrush->HashTable), sizeof(ncrush->HashTable));
+
+ if (flush)
+ ncrush->HistoryOffset = ncrush->HistoryBufferSize + 1;
+ else
+ ncrush->HistoryOffset = 0;
+
+ ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]);
+}
+
+NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor)
+{
+ NCRUSH_CONTEXT* ncrush = (NCRUSH_CONTEXT*)calloc(1, sizeof(NCRUSH_CONTEXT));
+
+ if (!ncrush)
+ goto fail;
+
+ ncrush->Compressor = Compressor;
+ ncrush->HistoryBufferSize = 65536;
+ ncrush->HistoryEndOffset = ncrush->HistoryBufferSize - 1;
+ ncrush->HistoryBufferFence = 0xABABABAB;
+ ncrush->HistoryOffset = 0;
+ ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]);
+
+ if (ncrush_generate_tables(ncrush) < 0)
+ {
+ WLog_DBG(TAG, "ncrush_context_new: failed to initialize tables");
+ goto fail;
+ }
+
+ ncrush_context_reset(ncrush, FALSE);
+
+ return ncrush;
+fail:
+ ncrush_context_free(ncrush);
+ return NULL;
+}
+
+void ncrush_context_free(NCRUSH_CONTEXT* ncrush)
+{
+ free(ncrush);
+}
diff --git a/libfreerdp/codec/ncrush.h b/libfreerdp/codec/ncrush.h
new file mode 100644
index 0000000..bc752ce
--- /dev/null
+++ b/libfreerdp/codec/ncrush.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NCrush (RDP6) 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.
+ */
+
+#ifndef FREERDP_CODEC_NCRUSH_H
+#define FREERDP_CODEC_NCRUSH_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include "mppc.h"
+
+#include <winpr/bitstream.h>
+
+typedef struct s_NCRUSH_CONTEXT NCRUSH_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData,
+ UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32 flags);
+
+ FREERDP_LOCAL void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush);
+
+ FREERDP_LOCAL NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor);
+ FREERDP_LOCAL void ncrush_context_free(NCRUSH_CONTEXT* ncrush);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_NCRUSH_H */
diff --git a/libfreerdp/codec/nsc.c b/libfreerdp/codec/nsc.c
new file mode 100644
index 0000000..049b541
--- /dev/null
+++ b/libfreerdp/codec/nsc.c
@@ -0,0 +1,502 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Codec
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/color.h>
+
+#include "nsc_types.h"
+#include "nsc_encode.h"
+
+#include "nsc_sse2.h"
+
+#include <freerdp/log.h>
+#define TAG FREERDP_TAG("codec.nsc")
+
+#ifndef NSC_INIT_SIMD
+#define NSC_INIT_SIMD(_nsc_context) \
+ do \
+ { \
+ } while (0)
+#endif
+
+static BOOL nsc_decode(NSC_CONTEXT* context)
+{
+ UINT16 rw = 0;
+ BYTE shift = 0;
+ BYTE* bmpdata = NULL;
+ size_t pos = 0;
+
+ if (!context)
+ return FALSE;
+
+ rw = ROUND_UP_TO(context->width, 8);
+ shift = context->ColorLossLevel - 1; /* colorloss recovery + YCoCg shift */
+ bmpdata = context->BitmapData;
+
+ if (!bmpdata)
+ return FALSE;
+
+ for (UINT32 y = 0; y < context->height; y++)
+ {
+ const BYTE* yplane = NULL;
+ const BYTE* coplane = NULL;
+ const BYTE* cgplane = NULL;
+ const BYTE* aplane = context->priv->PlaneBuffers[3] + y * context->width; /* A */
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw; /* Y */
+ coplane = context->priv->PlaneBuffers[1] + (y >> 1) * (rw >> 1); /* Co, supersampled */
+ cgplane = context->priv->PlaneBuffers[2] + (y >> 1) * (rw >> 1); /* Cg, supersampled */
+ }
+ else
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * context->width; /* Y */
+ coplane = context->priv->PlaneBuffers[1] + y * context->width; /* Co */
+ cgplane = context->priv->PlaneBuffers[2] + y * context->width; /* Cg */
+ }
+
+ for (UINT32 x = 0; x < context->width; x++)
+ {
+ INT16 y_val = (INT16)*yplane;
+ INT16 co_val = (INT16)(INT8)(*coplane << shift);
+ INT16 cg_val = (INT16)(INT8)(*cgplane << shift);
+ INT16 r_val = y_val + co_val - cg_val;
+ INT16 g_val = y_val + cg_val;
+ INT16 b_val = y_val - co_val - cg_val;
+
+ if (pos + 4 > context->BitmapDataLength)
+ return FALSE;
+
+ pos += 4;
+ *bmpdata++ = MINMAX(b_val, 0, 0xFF);
+ *bmpdata++ = MINMAX(g_val, 0, 0xFF);
+ *bmpdata++ = MINMAX(r_val, 0, 0xFF);
+ *bmpdata++ = *aplane;
+ yplane++;
+ coplane += (context->ChromaSubsamplingLevel ? x % 2 : 1);
+ cgplane += (context->ChromaSubsamplingLevel ? x % 2 : 1);
+ aplane++;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_rle_decode(const BYTE* in, size_t inSize, BYTE* out, UINT32 outSize,
+ UINT32 originalSize)
+{
+ UINT32 left = originalSize;
+
+ while (left > 4)
+ {
+ if (inSize < 1)
+ return FALSE;
+ inSize--;
+
+ const BYTE value = *in++;
+ UINT32 len = 0;
+
+ if (left == 5)
+ {
+ if (outSize < 1)
+ return FALSE;
+
+ outSize--;
+ *out++ = value;
+ left--;
+ }
+ else if (inSize < 1)
+ return FALSE;
+ else if (value == *in)
+ {
+ inSize--;
+ in++;
+
+ if (inSize < 1)
+ return FALSE;
+ else if (*in < 0xFF)
+ {
+ inSize--;
+ len = (UINT32)*in++;
+ len += 2;
+ }
+ else
+ {
+ if (inSize < 5)
+ return FALSE;
+ inSize -= 5;
+ in++;
+ len = ((UINT32)(*in++));
+ len |= ((UINT32)(*in++)) << 8U;
+ len |= ((UINT32)(*in++)) << 16U;
+ len |= ((UINT32)(*in++)) << 24U;
+ }
+
+ if (outSize < len)
+ return FALSE;
+
+ outSize -= len;
+ FillMemory(out, len, value);
+ out += len;
+ left -= len;
+ }
+ else
+ {
+ if (outSize < 1)
+ return FALSE;
+
+ outSize--;
+ *out++ = value;
+ left--;
+ }
+ }
+
+ if ((outSize < 4) || (left < 4))
+ return FALSE;
+
+ if (inSize < 4)
+ return FALSE;
+ memcpy(out, in, 4);
+ return TRUE;
+}
+
+static BOOL nsc_rle_decompress_data(NSC_CONTEXT* context)
+{
+ if (!context)
+ return FALSE;
+
+ const BYTE* rle = context->Planes;
+ size_t rleSize = context->PlanesSize;
+ WINPR_ASSERT(rle);
+
+ for (size_t i = 0; i < 4; i++)
+ {
+ const UINT32 originalSize = context->OrgByteCount[i];
+ const UINT32 planeSize = context->PlaneByteCount[i];
+
+ if (rleSize < planeSize)
+ return FALSE;
+
+ if (planeSize == 0)
+ {
+ if (context->priv->PlaneBuffersLength < originalSize)
+ return FALSE;
+
+ FillMemory(context->priv->PlaneBuffers[i], originalSize, 0xFF);
+ }
+ else if (planeSize < originalSize)
+ {
+ if (!nsc_rle_decode(rle, rleSize, context->priv->PlaneBuffers[i],
+ context->priv->PlaneBuffersLength, originalSize))
+ return FALSE;
+ }
+ else
+ {
+ if (context->priv->PlaneBuffersLength < originalSize)
+ return FALSE;
+
+ if (rleSize < originalSize)
+ return FALSE;
+
+ CopyMemory(context->priv->PlaneBuffers[i], rle, originalSize);
+ }
+
+ rle += planeSize;
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_stream_initialize(NSC_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20))
+ return FALSE;
+
+ size_t total = 0;
+ for (size_t i = 0; i < 4; i++)
+ {
+ Stream_Read_UINT32(s, context->PlaneByteCount[i]);
+ total += context->PlaneByteCount[i];
+ }
+
+ Stream_Read_UINT8(s, context->ColorLossLevel); /* ColorLossLevel (1 byte) */
+ Stream_Read_UINT8(s, context->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */
+ Stream_Seek(s, 2); /* Reserved (2 bytes) */
+ context->Planes = Stream_Pointer(s);
+ context->PlanesSize = total;
+ return Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, total);
+}
+
+static BOOL nsc_context_initialize(NSC_CONTEXT* context, wStream* s)
+{
+ if (!nsc_stream_initialize(context, s))
+ return FALSE;
+
+ const size_t blength = context->width * context->height * 4ull;
+
+ if (!context->BitmapData || (blength > context->BitmapDataLength))
+ {
+ void* tmp = winpr_aligned_recalloc(context->BitmapData, blength + 16, sizeof(BYTE), 32);
+
+ if (!tmp)
+ return FALSE;
+
+ context->BitmapData = tmp;
+ context->BitmapDataLength = blength;
+ }
+
+ const UINT32 tempWidth = ROUND_UP_TO(context->width, 8);
+ const UINT32 tempHeight = ROUND_UP_TO(context->height, 2);
+ /* The maximum length a decoded plane can reach in all cases */
+ const size_t plength = 1ull * tempWidth * tempHeight;
+
+ if (plength > context->priv->PlaneBuffersLength)
+ {
+ for (size_t i = 0; i < 4; i++)
+ {
+ void* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], plength,
+ sizeof(BYTE), 32);
+
+ if (!tmp)
+ return FALSE;
+
+ context->priv->PlaneBuffers[i] = tmp;
+ }
+
+ context->priv->PlaneBuffersLength = plength;
+ }
+
+ for (size_t i = 0; i < 4; i++)
+ context->OrgByteCount[i] = context->width * context->height;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ context->OrgByteCount[0] = tempWidth * context->height;
+ context->OrgByteCount[1] = (tempWidth >> 1) * (tempHeight >> 1);
+ context->OrgByteCount[2] = context->OrgByteCount[1];
+ }
+
+ return TRUE;
+}
+
+static void nsc_profiler_print(NSC_CONTEXT_PRIV* priv)
+{
+ WINPR_UNUSED(priv);
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(priv->prof_nsc_rle_decompress_data)
+ PROFILER_PRINT(priv->prof_nsc_decode)
+ PROFILER_PRINT(priv->prof_nsc_rle_compress_data)
+ PROFILER_PRINT(priv->prof_nsc_encode)
+ PROFILER_PRINT_FOOTER
+}
+
+BOOL nsc_context_reset(NSC_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ if ((width > UINT16_MAX) || (height > UINT16_MAX))
+ return FALSE;
+
+ context->width = (UINT16)width;
+ context->height = (UINT16)height;
+ return TRUE;
+}
+
+NSC_CONTEXT* nsc_context_new(void)
+{
+ NSC_CONTEXT* context = (NSC_CONTEXT*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ context->priv = (NSC_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT_PRIV), 32);
+
+ if (!context->priv)
+ goto error;
+
+ context->priv->log = WLog_Get("com.freerdp.codec.nsc");
+ WLog_OpenAppender(context->priv->log);
+ context->BitmapData = NULL;
+ context->decode = nsc_decode;
+ context->encode = nsc_encode;
+
+ PROFILER_CREATE(context->priv->prof_nsc_rle_decompress_data, "nsc_rle_decompress_data")
+ PROFILER_CREATE(context->priv->prof_nsc_decode, "nsc_decode")
+ PROFILER_CREATE(context->priv->prof_nsc_rle_compress_data, "nsc_rle_compress_data")
+ PROFILER_CREATE(context->priv->prof_nsc_encode, "nsc_encode")
+ /* Default encoding parameters */
+ context->ColorLossLevel = 3;
+ context->ChromaSubsamplingLevel = 1;
+ /* init optimized methods */
+ NSC_INIT_SIMD(context);
+ return context;
+error:
+ nsc_context_free(context);
+ return NULL;
+}
+
+void nsc_context_free(NSC_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ if (context->priv)
+ {
+ for (size_t i = 0; i < 5; i++)
+ winpr_aligned_free(context->priv->PlaneBuffers[i]);
+
+ nsc_profiler_print(context->priv);
+ PROFILER_FREE(context->priv->prof_nsc_rle_decompress_data)
+ PROFILER_FREE(context->priv->prof_nsc_decode)
+ PROFILER_FREE(context->priv->prof_nsc_rle_compress_data)
+ PROFILER_FREE(context->priv->prof_nsc_encode)
+ winpr_aligned_free(context->priv);
+ }
+
+ winpr_aligned_free(context->BitmapData);
+ winpr_aligned_free(context);
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL nsc_context_set_pixel_format(NSC_CONTEXT* context, UINT32 pixel_format)
+{
+ return nsc_context_set_parameters(context, NSC_COLOR_FORMAT, pixel_format);
+}
+#endif
+
+BOOL nsc_context_set_parameters(NSC_CONTEXT* context, NSC_PARAMETER what, UINT32 value)
+{
+ if (!context)
+ return FALSE;
+
+ switch (what)
+ {
+ case NSC_COLOR_LOSS_LEVEL:
+ context->ColorLossLevel = value;
+ break;
+ case NSC_ALLOW_SUBSAMPLING:
+ context->ChromaSubsamplingLevel = value;
+ break;
+ case NSC_DYNAMIC_COLOR_FIDELITY:
+ context->DynamicColorFidelity = value != 0;
+ break;
+ case NSC_COLOR_FORMAT:
+ context->format = value;
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL nsc_process_message(NSC_CONTEXT* context, UINT16 bpp, UINT32 width, UINT32 height,
+ const BYTE* data, UINT32 length, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStride, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth,
+ UINT32 nHeight, UINT32 flip)
+{
+ wStream* s = NULL;
+ wStream sbuffer = { 0 };
+ BOOL ret = 0;
+ if (!context || !data || !pDstData)
+ return FALSE;
+
+ s = Stream_StaticConstInit(&sbuffer, data, length);
+
+ if (!s)
+ return FALSE;
+
+ if (nDstStride == 0)
+ nDstStride = nWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ switch (bpp)
+ {
+ case 32:
+ context->format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case 24:
+ context->format = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ context->format = PIXEL_FORMAT_BGR16;
+ break;
+
+ case 8:
+ context->format = PIXEL_FORMAT_RGB8;
+ break;
+
+ case 4:
+ context->format = PIXEL_FORMAT_A4;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ context->width = width;
+ context->height = height;
+ ret = nsc_context_initialize(context, s);
+
+ if (!ret)
+ return FALSE;
+
+ /* RLE decode */
+ {
+ BOOL rc = 0;
+ PROFILER_ENTER(context->priv->prof_nsc_rle_decompress_data)
+ rc = nsc_rle_decompress_data(context);
+ PROFILER_EXIT(context->priv->prof_nsc_rle_decompress_data)
+
+ if (!rc)
+ return FALSE;
+ }
+ /* Colorloss recover, Chroma supersample and AYCoCg to ARGB Conversion in one step */
+ {
+ BOOL rc = 0;
+ PROFILER_ENTER(context->priv->prof_nsc_decode)
+ rc = context->decode(context);
+ PROFILER_EXIT(context->priv->prof_nsc_decode)
+
+ if (!rc)
+ return FALSE;
+ }
+
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStride, nXDst, nYDst, width, height,
+ context->BitmapData, PIXEL_FORMAT_BGRA32, 0, 0, 0, NULL, flip))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/nsc_encode.c b/libfreerdp/codec/nsc_encode.c
new file mode 100644
index 0000000..71393d9
--- /dev/null
+++ b/libfreerdp/codec/nsc_encode.c
@@ -0,0 +1,533 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Encoder
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/color.h>
+
+#include "nsc_types.h"
+#include "nsc_encode.h"
+
+typedef struct
+{
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+ const BYTE* data;
+ UINT32 scanline;
+ BYTE* PlaneBuffer;
+ UINT32 MaxPlaneSize;
+ BYTE* PlaneBuffers[5];
+ UINT32 OrgByteCount[4];
+
+ UINT32 LumaPlaneByteCount;
+ UINT32 OrangeChromaPlaneByteCount;
+ UINT32 GreenChromaPlaneByteCount;
+ UINT32 AlphaPlaneByteCount;
+ UINT8 ColorLossLevel;
+ UINT8 ChromaSubsamplingLevel;
+} NSC_MESSAGE;
+
+static BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message);
+
+static BOOL nsc_context_initialize_encode(NSC_CONTEXT* context)
+{
+ UINT32 length = 0;
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+ /* The maximum length a decoded plane can reach in all cases */
+ length = tempWidth * tempHeight + 16;
+
+ if (length > context->priv->PlaneBuffersLength)
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ BYTE* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], length,
+ sizeof(BYTE), 32);
+
+ if (!tmp)
+ goto fail;
+
+ context->priv->PlaneBuffers[i] = tmp;
+ }
+
+ context->priv->PlaneBuffersLength = length;
+ }
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ context->OrgByteCount[0] = tempWidth * context->height;
+ context->OrgByteCount[1] = tempWidth * tempHeight / 4;
+ context->OrgByteCount[2] = tempWidth * tempHeight / 4;
+ context->OrgByteCount[3] = context->width * context->height;
+ }
+ else
+ {
+ context->OrgByteCount[0] = context->width * context->height;
+ context->OrgByteCount[1] = context->width * context->height;
+ context->OrgByteCount[2] = context->width * context->height;
+ context->OrgByteCount[3] = context->width * context->height;
+ }
+
+ return TRUE;
+fail:
+
+ if (length > context->priv->PlaneBuffersLength)
+ {
+ for (int i = 0; i < 5; i++)
+ winpr_aligned_free(context->priv->PlaneBuffers[i]);
+ }
+
+ return FALSE;
+}
+
+static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ UINT16 x = 0;
+ UINT16 y = 0;
+ UINT16 rw = 0;
+ BYTE ccl = 0;
+ const BYTE* src = NULL;
+ BYTE* yplane = NULL;
+ BYTE* coplane = NULL;
+ BYTE* cgplane = NULL;
+ BYTE* aplane = NULL;
+ INT16 r_val = 0;
+ INT16 g_val = 0;
+ INT16 b_val = 0;
+ BYTE a_val = 0;
+ UINT32 tempWidth = 0;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ rw = (context->ChromaSubsamplingLevel ? tempWidth : context->width);
+ ccl = context->ColorLossLevel;
+
+ for (; y < context->height; y++)
+ {
+ src = data + (context->height - 1 - y) * scanline;
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ aplane = context->priv->PlaneBuffers[3] + y * context->width;
+
+ for (UINT16 x = 0; x < context->width; x++)
+ {
+ switch (context->format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ a_val = *src++;
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ a_val = *src++;
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ b_val = *src++;
+ g_val = *src++;
+ r_val = *src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ r_val = *src++;
+ g_val = *src++;
+ b_val = *src++;
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ b_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ r_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ a_val = 0xFF;
+ src += 2;
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ r_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ b_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ a_val = 0xFF;
+ src += 2;
+ break;
+
+ case PIXEL_FORMAT_A4:
+ {
+ int shift = 0;
+ BYTE idx = 0;
+ shift = (7 - (x % 8));
+ idx = ((*src) >> shift) & 1;
+ idx |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx *= 3;
+ r_val = (INT16)context->palette[idx];
+ g_val = (INT16)context->palette[idx + 1];
+ b_val = (INT16)context->palette[idx + 2];
+
+ if (shift == 0)
+ src += 4;
+ }
+
+ a_val = 0xFF;
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ {
+ int idx = (*src) * 3;
+ r_val = (INT16)context->palette[idx];
+ g_val = (INT16)context->palette[idx + 1];
+ b_val = (INT16)context->palette[idx + 2];
+ src++;
+ }
+
+ a_val = 0xFF;
+ break;
+
+ default:
+ r_val = g_val = b_val = a_val = 0;
+ break;
+ }
+
+ *yplane++ = (BYTE)((r_val >> 2) + (g_val >> 1) + (b_val >> 2));
+ /* Perform color loss reduction here */
+ *coplane++ = (BYTE)((r_val - b_val) >> ccl);
+ *cgplane++ = (BYTE)((-(r_val >> 1) + g_val - (b_val >> 1)) >> ccl);
+ *aplane++ = a_val;
+ }
+
+ if (context->ChromaSubsamplingLevel && (x % 2) == 1)
+ {
+ *yplane = *(yplane - 1);
+ *coplane = *(coplane - 1);
+ *cgplane = *(cgplane - 1);
+ }
+ }
+
+ if (context->ChromaSubsamplingLevel && (y % 2) == 1)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ CopyMemory(yplane, yplane - rw, rw);
+ CopyMemory(coplane, coplane - rw, rw);
+ CopyMemory(cgplane, cgplane - rw, rw);
+ }
+
+ return TRUE;
+}
+
+static BOOL nsc_encode_subsampling(NSC_CONTEXT* context)
+{
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+
+ if (!context)
+ return FALSE;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+
+ if (tempHeight == 0)
+ return FALSE;
+
+ if (tempWidth > context->priv->PlaneBuffersLength / tempHeight)
+ return FALSE;
+
+ for (UINT32 y = 0; y < tempHeight >> 1; y++)
+ {
+ BYTE* co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1);
+ BYTE* cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1);
+ const INT8* co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth;
+ const INT8* co_src1 = co_src0 + tempWidth;
+ const INT8* cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth;
+ const INT8* cg_src1 = cg_src0 + tempWidth;
+
+ for (UINT32 x = 0; x < tempWidth >> 1; x++)
+ {
+ *co_dst++ = (BYTE)(((INT16)*co_src0 + (INT16) * (co_src0 + 1) + (INT16)*co_src1 +
+ (INT16) * (co_src1 + 1)) >>
+ 2);
+ *cg_dst++ = (BYTE)(((INT16)*cg_src0 + (INT16) * (cg_src0 + 1) + (INT16)*cg_src1 +
+ (INT16) * (cg_src1 + 1)) >>
+ 2);
+ co_src0 += 2;
+ co_src1 += 2;
+ cg_src0 += 2;
+ cg_src1 += 2;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride)
+{
+ if (!context || !bmpdata || (rowstride == 0))
+ return FALSE;
+
+ if (!nsc_encode_argb_to_aycocg(context, bmpdata, rowstride))
+ return FALSE;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ if (!nsc_encode_subsampling(context))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT32 nsc_rle_encode(const BYTE* in, BYTE* out, UINT32 originalSize)
+{
+ UINT32 left = 0;
+ UINT32 runlength = 1;
+ UINT32 planeSize = 0;
+ left = originalSize;
+
+ /**
+ * We quit the loop if the running compressed size is larger than the original.
+ * In such cases data will be sent uncompressed.
+ */
+ while (left > 4 && planeSize < originalSize - 4)
+ {
+ if (left > 5 && *in == *(in + 1))
+ {
+ runlength++;
+ }
+ else if (runlength == 1)
+ {
+ *out++ = *in;
+ planeSize++;
+ }
+ else if (runlength < 256)
+ {
+ *out++ = *in;
+ *out++ = *in;
+ *out++ = runlength - 2;
+ runlength = 1;
+ planeSize += 3;
+ }
+ else
+ {
+ *out++ = *in;
+ *out++ = *in;
+ *out++ = 0xFF;
+ *out++ = (runlength & 0x000000FF);
+ *out++ = (runlength & 0x0000FF00) >> 8;
+ *out++ = (runlength & 0x00FF0000) >> 16;
+ *out++ = (runlength & 0xFF000000) >> 24;
+ runlength = 1;
+ planeSize += 7;
+ }
+
+ in++;
+ left--;
+ }
+
+ if (planeSize < originalSize - 4)
+ CopyMemory(out, in, 4);
+
+ planeSize += 4;
+ return planeSize;
+}
+
+static void nsc_rle_compress_data(NSC_CONTEXT* context)
+{
+ UINT32 planeSize = 0;
+ UINT32 originalSize = 0;
+
+ for (UINT16 i = 0; i < 4; i++)
+ {
+ originalSize = context->OrgByteCount[i];
+
+ if (originalSize == 0)
+ {
+ planeSize = 0;
+ }
+ else
+ {
+ planeSize = nsc_rle_encode(context->priv->PlaneBuffers[i],
+ context->priv->PlaneBuffers[4], originalSize);
+
+ if (planeSize < originalSize)
+ CopyMemory(context->priv->PlaneBuffers[i], context->priv->PlaneBuffers[4],
+ planeSize);
+ else
+ planeSize = originalSize;
+ }
+
+ context->PlaneByteCount[i] = planeSize;
+ }
+}
+
+static UINT32 nsc_compute_byte_count(NSC_CONTEXT* context, UINT32* ByteCount, UINT32 width,
+ UINT32 height)
+{
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ UINT32 maxPlaneSize = 0;
+ tempWidth = ROUND_UP_TO(width, 8);
+ tempHeight = ROUND_UP_TO(height, 2);
+ maxPlaneSize = tempWidth * tempHeight + 16;
+
+ if (context->ChromaSubsamplingLevel)
+ {
+ ByteCount[0] = tempWidth * height;
+ ByteCount[1] = tempWidth * tempHeight / 4;
+ ByteCount[2] = tempWidth * tempHeight / 4;
+ ByteCount[3] = width * height;
+ }
+ else
+ {
+ ByteCount[0] = width * height;
+ ByteCount[1] = width * height;
+ ByteCount[2] = width * height;
+ ByteCount[3] = width * height;
+ }
+
+ return maxPlaneSize;
+}
+
+BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message)
+{
+ UINT32 totalPlaneByteCount = 0;
+ totalPlaneByteCount = message->LumaPlaneByteCount + message->OrangeChromaPlaneByteCount +
+ message->GreenChromaPlaneByteCount + message->AlphaPlaneByteCount;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20 + totalPlaneByteCount))
+ return FALSE;
+
+ Stream_Write_UINT32(s, message->LumaPlaneByteCount); /* LumaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(
+ s, message->OrangeChromaPlaneByteCount); /* OrangeChromaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(
+ s, message->GreenChromaPlaneByteCount); /* GreenChromaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT32(s, message->AlphaPlaneByteCount); /* AlphaPlaneByteCount (4 bytes) */
+ Stream_Write_UINT8(s, message->ColorLossLevel); /* ColorLossLevel (1 byte) */
+ Stream_Write_UINT8(s, message->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */
+ Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */
+
+ if (message->LumaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[0], message->LumaPlaneByteCount); /* LumaPlane */
+
+ if (message->OrangeChromaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[1],
+ message->OrangeChromaPlaneByteCount); /* OrangeChromaPlane */
+
+ if (message->GreenChromaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[2],
+ message->GreenChromaPlaneByteCount); /* GreenChromaPlane */
+
+ if (message->AlphaPlaneByteCount)
+ Stream_Write(s, message->PlaneBuffers[3], message->AlphaPlaneByteCount); /* AlphaPlane */
+
+ return TRUE;
+}
+
+BOOL nsc_compose_message(NSC_CONTEXT* context, wStream* s, const BYTE* data, UINT32 width,
+ UINT32 height, UINT32 scanline)
+{
+ BOOL rc = 0;
+ NSC_MESSAGE message = { 0 };
+
+ if (!context || !s || !data)
+ return FALSE;
+
+ context->width = width;
+ context->height = height;
+
+ if (!nsc_context_initialize_encode(context))
+ return FALSE;
+
+ /* ARGB to AYCoCg conversion, chroma subsampling and colorloss reduction */
+ PROFILER_ENTER(context->priv->prof_nsc_encode)
+ rc = context->encode(context, data, scanline);
+ PROFILER_EXIT(context->priv->prof_nsc_encode)
+ if (!rc)
+ return FALSE;
+
+ /* RLE encode */
+ PROFILER_ENTER(context->priv->prof_nsc_rle_compress_data)
+ nsc_rle_compress_data(context);
+ PROFILER_EXIT(context->priv->prof_nsc_rle_compress_data)
+ message.PlaneBuffers[0] = context->priv->PlaneBuffers[0];
+ message.PlaneBuffers[1] = context->priv->PlaneBuffers[1];
+ message.PlaneBuffers[2] = context->priv->PlaneBuffers[2];
+ message.PlaneBuffers[3] = context->priv->PlaneBuffers[3];
+ message.LumaPlaneByteCount = context->PlaneByteCount[0];
+ message.OrangeChromaPlaneByteCount = context->PlaneByteCount[1];
+ message.GreenChromaPlaneByteCount = context->PlaneByteCount[2];
+ message.AlphaPlaneByteCount = context->PlaneByteCount[3];
+ message.ColorLossLevel = context->ColorLossLevel;
+ message.ChromaSubsamplingLevel = context->ChromaSubsamplingLevel;
+ return nsc_write_message(context, s, &message);
+}
+
+BOOL nsc_decompose_message(NSC_CONTEXT* context, wStream* s, BYTE* bmpdata, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height, UINT32 rowstride, UINT32 format,
+ UINT32 flip)
+{
+ size_t size = Stream_GetRemainingLength(s);
+
+ if (size > UINT32_MAX)
+ return FALSE;
+
+ if (!nsc_process_message(context, (UINT16)FreeRDPGetBitsPerPixel(context->format), width,
+ height, Stream_Pointer(s), (UINT32)size, bmpdata, format, rowstride, x,
+ y, width, height, flip))
+ return FALSE;
+ Stream_Seek(s, size);
+ return TRUE;
+}
diff --git a/libfreerdp/codec/nsc_encode.h b/libfreerdp/codec/nsc_encode.h
new file mode 100644
index 0000000..a84a519
--- /dev/null
+++ b/libfreerdp/codec/nsc_encode.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Encoder
+ *
+ * Copyright 2012 Vic Lee
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_NSC_ENCODE_H
+#define FREERDP_LIB_CODEC_NSC_ENCODE_H
+
+#include <freerdp/api.h>
+
+FREERDP_LOCAL BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride);
+
+#endif /* FREERDP_LIB_CODEC_NSC_ENCODE_H */
diff --git a/libfreerdp/codec/nsc_sse2.c b/libfreerdp/codec/nsc_sse2.c
new file mode 100644
index 0000000..7ef0275
--- /dev/null
+++ b/libfreerdp/codec/nsc_sse2.c
@@ -0,0 +1,384 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library - SSE2 Optimizations
+ *
+ * Copyright 2012 Vic Lee
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+#include <freerdp/codec/color.h>
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+
+#include "nsc_types.h"
+#include "nsc_sse2.h"
+
+static BOOL nsc_encode_argb_to_aycocg_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ UINT16 y = 0;
+ UINT16 rw = 0;
+ BYTE ccl = 0;
+ const BYTE* src = NULL;
+ BYTE* yplane = NULL;
+ BYTE* coplane = NULL;
+ BYTE* cgplane = NULL;
+ BYTE* aplane = NULL;
+ __m128i r_val;
+ __m128i g_val;
+ __m128i b_val;
+ __m128i a_val;
+ __m128i y_val;
+ __m128i co_val;
+ __m128i cg_val;
+ UINT32 tempWidth = 0;
+
+ if (!context || !data || (scanline == 0))
+ return FALSE;
+
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ rw = (context->ChromaSubsamplingLevel > 0 ? tempWidth : context->width);
+ ccl = context->ColorLossLevel;
+
+ for (; y < context->height; y++)
+ {
+ src = data + (context->height - 1 - y) * scanline;
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ aplane = context->priv->PlaneBuffers[3] + y * context->width;
+
+ for (UINT16 x = 0; x < context->width; x += 8)
+ {
+ switch (context->format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_BGRA32:
+ b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19),
+ *(src + 15), *(src + 11), *(src + 7), *(src + 3));
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_RGBA32:
+ r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16),
+ *(src + 12), *(src + 8), *(src + 4), *src);
+ g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17),
+ *(src + 13), *(src + 9), *(src + 5), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18),
+ *(src + 14), *(src + 10), *(src + 6), *(src + 2));
+ a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19),
+ *(src + 15), *(src + 11), *(src + 7), *(src + 3));
+ src += 32;
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ b_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12),
+ *(src + 9), *(src + 6), *(src + 3), *src);
+ g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13),
+ *(src + 10), *(src + 7), *(src + 4), *(src + 1));
+ r_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14),
+ *(src + 11), *(src + 8), *(src + 5), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 24;
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ r_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12),
+ *(src + 9), *(src + 6), *(src + 3), *src);
+ g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13),
+ *(src + 10), *(src + 7), *(src + 4), *(src + 1));
+ b_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14),
+ *(src + 11), *(src + 8), *(src + 5), *(src + 2));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 24;
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ b_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)),
+ (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)),
+ (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)),
+ (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)),
+ (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)),
+ (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)),
+ (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)),
+ (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)));
+ g_val = _mm_set_epi16(
+ ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)),
+ ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)),
+ ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)),
+ ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)),
+ ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)),
+ ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)),
+ ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)),
+ ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)));
+ r_val = _mm_set_epi16(
+ ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)),
+ ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)),
+ ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)),
+ ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)),
+ ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)),
+ ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)),
+ ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)),
+ ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 16;
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ r_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)),
+ (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)),
+ (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)),
+ (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)),
+ (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)),
+ (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)),
+ (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)),
+ (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)));
+ g_val = _mm_set_epi16(
+ ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)),
+ ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)),
+ ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)),
+ ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)),
+ ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)),
+ ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)),
+ ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)),
+ ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)));
+ b_val = _mm_set_epi16(
+ ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)),
+ ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)),
+ ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)),
+ ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)),
+ ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)),
+ ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)),
+ ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)),
+ ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)));
+ a_val = _mm_set1_epi16(0xFF);
+ src += 16;
+ break;
+
+ case PIXEL_FORMAT_A4:
+ {
+ BYTE idx[8] = { 0 };
+
+ for (int shift = 7; shift >= 0; shift--)
+ {
+ idx[shift] = ((*src) >> shift) & 1;
+ idx[shift] |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx[shift] |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx[shift] |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx[shift] *= 3;
+ }
+
+ r_val = _mm_set_epi16(context->palette[idx[0]], context->palette[idx[1]],
+ context->palette[idx[2]], context->palette[idx[3]],
+ context->palette[idx[4]], context->palette[idx[5]],
+ context->palette[idx[6]], context->palette[idx[7]]);
+ g_val =
+ _mm_set_epi16(context->palette[idx[0] + 1], context->palette[idx[1] + 1],
+ context->palette[idx[2] + 1], context->palette[idx[3] + 1],
+ context->palette[idx[4] + 1], context->palette[idx[5] + 1],
+ context->palette[idx[6] + 1], context->palette[idx[7] + 1]);
+ b_val =
+ _mm_set_epi16(context->palette[idx[0] + 2], context->palette[idx[1] + 2],
+ context->palette[idx[2] + 2], context->palette[idx[3] + 2],
+ context->palette[idx[4] + 2], context->palette[idx[5] + 2],
+ context->palette[idx[6] + 2], context->palette[idx[7] + 2]);
+ src += 4;
+ }
+
+ a_val = _mm_set1_epi16(0xFF);
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ {
+ r_val = _mm_set_epi16(
+ context->palette[(*(src + 7)) * 3], context->palette[(*(src + 6)) * 3],
+ context->palette[(*(src + 5)) * 3], context->palette[(*(src + 4)) * 3],
+ context->palette[(*(src + 3)) * 3], context->palette[(*(src + 2)) * 3],
+ context->palette[(*(src + 1)) * 3], context->palette[(*src) * 3]);
+ g_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 1],
+ context->palette[(*(src + 6)) * 3 + 1],
+ context->palette[(*(src + 5)) * 3 + 1],
+ context->palette[(*(src + 4)) * 3 + 1],
+ context->palette[(*(src + 3)) * 3 + 1],
+ context->palette[(*(src + 2)) * 3 + 1],
+ context->palette[(*(src + 1)) * 3 + 1],
+ context->palette[(*src) * 3 + 1]);
+ b_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 2],
+ context->palette[(*(src + 6)) * 3 + 2],
+ context->palette[(*(src + 5)) * 3 + 2],
+ context->palette[(*(src + 4)) * 3 + 2],
+ context->palette[(*(src + 3)) * 3 + 2],
+ context->palette[(*(src + 2)) * 3 + 2],
+ context->palette[(*(src + 1)) * 3 + 2],
+ context->palette[(*src) * 3 + 2]);
+ src += 8;
+ }
+
+ a_val = _mm_set1_epi16(0xFF);
+ break;
+
+ default:
+ r_val = g_val = b_val = a_val = _mm_set1_epi16(0);
+ break;
+ }
+
+ y_val = _mm_srai_epi16(r_val, 2);
+ y_val = _mm_add_epi16(y_val, _mm_srai_epi16(g_val, 1));
+ y_val = _mm_add_epi16(y_val, _mm_srai_epi16(b_val, 2));
+ co_val = _mm_sub_epi16(r_val, b_val);
+ co_val = _mm_srai_epi16(co_val, ccl);
+ cg_val = _mm_sub_epi16(g_val, _mm_srai_epi16(r_val, 1));
+ cg_val = _mm_sub_epi16(cg_val, _mm_srai_epi16(b_val, 1));
+ cg_val = _mm_srai_epi16(cg_val, ccl);
+ y_val = _mm_packus_epi16(y_val, y_val);
+ _mm_storeu_si128((__m128i*)yplane, y_val);
+ co_val = _mm_packs_epi16(co_val, co_val);
+ _mm_storeu_si128((__m128i*)coplane, co_val);
+ cg_val = _mm_packs_epi16(cg_val, cg_val);
+ _mm_storeu_si128((__m128i*)cgplane, cg_val);
+ a_val = _mm_packus_epi16(a_val, a_val);
+ _mm_storeu_si128((__m128i*)aplane, a_val);
+ yplane += 8;
+ coplane += 8;
+ cgplane += 8;
+ aplane += 8;
+ }
+
+ if (context->ChromaSubsamplingLevel > 0 && (context->width % 2) == 1)
+ {
+ context->priv->PlaneBuffers[0][y * rw + context->width] =
+ context->priv->PlaneBuffers[0][y * rw + context->width - 1];
+ context->priv->PlaneBuffers[1][y * rw + context->width] =
+ context->priv->PlaneBuffers[1][y * rw + context->width - 1];
+ context->priv->PlaneBuffers[2][y * rw + context->width] =
+ context->priv->PlaneBuffers[2][y * rw + context->width - 1];
+ }
+ }
+
+ if (context->ChromaSubsamplingLevel > 0 && (y % 2) == 1)
+ {
+ yplane = context->priv->PlaneBuffers[0] + y * rw;
+ coplane = context->priv->PlaneBuffers[1] + y * rw;
+ cgplane = context->priv->PlaneBuffers[2] + y * rw;
+ CopyMemory(yplane, yplane - rw, rw);
+ CopyMemory(coplane, coplane - rw, rw);
+ CopyMemory(cgplane, cgplane - rw, rw);
+ }
+
+ return TRUE;
+}
+
+static void nsc_encode_subsampling_sse2(NSC_CONTEXT* context)
+{
+ BYTE* co_dst = NULL;
+ BYTE* cg_dst = NULL;
+ INT8* co_src0 = NULL;
+ INT8* co_src1 = NULL;
+ INT8* cg_src0 = NULL;
+ INT8* cg_src1 = NULL;
+ UINT32 tempWidth = 0;
+ UINT32 tempHeight = 0;
+ __m128i t;
+ __m128i val;
+ __m128i mask = _mm_set1_epi16(0xFF);
+ tempWidth = ROUND_UP_TO(context->width, 8);
+ tempHeight = ROUND_UP_TO(context->height, 2);
+
+ for (UINT32 y = 0; y < tempHeight >> 1; y++)
+ {
+ co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1);
+ cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1);
+ co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth;
+ co_src1 = co_src0 + tempWidth;
+ cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth;
+ cg_src1 = cg_src0 + tempWidth;
+
+ for (UINT32 x = 0; x < tempWidth >> 1; x += 8)
+ {
+ t = _mm_loadu_si128((__m128i*)co_src0);
+ t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)co_src1));
+ val = _mm_and_si128(_mm_srli_si128(t, 1), mask);
+ val = _mm_avg_epu16(val, _mm_and_si128(t, mask));
+ val = _mm_packus_epi16(val, val);
+ _mm_storeu_si128((__m128i*)co_dst, val);
+ co_dst += 8;
+ co_src0 += 16;
+ co_src1 += 16;
+ t = _mm_loadu_si128((__m128i*)cg_src0);
+ t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)cg_src1));
+ val = _mm_and_si128(_mm_srli_si128(t, 1), mask);
+ val = _mm_avg_epu16(val, _mm_and_si128(t, mask));
+ val = _mm_packus_epi16(val, val);
+ _mm_storeu_si128((__m128i*)cg_dst, val);
+ cg_dst += 8;
+ cg_src0 += 16;
+ cg_src1 += 16;
+ }
+ }
+}
+
+static BOOL nsc_encode_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline)
+{
+ if (!nsc_encode_argb_to_aycocg_sse2(context, data, scanline))
+ return FALSE;
+
+ if (context->ChromaSubsamplingLevel > 0)
+ nsc_encode_subsampling_sse2(context);
+
+ return TRUE;
+}
+
+void nsc_init_sse2(NSC_CONTEXT* context)
+{
+ if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE))
+ return;
+
+ PROFILER_RENAME(context->priv->prof_nsc_encode, "nsc_encode_sse2")
+ context->encode = nsc_encode_sse2;
+}
diff --git a/libfreerdp/codec/nsc_sse2.h b/libfreerdp/codec/nsc_sse2.h
new file mode 100644
index 0000000..8b795d7
--- /dev/null
+++ b/libfreerdp/codec/nsc_sse2.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library - SSE2 Optimizations
+ *
+ * Copyright 2012 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_NSC_SSE2_H
+#define FREERDP_LIB_CODEC_NSC_SSE2_H
+
+#include <freerdp/codec/nsc.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void nsc_init_sse2(NSC_CONTEXT* context);
+
+#ifdef WITH_SSE2
+#ifndef NSC_INIT_SIMD
+#define NSC_INIT_SIMD(_context) nsc_init_sse2(_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_NSC_SSE2_H */
diff --git a/libfreerdp/codec/nsc_types.h b/libfreerdp/codec/nsc_types.h
new file mode 100644
index 0000000..731a08a
--- /dev/null
+++ b/libfreerdp/codec/nsc_types.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * NSCodec Library
+ *
+ * Copyright 2011 Samsung, Author Jiten Pathy
+ * Copyright 2012 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_NSC_TYPES_H
+#define FREERDP_LIB_CODEC_NSC_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/utils/profiler.h>
+#include <freerdp/codec/nsc.h>
+
+#define ROUND_UP_TO(_b, _n) (_b + ((~(_b & (_n - 1)) + 0x1) & (_n - 1)))
+#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v)))
+
+typedef struct
+{
+ wLog* log;
+
+ BYTE* PlaneBuffers[5]; /* Decompressed Plane Buffers in the respective order */
+ UINT32 PlaneBuffersLength; /* Lengths of each plane buffer */
+
+ /* profilers */
+ PROFILER_DEFINE(prof_nsc_rle_decompress_data)
+ PROFILER_DEFINE(prof_nsc_decode)
+ PROFILER_DEFINE(prof_nsc_rle_compress_data)
+ PROFILER_DEFINE(prof_nsc_encode)
+} NSC_CONTEXT_PRIV;
+
+struct S_NSC_CONTEXT
+{
+ UINT32 OrgByteCount[4];
+ UINT32 format;
+ UINT16 width;
+ UINT16 height;
+ BYTE* BitmapData;
+ UINT32 BitmapDataLength;
+
+ BYTE* Planes;
+ size_t PlanesSize;
+ UINT32 PlaneByteCount[4];
+ UINT32 ColorLossLevel;
+ UINT32 ChromaSubsamplingLevel;
+ BOOL DynamicColorFidelity;
+
+ /* color palette allocated by the application */
+ const BYTE* palette;
+
+ BOOL (*decode)(NSC_CONTEXT* context);
+ BOOL (*encode)(NSC_CONTEXT* context, const BYTE* BitmapData, UINT32 rowstride);
+
+ NSC_CONTEXT_PRIV* priv;
+};
+
+#endif /* FREERDP_LIB_CODEC_NSC_TYPES_H */
diff --git a/libfreerdp/codec/planar.c b/libfreerdp/codec/planar.c
new file mode 100644
index 0000000..0ec0862
--- /dev/null
+++ b/libfreerdp/codec/planar.c
@@ -0,0 +1,1753 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RDP6 Planar Codec
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define PLANAR_ALIGN(val, align) \
+ ((val) % (align) == 0) ? (val) : ((val) + (align) - (val) % (align))
+
+typedef struct
+{
+ /**
+ * controlByte:
+ * [0-3]: nRunLength
+ * [4-7]: cRawBytes
+ */
+ BYTE controlByte;
+ BYTE* rawValues;
+} RDP6_RLE_SEGMENT;
+
+typedef struct
+{
+ UINT32 cSegments;
+ RDP6_RLE_SEGMENT* segments;
+} RDP6_RLE_SEGMENTS;
+
+typedef struct
+{
+ /**
+ * formatHeader:
+ * [0-2]: Color Loss Level (CLL)
+ * [3] : Chroma Subsampling (CS)
+ * [4] : Run Length Encoding (RLE)
+ * [5] : No Alpha (NA)
+ * [6-7]: Reserved
+ */
+ BYTE formatHeader;
+} RDP6_BITMAP_STREAM;
+
+struct S_BITMAP_PLANAR_CONTEXT
+{
+ UINT32 maxWidth;
+ UINT32 maxHeight;
+ UINT32 maxPlaneSize;
+
+ BOOL AllowSkipAlpha;
+ BOOL AllowRunLengthEncoding;
+ BOOL AllowColorSubsampling;
+ BOOL AllowDynamicColorFidelity;
+
+ UINT32 ColorLossLevel;
+
+ BYTE* planes[4];
+ BYTE* planesBuffer;
+
+ BYTE* deltaPlanes[4];
+ BYTE* deltaPlanesBuffer;
+
+ BYTE* rlePlanes[4];
+ BYTE* rlePlanesBuffer;
+
+ BYTE* pTempData;
+ UINT32 nTempStep;
+
+ BOOL bgr;
+ BOOL topdown;
+};
+
+static INLINE UINT32 planar_invert_format(BITMAP_PLANAR_CONTEXT* planar, BOOL alpha,
+ UINT32 DstFormat)
+{
+
+ if (planar->bgr && alpha)
+ {
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_ARGB32:
+ DstFormat = PIXEL_FORMAT_ABGR32;
+ break;
+ case PIXEL_FORMAT_XRGB32:
+ DstFormat = PIXEL_FORMAT_XBGR32;
+ break;
+ case PIXEL_FORMAT_ABGR32:
+ DstFormat = PIXEL_FORMAT_ARGB32;
+ break;
+ case PIXEL_FORMAT_XBGR32:
+ DstFormat = PIXEL_FORMAT_XRGB32;
+ break;
+ case PIXEL_FORMAT_BGRA32:
+ DstFormat = PIXEL_FORMAT_RGBA32;
+ break;
+ case PIXEL_FORMAT_BGRX32:
+ DstFormat = PIXEL_FORMAT_RGBX32;
+ break;
+ case PIXEL_FORMAT_RGBA32:
+ DstFormat = PIXEL_FORMAT_BGRA32;
+ break;
+ case PIXEL_FORMAT_RGBX32:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+ case PIXEL_FORMAT_RGB24:
+ DstFormat = PIXEL_FORMAT_BGR24;
+ break;
+ case PIXEL_FORMAT_BGR24:
+ DstFormat = PIXEL_FORMAT_RGB24;
+ break;
+ case PIXEL_FORMAT_RGB16:
+ DstFormat = PIXEL_FORMAT_BGR16;
+ break;
+ case PIXEL_FORMAT_BGR16:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+ case PIXEL_FORMAT_ARGB15:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case PIXEL_FORMAT_RGB15:
+ DstFormat = PIXEL_FORMAT_BGR15;
+ break;
+ case PIXEL_FORMAT_ABGR15:
+ DstFormat = PIXEL_FORMAT_ARGB15;
+ break;
+ case PIXEL_FORMAT_BGR15:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+ default:
+ break;
+ }
+ }
+ return DstFormat;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* plane, UINT32 width,
+ UINT32 height, BYTE* outPlane,
+ UINT32* dstSize);
+static INLINE BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width,
+ UINT32 height, BYTE* outPlane);
+
+static INLINE INT32 planar_skip_plane_rle(const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth,
+ UINT32 nHeight)
+{
+ UINT32 used = 0;
+ BYTE controlByte = 0;
+
+ WINPR_ASSERT(pSrcData);
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ for (UINT32 x = 0; x < nWidth;)
+ {
+ int cRawBytes = 0;
+ int nRunLength = 0;
+
+ if (used >= SrcSize)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used,
+ SrcSize);
+ return -1;
+ }
+
+ controlByte = pSrcData[used++];
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ used += cRawBytes;
+ x += cRawBytes;
+ x += nRunLength;
+
+ if (x > nWidth)
+ {
+ WLog_ERR(TAG, "planar plane x %" PRIu32 " exceeds width %" PRIu32, x, nWidth);
+ return -1;
+ }
+
+ if (used > SrcSize)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used,
+ INT32_MAX);
+ return -1;
+ }
+ }
+ }
+
+ if (used > INT32_MAX)
+ {
+ WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used, SrcSize);
+ return -1;
+ }
+ return (INT32)used;
+}
+
+static INLINE INT32 planar_decompress_plane_rle_only(const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 nWidth, UINT32 nHeight)
+{
+ UINT32 pixel = 0;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ INT32 deltaValue = 0;
+ BYTE controlByte = 0;
+ BYTE* currentScanline = NULL;
+ BYTE* previousScanline = NULL;
+ const BYTE* srcp = pSrcData;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+
+ previousScanline = NULL;
+
+ for (INT32 y = 0; y < (INT32)nHeight; y++)
+ {
+ BYTE* dstp = &pDstData[((y) * (INT32)nWidth)];
+ pixel = 0;
+ currentScanline = dstp;
+
+ for (INT32 x = 0; x < (INT32)nWidth;)
+ {
+ controlByte = *srcp;
+ srcp++;
+
+ if ((srcp - pSrcData) > SrcSize * 1ll)
+ {
+ WLog_ERR(TAG, "error reading input buffer");
+ return -1;
+ }
+
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 1ll)
+ {
+ WLog_ERR(TAG, "too many pixels in scanline");
+ return -1;
+ }
+
+ if (!previousScanline)
+ {
+ /* first scanline, absolute values */
+ while (cRawBytes > 0)
+ {
+ pixel = *srcp;
+ srcp++;
+ *dstp = pixel;
+ dstp++;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ *dstp = pixel;
+ dstp++;
+ x++;
+ nRunLength--;
+ }
+ }
+ else
+ {
+ /* delta values relative to previous scanline */
+ while (cRawBytes > 0)
+ {
+ deltaValue = *srcp;
+ srcp++;
+
+ if (deltaValue & 1)
+ {
+ deltaValue = deltaValue >> 1;
+ deltaValue = deltaValue + 1;
+ pixel = -deltaValue;
+ }
+ else
+ {
+ deltaValue = deltaValue >> 1;
+ pixel = deltaValue;
+ }
+
+ deltaValue = previousScanline[x] + pixel;
+ *dstp = deltaValue;
+ dstp++;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ deltaValue = previousScanline[x] + pixel;
+ *dstp = deltaValue;
+ dstp++;
+ x++;
+ nRunLength--;
+ }
+ }
+ }
+
+ previousScanline = currentScanline;
+ }
+
+ return (INT32)(srcp - pSrcData);
+}
+
+static INLINE INT32 planar_decompress_plane_rle(const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, INT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ UINT32 nChannel, BOOL vFlip)
+{
+ UINT32 pixel = 0;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ INT32 deltaValue = 0;
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+ BYTE controlByte = 0;
+ BYTE* currentScanline = NULL;
+ BYTE* previousScanline = NULL;
+ const BYTE* srcp = pSrcData;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+ WINPR_ASSERT(nDstStep <= INT32_MAX);
+
+ previousScanline = NULL;
+
+ if (vFlip)
+ {
+ beg = (INT32)nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = (INT32)nHeight;
+ inc = 1;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel];
+ pixel = 0;
+ currentScanline = dstp;
+
+ for (INT32 x = 0; x < (INT32)nWidth;)
+ {
+ controlByte = *srcp;
+ srcp++;
+
+ if ((srcp - pSrcData) > SrcSize * 1ll)
+ {
+ WLog_ERR(TAG, "error reading input buffer");
+ return -1;
+ }
+
+ nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte);
+ cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte);
+
+ if (nRunLength == 1)
+ {
+ nRunLength = cRawBytes + 16;
+ cRawBytes = 0;
+ }
+ else if (nRunLength == 2)
+ {
+ nRunLength = cRawBytes + 32;
+ cRawBytes = 0;
+ }
+
+ if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 4ll)
+ {
+ WLog_ERR(TAG, "too many pixels in scanline");
+ return -1;
+ }
+
+ if (!previousScanline)
+ {
+ /* first scanline, absolute values */
+ while (cRawBytes > 0)
+ {
+ pixel = *srcp;
+ srcp++;
+ *dstp = pixel;
+ dstp += 4;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ *dstp = pixel;
+ dstp += 4;
+ x++;
+ nRunLength--;
+ }
+ }
+ else
+ {
+ /* delta values relative to previous scanline */
+ while (cRawBytes > 0)
+ {
+ deltaValue = *srcp;
+ srcp++;
+
+ if (deltaValue & 1)
+ {
+ deltaValue = deltaValue >> 1;
+ deltaValue = deltaValue + 1;
+ pixel = -deltaValue;
+ }
+ else
+ {
+ deltaValue = deltaValue >> 1;
+ pixel = deltaValue;
+ }
+
+ deltaValue = previousScanline[x * 4] + pixel;
+ *dstp = deltaValue;
+ dstp += 4;
+ x++;
+ cRawBytes--;
+ }
+
+ while (nRunLength > 0)
+ {
+ deltaValue = previousScanline[x * 4] + pixel;
+ *dstp = deltaValue;
+ dstp += 4;
+ x++;
+ nRunLength--;
+ }
+ }
+ }
+
+ previousScanline = currentScanline;
+ }
+
+ return (INT32)(srcp - pSrcData);
+}
+
+static INLINE INT32 planar_set_plane(BYTE bValue, BYTE* pDstData, INT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 nChannel,
+ BOOL vFlip)
+{
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+
+ WINPR_ASSERT(nHeight <= INT32_MAX);
+ WINPR_ASSERT(nWidth <= INT32_MAX);
+ WINPR_ASSERT(nDstStep <= INT32_MAX);
+
+ if (vFlip)
+ {
+ beg = (INT32)nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = (INT32)nHeight;
+ inc = 1;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel];
+
+ for (INT32 x = 0; x < (INT32)nWidth; ++x)
+ {
+ *dstp = bValue;
+ dstp += 4;
+ }
+ }
+
+ return 0;
+}
+
+static INLINE BOOL writeLine(BYTE** ppRgba, UINT32 DstFormat, UINT32 width, const BYTE** ppR,
+ const BYTE** ppG, const BYTE** ppB, const BYTE** ppA)
+{
+ WINPR_ASSERT(ppRgba);
+ WINPR_ASSERT(ppR);
+ WINPR_ASSERT(ppG);
+ WINPR_ASSERT(ppB);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_BGRA32:
+ for (UINT32 x = 0; x < width; x++)
+ {
+ *(*ppRgba)++ = *(*ppB)++;
+ *(*ppRgba)++ = *(*ppG)++;
+ *(*ppRgba)++ = *(*ppR)++;
+ *(*ppRgba)++ = *(*ppA)++;
+ }
+
+ return TRUE;
+
+ case PIXEL_FORMAT_BGRX32:
+ for (UINT32 x = 0; x < width; x++)
+ {
+ *(*ppRgba)++ = *(*ppB)++;
+ *(*ppRgba)++ = *(*ppG)++;
+ *(*ppRgba)++ = *(*ppR)++;
+ *(*ppRgba)++ = 0xFF;
+ }
+
+ return TRUE;
+
+ default:
+ if (ppA)
+ {
+ for (UINT32 x = 0; x < width; x++)
+ {
+ BYTE alpha = *(*ppA)++;
+ UINT32 color =
+ FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha);
+ FreeRDPWriteColor(*ppRgba, DstFormat, color);
+ *ppRgba += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+ else
+ {
+ const BYTE alpha = 0xFF;
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ UINT32 color =
+ FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha);
+ FreeRDPWriteColor(*ppRgba, DstFormat, color);
+ *ppRgba += FreeRDPGetBytesPerPixel(DstFormat);
+ }
+ }
+
+ return TRUE;
+ }
+}
+
+static INLINE BOOL planar_decompress_planes_raw(const BYTE* pSrcData[4], BYTE* pDstData,
+ UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, UINT32 nWidth, UINT32 nHeight,
+ BOOL vFlip, UINT32 totalHeight)
+{
+ INT32 beg = 0;
+ INT32 end = 0;
+ INT32 inc = 0;
+ const BYTE* pR = pSrcData[0];
+ const BYTE* pG = pSrcData[1];
+ const BYTE* pB = pSrcData[2];
+ const BYTE* pA = pSrcData[3];
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (vFlip)
+ {
+ beg = nHeight - 1;
+ end = -1;
+ inc = -1;
+ }
+ else
+ {
+ beg = 0;
+ end = nHeight;
+ inc = 1;
+ }
+
+ if (nYDst + nHeight > totalHeight)
+ {
+ WLog_ERR(TAG,
+ "planar plane destination Y %" PRIu32 " + height %" PRIu32
+ " exceeds totalHeight %" PRIu32,
+ nYDst, nHeight, totalHeight);
+ return FALSE;
+ }
+
+ if ((nXDst + nWidth) * bpp > nDstStep)
+ {
+ WLog_ERR(TAG,
+ "planar plane destination (X %" PRIu32 " + width %" PRIu32 ") * bpp %" PRIu32
+ " exceeds stride %" PRIu32,
+ nXDst, nWidth, bpp, nDstStep);
+ return FALSE;
+ }
+
+ for (INT32 y = beg; y != end; y += inc)
+ {
+ BYTE* pRGB = NULL;
+
+ if (y > (INT64)nHeight)
+ {
+ WLog_ERR(TAG, "planar plane destination Y %" PRId32 " exceeds height %" PRIu32, y,
+ nHeight);
+ return FALSE;
+ }
+
+ pRGB = &pDstData[((nYDst + y) * nDstStep) + (nXDst * bpp)];
+
+ if (!writeLine(&pRGB, DstFormat, nWidth, &pR, &pG, &pB, &pA))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL planar_subsample_expand(const BYTE* plane, size_t planeLength, UINT32 nWidth,
+ UINT32 nHeight, UINT32 nPlaneWidth, UINT32 nPlaneHeight,
+ BYTE* deltaPlane)
+{
+ size_t pos = 0;
+ WINPR_UNUSED(planeLength);
+
+ WINPR_ASSERT(plane);
+ WINPR_ASSERT(deltaPlane);
+
+ if (nWidth > nPlaneWidth * 2)
+ {
+ WLog_ERR(TAG, "planar subsample width %" PRIu32 " > PlaneWidth %" PRIu32 " * 2", nWidth,
+ nPlaneWidth);
+ return FALSE;
+ }
+
+ if (nHeight > nPlaneHeight * 2)
+ {
+ WLog_ERR(TAG, "planar subsample height %" PRIu32 " > PlaneHeight %" PRIu32 " * 2", nHeight,
+ nPlaneHeight);
+ return FALSE;
+ }
+
+ for (UINT32 y = 0; y < nHeight; y++)
+ {
+ const BYTE* src = plane + y / 2 * nPlaneWidth;
+
+ for (UINT32 x = 0; x < nWidth; x++)
+ {
+ deltaPlane[pos++] = src[x / 2];
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL planar_decompress(BITMAP_PLANAR_CONTEXT* planar, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE* pDstData, UINT32 DstFormat,
+ UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth,
+ UINT32 nDstHeight, BOOL vFlip)
+{
+ BOOL cs = 0;
+ BOOL rle = 0;
+ UINT32 cll = 0;
+ BOOL alpha = 0;
+ BOOL useAlpha = FALSE;
+ INT32 status = 0;
+ const BYTE* srcp = NULL;
+ UINT32 subSize = 0;
+ UINT32 subWidth = 0;
+ UINT32 subHeight = 0;
+ UINT32 planeSize = 0;
+ INT32 rleSizes[4] = { 0, 0, 0, 0 };
+ UINT32 rawSizes[4];
+ UINT32 rawWidths[4];
+ UINT32 rawHeights[4];
+ BYTE FormatHeader = 0;
+ const BYTE* planes[4] = { 0 };
+ const UINT32 w = MIN(nSrcWidth, nDstWidth);
+ const UINT32 h = MIN(nSrcHeight, nDstHeight);
+ const primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(planar);
+ WINPR_ASSERT(prims);
+
+ if (nDstStep <= 0)
+ nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat);
+
+ srcp = pSrcData;
+
+ if (!pSrcData)
+ {
+ WLog_ERR(TAG, "Invalid argument pSrcData=NULL");
+ return FALSE;
+ }
+
+ if (!pDstData)
+ {
+ WLog_ERR(TAG, "Invalid argument pDstData=NULL");
+ return FALSE;
+ }
+
+ FormatHeader = *srcp++;
+ cll = (FormatHeader & PLANAR_FORMAT_HEADER_CLL_MASK);
+ cs = (FormatHeader & PLANAR_FORMAT_HEADER_CS) ? TRUE : FALSE;
+ rle = (FormatHeader & PLANAR_FORMAT_HEADER_RLE) ? TRUE : FALSE;
+ alpha = (FormatHeader & PLANAR_FORMAT_HEADER_NA) ? FALSE : TRUE;
+
+ DstFormat = planar_invert_format(planar, alpha, DstFormat);
+
+ if (alpha)
+ useAlpha = FreeRDPColorHasAlpha(DstFormat);
+
+ // WLog_INFO(TAG, "CLL: %"PRIu32" CS: %"PRIu8" RLE: %"PRIu8" ALPHA: %"PRIu8"", cll, cs, rle,
+ // alpha);
+
+ if (!cll && cs)
+ {
+ WLog_ERR(TAG, "Chroma subsampling requires YCoCg and does not work with RGB data");
+ return FALSE; /* Chroma subsampling requires YCoCg */
+ }
+
+ subWidth = (nSrcWidth / 2) + (nSrcWidth % 2);
+ subHeight = (nSrcHeight / 2) + (nSrcHeight % 2);
+ planeSize = nSrcWidth * nSrcHeight;
+ subSize = subWidth * subHeight;
+
+ if (!cs)
+ {
+ rawSizes[0] = planeSize; /* LumaOrRedPlane */
+ rawWidths[0] = nSrcWidth;
+ rawHeights[0] = nSrcHeight;
+ rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = nSrcWidth;
+ rawHeights[1] = nSrcHeight;
+ rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = nSrcWidth;
+ rawHeights[2] = nSrcHeight;
+ rawSizes[3] = planeSize; /* AlphaPlane */
+ rawWidths[3] = nSrcWidth;
+ rawHeights[3] = nSrcHeight;
+ }
+ else /* Chroma Subsampling */
+ {
+ rawSizes[0] = planeSize; /* LumaOrRedPlane */
+ rawWidths[0] = nSrcWidth;
+ rawHeights[0] = nSrcHeight;
+ rawSizes[1] = subSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = subWidth;
+ rawHeights[1] = subHeight;
+ rawSizes[2] = subSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = subWidth;
+ rawHeights[2] = subHeight;
+ rawSizes[3] = planeSize; /* AlphaPlane */
+ rawWidths[3] = nSrcWidth;
+ rawHeights[3] = nSrcHeight;
+ }
+
+ if (!rle) /* RAW */
+ {
+ UINT32 base = planeSize * 3;
+ if (cs)
+ base = planeSize + planeSize / 2;
+
+ if (alpha)
+ {
+ if ((SrcSize - (srcp - pSrcData)) < (planeSize + base))
+ {
+ WLog_ERR(TAG, "Alpha plane size mismatch %" PRIu32 " < %" PRIu32,
+ SrcSize - (srcp - pSrcData), (planeSize + base));
+ return FALSE;
+ }
+
+ planes[3] = srcp; /* AlphaPlane */
+ planes[0] = planes[3] + rawSizes[3]; /* LumaOrRedPlane */
+ planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */
+ planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */
+
+ if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize])
+ {
+ WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2],
+ &pSrcData[SrcSize]);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if ((SrcSize - (srcp - pSrcData)) < base)
+ {
+ WLog_ERR(TAG, "plane size mismatch %" PRIu32 " < %" PRIu32,
+ SrcSize - (srcp - pSrcData), base);
+ return FALSE;
+ }
+
+ planes[0] = srcp; /* LumaOrRedPlane */
+ planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */
+ planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */
+
+ if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize])
+ {
+ WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2],
+ &pSrcData[SrcSize]);
+ return FALSE;
+ }
+ }
+ }
+ else /* RLE */
+ {
+ if (alpha)
+ {
+ planes[3] = srcp;
+ rleSizes[3] = planar_skip_plane_rle(planes[3], SrcSize - (planes[3] - pSrcData),
+ rawWidths[3], rawHeights[3]); /* AlphaPlane */
+
+ if (rleSizes[3] < 0)
+ return FALSE;
+
+ planes[0] = planes[3] + rleSizes[3];
+ }
+ else
+ planes[0] = srcp;
+
+ rleSizes[0] = planar_skip_plane_rle(planes[0], SrcSize - (planes[0] - pSrcData),
+ rawWidths[0], rawHeights[0]); /* RedPlane */
+
+ if (rleSizes[0] < 0)
+ return FALSE;
+
+ planes[1] = planes[0] + rleSizes[0];
+ rleSizes[1] = planar_skip_plane_rle(planes[1], SrcSize - (planes[1] - pSrcData),
+ rawWidths[1], rawHeights[1]); /* GreenPlane */
+
+ if (rleSizes[1] < 1)
+ return FALSE;
+
+ planes[2] = planes[1] + rleSizes[1];
+ rleSizes[2] = planar_skip_plane_rle(planes[2], SrcSize - (planes[2] - pSrcData),
+ rawWidths[2], rawHeights[2]); /* BluePlane */
+
+ if (rleSizes[2] < 1)
+ return FALSE;
+ }
+
+ if (!cll) /* RGB */
+ {
+ UINT32 TempFormat = 0;
+ BYTE* pTempData = pDstData;
+ UINT32 nTempStep = nDstStep;
+ UINT32 nTotalHeight = nYDst + nDstHeight;
+
+ if (useAlpha)
+ TempFormat = PIXEL_FORMAT_BGRA32;
+ else
+ TempFormat = PIXEL_FORMAT_BGRX32;
+
+ TempFormat = planar_invert_format(planar, alpha, TempFormat);
+
+ if ((TempFormat != DstFormat) || (nSrcWidth != nDstWidth) || (nSrcHeight != nDstHeight))
+ {
+ pTempData = planar->pTempData;
+ nTempStep = planar->nTempStep;
+ nTotalHeight = planar->maxHeight;
+ }
+
+ if (!rle) /* RAW */
+ {
+ if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight))
+ return FALSE;
+
+ if (alpha)
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3];
+ else /* NoAlpha */
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2];
+
+ if ((SrcSize - (srcp - pSrcData)) == 1)
+ srcp++; /* pad */
+ }
+ else /* RLE */
+ {
+ status =
+ planar_decompress_plane_rle(planes[0], rleSizes[0], pTempData, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, 2, vFlip); /* RedPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status = planar_decompress_plane_rle(planes[1], rleSizes[1], pTempData, nTempStep,
+ nXDst, nYDst, nSrcWidth, nSrcHeight, 1,
+ vFlip); /* GreenPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle(planes[2], rleSizes[2], pTempData, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, 0, vFlip); /* BluePlane */
+
+ if (status < 0)
+ return FALSE;
+
+ srcp += rleSizes[0] + rleSizes[1] + rleSizes[2];
+
+ if (useAlpha)
+ {
+ status = planar_decompress_plane_rle(planes[3], rleSizes[3], pTempData, nTempStep,
+ nXDst, nYDst, nSrcWidth, nSrcHeight, 3,
+ vFlip); /* AlphaPlane */
+ }
+ else
+ status = planar_set_plane(0xFF, pTempData, nTempStep, nXDst, nYDst, nSrcWidth,
+ nSrcHeight, 3, vFlip);
+
+ if (status < 0)
+ return FALSE;
+
+ if (alpha)
+ srcp += rleSizes[3];
+ }
+
+ if (pTempData != pDstData)
+ {
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, w, h, pTempData,
+ TempFormat, nTempStep, nXDst, nYDst, NULL, FREERDP_FLIP_NONE))
+ {
+ WLog_ERR(TAG, "planar image copy failed");
+ return FALSE;
+ }
+ }
+ }
+ else /* YCoCg */
+ {
+ UINT32 TempFormat = 0;
+ BYTE* pTempData = planar->pTempData;
+ UINT32 nTempStep = planar->nTempStep;
+ UINT32 nTotalHeight = planar->maxHeight;
+ BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep];
+
+ if (useAlpha)
+ TempFormat = PIXEL_FORMAT_BGRA32;
+ else
+ TempFormat = PIXEL_FORMAT_BGRX32;
+
+ if (!pTempData)
+ return FALSE;
+
+ if (rle) /* RLE encoded data. Decode and handle it like raw data. */
+ {
+ BYTE* rleBuffer[4] = { 0 };
+
+ if (!planar->rlePlanesBuffer)
+ return FALSE;
+
+ rleBuffer[3] = planar->rlePlanesBuffer; /* AlphaPlane */
+ rleBuffer[0] = rleBuffer[3] + planeSize; /* LumaOrRedPlane */
+ rleBuffer[1] = rleBuffer[0] + planeSize; /* OrangeChromaOrGreenPlane */
+ rleBuffer[2] = rleBuffer[1] + planeSize; /* GreenChromaOrBluePlane */
+ if (useAlpha)
+ {
+ status =
+ planar_decompress_plane_rle_only(planes[3], rleSizes[3], rleBuffer[3],
+ rawWidths[3], rawHeights[3]); /* AlphaPlane */
+
+ if (status < 0)
+ return FALSE;
+ }
+
+ if (alpha)
+ srcp += rleSizes[3];
+
+ status = planar_decompress_plane_rle_only(planes[0], rleSizes[0], rleBuffer[0],
+ rawWidths[0], rawHeights[0]); /* LumaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle_only(planes[1], rleSizes[1], rleBuffer[1], rawWidths[1],
+ rawHeights[1]); /* OrangeChromaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ status =
+ planar_decompress_plane_rle_only(planes[2], rleSizes[2], rleBuffer[2], rawWidths[2],
+ rawHeights[2]); /* GreenChromaPlane */
+
+ if (status < 0)
+ return FALSE;
+
+ planes[0] = rleBuffer[0];
+ planes[1] = rleBuffer[1];
+ planes[2] = rleBuffer[2];
+ planes[3] = rleBuffer[3];
+ }
+
+ /* RAW */
+ {
+ if (cs)
+ { /* Chroma subsampling for Co and Cg:
+ * Each pixel contains the value that should be expanded to
+ * [2x,2y;2x+1,2y;2x+1,2y+1;2x;2y+1] */
+ if (!planar_subsample_expand(planes[1], rawSizes[1], nSrcWidth, nSrcHeight,
+ rawWidths[1], rawHeights[1], planar->deltaPlanes[0]))
+ return FALSE;
+
+ planes[1] = planar->deltaPlanes[0];
+ rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */
+ rawWidths[1] = nSrcWidth;
+ rawHeights[1] = nSrcHeight;
+
+ if (!planar_subsample_expand(planes[2], rawSizes[2], nSrcWidth, nSrcHeight,
+ rawWidths[2], rawHeights[2], planar->deltaPlanes[1]))
+ return FALSE;
+
+ planes[2] = planar->deltaPlanes[1];
+ rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */
+ rawWidths[2] = nSrcWidth;
+ rawHeights[2] = nSrcHeight;
+ }
+
+ if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst,
+ nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight))
+ return FALSE;
+
+ if (alpha)
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3];
+ else /* NoAlpha */
+ srcp += rawSizes[0] + rawSizes[1] + rawSizes[2];
+
+ if ((SrcSize - (srcp - pSrcData)) == 1)
+ srcp++; /* pad */
+ }
+
+ WINPR_ASSERT(prims->YCoCgToRGB_8u_AC4R);
+ int rc = prims->YCoCgToRGB_8u_AC4R(pTempData, nTempStep, dst, DstFormat, nDstStep, w, h,
+ cll, useAlpha);
+ if (rc != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "YCoCgToRGB_8u_AC4R failed with %d", rc);
+ return FALSE;
+ }
+ }
+
+ WINPR_UNUSED(srcp);
+ return TRUE;
+}
+
+static INLINE BOOL freerdp_split_color_planes(BITMAP_PLANAR_CONTEXT* planar, const BYTE* data,
+ UINT32 format, UINT32 width, UINT32 height,
+ UINT32 scanline, BYTE* planes[4])
+{
+ WINPR_ASSERT(planar);
+
+ if ((width > INT32_MAX) || (height > INT32_MAX) || (scanline > INT32_MAX))
+ return FALSE;
+
+ if (scanline == 0)
+ scanline = width * FreeRDPGetBytesPerPixel(format);
+
+ if (planar->topdown)
+ {
+ UINT32 k = 0;
+ for (UINT32 i = 0; i < height; i++)
+ {
+ const BYTE* pixel = &data[scanline * (UINT32)i];
+
+ for (UINT32 j = 0; j < width; j++)
+ {
+ const UINT32 color = FreeRDPReadColor(pixel, format);
+ pixel += FreeRDPGetBytesPerPixel(format);
+ FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k],
+ &planes[0][k], NULL);
+ k++;
+ }
+ }
+ }
+ else
+ {
+ UINT32 k = 0;
+
+ for (INT64 i = (INT64)height - 1; i >= 0; i--)
+ {
+ const BYTE* pixel = &data[scanline * (UINT32)i];
+
+ for (UINT32 j = 0; j < width; j++)
+ {
+ const UINT32 color = FreeRDPReadColor(pixel, format);
+ pixel += FreeRDPGetBytesPerPixel(format);
+ FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k],
+ &planes[0][k], NULL);
+ k++;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static INLINE UINT32 freerdp_bitmap_planar_write_rle_bytes(const BYTE* pInBuffer, UINT32 cRawBytes,
+ UINT32 nRunLength, BYTE* pOutBuffer,
+ UINT32 outBufferSize)
+{
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ BYTE controlByte = 0;
+ UINT32 nBytesToWrite = 0;
+ pInput = pInBuffer;
+ pOutput = pOutBuffer;
+
+ if (!cRawBytes && !nRunLength)
+ return 0;
+
+ if (nRunLength < 3)
+ {
+ cRawBytes += nRunLength;
+ nRunLength = 0;
+ }
+
+ while (cRawBytes)
+ {
+ if (cRawBytes < 16)
+ {
+ if (nRunLength > 15)
+ {
+ if (nRunLength < 18)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(13, cRawBytes);
+ nRunLength -= 13;
+ cRawBytes = 0;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(15, cRawBytes);
+ nRunLength -= 15;
+ cRawBytes = 0;
+ }
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(nRunLength, cRawBytes);
+ nRunLength = 0;
+ cRawBytes = 0;
+ }
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(0, 15);
+ cRawBytes -= 15;
+ }
+
+ if (outBufferSize < 1)
+ return 0;
+
+ outBufferSize--;
+ *pOutput = controlByte;
+ pOutput++;
+ nBytesToWrite = (int)(controlByte >> 4);
+
+ if (nBytesToWrite)
+ {
+ if (outBufferSize < nBytesToWrite)
+ return 0;
+
+ outBufferSize -= nBytesToWrite;
+ CopyMemory(pOutput, pInput, nBytesToWrite);
+ pOutput += nBytesToWrite;
+ pInput += nBytesToWrite;
+ }
+ }
+
+ while (nRunLength)
+ {
+ if (nRunLength > 47)
+ {
+ if (nRunLength < 50)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, 13);
+ nRunLength -= 45;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, 15);
+ nRunLength -= 47;
+ }
+ }
+ else if (nRunLength > 31)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(2, (nRunLength - 32));
+ nRunLength = 0;
+ }
+ else if (nRunLength > 15)
+ {
+ controlByte = PLANAR_CONTROL_BYTE(1, (nRunLength - 16));
+ nRunLength = 0;
+ }
+ else
+ {
+ controlByte = PLANAR_CONTROL_BYTE(nRunLength, 0);
+ nRunLength = 0;
+ }
+
+ if (outBufferSize < 1)
+ return 0;
+
+ --outBufferSize;
+ *pOutput = controlByte;
+ pOutput++;
+ }
+
+ return (pOutput - pOutBuffer);
+}
+
+static INLINE UINT32 freerdp_bitmap_planar_encode_rle_bytes(const BYTE* pInBuffer,
+ UINT32 inBufferSize, BYTE* pOutBuffer,
+ UINT32 outBufferSize)
+{
+ BYTE symbol = 0;
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ const BYTE* pBytes = NULL;
+ UINT32 cRawBytes = 0;
+ UINT32 nRunLength = 0;
+ UINT32 bSymbolMatch = 0;
+ UINT32 nBytesWritten = 0;
+ UINT32 nTotalBytesWritten = 0;
+ symbol = 0;
+ cRawBytes = 0;
+ nRunLength = 0;
+ pInput = pInBuffer;
+ pOutput = pOutBuffer;
+ nTotalBytesWritten = 0;
+
+ if (!outBufferSize)
+ return 0;
+
+ do
+ {
+ if (!inBufferSize)
+ break;
+
+ bSymbolMatch = (symbol == *pInput) ? TRUE : FALSE;
+ symbol = *pInput;
+ pInput++;
+ inBufferSize--;
+
+ if (nRunLength && !bSymbolMatch)
+ {
+ if (nRunLength < 3)
+ {
+ cRawBytes += nRunLength;
+ nRunLength = 0;
+ }
+ else
+ {
+ pBytes = pInput - (cRawBytes + nRunLength + 1);
+ nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength,
+ pOutput, outBufferSize);
+ nRunLength = 0;
+
+ if (!nBytesWritten || (nBytesWritten > outBufferSize))
+ return nRunLength;
+
+ nTotalBytesWritten += nBytesWritten;
+ outBufferSize -= nBytesWritten;
+ pOutput += nBytesWritten;
+ cRawBytes = 0;
+ }
+ }
+
+ nRunLength += bSymbolMatch;
+ cRawBytes += (!bSymbolMatch) ? TRUE : FALSE;
+ } while (outBufferSize);
+
+ if (cRawBytes || nRunLength)
+ {
+ pBytes = pInput - (cRawBytes + nRunLength);
+ nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength,
+ pOutput, outBufferSize);
+
+ if (!nBytesWritten)
+ return 0;
+
+ nTotalBytesWritten += nBytesWritten;
+ }
+
+ if (inBufferSize)
+ return 0;
+
+ return nTotalBytesWritten;
+}
+
+BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* inPlane, UINT32 width, UINT32 height,
+ BYTE* outPlane, UINT32* dstSize)
+{
+ UINT32 index = 0;
+ const BYTE* pInput = NULL;
+ BYTE* pOutput = NULL;
+ UINT32 outBufferSize = 0;
+ UINT32 nBytesWritten = 0;
+ UINT32 nTotalBytesWritten = 0;
+
+ if (!outPlane)
+ return FALSE;
+
+ index = 0;
+ pInput = inPlane;
+ pOutput = outPlane;
+ outBufferSize = *dstSize;
+ nTotalBytesWritten = 0;
+
+ while (outBufferSize)
+ {
+ nBytesWritten =
+ freerdp_bitmap_planar_encode_rle_bytes(pInput, width, pOutput, outBufferSize);
+
+ if ((!nBytesWritten) || (nBytesWritten > outBufferSize))
+ return FALSE;
+
+ outBufferSize -= nBytesWritten;
+ nTotalBytesWritten += nBytesWritten;
+ pOutput += nBytesWritten;
+ pInput += width;
+ index++;
+
+ if (index >= height)
+ break;
+ }
+
+ *dstSize = nTotalBytesWritten;
+ return TRUE;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_compress_planes_rle(BYTE* inPlanes[4], UINT32 width,
+ UINT32 height, BYTE* outPlanes,
+ UINT32* dstSizes, BOOL skipAlpha)
+{
+ UINT32 outPlanesSize = width * height * 4;
+
+ /* AlphaPlane */
+ if (skipAlpha)
+ {
+ dstSizes[0] = 0;
+ }
+ else
+ {
+ dstSizes[0] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[0], width, height, outPlanes,
+ &dstSizes[0]))
+ return FALSE;
+
+ outPlanes += dstSizes[0];
+ outPlanesSize -= dstSizes[0];
+ }
+
+ /* LumaOrRedPlane */
+ dstSizes[1] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[1], width, height, outPlanes,
+ &dstSizes[1]))
+ return FALSE;
+
+ outPlanes += dstSizes[1];
+ outPlanesSize -= dstSizes[1];
+ /* OrangeChromaOrGreenPlane */
+ dstSizes[2] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[2], width, height, outPlanes,
+ &dstSizes[2]))
+ return FALSE;
+
+ outPlanes += dstSizes[2];
+ outPlanesSize -= dstSizes[2];
+ /* GreenChromeOrBluePlane */
+ dstSizes[3] = outPlanesSize;
+
+ if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[3], width, height, outPlanes,
+ &dstSizes[3]))
+ return FALSE;
+
+ return TRUE;
+}
+
+BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width, UINT32 height,
+ BYTE* outPlane)
+{
+ char s2c = 0;
+ BYTE* outPtr = NULL;
+ const BYTE* srcPtr = NULL;
+ const BYTE* prevLinePtr = NULL;
+
+ if (!outPlane)
+ {
+ if (width * height == 0)
+ return NULL;
+
+ if (!(outPlane = (BYTE*)calloc(height, width)))
+ return NULL;
+ }
+
+ // first line is copied as is
+ CopyMemory(outPlane, inPlane, width);
+ outPtr = outPlane + width;
+ srcPtr = inPlane + width;
+ prevLinePtr = inPlane;
+
+ for (UINT32 y = 1; y < height; y++)
+ {
+ for (UINT32 x = 0; x < width; x++, outPtr++, srcPtr++, prevLinePtr++)
+ {
+ INT32 delta = *srcPtr - *prevLinePtr;
+ s2c = (delta >= 0) ? (char)delta : (char)(~((BYTE)(-delta)) + 1);
+ s2c = (s2c >= 0) ? (char)((UINT32)s2c << 1)
+ : (char)(((UINT32)(~((BYTE)s2c) + 1) << 1) - 1);
+ *outPtr = (BYTE)s2c;
+ }
+ }
+
+ return outPlane;
+}
+
+static INLINE BOOL freerdp_bitmap_planar_delta_encode_planes(BYTE* inPlanes[4], UINT32 width,
+ UINT32 height, BYTE* outPlanes[4])
+{
+ for (UINT32 i = 0; i < 4; i++)
+ {
+ outPlanes[i] =
+ freerdp_bitmap_planar_delta_encode_plane(inPlanes[i], width, height, outPlanes[i]);
+
+ if (!outPlanes[i])
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BYTE* freerdp_bitmap_compress_planar(BITMAP_PLANAR_CONTEXT* context, const BYTE* data,
+ UINT32 format, UINT32 width, UINT32 height, UINT32 scanline,
+ BYTE* dstData, UINT32* pDstSize)
+{
+ UINT32 size = 0;
+ BYTE* dstp = NULL;
+ UINT32 planeSize = 0;
+ UINT32 dstSizes[4] = { 0 };
+ BYTE FormatHeader = 0;
+
+ if (!context || !context->rlePlanesBuffer)
+ return NULL;
+
+ if (context->AllowSkipAlpha)
+ FormatHeader |= PLANAR_FORMAT_HEADER_NA;
+
+ planeSize = width * height;
+
+ if (!context->AllowSkipAlpha)
+ format = planar_invert_format(context, TRUE, format);
+
+ if (!freerdp_split_color_planes(context, data, format, width, height, scanline,
+ context->planes))
+ return NULL;
+
+ if (context->AllowRunLengthEncoding)
+ {
+ if (!freerdp_bitmap_planar_delta_encode_planes(context->planes, width, height,
+ context->deltaPlanes))
+ return NULL;
+
+ if (!freerdp_bitmap_planar_compress_planes_rle(context->deltaPlanes, width, height,
+ context->rlePlanesBuffer, dstSizes,
+ context->AllowSkipAlpha))
+ return NULL;
+
+ {
+ int offset = 0;
+ FormatHeader |= PLANAR_FORMAT_HEADER_RLE;
+ context->rlePlanes[0] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[0];
+ context->rlePlanes[1] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[1];
+ context->rlePlanes[2] = &context->rlePlanesBuffer[offset];
+ offset += dstSizes[2];
+ context->rlePlanes[3] = &context->rlePlanesBuffer[offset];
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_DBG(TAG,
+ "R: [%" PRIu32 "/%" PRIu32 "] G: [%" PRIu32 "/%" PRIu32 "] B: [%" PRIu32
+ " / %" PRIu32 "] ",
+ dstSizes[1], planeSize, dstSizes[2], planeSize, dstSizes[3], planeSize);
+#endif
+ }
+ }
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ if (!context->AllowRunLengthEncoding)
+ return NULL;
+
+ if (context->rlePlanes[0] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[1] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[2] == NULL)
+ return NULL;
+
+ if (context->rlePlanes[3] == NULL)
+ return NULL;
+ }
+
+ if (!dstData)
+ {
+ size = 1;
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA))
+ {
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ size += dstSizes[0];
+ else
+ size += planeSize;
+ }
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ size += (dstSizes[1] + dstSizes[2] + dstSizes[3]);
+ else
+ size += (planeSize * 3);
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE))
+ size++;
+
+ dstData = malloc(size);
+
+ if (!dstData)
+ return NULL;
+
+ *pDstSize = size;
+ }
+
+ dstp = dstData;
+ *dstp = FormatHeader; /* FormatHeader */
+ dstp++;
+
+ /* AlphaPlane */
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA))
+ {
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[0], dstSizes[0]); /* Alpha */
+ dstp += dstSizes[0];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[0], planeSize); /* Alpha */
+ dstp += planeSize;
+ }
+ }
+
+ /* LumaOrRedPlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[1], dstSizes[1]); /* Red */
+ dstp += dstSizes[1];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[1], planeSize); /* Red */
+ dstp += planeSize;
+ }
+
+ /* OrangeChromaOrGreenPlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[2], dstSizes[2]); /* Green */
+ dstp += dstSizes[2];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[2], planeSize); /* Green */
+ dstp += planeSize;
+ }
+
+ /* GreenChromeOrBluePlane */
+
+ if (FormatHeader & PLANAR_FORMAT_HEADER_RLE)
+ {
+ CopyMemory(dstp, context->rlePlanes[3], dstSizes[3]); /* Blue */
+ dstp += dstSizes[3];
+ }
+ else
+ {
+ CopyMemory(dstp, context->planes[3], planeSize); /* Blue */
+ dstp += planeSize;
+ }
+
+ /* Pad1 (1 byte) */
+
+ if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE))
+ {
+ *dstp = 0;
+ dstp++;
+ }
+
+ size = (dstp - dstData);
+ *pDstSize = size;
+ return dstData;
+}
+
+BOOL freerdp_bitmap_planar_context_reset(BITMAP_PLANAR_CONTEXT* context, UINT32 width,
+ UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ context->bgr = FALSE;
+ context->maxWidth = PLANAR_ALIGN(width, 4);
+ context->maxHeight = PLANAR_ALIGN(height, 4);
+ const UINT64 tmp = (UINT64)context->maxWidth * context->maxHeight;
+ if (tmp > UINT32_MAX)
+ return FALSE;
+ context->maxPlaneSize = tmp;
+
+ if (context->maxWidth > UINT32_MAX / 4)
+ return FALSE;
+ context->nTempStep = context->maxWidth * 4;
+
+ memset(context->planes, 0, sizeof(context->planes));
+ memset(context->rlePlanes, 0, sizeof(context->rlePlanes));
+ memset(context->deltaPlanes, 0, sizeof(context->deltaPlanes));
+
+ if (context->maxPlaneSize > 0)
+ {
+ void* tmp = winpr_aligned_recalloc(context->planesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->planesBuffer = tmp;
+
+ tmp = winpr_aligned_recalloc(context->pTempData, context->maxPlaneSize, 6, 32);
+ if (!tmp)
+ return FALSE;
+ context->pTempData = tmp;
+
+ tmp = winpr_aligned_recalloc(context->deltaPlanesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->deltaPlanesBuffer = tmp;
+
+ tmp = winpr_aligned_recalloc(context->rlePlanesBuffer, context->maxPlaneSize, 4, 32);
+ if (!tmp)
+ return FALSE;
+ context->rlePlanesBuffer = tmp;
+
+ context->planes[0] = &context->planesBuffer[context->maxPlaneSize * 0];
+ context->planes[1] = &context->planesBuffer[context->maxPlaneSize * 1];
+ context->planes[2] = &context->planesBuffer[context->maxPlaneSize * 2];
+ context->planes[3] = &context->planesBuffer[context->maxPlaneSize * 3];
+ context->deltaPlanes[0] = &context->deltaPlanesBuffer[context->maxPlaneSize * 0];
+ context->deltaPlanes[1] = &context->deltaPlanesBuffer[context->maxPlaneSize * 1];
+ context->deltaPlanes[2] = &context->deltaPlanesBuffer[context->maxPlaneSize * 2];
+ context->deltaPlanes[3] = &context->deltaPlanesBuffer[context->maxPlaneSize * 3];
+ }
+ return TRUE;
+}
+
+BITMAP_PLANAR_CONTEXT* freerdp_bitmap_planar_context_new(DWORD flags, UINT32 maxWidth,
+ UINT32 maxHeight)
+{
+ BITMAP_PLANAR_CONTEXT* context =
+ (BITMAP_PLANAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(BITMAP_PLANAR_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ if (flags & PLANAR_FORMAT_HEADER_NA)
+ context->AllowSkipAlpha = TRUE;
+
+ if (flags & PLANAR_FORMAT_HEADER_RLE)
+ context->AllowRunLengthEncoding = TRUE;
+
+ if (flags & PLANAR_FORMAT_HEADER_CS)
+ context->AllowColorSubsampling = TRUE;
+
+ context->ColorLossLevel = flags & PLANAR_FORMAT_HEADER_CLL_MASK;
+
+ if (context->ColorLossLevel)
+ context->AllowDynamicColorFidelity = TRUE;
+
+ if (!freerdp_bitmap_planar_context_reset(context, maxWidth, maxHeight))
+ {
+ freerdp_bitmap_planar_context_free(context);
+ return NULL;
+ }
+
+ return context;
+}
+
+void freerdp_bitmap_planar_context_free(BITMAP_PLANAR_CONTEXT* context)
+{
+ if (!context)
+ return;
+
+ winpr_aligned_free(context->pTempData);
+ winpr_aligned_free(context->planesBuffer);
+ winpr_aligned_free(context->deltaPlanesBuffer);
+ winpr_aligned_free(context->rlePlanesBuffer);
+ winpr_aligned_free(context);
+}
+
+void freerdp_planar_switch_bgr(BITMAP_PLANAR_CONTEXT* planar, BOOL bgr)
+{
+ WINPR_ASSERT(planar);
+ planar->bgr = bgr;
+}
+
+void freerdp_planar_topdown_image(BITMAP_PLANAR_CONTEXT* planar, BOOL topdown)
+{
+ WINPR_ASSERT(planar);
+ planar->topdown = topdown;
+}
diff --git a/libfreerdp/codec/progressive.c b/libfreerdp/codec/progressive.c
new file mode 100644
index 0000000..df98ad3
--- /dev/null
+++ b/libfreerdp/codec/progressive.c
@@ -0,0 +1,2651 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Progressive Codec Bitmap Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/primitives.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/progressive.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/log.h>
+
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+#include "rfx_rlgr.h"
+#include "rfx_constants.h"
+#include "rfx_types.h"
+#include "progressive.h"
+
+#define TAG FREERDP_TAG("codec.progressive")
+
+typedef struct
+{
+ BOOL nonLL;
+ wBitStream* srl;
+ wBitStream* raw;
+
+ /* SRL state */
+
+ UINT32 kp;
+ int nz;
+ BOOL mode;
+} RFX_PROGRESSIVE_UPGRADE_STATE;
+
+static INLINE void progressive_component_codec_quant_read(wStream* s,
+ RFX_COMPONENT_CODEC_QUANT* quantVal)
+{
+ BYTE b = 0;
+ Stream_Read_UINT8(s, b);
+ quantVal->LL3 = b & 0x0F;
+ quantVal->HL3 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->LH3 = b & 0x0F;
+ quantVal->HH3 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->HL2 = b & 0x0F;
+ quantVal->LH2 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->HH2 = b & 0x0F;
+ quantVal->HL1 = b >> 4;
+ Stream_Read_UINT8(s, b);
+ quantVal->LH1 = b & 0x0F;
+ quantVal->HH1 = b >> 4;
+}
+
+static INLINE void progressive_rfx_quant_ladd(RFX_COMPONENT_CODEC_QUANT* q, int val)
+{
+ q->HL1 += val; /* HL1 */
+ q->LH1 += val; /* LH1 */
+ q->HH1 += val; /* HH1 */
+ q->HL2 += val; /* HL2 */
+ q->LH2 += val; /* LH2 */
+ q->HH2 += val; /* HH2 */
+ q->HL3 += val; /* HL3 */
+ q->LH3 += val; /* LH3 */
+ q->HH3 += val; /* HH3 */
+ q->LL3 += val; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_add(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2,
+ RFX_COMPONENT_CODEC_QUANT* dst)
+{
+ dst->HL1 = q1->HL1 + q2->HL1; /* HL1 */
+ dst->LH1 = q1->LH1 + q2->LH1; /* LH1 */
+ dst->HH1 = q1->HH1 + q2->HH1; /* HH1 */
+ dst->HL2 = q1->HL2 + q2->HL2; /* HL2 */
+ dst->LH2 = q1->LH2 + q2->LH2; /* LH2 */
+ dst->HH2 = q1->HH2 + q2->HH2; /* HH2 */
+ dst->HL3 = q1->HL3 + q2->HL3; /* HL3 */
+ dst->LH3 = q1->LH3 + q2->LH3; /* LH3 */
+ dst->HH3 = q1->HH3 + q2->HH3; /* HH3 */
+ dst->LL3 = q1->LL3 + q2->LL3; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_lsub(RFX_COMPONENT_CODEC_QUANT* q, int val)
+{
+ q->HL1 -= val; /* HL1 */
+ q->LH1 -= val; /* LH1 */
+ q->HH1 -= val; /* HH1 */
+ q->HL2 -= val; /* HL2 */
+ q->LH2 -= val; /* LH2 */
+ q->HH2 -= val; /* HH2 */
+ q->HL3 -= val; /* HL3 */
+ q->LH3 -= val; /* LH3 */
+ q->HH3 -= val; /* HH3 */
+ q->LL3 -= val; /* LL3 */
+}
+
+static INLINE void progressive_rfx_quant_sub(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2,
+ RFX_COMPONENT_CODEC_QUANT* dst)
+{
+ dst->HL1 = q1->HL1 - q2->HL1; /* HL1 */
+ dst->LH1 = q1->LH1 - q2->LH1; /* LH1 */
+ dst->HH1 = q1->HH1 - q2->HH1; /* HH1 */
+ dst->HL2 = q1->HL2 - q2->HL2; /* HL2 */
+ dst->LH2 = q1->LH2 - q2->LH2; /* LH2 */
+ dst->HH2 = q1->HH2 - q2->HH2; /* HH2 */
+ dst->HL3 = q1->HL3 - q2->HL3; /* HL3 */
+ dst->LH3 = q1->LH3 - q2->LH3; /* LH3 */
+ dst->HH3 = q1->HH3 - q2->HH3; /* HH3 */
+ dst->LL3 = q1->LL3 - q2->LL3; /* LL3 */
+}
+
+static INLINE BOOL progressive_rfx_quant_lcmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q,
+ int val)
+{
+ if (q->HL1 > val)
+ return FALSE; /* HL1 */
+
+ if (q->LH1 > val)
+ return FALSE; /* LH1 */
+
+ if (q->HH1 > val)
+ return FALSE; /* HH1 */
+
+ if (q->HL2 > val)
+ return FALSE; /* HL2 */
+
+ if (q->LH2 > val)
+ return FALSE; /* LH2 */
+
+ if (q->HH2 > val)
+ return FALSE; /* HH2 */
+
+ if (q->HL3 > val)
+ return FALSE; /* HL3 */
+
+ if (q->LH3 > val)
+ return FALSE; /* LH3 */
+
+ if (q->HH3 > val)
+ return FALSE; /* HH3 */
+
+ if (q->LL3 > val)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 > q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 > q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 > q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 > q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 > q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 > q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 > q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 > q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 > q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 > q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_lcmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q,
+ int val)
+{
+ if (q->HL1 < val)
+ return FALSE; /* HL1 */
+
+ if (q->LH1 < val)
+ return FALSE; /* LH1 */
+
+ if (q->HH1 < val)
+ return FALSE; /* HH1 */
+
+ if (q->HL2 < val)
+ return FALSE; /* HL2 */
+
+ if (q->LH2 < val)
+ return FALSE; /* LH2 */
+
+ if (q->HH2 < val)
+ return FALSE; /* HH2 */
+
+ if (q->HL3 < val)
+ return FALSE; /* HL3 */
+
+ if (q->LH3 < val)
+ return FALSE; /* LH3 */
+
+ if (q->HH3 < val)
+ return FALSE; /* HH3 */
+
+ if (q->LL3 < val)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 < q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 < q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 < q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 < q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 < q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 < q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 < q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 < q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 < q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 < q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_rfx_quant_cmp_equal(const RFX_COMPONENT_CODEC_QUANT* q1,
+ const RFX_COMPONENT_CODEC_QUANT* q2)
+{
+ if (q1->HL1 != q2->HL1)
+ return FALSE; /* HL1 */
+
+ if (q1->LH1 != q2->LH1)
+ return FALSE; /* LH1 */
+
+ if (q1->HH1 != q2->HH1)
+ return FALSE; /* HH1 */
+
+ if (q1->HL2 != q2->HL2)
+ return FALSE; /* HL2 */
+
+ if (q1->LH2 != q2->LH2)
+ return FALSE; /* LH2 */
+
+ if (q1->HH2 != q2->HH2)
+ return FALSE; /* HH2 */
+
+ if (q1->HL3 != q2->HL3)
+ return FALSE; /* HL3 */
+
+ if (q1->LH3 != q2->LH3)
+ return FALSE; /* LH3 */
+
+ if (q1->HH3 != q2->HH3)
+ return FALSE; /* HH3 */
+
+ if (q1->LL3 != q2->LL3)
+ return FALSE; /* LL3 */
+
+ return TRUE;
+}
+
+static INLINE BOOL progressive_set_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId,
+ void* pData)
+{
+ ULONG_PTR key = 0;
+ key = ((ULONG_PTR)surfaceId) + 1;
+
+ if (pData)
+ return HashTable_Insert(progressive->SurfaceContexts, (void*)key, pData);
+
+ HashTable_Remove(progressive->SurfaceContexts, (void*)key);
+ return TRUE;
+}
+
+static INLINE PROGRESSIVE_SURFACE_CONTEXT*
+progressive_get_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId)
+{
+ void* key = (void*)(((ULONG_PTR)surfaceId) + 1);
+
+ if (!progressive)
+ return NULL;
+
+ return HashTable_GetItemValue(progressive->SurfaceContexts, key);
+}
+
+static void progressive_tile_free(RFX_PROGRESSIVE_TILE* tile)
+{
+ if (tile)
+ {
+ winpr_aligned_free(tile->sign);
+ winpr_aligned_free(tile->current);
+ winpr_aligned_free(tile->data);
+ winpr_aligned_free(tile);
+ }
+}
+
+static void progressive_surface_context_free(void* ptr)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = ptr;
+
+ if (!surface)
+ return;
+
+ if (surface->tiles)
+ {
+ for (size_t index = 0; index < surface->tilesSize; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = surface->tiles[index];
+ progressive_tile_free(tile);
+ }
+ }
+
+ winpr_aligned_free(surface->tiles);
+ winpr_aligned_free(surface->updatedTileIndices);
+ winpr_aligned_free(surface);
+}
+
+static INLINE RFX_PROGRESSIVE_TILE* progressive_tile_new(void)
+{
+ RFX_PROGRESSIVE_TILE* tile = winpr_aligned_calloc(1, sizeof(RFX_PROGRESSIVE_TILE), 32);
+ if (!tile)
+ goto fail;
+
+ tile->width = 64;
+ tile->height = 64;
+ tile->stride = 4 * tile->width;
+
+ size_t dataLen = 1ull * tile->stride * tile->height;
+ tile->data = (BYTE*)winpr_aligned_malloc(dataLen, 16);
+ if (!tile->data)
+ goto fail;
+ memset(tile->data, 0xFF, dataLen);
+
+ size_t signLen = (8192 + 32) * 3;
+ tile->sign = (BYTE*)winpr_aligned_calloc(signLen, sizeof(BYTE), 16);
+ if (!tile->sign)
+ goto fail;
+
+ size_t currentLen = (8192 + 32) * 3;
+ tile->current = (BYTE*)winpr_aligned_calloc(currentLen, sizeof(BYTE), 16);
+ if (!tile->current)
+ goto fail;
+
+ return tile;
+
+fail:
+ progressive_tile_free(tile);
+ return NULL;
+}
+
+static BOOL progressive_allocate_tile_cache(PROGRESSIVE_SURFACE_CONTEXT* surface, size_t min)
+{
+ size_t oldIndex = 0;
+
+ WINPR_ASSERT(surface);
+ WINPR_ASSERT(surface->gridSize > 0);
+
+ if (surface->tiles)
+ {
+ oldIndex = surface->gridSize;
+ while (surface->gridSize < min)
+ surface->gridSize += 1024;
+ }
+
+ void* tmp = winpr_aligned_recalloc(surface->tiles, surface->gridSize,
+ sizeof(RFX_PROGRESSIVE_TILE*), 32);
+ if (!tmp)
+ return FALSE;
+ surface->tilesSize = surface->gridSize;
+ surface->tiles = tmp;
+
+ for (size_t x = oldIndex; x < surface->tilesSize; x++)
+ {
+ surface->tiles[x] = progressive_tile_new();
+ if (!surface->tiles[x])
+ return FALSE;
+ }
+
+ tmp =
+ winpr_aligned_recalloc(surface->updatedTileIndices, surface->gridSize, sizeof(UINT32), 32);
+ if (!tmp)
+ return FALSE;
+
+ surface->updatedTileIndices = tmp;
+
+ return TRUE;
+}
+
+static PROGRESSIVE_SURFACE_CONTEXT* progressive_surface_context_new(UINT16 surfaceId, UINT32 width,
+ UINT32 height)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = (PROGRESSIVE_SURFACE_CONTEXT*)winpr_aligned_calloc(
+ 1, sizeof(PROGRESSIVE_SURFACE_CONTEXT), 32);
+
+ if (!surface)
+ return NULL;
+
+ surface->id = surfaceId;
+ surface->width = width;
+ surface->height = height;
+ surface->gridWidth = (width + (64 - width % 64)) / 64;
+ surface->gridHeight = (height + (64 - height % 64)) / 64;
+ surface->gridSize = surface->gridWidth * surface->gridHeight;
+
+ if (!progressive_allocate_tile_cache(surface, surface->gridSize))
+ {
+ progressive_surface_context_free(surface);
+ return NULL;
+ }
+
+ return surface;
+}
+
+static BOOL progressive_surface_tile_replace(PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const RFX_PROGRESSIVE_TILE* tile, BOOL upgrade)
+{
+ RFX_PROGRESSIVE_TILE* t = NULL;
+
+ size_t zIdx = 0;
+ if (!surface || !tile)
+ return FALSE;
+
+ zIdx = (tile->yIdx * surface->gridWidth) + tile->xIdx;
+
+ if (zIdx >= surface->tilesSize)
+ {
+ WLog_ERR(TAG, "Invalid zIndex %" PRIuz, zIdx);
+ return FALSE;
+ }
+
+ t = surface->tiles[zIdx];
+
+ t->blockType = tile->blockType;
+ t->blockLen = tile->blockLen;
+ t->quantIdxY = tile->quantIdxY;
+ t->quantIdxCb = tile->quantIdxCb;
+ t->quantIdxCr = tile->quantIdxCr;
+ t->xIdx = tile->xIdx;
+ t->yIdx = tile->yIdx;
+ t->flags = tile->flags;
+ t->quality = tile->quality;
+ t->x = tile->xIdx * t->width;
+ t->y = tile->yIdx * t->height;
+
+ if (upgrade)
+ {
+ t->ySrlLen = tile->ySrlLen;
+ t->yRawLen = tile->yRawLen;
+ t->cbSrlLen = tile->cbSrlLen;
+ t->cbRawLen = tile->cbRawLen;
+ t->crSrlLen = tile->crSrlLen;
+ t->crRawLen = tile->crRawLen;
+ t->ySrlData = tile->ySrlData;
+ t->yRawData = tile->yRawData;
+ t->cbSrlData = tile->cbSrlData;
+ t->cbRawData = tile->cbRawData;
+ t->crSrlData = tile->crSrlData;
+ t->crRawData = tile->crRawData;
+ }
+ else
+ {
+ t->yLen = tile->yLen;
+ t->cbLen = tile->cbLen;
+ t->crLen = tile->crLen;
+ t->tailLen = tile->tailLen;
+ t->yData = tile->yData;
+ t->cbData = tile->cbData;
+ t->crData = tile->crData;
+ t->tailData = tile->tailData;
+ }
+
+ if (region->usedTiles >= region->numTiles)
+ {
+ WLog_ERR(TAG, "Invalid tile count, only expected %" PRIu16 ", got %" PRIu16,
+ region->numTiles, region->usedTiles);
+ return FALSE;
+ }
+
+ region->tiles[region->usedTiles++] = t;
+ if (!t->dirty)
+ {
+ if (surface->numUpdatedTiles >= surface->gridSize)
+ {
+ if (!progressive_allocate_tile_cache(surface, surface->numUpdatedTiles + 1))
+ return FALSE;
+ }
+
+ surface->updatedTileIndices[surface->numUpdatedTiles++] = (UINT32)zIdx;
+ }
+
+ t->dirty = TRUE;
+ return TRUE;
+}
+
+INT32 progressive_create_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId,
+ UINT32 width, UINT32 height)
+{
+ PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId);
+
+ if (!surface)
+ {
+ surface = progressive_surface_context_new(surfaceId, width, height);
+
+ if (!surface)
+ return -1;
+
+ if (!progressive_set_surface_data(progressive, surfaceId, (void*)surface))
+ {
+ progressive_surface_context_free(surface);
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+int progressive_delete_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId)
+{
+ progressive_set_surface_data(progressive, surfaceId, NULL);
+
+ return 1;
+}
+
+/*
+ * Band Offset Dimensions Size
+ *
+ * HL1 0 31x33 1023
+ * LH1 1023 33x31 1023
+ * HH1 2046 31x31 961
+ *
+ * HL2 3007 16x17 272
+ * LH2 3279 17x16 272
+ * HH2 3551 16x16 256
+ *
+ * HL3 3807 8x9 72
+ * LH3 3879 9x8 72
+ * HH3 3951 8x8 64
+ *
+ * LL3 4015 9x9 81
+ */
+
+static INLINE void progressive_rfx_idwt_x(const INT16* pLowBand, size_t nLowStep,
+ const INT16* pHighBand, size_t nHighStep, INT16* pDstBand,
+ size_t nDstStep, size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ INT16 L0 = 0;
+ INT16 H0 = 0;
+ INT16 H1 = 0;
+ INT16 X0 = 0;
+ INT16 X1 = 0;
+ INT16 X2 = 0;
+
+ for (size_t i = 0; i < nDstCount; i++)
+ {
+ const INT16* pL = pLowBand;
+ const INT16* pH = pHighBand;
+ INT16* pX = pDstBand;
+ H0 = *pH++;
+ L0 = *pL++;
+ X0 = L0 - H0;
+ X2 = L0 - H0;
+
+ for (size_t j = 0; j < (nHighCount - 1); j++)
+ {
+ H1 = *pH;
+ pH++;
+ L0 = *pL;
+ pL++;
+ X2 = L0 - ((H0 + H1) / 2);
+ X1 = ((X0 + X2) / 2) + (2 * H0);
+ pX[0] = X0;
+ pX[1] = X1;
+ pX += 2;
+ X0 = X2;
+ H0 = H1;
+ }
+
+ if (nLowCount <= (nHighCount + 1))
+ {
+ if (nLowCount <= nHighCount)
+ {
+ pX[0] = X2;
+ pX[1] = X2 + (2 * H0);
+ }
+ else
+ {
+ L0 = *pL;
+ pL++;
+ X0 = L0 - H0;
+ pX[0] = X2;
+ pX[1] = ((X0 + X2) / 2) + (2 * H0);
+ pX[2] = X0;
+ }
+ }
+ else
+ {
+ L0 = *pL;
+ pL++;
+ X0 = L0 - (H0 / 2);
+ pX[0] = X2;
+ pX[1] = ((X0 + X2) / 2) + (2 * H0);
+ pX[2] = X0;
+ L0 = *pL;
+ pL++;
+ pX[3] = (X0 + L0) / 2;
+ }
+
+ pLowBand += nLowStep;
+ pHighBand += nHighStep;
+ pDstBand += nDstStep;
+ }
+}
+
+static INLINE void progressive_rfx_idwt_y(const INT16* pLowBand, size_t nLowStep,
+ const INT16* pHighBand, size_t nHighStep, INT16* pDstBand,
+ size_t nDstStep, size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ INT16 L0 = 0;
+ INT16 H0 = 0;
+ INT16 H1 = 0;
+ INT16 X0 = 0;
+ INT16 X1 = 0;
+ INT16 X2 = 0;
+
+ for (size_t i = 0; i < nDstCount; i++)
+ {
+ const INT16* pL = pLowBand;
+ const INT16* pH = pHighBand;
+ INT16* pX = pDstBand;
+ H0 = *pH;
+ pH += nHighStep;
+ L0 = *pL;
+ pL += nLowStep;
+ X0 = L0 - H0;
+ X2 = L0 - H0;
+
+ for (size_t j = 0; j < (nHighCount - 1); j++)
+ {
+ H1 = *pH;
+ pH += nHighStep;
+ L0 = *pL;
+ pL += nLowStep;
+ X2 = L0 - ((H0 + H1) / 2);
+ X1 = ((X0 + X2) / 2) + (2 * H0);
+ *pX = X0;
+ pX += nDstStep;
+ *pX = X1;
+ pX += nDstStep;
+ X0 = X2;
+ H0 = H1;
+ }
+
+ if (nLowCount <= (nHighCount + 1))
+ {
+ if (nLowCount <= nHighCount)
+ {
+ *pX = X2;
+ pX += nDstStep;
+ *pX = X2 + (2 * H0);
+ }
+ else
+ {
+ L0 = *pL;
+ X0 = L0 - H0;
+ *pX = X2;
+ pX += nDstStep;
+ *pX = ((X0 + X2) / 2) + (2 * H0);
+ pX += nDstStep;
+ *pX = X0;
+ }
+ }
+ else
+ {
+ L0 = *pL;
+ pL += nLowStep;
+ X0 = L0 - (H0 / 2);
+ *pX = X2;
+ pX += nDstStep;
+ *pX = ((X0 + X2) / 2) + (2 * H0);
+ pX += nDstStep;
+ *pX = X0;
+ pX += nDstStep;
+ L0 = *pL;
+ *pX = (X0 + L0) / 2;
+ }
+
+ pLowBand++;
+ pHighBand++;
+ pDstBand++;
+ }
+}
+
+static INLINE size_t progressive_rfx_get_band_l_count(size_t level)
+{
+ return (64 >> level) + 1;
+}
+
+static INLINE size_t progressive_rfx_get_band_h_count(size_t level)
+{
+ if (level == 1)
+ return (64 >> 1) - 1;
+ else
+ return (64 + (1 << (level - 1))) >> level;
+}
+
+static INLINE void progressive_rfx_dwt_2d_decode_block(INT16* buffer, INT16* temp, size_t level)
+{
+ size_t nDstStepX = 0;
+ size_t nDstStepY = 0;
+ INT16* HL = NULL;
+ INT16* LH = NULL;
+ INT16* HH = NULL;
+ INT16* LL = NULL;
+ INT16* L = NULL;
+ INT16* H = NULL;
+ INT16* LLx = NULL;
+
+ const size_t nBandL = progressive_rfx_get_band_l_count(level);
+ const size_t nBandH = progressive_rfx_get_band_h_count(level);
+ size_t offset = 0;
+
+ HL = &buffer[offset];
+ offset += (nBandH * nBandL);
+ LH = &buffer[offset];
+ offset += (nBandL * nBandH);
+ HH = &buffer[offset];
+ offset += (nBandH * nBandH);
+ LL = &buffer[offset];
+ nDstStepX = (nBandL + nBandH);
+ nDstStepY = (nBandL + nBandH);
+ offset = 0;
+ L = &temp[offset];
+ offset += (nBandL * nDstStepX);
+ H = &temp[offset];
+ LLx = &buffer[0];
+
+ /* horizontal (LL + HL -> L) */
+ progressive_rfx_idwt_x(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL);
+
+ /* horizontal (LH + HH -> H) */
+ progressive_rfx_idwt_x(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH);
+
+ /* vertical (L + H -> LL) */
+ progressive_rfx_idwt_y(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH,
+ nBandL + nBandH);
+}
+
+void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* temp)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+ progressive_rfx_dwt_2d_decode_block(&buffer[3807], temp, 3);
+ progressive_rfx_dwt_2d_decode_block(&buffer[3007], temp, 2);
+ progressive_rfx_dwt_2d_decode_block(&buffer[0], temp, 1);
+}
+
+static INLINE int progressive_rfx_dwt_2d_decode(PROGRESSIVE_CONTEXT* progressive, INT16* buffer,
+ INT16* current, BOOL coeffDiff, BOOL extrapolate,
+ BOOL reverse)
+{
+ const primitives_t* prims = primitives_get();
+
+ if (!progressive || !buffer || !current)
+ return -1;
+
+ INT16 dst[4096] = { 0 };
+ if (reverse)
+ memcpy(buffer, current, sizeof(dst));
+ else
+ {
+ if (coeffDiff)
+ {
+ prims->add_16s(buffer, current, dst, ARRAYSIZE(dst));
+ memcpy(current, dst, sizeof(dst));
+ memcpy(buffer, dst, sizeof(dst));
+ }
+ else
+ memcpy(current, buffer, sizeof(dst));
+ }
+
+ INT16* temp = (INT16*)BufferPool_Take(progressive->bufferPool, -1); /* DWT buffer */
+
+ if (!temp)
+ return -2;
+
+ if (!extrapolate)
+ {
+ progressive->rfx_context->dwt_2d_decode(buffer, temp);
+ }
+ else
+ {
+ WINPR_ASSERT(progressive->rfx_context->dwt_2d_extrapolate_decode);
+ progressive->rfx_context->dwt_2d_extrapolate_decode(buffer, temp);
+ }
+ BufferPool_Return(progressive->bufferPool, temp);
+ return 1;
+}
+
+static INLINE void progressive_rfx_decode_block(const primitives_t* prims, INT16* buffer,
+ UINT32 length, UINT32 shift)
+{
+ if (!shift)
+ return;
+
+ prims->lShiftC_16s(buffer, shift, buffer, length);
+}
+
+static INLINE int progressive_rfx_decode_component(PROGRESSIVE_CONTEXT* progressive,
+ const RFX_COMPONENT_CODEC_QUANT* shift,
+ const BYTE* data, UINT32 length, INT16* buffer,
+ INT16* current, INT16* sign, BOOL coeffDiff,
+ BOOL subbandDiff, BOOL extrapolate)
+{
+ int status = 0;
+ const primitives_t* prims = primitives_get();
+
+ status = progressive->rfx_context->rlgr_decode(RLGR1, data, length, buffer, 4096);
+
+ if (status < 0)
+ return status;
+
+ CopyMemory(sign, buffer, 4096 * 2);
+ if (!extrapolate)
+ {
+ rfx_differential_decode(buffer + 4032, 64);
+ progressive_rfx_decode_block(prims, &buffer[0], 1024, shift->HL1); /* HL1 */
+ progressive_rfx_decode_block(prims, &buffer[1024], 1024, shift->LH1); /* LH1 */
+ progressive_rfx_decode_block(prims, &buffer[2048], 1024, shift->HH1); /* HH1 */
+ progressive_rfx_decode_block(prims, &buffer[3072], 256, shift->HL2); /* HL2 */
+ progressive_rfx_decode_block(prims, &buffer[3328], 256, shift->LH2); /* LH2 */
+ progressive_rfx_decode_block(prims, &buffer[3584], 256, shift->HH2); /* HH2 */
+ progressive_rfx_decode_block(prims, &buffer[3840], 64, shift->HL3); /* HL3 */
+ progressive_rfx_decode_block(prims, &buffer[3904], 64, shift->LH3); /* LH3 */
+ progressive_rfx_decode_block(prims, &buffer[3968], 64, shift->HH3); /* HH3 */
+ progressive_rfx_decode_block(prims, &buffer[4032], 64, shift->LL3); /* LL3 */
+ }
+ else
+ {
+ progressive_rfx_decode_block(prims, &buffer[0], 1023, shift->HL1); /* HL1 */
+ progressive_rfx_decode_block(prims, &buffer[1023], 1023, shift->LH1); /* LH1 */
+ progressive_rfx_decode_block(prims, &buffer[2046], 961, shift->HH1); /* HH1 */
+ progressive_rfx_decode_block(prims, &buffer[3007], 272, shift->HL2); /* HL2 */
+ progressive_rfx_decode_block(prims, &buffer[3279], 272, shift->LH2); /* LH2 */
+ progressive_rfx_decode_block(prims, &buffer[3551], 256, shift->HH2); /* HH2 */
+ progressive_rfx_decode_block(prims, &buffer[3807], 72, shift->HL3); /* HL3 */
+ progressive_rfx_decode_block(prims, &buffer[3879], 72, shift->LH3); /* LH3 */
+ progressive_rfx_decode_block(prims, &buffer[3951], 64, shift->HH3); /* HH3 */
+ rfx_differential_decode(&buffer[4015], 81); /* LL3 */
+ progressive_rfx_decode_block(prims, &buffer[4015], 81, shift->LL3); /* LL3 */
+ }
+ return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate,
+ FALSE);
+}
+
+static INLINE int progressive_decompress_tile_first(PROGRESSIVE_CONTEXT* progressive,
+ RFX_PROGRESSIVE_TILE* tile,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int rc = 0;
+ BOOL diff = 0;
+ BOOL sub = 0;
+ BOOL extrapolate = 0;
+ BYTE* pBuffer = NULL;
+ INT16* pSign[3];
+ INT16* pSrcDst[3];
+ INT16* pCurrent[3];
+ RFX_COMPONENT_CODEC_QUANT shiftY = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 };
+ RFX_COMPONENT_CODEC_QUANT* quantY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCr = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL;
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+
+ tile->pass = 1;
+ diff = tile->flags & RFX_TILE_DIFFERENCE;
+ sub = context->flags & RFX_SUBBAND_DIFFING;
+ extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveTile%s: quantIdx Y: %" PRIu8 " Cb: %" PRIu8 " Cr: %" PRIu8
+ " xIdx: %" PRIu16 " yIdx: %" PRIu16 " flags: 0x%02" PRIX8 " quality: %" PRIu8
+ " yLen: %" PRIu16 " cbLen: %" PRIu16 " crLen: %" PRIu16 " tailLen: %" PRIu16 "",
+ (tile->blockType == PROGRESSIVE_WBT_TILE_FIRST) ? "First" : "Simple",
+ tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx, tile->yIdx,
+ tile->flags, tile->quality, tile->yLen, tile->cbLen, tile->crLen, tile->tailLen);
+#endif
+
+ if (tile->quantIdxY >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant);
+ return -1;
+ }
+
+ quantY = &(region->quantVals[tile->quantIdxY]);
+
+ if (tile->quantIdxCb >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCb = &(region->quantVals[tile->quantIdxCb]);
+
+ if (tile->quantIdxCr >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCr = &(region->quantVals[tile->quantIdxCr]);
+
+ if (tile->quality == 0xFF)
+ {
+ quantProgVal = &(progressive->quantProgValFull);
+ }
+ else
+ {
+ if (tile->quality >= region->numProgQuant)
+ {
+ WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality,
+ region->numProgQuant);
+ return -1;
+ }
+
+ quantProgVal = &(region->quantProgVals[tile->quality]);
+ }
+
+ quantProgY = &(quantProgVal->yQuantValues);
+ quantProgCb = &(quantProgVal->cbQuantValues);
+ quantProgCr = &(quantProgVal->crQuantValues);
+
+ tile->yQuant = *quantY;
+ tile->cbQuant = *quantCb;
+ tile->crQuant = *quantCr;
+ tile->yProgQuant = *quantProgY;
+ tile->cbProgQuant = *quantProgCb;
+ tile->crProgQuant = *quantProgCr;
+
+ progressive_rfx_quant_add(quantY, quantProgY, &(tile->yBitPos));
+ progressive_rfx_quant_add(quantCb, quantProgCb, &(tile->cbBitPos));
+ progressive_rfx_quant_add(quantCr, quantProgCr, &(tile->crBitPos));
+ progressive_rfx_quant_add(quantY, quantProgY, &shiftY);
+ progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb);
+ progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr);
+ progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */
+
+ pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ rc = progressive_rfx_decode_component(progressive, &shiftY, tile->yData, tile->yLen, pSrcDst[0],
+ pCurrent[0], pSign[0], diff, sub, extrapolate); /* Y */
+ if (rc < 0)
+ goto fail;
+ rc = progressive_rfx_decode_component(progressive, &shiftCb, tile->cbData, tile->cbLen,
+ pSrcDst[1], pCurrent[1], pSign[1], diff, sub,
+ extrapolate); /* Cb */
+ if (rc < 0)
+ goto fail;
+ rc = progressive_rfx_decode_component(progressive, &shiftCr, tile->crData, tile->crLen,
+ pSrcDst[2], pCurrent[2], pSign[2], diff, sub,
+ extrapolate); /* Cr */
+ if (rc < 0)
+ goto fail;
+
+ rc = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data,
+ tile->stride, progressive->format, &roi_64x64);
+fail:
+ BufferPool_Return(progressive->bufferPool, pBuffer);
+ return rc;
+}
+
+static INLINE INT16 progressive_rfx_srl_read(RFX_PROGRESSIVE_UPGRADE_STATE* state, UINT32 numBits)
+{
+ UINT32 k = 0;
+ UINT32 bit = 0;
+ UINT32 max = 0;
+ UINT32 mag = 0;
+ UINT32 sign = 0;
+ wBitStream* bs = state->srl;
+
+ if (state->nz)
+ {
+ state->nz--;
+ return 0;
+ }
+
+ k = state->kp / 8;
+
+ if (!state->mode)
+ {
+ /* zero encoding */
+ bit = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (!bit)
+ {
+ /* '0' bit, nz >= (1 << k), nz = (1 << k) */
+ state->nz = (1 << k);
+ state->kp += 4;
+
+ if (state->kp > 80)
+ state->kp = 80;
+
+ state->nz--;
+ return 0;
+ }
+ else
+ {
+ /* '1' bit, nz < (1 << k), nz = next k bits */
+ state->nz = 0;
+ state->mode = 1; /* unary encoding is next */
+
+ if (k)
+ {
+ bs->mask = ((1 << k) - 1);
+ state->nz = ((bs->accumulator >> (32u - k)) & bs->mask);
+ BitStream_Shift(bs, k);
+ }
+
+ if (state->nz)
+ {
+ state->nz--;
+ return 0;
+ }
+ }
+ }
+
+ state->mode = 0; /* zero encoding is next */
+ /* unary encoding */
+ /* read sign bit */
+ sign = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (state->kp < 6)
+ state->kp = 0;
+ else
+ state->kp -= 6;
+
+ if (numBits == 1)
+ return sign ? -1 : 1;
+
+ mag = 1;
+ max = (1 << numBits) - 1;
+
+ while (mag < max)
+ {
+ bit = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ if (bit)
+ break;
+
+ mag++;
+ }
+
+ return sign ? -1 * mag : mag;
+}
+
+static INLINE int progressive_rfx_upgrade_state_finish(RFX_PROGRESSIVE_UPGRADE_STATE* state)
+{
+ UINT32 pad = 0;
+ wBitStream* srl = NULL;
+ wBitStream* raw = NULL;
+ if (!state)
+ return -1;
+
+ srl = state->srl;
+ raw = state->raw;
+ /* Read trailing bits from RAW/SRL bit streams */
+ pad = (raw->position % 8) ? (8 - (raw->position % 8)) : 0;
+
+ if (pad)
+ BitStream_Shift(raw, pad);
+
+ pad = (srl->position % 8) ? (8 - (srl->position % 8)) : 0;
+
+ if (pad)
+ BitStream_Shift(srl, pad);
+
+ if (BitStream_GetRemainingLength(srl) == 8)
+ BitStream_Shift(srl, 8);
+
+ return 1;
+}
+
+static INLINE int progressive_rfx_upgrade_block(RFX_PROGRESSIVE_UPGRADE_STATE* state, INT16* buffer,
+ INT16* sign, UINT32 length, UINT32 shift,
+ UINT32 bitPos, UINT32 numBits)
+{
+ INT16 input = 0;
+ wBitStream* raw = NULL;
+
+ if (!numBits)
+ return 1;
+
+ raw = state->raw;
+
+ if (!state->nonLL)
+ {
+ for (UINT32 index = 0; index < length; index++)
+ {
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ buffer[index] += (input << shift);
+ }
+
+ return 1;
+ }
+
+ for (UINT32 index = 0; index < length; index++)
+ {
+ if (sign[index] > 0)
+ {
+ /* sign > 0, read from raw */
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ }
+ else if (sign[index] < 0)
+ {
+ /* sign < 0, read from raw */
+ raw->mask = ((1 << numBits) - 1);
+ input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask);
+ BitStream_Shift(raw, numBits);
+ input *= -1;
+ }
+ else
+ {
+ /* sign == 0, read from srl */
+ input = progressive_rfx_srl_read(state, numBits);
+ sign[index] = input;
+ }
+
+ buffer[index] += (INT16)((UINT32)input << shift);
+ }
+
+ return 1;
+}
+
+static INLINE int progressive_rfx_upgrade_component(
+ PROGRESSIVE_CONTEXT* progressive, const RFX_COMPONENT_CODEC_QUANT* shift,
+ const RFX_COMPONENT_CODEC_QUANT* bitPos, const RFX_COMPONENT_CODEC_QUANT* numBits,
+ INT16* buffer, INT16* current, INT16* sign, const BYTE* srlData, UINT32 srlLen,
+ const BYTE* rawData, UINT32 rawLen, BOOL coeffDiff, BOOL subbandDiff, BOOL extrapolate)
+{
+ int rc = 0;
+ UINT32 aRawLen = 0;
+ UINT32 aSrlLen = 0;
+ wBitStream s_srl = { 0 };
+ wBitStream s_raw = { 0 };
+ RFX_PROGRESSIVE_UPGRADE_STATE state = { 0 };
+
+ state.kp = 8;
+ state.mode = 0;
+ state.srl = &s_srl;
+ state.raw = &s_raw;
+ BitStream_Attach(state.srl, srlData, srlLen);
+ BitStream_Fetch(state.srl);
+ BitStream_Attach(state.raw, rawData, rawLen);
+ BitStream_Fetch(state.raw);
+
+ state.nonLL = TRUE;
+ rc = progressive_rfx_upgrade_block(&state, &current[0], &sign[0], 1023, shift->HL1, bitPos->HL1,
+ numBits->HL1); /* HL1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[1023], &sign[1023], 1023, shift->LH1,
+ bitPos->LH1, numBits->LH1); /* LH1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[2046], &sign[2046], 961, shift->HH1,
+ bitPos->HH1, numBits->HH1); /* HH1 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3007], &sign[3007], 272, shift->HL2,
+ bitPos->HL2, numBits->HL2); /* HL2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3279], &sign[3279], 272, shift->LH2,
+ bitPos->LH2, numBits->LH2); /* LH2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3551], &sign[3551], 256, shift->HH2,
+ bitPos->HH2, numBits->HH2); /* HH2 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3807], &sign[3807], 72, shift->HL3,
+ bitPos->HL3, numBits->HL3); /* HL3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3879], &sign[3879], 72, shift->LH3,
+ bitPos->LH3, numBits->LH3); /* LH3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_block(&state, &current[3951], &sign[3951], 64, shift->HH3,
+ bitPos->HH3, numBits->HH3); /* HH3 */
+ if (rc < 0)
+ return rc;
+
+ state.nonLL = FALSE;
+ rc = progressive_rfx_upgrade_block(&state, &current[4015], &sign[4015], 81, shift->LL3,
+ bitPos->LL3, numBits->LL3); /* LL3 */
+ if (rc < 0)
+ return rc;
+ rc = progressive_rfx_upgrade_state_finish(&state);
+ if (rc < 0)
+ return rc;
+ aRawLen = (state.raw->position + 7) / 8;
+ aSrlLen = (state.srl->position + 7) / 8;
+
+ if ((aRawLen != rawLen) || (aSrlLen != srlLen))
+ {
+ int pRawLen = 0;
+ int pSrlLen = 0;
+
+ if (rawLen)
+ pRawLen = (int)((((float)aRawLen) / ((float)rawLen)) * 100.0f);
+
+ if (srlLen)
+ pSrlLen = (int)((((float)aSrlLen) / ((float)srlLen)) * 100.0f);
+
+ WLog_Print(progressive->log, WLOG_WARN,
+ "RAW: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32
+ ")\tSRL: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32 ")",
+ aRawLen, rawLen, pRawLen, state.raw->position, rawLen * 8,
+ (rawLen * 8) - state.raw->position, aSrlLen, srlLen, pSrlLen,
+ state.srl->position, srlLen * 8, (srlLen * 8) - state.srl->position);
+ return -1;
+ }
+
+ return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate,
+ TRUE);
+}
+
+static INLINE int progressive_decompress_tile_upgrade(PROGRESSIVE_CONTEXT* progressive,
+ RFX_PROGRESSIVE_TILE* tile,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int status = 0;
+ BOOL coeffDiff = 0;
+ BOOL sub = 0;
+ BOOL extrapolate = 0;
+ BYTE* pBuffer = NULL;
+ INT16* pSign[3] = { 0 };
+ INT16* pSrcDst[3] = { 0 };
+ INT16* pCurrent[3] = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftY = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 };
+ RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 };
+ RFX_COMPONENT_CODEC_QUANT yBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT cbBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT crBitPos = { 0 };
+ RFX_COMPONENT_CODEC_QUANT yNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT cbNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT crNumBits = { 0 };
+ RFX_COMPONENT_CODEC_QUANT* quantY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantCr = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL;
+ RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL;
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProg = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+
+ coeffDiff = tile->flags & RFX_TILE_DIFFERENCE;
+ sub = context->flags & RFX_SUBBAND_DIFFING;
+ extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE;
+
+ tile->pass++;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveTileUpgrade: pass: %" PRIu16 " quantIdx Y: %" PRIu8 " Cb: %" PRIu8
+ " Cr: %" PRIu8 " xIdx: %" PRIu16 " yIdx: %" PRIu16 " quality: %" PRIu8
+ " ySrlLen: %" PRIu16 " yRawLen: %" PRIu16 " cbSrlLen: %" PRIu16 " cbRawLen: %" PRIu16
+ " crSrlLen: %" PRIu16 " crRawLen: %" PRIu16 "",
+ tile->pass, tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx,
+ tile->yIdx, tile->quality, tile->ySrlLen, tile->yRawLen, tile->cbSrlLen,
+ tile->cbRawLen, tile->crSrlLen, tile->crRawLen);
+#endif
+
+ if (tile->quantIdxY >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant);
+ return -1;
+ }
+
+ quantY = &(region->quantVals[tile->quantIdxY]);
+
+ if (tile->quantIdxCb >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCb = &(region->quantVals[tile->quantIdxCb]);
+
+ if (tile->quantIdxCr >= region->numQuant)
+ {
+ WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr,
+ region->numQuant);
+ return -1;
+ }
+
+ quantCr = &(region->quantVals[tile->quantIdxCr]);
+
+ if (tile->quality == 0xFF)
+ {
+ quantProg = &(progressive->quantProgValFull);
+ }
+ else
+ {
+ if (tile->quality >= region->numProgQuant)
+ {
+ WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality,
+ region->numProgQuant);
+ return -1;
+ }
+
+ quantProg = &(region->quantProgVals[tile->quality]);
+ }
+
+ quantProgY = &(quantProg->yQuantValues);
+ quantProgCb = &(quantProg->cbQuantValues);
+ quantProgCr = &(quantProg->crQuantValues);
+
+ if (!progressive_rfx_quant_cmp_equal(quantY, &(tile->yQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantY has changed!");
+
+ if (!progressive_rfx_quant_cmp_equal(quantCb, &(tile->cbQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCb has changed!");
+
+ if (!progressive_rfx_quant_cmp_equal(quantCr, &(tile->crQuant)))
+ WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCr has changed!");
+
+ if (!(context->flags & RFX_SUBBAND_DIFFING))
+ WLog_WARN(TAG, "PROGRESSIVE_BLOCK_CONTEXT::flags & RFX_SUBBAND_DIFFING not set");
+
+ progressive_rfx_quant_add(quantY, quantProgY, &yBitPos);
+ progressive_rfx_quant_add(quantCb, quantProgCb, &cbBitPos);
+ progressive_rfx_quant_add(quantCr, quantProgCr, &crBitPos);
+ progressive_rfx_quant_sub(&(tile->yBitPos), &yBitPos, &yNumBits);
+ progressive_rfx_quant_sub(&(tile->cbBitPos), &cbBitPos, &cbNumBits);
+ progressive_rfx_quant_sub(&(tile->crBitPos), &crBitPos, &crNumBits);
+ progressive_rfx_quant_add(quantY, quantProgY, &shiftY);
+ progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb);
+ progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */
+ progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr);
+ progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */
+
+ tile->yBitPos = yBitPos;
+ tile->cbBitPos = cbBitPos;
+ tile->crBitPos = crBitPos;
+ tile->yQuant = *quantY;
+ tile->cbQuant = *quantCb;
+ tile->crQuant = *quantCr;
+ tile->yProgQuant = *quantProgY;
+ tile->cbProgQuant = *quantProgCb;
+ tile->crProgQuant = *quantProgCr;
+
+ pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftY, quantProgY, &yNumBits,
+ pSrcDst[0], pCurrent[0], pSign[0], tile->ySrlData,
+ tile->ySrlLen, tile->yRawData, tile->yRawLen,
+ coeffDiff, sub, extrapolate); /* Y */
+
+ if (status < 0)
+ goto fail;
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftCb, quantProgCb, &cbNumBits,
+ pSrcDst[1], pCurrent[1], pSign[1], tile->cbSrlData,
+ tile->cbSrlLen, tile->cbRawData, tile->cbRawLen,
+ coeffDiff, sub, extrapolate); /* Cb */
+
+ if (status < 0)
+ goto fail;
+
+ status = progressive_rfx_upgrade_component(progressive, &shiftCr, quantProgCr, &crNumBits,
+ pSrcDst[2], pCurrent[2], pSign[2], tile->crSrlData,
+ tile->crSrlLen, tile->crRawData, tile->crRawLen,
+ coeffDiff, sub, extrapolate); /* Cr */
+
+ if (status < 0)
+ goto fail;
+
+ status = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data,
+ tile->stride, progressive->format, &roi_64x64);
+fail:
+ BufferPool_Return(progressive->bufferPool, pBuffer);
+ return status;
+}
+
+static INLINE BOOL progressive_tile_read_upgrade(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ RFX_PROGRESSIVE_TILE tile = { 0 };
+ const size_t expect = 20;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, expect))
+ return FALSE;
+
+ tile.blockType = blockType;
+ tile.blockLen = blockLen;
+ tile.flags = 0;
+
+ Stream_Read_UINT8(s, tile.quantIdxY);
+ Stream_Read_UINT8(s, tile.quantIdxCb);
+ Stream_Read_UINT8(s, tile.quantIdxCr);
+ Stream_Read_UINT16(s, tile.xIdx);
+ Stream_Read_UINT16(s, tile.yIdx);
+ Stream_Read_UINT8(s, tile.quality);
+ Stream_Read_UINT16(s, tile.ySrlLen);
+ Stream_Read_UINT16(s, tile.yRawLen);
+ Stream_Read_UINT16(s, tile.cbSrlLen);
+ Stream_Read_UINT16(s, tile.cbRawLen);
+ Stream_Read_UINT16(s, tile.crSrlLen);
+ Stream_Read_UINT16(s, tile.crRawLen);
+
+ tile.ySrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.ySrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.ySrlLen);
+ return FALSE;
+ }
+
+ tile.yRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.yRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yRawLen);
+ return FALSE;
+ }
+
+ tile.cbSrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbSrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.cbSrlLen);
+ return FALSE;
+ }
+
+ tile.cbRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.cbRawLen);
+ return FALSE;
+ }
+
+ tile.crSrlData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crSrlLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.crSrlLen);
+ return FALSE;
+ }
+
+ tile.crRawData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crRawLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes",
+ tile.crRawLen);
+ return FALSE;
+ }
+
+ return progressive_surface_tile_replace(surface, region, &tile, TRUE);
+}
+
+static INLINE BOOL progressive_tile_read(PROGRESSIVE_CONTEXT* progressive, BOOL simple, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ RFX_PROGRESSIVE_TILE tile = { 0 };
+ size_t expect = simple ? 16 : 17;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, expect))
+ return FALSE;
+
+ tile.blockType = blockType;
+ tile.blockLen = blockLen;
+
+ Stream_Read_UINT8(s, tile.quantIdxY);
+ Stream_Read_UINT8(s, tile.quantIdxCb);
+ Stream_Read_UINT8(s, tile.quantIdxCr);
+ Stream_Read_UINT16(s, tile.xIdx);
+ Stream_Read_UINT16(s, tile.yIdx);
+ Stream_Read_UINT8(s, tile.flags);
+
+ if (!simple)
+ Stream_Read_UINT8(s, tile.quality);
+ else
+ tile.quality = 0xFF;
+ Stream_Read_UINT16(s, tile.yLen);
+ Stream_Read_UINT16(s, tile.cbLen);
+ Stream_Read_UINT16(s, tile.crLen);
+ Stream_Read_UINT16(s, tile.tailLen);
+
+ tile.yData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.yLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yLen);
+ return FALSE;
+ }
+
+ tile.cbData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.cbLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.cbLen);
+ return FALSE;
+ }
+
+ tile.crData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.crLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.crLen);
+ return FALSE;
+ }
+
+ tile.tailData = Stream_Pointer(s);
+ if (!Stream_SafeSeek(s, tile.tailLen))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.tailLen);
+ return FALSE;
+ }
+
+ return progressive_surface_tile_replace(surface, region, &tile, FALSE);
+}
+
+typedef struct
+{
+ PROGRESSIVE_CONTEXT* progressive;
+ PROGRESSIVE_BLOCK_REGION* region;
+ const PROGRESSIVE_BLOCK_CONTEXT* context;
+ RFX_PROGRESSIVE_TILE* tile;
+} PROGRESSIVE_TILE_PROCESS_WORK_PARAM;
+
+static void CALLBACK progressive_process_tiles_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)context;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+
+ switch (param->tile->blockType)
+ {
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ progressive_decompress_tile_first(param->progressive, param->tile, param->region,
+ param->context);
+ break;
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ progressive_decompress_tile_upgrade(param->progressive, param->tile, param->region,
+ param->context);
+ break;
+ default:
+ WLog_Print(param->progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16 " (%s)",
+ param->tile->blockType,
+ rfx_get_progressive_block_type_string(param->tile->blockType));
+ break;
+ }
+}
+
+static INLINE SSIZE_T progressive_process_tiles(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ PROGRESSIVE_BLOCK_REGION* region,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ const PROGRESSIVE_BLOCK_CONTEXT* context)
+{
+ int status = 0;
+ size_t end = 0;
+ const size_t start = Stream_GetPosition(s);
+ UINT16 index = 0;
+ UINT16 blockType = 0;
+ UINT32 blockLen = 0;
+ UINT32 count = 0;
+ PTP_WORK* work_objects = NULL;
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* params = NULL;
+ UINT16 close_cnt = 0;
+
+ WINPR_ASSERT(progressive);
+ WINPR_ASSERT(region);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, region->tileDataSize))
+ return -1;
+
+ while ((Stream_GetRemainingLength(s) >= 6) &&
+ (region->tileDataSize > (Stream_GetPosition(s) - start)))
+ {
+ const size_t pos = Stream_GetPosition(s);
+
+ Stream_Read_UINT16(s, blockType);
+ Stream_Read_UINT32(s, blockLen);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "%s",
+ rfx_get_progressive_block_type_string(blockType));
+#endif
+
+ if (blockLen < 6)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "Expected >= %" PRIu32 " remaining %" PRIuz, 6,
+ blockLen);
+ return -1003;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6))
+ return -1003;
+
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ if (!progressive_tile_read(progressive, TRUE, s, blockType, blockLen, surface,
+ region, context))
+ return -1022;
+ break;
+
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ if (!progressive_tile_read(progressive, FALSE, s, blockType, blockLen, surface,
+ region, context))
+ return -1027;
+ break;
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ if (!progressive_tile_read_upgrade(progressive, s, blockType, blockLen, surface,
+ region, context))
+ return -1032;
+ break;
+ default:
+ WLog_ERR(TAG, "Invalid block type %04" PRIx16 " (%s)", blockType,
+ rfx_get_progressive_block_type_string(blockType));
+ return -1039;
+ }
+
+ size_t rem = Stream_GetPosition(s);
+ if ((rem - pos) != blockLen)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Actual block read %" PRIuz " but expected %" PRIu32, rem - pos, blockLen);
+ return -1040;
+ }
+ count++;
+ }
+
+ end = Stream_GetPosition(s);
+ if ((end - start) != region->tileDataSize)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Actual total blocks read %" PRIuz " but expected %" PRIu32, end - start,
+ region->tileDataSize);
+ return -1041;
+ }
+
+ if (count != region->numTiles)
+ {
+ WLog_Print(progressive->log, WLOG_WARN,
+ "numTiles inconsistency: actual: %" PRIu32 ", expected: %" PRIu16 "\n", count,
+ region->numTiles);
+ return -1044;
+ }
+
+ {
+ size_t tcount = 1;
+ if (progressive->rfx_context->priv->UseThreads)
+ tcount = region->numTiles;
+
+ work_objects = (PTP_WORK*)winpr_aligned_calloc(tcount, sizeof(PTP_WORK), 32);
+ if (!work_objects)
+ return -1;
+ }
+
+ params = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)winpr_aligned_calloc(
+ region->numTiles, sizeof(PROGRESSIVE_TILE_PROCESS_WORK_PARAM), 32);
+ if (!params)
+ {
+ winpr_aligned_free(work_objects);
+ return -1;
+ }
+
+ for (UINT32 index = 0; index < region->numTiles; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = region->tiles[index];
+ PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = &params[index];
+ param->progressive = progressive;
+ param->region = region;
+ param->context = context;
+ param->tile = tile;
+
+ if (progressive->rfx_context->priv->UseThreads)
+ {
+ if (!(work_objects[index] = CreateThreadpoolWork(
+ progressive_process_tiles_tile_work_callback, (void*)&params[index],
+ &progressive->rfx_context->priv->ThreadPoolEnv)))
+ {
+ WLog_ERR(TAG, "CreateThreadpoolWork failed.");
+ status = -1;
+ break;
+ }
+
+ SubmitThreadpoolWork(work_objects[index]);
+ close_cnt = index + 1;
+ }
+ else
+ {
+ progressive_process_tiles_tile_work_callback(0, &params[index], 0);
+ }
+
+ if (status < 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "Failed to decompress %s at %" PRIu16,
+ rfx_get_progressive_block_type_string(tile->blockType), index);
+ goto fail;
+ }
+ }
+
+ if (status < 0)
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "Failed to create ThreadpoolWork for tile %" PRIu16, index);
+
+ if (progressive->rfx_context->priv->UseThreads)
+ {
+ for (UINT32 index = 0; index < close_cnt; index++)
+ {
+ WaitForThreadpoolWorkCallbacks(work_objects[index], FALSE);
+ CloseThreadpoolWork(work_objects[index]);
+ }
+ }
+
+fail:
+ winpr_aligned_free(work_objects);
+ winpr_aligned_free(params);
+
+ if (status < 0)
+ return -1;
+
+ return (SSIZE_T)(end - start);
+}
+
+static INLINE SSIZE_T progressive_wb_sync(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ const UINT32 magic = 0xCACCACCA;
+ const UINT16 version = 0x0100;
+ PROGRESSIVE_BLOCK_SYNC sync;
+
+ sync.blockType = blockType;
+ sync.blockLen = blockLen;
+
+ if (sync.blockLen != 12)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ sync.blockLen, 12);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1004;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveSync");
+#endif
+
+ Stream_Read_UINT32(s, sync.magic);
+ Stream_Read_UINT16(s, sync.version);
+
+ if (sync.magic != magic)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::magic = 0x%08" PRIx32 " != 0x%08" PRIx32, sync.magic,
+ magic);
+ return -1005;
+ }
+
+ if (sync.version != 0x0100)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "PROGRESSIVE_BLOCK_SYNC::version = 0x%04" PRIx16 " != 0x%04" PRIu16,
+ sync.version, version);
+ return -1006;
+ }
+
+ if ((progressive->state & FLAG_WBT_SYNC) != 0)
+ WLog_WARN(TAG, "Duplicate PROGRESSIVE_BLOCK_SYNC, ignoring");
+
+ progressive->state |= FLAG_WBT_SYNC;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_frame_begin(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_FRAME_BEGIN frameBegin;
+
+ frameBegin.blockType = blockType;
+ frameBegin.blockLen = blockLen;
+
+ if (frameBegin.blockLen != 12)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ " RFX_PROGRESSIVE_FRAME_BEGIN::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ frameBegin.blockLen, 12);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1007;
+
+ Stream_Read_UINT32(s, frameBegin.frameIndex);
+ Stream_Read_UINT16(s, frameBegin.regionCount);
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveFrameBegin: frameIndex: %" PRIu32 " regionCount: %" PRIu16 "",
+ frameBegin.frameIndex, frameBegin.regionCount);
+#endif
+
+ /**
+ * If the number of elements specified by the regionCount field is
+ * larger than the actual number of elements in the regions field,
+ * the decoder SHOULD ignore this inconsistency.
+ */
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0)
+ {
+ WLog_ERR(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_BEGIN in stream, this is not allowed!");
+ return -1008;
+ }
+
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ {
+ WLog_ERR(TAG, "RFX_PROGRESSIVE_FRAME_BEGIN after RFX_PROGRESSIVE_FRAME_END in stream, this "
+ "is not allowed!");
+ return -1008;
+ }
+
+ progressive->state |= FLAG_WBT_FRAME_BEGIN;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_frame_end(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_FRAME_END frameEnd;
+
+ frameEnd.blockType = blockType;
+ frameEnd.blockLen = blockLen;
+
+ if (frameEnd.blockLen != 6)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ " RFX_PROGRESSIVE_FRAME_END::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ frameEnd.blockLen, 6);
+ return -1005;
+ }
+
+ if (Stream_GetRemainingLength(s) != 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveFrameEnd short %" PRIuz ", expected %" PRIuz,
+ Stream_GetRemainingLength(s), 0);
+ return -1008;
+ }
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveFrameEnd");
+#endif
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_FRAME_END before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring");
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_END, ignoring");
+
+ progressive->state |= FLAG_WBT_FRAME_END;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_context(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context;
+ context->blockType = blockType;
+ context->blockLen = blockLen;
+
+ if (context->blockLen != 10)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "RFX_PROGRESSIVE_CONTEXT::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32,
+ context->blockLen, 10);
+ return -1005;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return -1009;
+
+ Stream_Read_UINT8(s, context->ctxId);
+ Stream_Read_UINT16(s, context->tileSize);
+ Stream_Read_UINT8(s, context->flags);
+
+ if (context->ctxId != 0x00)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT::ctxId != 0x00: %" PRIu8, context->ctxId);
+
+ if (context->tileSize != 64)
+ {
+ WLog_ERR(TAG, "RFX_PROGRESSIVE_CONTEXT::tileSize != 0x40: %" PRIu16, context->tileSize);
+ return -1010;
+ }
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_BEGIN");
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_END");
+ if ((progressive->state & FLAG_WBT_CONTEXT) != 0)
+ WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_CONTEXT received, ignoring.");
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveContext: flags: 0x%02" PRIX8 "",
+ context->flags);
+#endif
+
+ progressive->state |= FLAG_WBT_CONTEXT;
+ return 0;
+}
+
+static INLINE SSIZE_T progressive_wb_read_region_header(PROGRESSIVE_CONTEXT* progressive,
+ wStream* s, UINT16 blockType,
+ UINT32 blockLen,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ SSIZE_T len = 0;
+
+ memset(region, 0, sizeof(PROGRESSIVE_BLOCK_REGION));
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
+ return -1011;
+
+ region->blockType = blockType;
+ region->blockLen = blockLen;
+ Stream_Read_UINT8(s, region->tileSize);
+ Stream_Read_UINT16(s, region->numRects);
+ Stream_Read_UINT8(s, region->numQuant);
+ Stream_Read_UINT8(s, region->numProgQuant);
+ Stream_Read_UINT8(s, region->flags);
+ Stream_Read_UINT16(s, region->numTiles);
+ Stream_Read_UINT32(s, region->tileDataSize);
+
+ if (region->tileSize != 64)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion tile size %" PRIu8 ", expected %" PRIuz, region->tileSize,
+ 64);
+ return -1012;
+ }
+
+ if (region->numRects < 1)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion missing rect count %" PRIu16,
+ region->numRects);
+ return -1013;
+ }
+
+ if (region->numQuant > 7)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion quant count too high %" PRIu8 ", expected < %" PRIuz,
+ region->numQuant, 7);
+ return -1014;
+ }
+
+ len = Stream_GetRemainingLength(s);
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, region->numRects, 8ull))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->rects");
+ return -1015;
+ }
+ len -= region->numRects * 8ULL;
+
+ if (len / 5 < region->numQuant)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->cQuant");
+ return -1018;
+ }
+ len -= region->numQuant * 5ULL;
+
+ if (len / 16 < region->numProgQuant)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion data short for region->cProgQuant");
+ return -1021;
+ }
+ len -= region->numProgQuant * 16ULL;
+
+ if (len < region->tileDataSize * 1ll)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->tiles");
+ return -1024;
+ }
+ len -= region->tileDataSize;
+ if (len > 0)
+ WLog_Print(progressive->log, WLOG_WARN,
+ "Unused bytes detected, %" PRIuz " bytes not processed", len);
+ return len;
+}
+
+static INLINE SSIZE_T progressive_wb_skip_region(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen)
+{
+ SSIZE_T rc = 0;
+ size_t total = 0;
+ PROGRESSIVE_BLOCK_REGION* region = &progressive->region;
+
+ rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region);
+ if (rc < 0)
+ return rc;
+
+ total = (region->numRects * 8);
+ total += (region->numQuant * 5);
+ total += (region->numProgQuant * 16);
+ total += region->tileDataSize;
+ if (!Stream_SafeSeek(s, total))
+ return -1111;
+
+ return rc;
+}
+
+static INLINE SSIZE_T progressive_wb_region(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ UINT16 blockType, UINT32 blockLen,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ SSIZE_T rc = -1;
+ UINT16 boxLeft = 0;
+ UINT16 boxTop = 0;
+ UINT16 boxRight = 0;
+ UINT16 boxBottom = 0;
+ UINT16 idxLeft = 0;
+ UINT16 idxTop = 0;
+ UINT16 idxRight = 0;
+ UINT16 idxBottom = 0;
+ const PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context;
+
+ if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0)
+ {
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring");
+ return progressive_wb_skip_region(progressive, s, blockType, blockLen);
+ }
+ if ((progressive->state & FLAG_WBT_FRAME_END) != 0)
+ {
+ WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION after RFX_PROGRESSIVE_FRAME_END, ignoring");
+ return progressive_wb_skip_region(progressive, s, blockType, blockLen);
+ }
+
+ progressive->state |= FLAG_WBT_REGION;
+
+ rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region);
+ if (rc < 0)
+ return rc;
+
+ for (UINT16 index = 0; index < region->numRects; index++)
+ {
+ RFX_RECT* rect = &(region->rects[index]);
+ Stream_Read_UINT16(s, rect->x);
+ Stream_Read_UINT16(s, rect->y);
+ Stream_Read_UINT16(s, rect->width);
+ Stream_Read_UINT16(s, rect->height);
+ }
+
+ for (BYTE index = 0; index < region->numQuant; index++)
+ {
+ RFX_COMPONENT_CODEC_QUANT* quantVal = &(region->quantVals[index]);
+ progressive_component_codec_quant_read(s, quantVal);
+
+ if (!progressive_rfx_quant_lcmp_greater_equal(quantVal, 6))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion region->cQuant[%" PRIu32 "] < 6", index);
+ return -1;
+ }
+
+ if (!progressive_rfx_quant_lcmp_less_equal(quantVal, 15))
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "ProgressiveRegion region->cQuant[%" PRIu32 "] > 15", index);
+ return -1;
+ }
+ }
+
+ for (BYTE index = 0; index < region->numProgQuant; index++)
+ {
+ RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = &(region->quantProgVals[index]);
+
+ Stream_Read_UINT8(s, quantProgVal->quality);
+
+ progressive_component_codec_quant_read(s, &(quantProgVal->yQuantValues));
+ progressive_component_codec_quant_read(s, &(quantProgVal->cbQuantValues));
+ progressive_component_codec_quant_read(s, &(quantProgVal->crQuantValues));
+ }
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "ProgressiveRegion: numRects: %" PRIu16 " numTiles: %" PRIu16
+ " tileDataSize: %" PRIu32 " flags: 0x%02" PRIX8 " numQuant: %" PRIu8
+ " numProgQuant: %" PRIu8 "",
+ region->numRects, region->numTiles, region->tileDataSize, region->flags,
+ region->numQuant, region->numProgQuant);
+#endif
+
+ boxLeft = surface->gridWidth;
+ boxTop = surface->gridHeight;
+ boxRight = 0;
+ boxBottom = 0;
+
+ for (UINT16 index = 0; index < region->numRects; index++)
+ {
+ RFX_RECT* rect = &(region->rects[index]);
+ idxLeft = rect->x / 64;
+ idxTop = rect->y / 64;
+ idxRight = (rect->x + rect->width + 63) / 64;
+ idxBottom = (rect->y + rect->height + 63) / 64;
+
+ if (idxLeft < boxLeft)
+ boxLeft = idxLeft;
+
+ if (idxTop < boxTop)
+ boxTop = idxTop;
+
+ if (idxRight > boxRight)
+ boxRight = idxRight;
+
+ if (idxBottom > boxBottom)
+ boxBottom = idxBottom;
+
+#if defined(WITH_DEBUG_CODECS)
+ WLog_Print(progressive->log, WLOG_DEBUG,
+ "rect[%" PRIu16 "]: x: %" PRIu16 " y: %" PRIu16 " w: %" PRIu16 " h: %" PRIu16 "",
+ index, rect->x, rect->y, rect->width, rect->height);
+#endif
+ }
+
+ const SSIZE_T res = progressive_process_tiles(progressive, s, region, surface, context);
+ if (res < 0)
+ return -1;
+ return (size_t)rc;
+}
+
+static SSIZE_T progressive_parse_block(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ PROGRESSIVE_SURFACE_CONTEXT* surface,
+ PROGRESSIVE_BLOCK_REGION* region)
+{
+ UINT16 blockType = 0;
+ UINT32 blockLen = 0;
+ SSIZE_T rc = -1;
+ wStream sub = { 0 };
+
+ WINPR_ASSERT(progressive);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 6))
+ return -1;
+
+ Stream_Read_UINT16(s, blockType);
+ Stream_Read_UINT32(s, blockLen);
+
+ if (blockLen < 6)
+ {
+ WLog_WARN(TAG, "Invalid blockLen %" PRIu32 ", expected >= 6", blockLen);
+ return -1;
+ }
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6))
+ return -1;
+ Stream_StaticConstInit(&sub, Stream_Pointer(s), blockLen - 6);
+ Stream_Seek(s, blockLen - 6);
+
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_SYNC:
+ rc = progressive_wb_sync(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_FRAME_BEGIN:
+ rc = progressive_wb_frame_begin(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_FRAME_END:
+ rc = progressive_wb_frame_end(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_CONTEXT:
+ rc = progressive_wb_context(progressive, &sub, blockType, blockLen);
+ break;
+
+ case PROGRESSIVE_WBT_REGION:
+ rc = progressive_wb_region(progressive, &sub, blockType, blockLen, surface, region);
+ break;
+
+ default:
+ WLog_Print(progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16, blockType);
+ return -1;
+ }
+
+ if (rc < 0)
+ return -1;
+
+ if (Stream_GetRemainingLength(&sub) > 0)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "block len %" PRIu32 " does not match read data %" PRIuz, blockLen,
+ blockLen - Stream_GetRemainingLength(&sub));
+ return -1;
+ }
+
+ return rc;
+}
+
+INT32 progressive_decompress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst,
+ UINT32 nYDst, REGION16* invalidRegion, UINT16 surfaceId,
+ UINT32 frameId)
+{
+ INT32 rc = 1;
+
+ WINPR_ASSERT(progressive);
+ PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId);
+
+ if (!surface)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion no surface for %" PRIu16,
+ surfaceId);
+ return -1001;
+ }
+
+ PROGRESSIVE_BLOCK_REGION* region = &progressive->region;
+ WINPR_ASSERT(region);
+
+ if (surface->frameId != frameId)
+ {
+ surface->frameId = frameId;
+ surface->numUpdatedTiles = 0;
+ }
+
+ wStream ss = { 0 };
+ wStream* s = Stream_StaticConstInit(&ss, pSrcData, SrcSize);
+ WINPR_ASSERT(s);
+
+ switch (DstFormat)
+ {
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ progressive->format = DstFormat;
+ break;
+ default:
+ progressive->format = PIXEL_FORMAT_XRGB32;
+ break;
+ }
+
+ const size_t start = Stream_GetPosition(s);
+ progressive->state = 0; /* Set state to not initialized */
+ while (Stream_GetRemainingLength(s) > 0)
+ {
+ if (progressive_parse_block(progressive, s, surface, region) < 0)
+ goto fail;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ if ((end - start) != SrcSize)
+ {
+ WLog_Print(progressive->log, WLOG_ERROR,
+ "total block len %" PRIuz " does not match read data %" PRIu32, end - start,
+ SrcSize);
+ rc = -1041;
+ goto fail;
+ }
+
+ REGION16 clippingRects = { 0 };
+ region16_init(&clippingRects);
+
+ for (UINT32 i = 0; i < region->numRects; i++)
+ {
+ RECTANGLE_16 clippingRect = { 0 };
+ const RFX_RECT* rect = &(region->rects[i]);
+
+ clippingRect.left = (UINT16)nXDst + rect->x;
+ clippingRect.top = (UINT16)nYDst + rect->y;
+ clippingRect.right = clippingRect.left + rect->width;
+ clippingRect.bottom = clippingRect.top + rect->height;
+ region16_union_rect(&clippingRects, &clippingRects, &clippingRect);
+ }
+
+ for (UINT32 i = 0; i < surface->numUpdatedTiles; i++)
+ {
+ UINT32 nbUpdateRects = 0;
+ const RECTANGLE_16* updateRects = NULL;
+ RECTANGLE_16 updateRect = { 0 };
+
+ WINPR_ASSERT(surface->updatedTileIndices);
+ const UINT32 index = surface->updatedTileIndices[i];
+
+ WINPR_ASSERT(index < surface->tilesSize);
+ RFX_PROGRESSIVE_TILE* tile = surface->tiles[index];
+ WINPR_ASSERT(tile);
+
+ updateRect.left = nXDst + tile->x;
+ updateRect.top = nYDst + tile->y;
+ updateRect.right = updateRect.left + 64;
+ updateRect.bottom = updateRect.top + 64;
+
+ REGION16 updateRegion = { 0 };
+ region16_init(&updateRegion);
+ region16_intersect_rect(&updateRegion, &clippingRects, &updateRect);
+ updateRects = region16_rects(&updateRegion, &nbUpdateRects);
+
+ for (UINT32 j = 0; j < nbUpdateRects; j++)
+ {
+ const RECTANGLE_16* rect = &updateRects[j];
+ if (rect->left < updateRect.left)
+ goto fail;
+ const UINT32 nXSrc = rect->left - updateRect.left;
+ const UINT32 nYSrc = rect->top - updateRect.top;
+ const UINT32 width = rect->right - rect->left;
+ const UINT32 height = rect->bottom - rect->top;
+
+ if (rect->left + width > surface->width)
+ goto fail;
+ if (rect->top + height > surface->height)
+ goto fail;
+ if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, rect->left, rect->top, width,
+ height, tile->data, progressive->format, tile->stride, nXSrc,
+ nYSrc, NULL, FREERDP_KEEP_DST_ALPHA))
+ {
+ rc = -42;
+ break;
+ }
+
+ if (invalidRegion)
+ region16_union_rect(invalidRegion, invalidRegion, rect);
+ }
+
+ region16_uninit(&updateRegion);
+ tile->dirty = FALSE;
+ }
+
+ region16_uninit(&clippingRects);
+ surface->numUpdatedTiles = 0;
+fail:
+ return rc;
+}
+
+BOOL progressive_rfx_write_message_progressive_simple(PROGRESSIVE_CONTEXT* progressive, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ RFX_CONTEXT* context = NULL;
+
+ WINPR_ASSERT(progressive);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+ context = progressive->rfx_context;
+ return rfx_write_message_progressive_simple(context, s, msg);
+}
+
+int progressive_compress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize,
+ UINT32 SrcFormat, UINT32 Width, UINT32 Height, UINT32 ScanLine,
+ const REGION16* invalidRegion, BYTE** ppDstData, UINT32* pDstSize)
+{
+ BOOL rc = FALSE;
+ int res = -6;
+ wStream* s = NULL;
+ UINT32 numRects = 0;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ RFX_RECT* rects = NULL;
+ RFX_MESSAGE* message = NULL;
+
+ if (!progressive || !pSrcData || !ppDstData || !pDstSize)
+ {
+ return -1;
+ }
+
+ if (ScanLine == 0)
+ {
+ switch (SrcFormat)
+ {
+ case PIXEL_FORMAT_ABGR32:
+ case PIXEL_FORMAT_ARGB32:
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_BGRA32:
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_RGBA32:
+ case PIXEL_FORMAT_RGBX32:
+ ScanLine = Width * 4;
+ break;
+ default:
+ return -2;
+ }
+ }
+
+ if (SrcSize < Height * ScanLine)
+ return -4;
+
+ if (!invalidRegion)
+ {
+ numRects = (Width + 63) / 64;
+ numRects *= (Height + 63) / 64;
+ }
+ else
+ numRects = region16_n_rects(invalidRegion);
+
+ if (numRects == 0)
+ return 0;
+
+ if (!Stream_EnsureCapacity(progressive->rects, numRects * sizeof(RFX_RECT)))
+ return -5;
+ rects = (RFX_RECT*)Stream_Buffer(progressive->rects);
+ if (invalidRegion)
+ {
+ const RECTANGLE_16* region_rects = region16_rects(invalidRegion, NULL);
+ for (UINT32 x = 0; x < numRects; x++)
+ {
+ const RECTANGLE_16* r = &region_rects[x];
+ RFX_RECT* rect = &rects[x];
+
+ rect->x = r->left;
+ rect->y = r->top;
+ rect->width = r->right - r->left;
+ rect->height = r->bottom - r->top;
+ }
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ for (UINT32 i = 0; i < numRects; i++)
+ {
+ RFX_RECT* r = &rects[i];
+ r->x = x;
+ r->y = y;
+ r->width = MIN(64, Width - x);
+ r->height = MIN(64, Height - y);
+
+ if (x + 64 >= Width)
+ {
+ y += 64;
+ x = 0;
+ }
+ else
+ x += 64;
+
+ WINPR_ASSERT(r->x % 64 == 0);
+ WINPR_ASSERT(r->y % 64 == 0);
+ WINPR_ASSERT(r->width <= 64);
+ WINPR_ASSERT(r->height <= 64);
+ }
+ }
+ s = progressive->buffer;
+ Stream_SetPosition(s, 0);
+
+ progressive->rfx_context->mode = RLGR1;
+ progressive->rfx_context->width = Width;
+ progressive->rfx_context->height = Height;
+ rfx_context_set_pixel_format(progressive->rfx_context, SrcFormat);
+ message = rfx_encode_message(progressive->rfx_context, rects, numRects, pSrcData, Width, Height,
+ ScanLine);
+ if (!message)
+ {
+ WLog_ERR(TAG, "failed to encode rfx message");
+ goto fail;
+ }
+
+ rc = progressive_rfx_write_message_progressive_simple(progressive, s, message);
+ rfx_message_free(progressive->rfx_context, message);
+ if (!rc)
+ goto fail;
+
+ const size_t pos = Stream_GetPosition(s);
+ WINPR_ASSERT(pos <= UINT32_MAX);
+ *pDstSize = (UINT32)pos;
+ *ppDstData = Stream_Buffer(s);
+ res = 1;
+fail:
+ return res;
+}
+
+BOOL progressive_context_reset(PROGRESSIVE_CONTEXT* progressive)
+{
+ if (!progressive)
+ return FALSE;
+
+ return TRUE;
+}
+
+PROGRESSIVE_CONTEXT* progressive_context_new(BOOL Compressor)
+{
+ return progressive_context_new_ex(Compressor, 0);
+}
+
+PROGRESSIVE_CONTEXT* progressive_context_new_ex(BOOL Compressor, UINT32 ThreadingFlags)
+{
+ PROGRESSIVE_CONTEXT* progressive =
+ (PROGRESSIVE_CONTEXT*)winpr_aligned_calloc(1, sizeof(PROGRESSIVE_CONTEXT), 32);
+
+ if (!progressive)
+ return NULL;
+
+ progressive->Compressor = Compressor;
+ progressive->quantProgValFull.quality = 100;
+ progressive->log = WLog_Get(TAG);
+ if (!progressive->log)
+ goto fail;
+ progressive->rfx_context = rfx_context_new_ex(Compressor, ThreadingFlags);
+ if (!progressive->rfx_context)
+ goto fail;
+ progressive->buffer = Stream_New(NULL, 1024);
+ if (!progressive->buffer)
+ goto fail;
+ progressive->rects = Stream_New(NULL, 1024);
+ if (!progressive->rects)
+ goto fail;
+ progressive->bufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16);
+ if (!progressive->bufferPool)
+ goto fail;
+ progressive->SurfaceContexts = HashTable_New(TRUE);
+ if (!progressive->SurfaceContexts)
+ goto fail;
+
+ {
+ wObject* obj = HashTable_ValueObject(progressive->SurfaceContexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = progressive_surface_context_free;
+ }
+ return progressive;
+fail:
+ progressive_context_free(progressive);
+ return NULL;
+}
+
+void progressive_context_free(PROGRESSIVE_CONTEXT* progressive)
+{
+ if (!progressive)
+ return;
+
+ Stream_Free(progressive->buffer, TRUE);
+ Stream_Free(progressive->rects, TRUE);
+ rfx_context_free(progressive->rfx_context);
+
+ BufferPool_Free(progressive->bufferPool);
+ HashTable_Free(progressive->SurfaceContexts);
+
+ winpr_aligned_free(progressive);
+}
diff --git a/libfreerdp/codec/progressive.h b/libfreerdp/codec/progressive.h
new file mode 100644
index 0000000..5c5f2a6
--- /dev/null
+++ b/libfreerdp/codec/progressive.h
@@ -0,0 +1,221 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Progressive Codec Bitmap Compression
+ *
+ * Copyright 2017 Armin Novak <anovak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#ifndef INTERNAL_CODEC_PROGRESSIVE_H
+#define INTERNAL_CODEC_PROGRESSIVE_H
+
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/codec/rfx.h>
+
+#define RFX_SUBBAND_DIFFING 0x01
+
+#define RFX_TILE_DIFFERENCE 0x01
+
+#define RFX_DWT_REDUCE_EXTRAPOLATE 0x01
+
+typedef struct
+{
+ BYTE LL3;
+ BYTE HL3;
+ BYTE LH3;
+ BYTE HH3;
+ BYTE HL2;
+ BYTE LH2;
+ BYTE HH2;
+ BYTE HL1;
+ BYTE LH1;
+ BYTE HH1;
+} RFX_COMPONENT_CODEC_QUANT;
+
+typedef struct
+{
+ BYTE quality;
+ RFX_COMPONENT_CODEC_QUANT yQuantValues;
+ RFX_COMPONENT_CODEC_QUANT cbQuantValues;
+ RFX_COMPONENT_CODEC_QUANT crQuantValues;
+} RFX_PROGRESSIVE_CODEC_QUANT;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+} PROGRESSIVE_BLOCK;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ UINT32 magic;
+ UINT16 version;
+} PROGRESSIVE_BLOCK_SYNC;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE ctxId;
+ UINT16 tileSize;
+ BYTE flags;
+} PROGRESSIVE_BLOCK_CONTEXT;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE quantIdxY;
+ BYTE quantIdxCb;
+ BYTE quantIdxCr;
+ UINT16 xIdx;
+ UINT16 yIdx;
+
+ BYTE flags;
+ BYTE quality;
+ BOOL dirty;
+
+ UINT16 yLen;
+ UINT16 cbLen;
+ UINT16 crLen;
+ UINT16 tailLen;
+ const BYTE* yData;
+ const BYTE* cbData;
+ const BYTE* crData;
+ const BYTE* tailData;
+
+ UINT16 ySrlLen;
+ UINT16 yRawLen;
+ UINT16 cbSrlLen;
+ UINT16 cbRawLen;
+ UINT16 crSrlLen;
+ UINT16 crRawLen;
+ const BYTE* ySrlData;
+ const BYTE* yRawData;
+ const BYTE* cbSrlData;
+ const BYTE* cbRawData;
+ const BYTE* crSrlData;
+ const BYTE* crRawData;
+
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+ UINT32 stride;
+
+ BYTE* data;
+ BYTE* current;
+
+ UINT16 pass;
+ BYTE* sign;
+
+ RFX_COMPONENT_CODEC_QUANT yBitPos;
+ RFX_COMPONENT_CODEC_QUANT cbBitPos;
+ RFX_COMPONENT_CODEC_QUANT crBitPos;
+ RFX_COMPONENT_CODEC_QUANT yQuant;
+ RFX_COMPONENT_CODEC_QUANT cbQuant;
+ RFX_COMPONENT_CODEC_QUANT crQuant;
+ RFX_COMPONENT_CODEC_QUANT yProgQuant;
+ RFX_COMPONENT_CODEC_QUANT cbProgQuant;
+ RFX_COMPONENT_CODEC_QUANT crProgQuant;
+} RFX_PROGRESSIVE_TILE;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ BYTE tileSize;
+ UINT16 numRects;
+ BYTE numQuant;
+ BYTE numProgQuant;
+ BYTE flags;
+ UINT16 numTiles;
+ UINT16 usedTiles;
+ UINT32 tileDataSize;
+ RFX_RECT rects[0x10000];
+ RFX_COMPONENT_CODEC_QUANT quantVals[0x100];
+ RFX_PROGRESSIVE_CODEC_QUANT quantProgVals[0x100];
+ RFX_PROGRESSIVE_TILE* tiles[0x10000];
+} PROGRESSIVE_BLOCK_REGION;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+
+ UINT32 frameIndex;
+ UINT16 regionCount;
+ PROGRESSIVE_BLOCK_REGION* regions;
+} PROGRESSIVE_BLOCK_FRAME_BEGIN;
+
+typedef struct
+{
+ UINT16 blockType;
+ UINT32 blockLen;
+} PROGRESSIVE_BLOCK_FRAME_END;
+
+typedef struct
+{
+ UINT16 id;
+ UINT32 width;
+ UINT32 height;
+ UINT32 gridWidth;
+ UINT32 gridHeight;
+ UINT32 gridSize;
+ RFX_PROGRESSIVE_TILE** tiles;
+ size_t tilesSize;
+ UINT32 frameId;
+ UINT32 numUpdatedTiles;
+ UINT32* updatedTileIndices;
+} PROGRESSIVE_SURFACE_CONTEXT;
+
+typedef enum
+{
+ FLAG_WBT_SYNC = 0x01,
+ FLAG_WBT_FRAME_BEGIN = 0x02,
+ FLAG_WBT_FRAME_END = 0x04,
+ FLAG_WBT_CONTEXT = 0x08,
+ FLAG_WBT_REGION = 0x10
+} WBT_STATE_FLAG;
+
+struct S_PROGRESSIVE_CONTEXT
+{
+ BOOL Compressor;
+
+ wBufferPool* bufferPool;
+
+ UINT32 format;
+ UINT32 state;
+
+ PROGRESSIVE_BLOCK_CONTEXT context;
+ PROGRESSIVE_BLOCK_REGION region;
+ RFX_PROGRESSIVE_CODEC_QUANT quantProgValFull;
+
+ wHashTable* SurfaceContexts;
+ wLog* log;
+ wStream* buffer;
+ wStream* rects;
+ RFX_CONTEXT* rfx_context;
+};
+
+#endif /* INTERNAL_CODEC_PROGRESSIVE_H */
diff --git a/libfreerdp/codec/region.c b/libfreerdp/codec/region.c
new file mode 100644
index 0000000..ba7f3d2
--- /dev/null
+++ b/libfreerdp/codec/region.c
@@ -0,0 +1,820 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <contact@hardening-consulting.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <winpr/memory.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/region.h>
+
+#define TAG FREERDP_TAG("codec")
+
+/*
+ * The functions in this file implement the Region abstraction largely inspired from
+ * pixman library. The following comment is taken from the pixman code.
+ *
+ * A Region is simply a set of disjoint(non-overlapping) rectangles, plus an
+ * "extent" rectangle which is the smallest single rectangle that contains all
+ * the non-overlapping rectangles.
+ *
+ * A Region is implemented as a "y-x-banded" array of rectangles. This array
+ * imposes two degrees of order. First, all rectangles are sorted by top side
+ * y coordinate first (y1), and then by left side x coordinate (x1).
+ *
+ * Furthermore, the rectangles are grouped into "bands". Each rectangle in a
+ * band has the same top y coordinate (y1), and each has the same bottom y
+ * coordinate (y2). Thus all rectangles in a band differ only in their left
+ * and right side (x1 and x2). Bands are implicit in the array of rectangles:
+ * there is no separate list of band start pointers.
+ *
+ * The y-x band representation does not minimize rectangles. In particular,
+ * if a rectangle vertically crosses a band (the rectangle has scanlines in
+ * the y1 to y2 area spanned by the band), then the rectangle may be broken
+ * down into two or more smaller rectangles stacked one atop the other.
+ *
+ * ----------- -----------
+ * | | | | band 0
+ * | | -------- ----------- --------
+ * | | | | in y-x banded | | | | band 1
+ * | | | | form is | | | |
+ * ----------- | | ----------- --------
+ * | | | | band 2
+ * -------- --------
+ *
+ * An added constraint on the rectangles is that they must cover as much
+ * horizontal area as possible: no two rectangles within a band are allowed
+ * to touch.
+ *
+ * Whenever possible, bands will be merged together to cover a greater vertical
+ * distance (and thus reduce the number of rectangles). Two bands can be merged
+ * only if the bottom of one touches the top of the other and they have
+ * rectangles in the same places (of the same width, of course).
+ */
+
+struct S_REGION16_DATA
+{
+ long size;
+ long nbRects;
+};
+
+static REGION16_DATA empty_region = { 0, 0 };
+
+void region16_init(REGION16* region)
+{
+ WINPR_ASSERT(region);
+ ZeroMemory(region, sizeof(REGION16));
+ region->data = &empty_region;
+}
+
+int region16_n_rects(const REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+ return region->data->nbRects;
+}
+
+const RECTANGLE_16* region16_rects(const REGION16* region, UINT32* nbRects)
+{
+ REGION16_DATA* data = NULL;
+
+ if (nbRects)
+ *nbRects = 0;
+
+ if (!region)
+ return NULL;
+
+ data = region->data;
+
+ if (!data)
+ return NULL;
+
+ if (nbRects)
+ *nbRects = data->nbRects;
+
+ return (RECTANGLE_16*)(data + 1);
+}
+
+static INLINE RECTANGLE_16* region16_rects_noconst(REGION16* region)
+{
+ REGION16_DATA* data = NULL;
+ data = region->data;
+
+ if (!data)
+ return NULL;
+
+ return (RECTANGLE_16*)(&data[1]);
+}
+
+const RECTANGLE_16* region16_extents(const REGION16* region)
+{
+ if (!region)
+ return NULL;
+
+ return &region->extents;
+}
+
+static RECTANGLE_16* region16_extents_noconst(REGION16* region)
+{
+ if (!region)
+ return NULL;
+
+ return &region->extents;
+}
+
+BOOL rectangle_is_empty(const RECTANGLE_16* rect)
+{
+ /* A rectangle with width <= 0 or height <= 0 should be regarded
+ * as empty.
+ */
+ return ((rect->left >= rect->right) || (rect->top >= rect->bottom)) ? TRUE : FALSE;
+}
+
+BOOL region16_is_empty(const REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+ return (region->data->nbRects == 0);
+}
+
+BOOL rectangles_equal(const RECTANGLE_16* r1, const RECTANGLE_16* r2)
+{
+ return ((r1->left == r2->left) && (r1->top == r2->top) && (r1->right == r2->right) &&
+ (r1->bottom == r2->bottom))
+ ? TRUE
+ : FALSE;
+}
+
+BOOL rectangles_intersects(const RECTANGLE_16* r1, const RECTANGLE_16* r2)
+{
+ RECTANGLE_16 tmp = { 0 };
+ return rectangles_intersection(r1, r2, &tmp);
+}
+
+BOOL rectangles_intersection(const RECTANGLE_16* r1, const RECTANGLE_16* r2, RECTANGLE_16* dst)
+{
+ dst->left = MAX(r1->left, r2->left);
+ dst->right = MIN(r1->right, r2->right);
+ dst->top = MAX(r1->top, r2->top);
+ dst->bottom = MIN(r1->bottom, r2->bottom);
+ return (dst->left < dst->right) && (dst->top < dst->bottom);
+}
+
+void region16_clear(REGION16* region)
+{
+ WINPR_ASSERT(region);
+ WINPR_ASSERT(region->data);
+
+ if ((region->data->size > 0) && (region->data != &empty_region))
+ free(region->data);
+
+ region->data = &empty_region;
+ ZeroMemory(&region->extents, sizeof(region->extents));
+}
+
+static INLINE REGION16_DATA* allocateRegion(long nbItems)
+{
+ long allocSize = sizeof(REGION16_DATA) + (nbItems * sizeof(RECTANGLE_16));
+ REGION16_DATA* ret = (REGION16_DATA*)malloc(allocSize);
+
+ if (!ret)
+ return ret;
+
+ ret->size = allocSize;
+ ret->nbRects = nbItems;
+ return ret;
+}
+
+BOOL region16_copy(REGION16* dst, const REGION16* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(dst->data);
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(src->data);
+
+ if (dst == src)
+ return TRUE;
+
+ dst->extents = src->extents;
+
+ if ((dst->data->size > 0) && (dst->data != &empty_region))
+ free(dst->data);
+
+ if (src->data->size == 0)
+ dst->data = &empty_region;
+ else
+ {
+ dst->data = allocateRegion(src->data->nbRects);
+
+ if (!dst->data)
+ return FALSE;
+
+ CopyMemory(dst->data, src->data, src->data->size);
+ }
+
+ return TRUE;
+}
+
+void region16_print(const REGION16* region)
+{
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ int currentBandY = -1;
+ rects = region16_rects(region, &nbRects);
+ WLog_DBG(TAG, "nrects=%" PRIu32 "", nbRects);
+
+ for (UINT32 i = 0; i < nbRects; i++, rects++)
+ {
+ if (rects->top != currentBandY)
+ {
+ currentBandY = rects->top;
+ WLog_DBG(TAG, "band %d: ", currentBandY);
+ }
+
+ WLog_DBG(TAG, "(%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")", rects->left, rects->top,
+ rects->right, rects->bottom);
+ }
+}
+
+static void region16_copy_band_with_union(RECTANGLE_16* dst, const RECTANGLE_16* src,
+ const RECTANGLE_16* end, UINT16 newTop, UINT16 newBottom,
+ const RECTANGLE_16* unionRect, UINT32* dstCounter,
+ const RECTANGLE_16** srcPtr, RECTANGLE_16** dstPtr)
+{
+ UINT16 refY = src->top;
+ const RECTANGLE_16* startOverlap = NULL;
+ const RECTANGLE_16* endOverlap = NULL;
+
+ /* merges a band with the given rect
+ * Input:
+ * unionRect
+ * | |
+ * | |
+ * ==============+===============+================================
+ * |Item1| |Item2| |Item3| |Item4| |Item5| Band
+ * ==============+===============+================================
+ * before | overlap | after
+ *
+ * Resulting band:
+ * +-----+ +----------------------+ +-----+
+ * |Item1| | Item2 | |Item3|
+ * +-----+ +----------------------+ +-----+
+ *
+ * We first copy as-is items that are before Item2, the first overlapping
+ * item.
+ * Then we find the last one that overlap unionRect to agregate Item2, Item3
+ * and Item4 to create Item2.
+ * Finally Item5 is copied as Item3.
+ *
+ * When no unionRect is provided, we skip the two first steps to just copy items
+ */
+
+ if (unionRect)
+ {
+ /* items before unionRect */
+ while ((src < end) && (src->top == refY) && (src->right < unionRect->left))
+ {
+ dst->top = newTop;
+ dst->bottom = newBottom;
+ dst->right = src->right;
+ dst->left = src->left;
+ src++;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ /* treat items overlapping with unionRect */
+ startOverlap = unionRect;
+ endOverlap = unionRect;
+
+ if ((src < end) && (src->top == refY) && (src->left < unionRect->left))
+ startOverlap = src;
+
+ while ((src < end) && (src->top == refY) && (src->right < unionRect->right))
+ {
+ src++;
+ }
+
+ if ((src < end) && (src->top == refY) && (src->left < unionRect->right))
+ {
+ endOverlap = src;
+ src++;
+ }
+
+ dst->bottom = newBottom;
+ dst->top = newTop;
+ dst->left = startOverlap->left;
+ dst->right = endOverlap->right;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ /* treat remaining items on the same band */
+ while ((src < end) && (src->top == refY))
+ {
+ dst->top = newTop;
+ dst->bottom = newBottom;
+ dst->right = src->right;
+ dst->left = src->left;
+ src++;
+ dst++;
+ *dstCounter += 1;
+ }
+
+ if (srcPtr)
+ *srcPtr = src;
+
+ *dstPtr = dst;
+}
+
+static RECTANGLE_16* next_band(RECTANGLE_16* band1, RECTANGLE_16* endPtr, int* nbItems)
+{
+ UINT16 refY = band1->top;
+ *nbItems = 0;
+
+ while ((band1 < endPtr) && (band1->top == refY))
+ {
+ band1++;
+ *nbItems += 1;
+ }
+
+ return band1;
+}
+
+static BOOL band_match(const RECTANGLE_16* band1, const RECTANGLE_16* band2,
+ const RECTANGLE_16* endPtr)
+{
+ int refBand2 = band2->top;
+ const RECTANGLE_16* band2Start = band2;
+
+ while ((band1 < band2Start) && (band2 < endPtr) && (band2->top == refBand2))
+ {
+ if ((band1->left != band2->left) || (band1->right != band2->right))
+ return FALSE;
+
+ band1++;
+ band2++;
+ }
+
+ if (band1 != band2Start)
+ return FALSE;
+
+ return (band2 == endPtr) || (band2->top != refBand2);
+}
+
+/** compute if the rectangle is fully included in the band
+ * @param band a pointer on the beginning of the band
+ * @param endPtr end of the region
+ * @param rect the rectangle to test
+ * @return if rect is fully included in an item of the band
+ */
+static BOOL rectangle_contained_in_band(const RECTANGLE_16* band, const RECTANGLE_16* endPtr,
+ const RECTANGLE_16* rect)
+{
+ UINT16 refY = band->top;
+
+ if ((band->top > rect->top) || (rect->bottom > band->bottom))
+ return FALSE;
+
+ /* note: as the band is sorted from left to right, once we've seen an item
+ * that is after rect->left we're sure that the result is False.
+ */
+ while ((band < endPtr) && (band->top == refY) && (band->left <= rect->left))
+ {
+ if (rect->right <= band->right)
+ return TRUE;
+
+ band++;
+ }
+
+ return FALSE;
+}
+
+static BOOL region16_simplify_bands(REGION16* region)
+{
+ /** Simplify consecutive bands that touch and have the same items
+ *
+ * ==================== ====================
+ * | 1 | | 2 | | | | |
+ * ==================== | | | |
+ * | 1 | | 2 | ====> | 1 | | 2 |
+ * ==================== | | | |
+ * | 1 | | 2 | | | | |
+ * ==================== ====================
+ *
+ */
+ RECTANGLE_16* endBand = NULL;
+ int nbRects = 0;
+ int finalNbRects = 0;
+ int bandItems = 0;
+ int toMove = 0;
+ finalNbRects = nbRects = region16_n_rects(region);
+
+ if (nbRects < 2)
+ return TRUE;
+
+ RECTANGLE_16* band1 = region16_rects_noconst(region);
+ RECTANGLE_16* endPtr = band1 + nbRects;
+
+ do
+ {
+ RECTANGLE_16* band2 = next_band(band1, endPtr, &bandItems);
+
+ if (band2 == endPtr)
+ break;
+
+ if ((band1->bottom == band2->top) && band_match(band1, band2, endPtr))
+ {
+ /* adjust the bottom of band1 items */
+ RECTANGLE_16* tmp = band1;
+
+ while (tmp < band2)
+ {
+ tmp->bottom = band2->bottom;
+ tmp++;
+ }
+
+ /* override band2, we don't move band1 pointer as the band after band2
+ * may be merged too */
+ endBand = band2 + bandItems;
+ toMove = (endPtr - endBand) * sizeof(RECTANGLE_16);
+
+ if (toMove)
+ MoveMemory(band2, endBand, toMove);
+
+ finalNbRects -= bandItems;
+ endPtr -= bandItems;
+ }
+ else
+ {
+ band1 = band2;
+ }
+ } while (TRUE);
+
+ if (finalNbRects != nbRects)
+ {
+ size_t allocSize = sizeof(REGION16_DATA) + (finalNbRects * sizeof(RECTANGLE_16));
+ REGION16_DATA* data = realloc(region->data, allocSize);
+ if (!data)
+ free(region->data);
+ region->data = data;
+
+ if (!region->data)
+ {
+ region->data = &empty_region;
+ return FALSE;
+ }
+
+ region->data->nbRects = finalNbRects;
+ region->data->size = allocSize;
+ }
+
+ return TRUE;
+}
+
+BOOL region16_union_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect)
+{
+ const RECTANGLE_16* srcExtents = NULL;
+ RECTANGLE_16* dstExtents = NULL;
+ const RECTANGLE_16* currentBand = NULL;
+ const RECTANGLE_16* endSrcRect = NULL;
+ const RECTANGLE_16* nextBand = NULL;
+ REGION16_DATA* newItems = NULL;
+ REGION16_DATA* tmpItems = NULL;
+ RECTANGLE_16* dstRect = NULL;
+ UINT32 usedRects = 0;
+ UINT32 srcNbRects = 0;
+ UINT16 topInterBand = 0;
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(dst);
+ srcExtents = region16_extents(src);
+ dstExtents = region16_extents_noconst(dst);
+
+ if (!region16_n_rects(src))
+ {
+ /* source is empty, so the union is rect */
+ dst->extents = *rect;
+ dst->data = allocateRegion(1);
+
+ if (!dst->data)
+ return FALSE;
+
+ dstRect = region16_rects_noconst(dst);
+ dstRect->top = rect->top;
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = rect->bottom;
+ return TRUE;
+ }
+
+ newItems = allocateRegion((1 + region16_n_rects(src)) * 4);
+
+ if (!newItems)
+ return FALSE;
+
+ dstRect = (RECTANGLE_16*)(&newItems[1]);
+ usedRects = 0;
+
+ /* adds the piece of rect that is on the top of src */
+ if (rect->top < srcExtents->top)
+ {
+ dstRect->top = rect->top;
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = MIN(srcExtents->top, rect->bottom);
+ usedRects++;
+ dstRect++;
+ }
+
+ /* treat possibly overlapping region */
+ currentBand = region16_rects(src, &srcNbRects);
+ endSrcRect = currentBand + srcNbRects;
+
+ while (currentBand < endSrcRect)
+ {
+ if ((currentBand->bottom <= rect->top) || (rect->bottom <= currentBand->top) ||
+ rectangle_contained_in_band(currentBand, endSrcRect, rect))
+ {
+ /* no overlap between rect and the band, rect is totally below or totally above
+ * the current band, or rect is already covered by an item of the band.
+ * let's copy all the rectangles from this band
+ +----+
+ | | rect (case 1)
+ +----+
+
+ =================
+ band of srcRect
+ =================
+ +----+
+ | | rect (case 2)
+ +----+
+ */
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top,
+ currentBand->bottom, NULL, &usedRects, &nextBand,
+ &dstRect);
+ topInterBand = rect->top;
+ }
+ else
+ {
+ /* rect overlaps the band:
+ | | | |
+ ====^=================| |==| |=========================== band
+ | top split | | | |
+ v | 1 | | 2 |
+ ^ | | | | +----+ +----+
+ | merge zone | | | | | | | 4 |
+ v +----+ | | | | +----+
+ ^ | | | 3 |
+ | bottom split | | | |
+ ====v=========================| |==| |===================
+ | | | |
+
+ possible cases:
+ 1) no top split, merge zone then a bottom split. The band will be splitted
+ in two
+ 2) not band split, only the merge zone, band merged with rect but not splitted
+ 3) a top split, the merge zone and no bottom split. The band will be split
+ in two
+ 4) a top split, the merge zone and also a bottom split. The band will be
+ splitted in 3, but the coalesce algorithm may merge the created bands
+ */
+ UINT16 mergeTop = currentBand->top;
+ UINT16 mergeBottom = currentBand->bottom;
+
+ /* test if we need a top split, case 3 and 4 */
+ if (rect->top > currentBand->top)
+ {
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top,
+ rect->top, NULL, &usedRects, &nextBand, &dstRect);
+ mergeTop = rect->top;
+ }
+
+ /* do the merge zone (all cases) */
+ if (rect->bottom < currentBand->bottom)
+ mergeBottom = rect->bottom;
+
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeTop, mergeBottom,
+ rect, &usedRects, &nextBand, &dstRect);
+
+ /* test if we need a bottom split, case 1 and 4 */
+ if (rect->bottom < currentBand->bottom)
+ {
+ region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeBottom,
+ currentBand->bottom, NULL, &usedRects, &nextBand,
+ &dstRect);
+ }
+
+ topInterBand = currentBand->bottom;
+ }
+
+ /* test if a piece of rect should be inserted as a new band between
+ * the current band and the next one. band n and n+1 shouldn't touch.
+ *
+ * ==============================================================
+ * band n
+ * +------+ +------+
+ * ===========| rect |====================| |===============
+ * | | +------+ | |
+ * +------+ | rect | | rect |
+ * +------+ | |
+ * =======================================| |================
+ * +------+ band n+1
+ * ===============================================================
+ *
+ */
+ if ((nextBand < endSrcRect) && (nextBand->top != currentBand->bottom) &&
+ (rect->bottom > currentBand->bottom) && (rect->top < nextBand->top))
+ {
+ dstRect->right = rect->right;
+ dstRect->left = rect->left;
+ dstRect->top = topInterBand;
+ dstRect->bottom = MIN(nextBand->top, rect->bottom);
+ dstRect++;
+ usedRects++;
+ }
+
+ currentBand = nextBand;
+ }
+
+ /* adds the piece of rect that is below src */
+ if (srcExtents->bottom < rect->bottom)
+ {
+ dstRect->top = MAX(srcExtents->bottom, rect->top);
+ dstRect->left = rect->left;
+ dstRect->right = rect->right;
+ dstRect->bottom = rect->bottom;
+ usedRects++;
+ dstRect++;
+ }
+
+ if ((src == dst) && (dst->data != &empty_region))
+ free(dst->data);
+
+ dstExtents->top = MIN(rect->top, srcExtents->top);
+ dstExtents->left = MIN(rect->left, srcExtents->left);
+ dstExtents->bottom = MAX(rect->bottom, srcExtents->bottom);
+ dstExtents->right = MAX(rect->right, srcExtents->right);
+ newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16));
+ tmpItems = realloc(newItems, newItems->size);
+ if (!tmpItems)
+ free(newItems);
+ newItems = tmpItems;
+ dst->data = newItems;
+
+ if (!dst->data)
+ return FALSE;
+
+ dst->data->nbRects = usedRects;
+ return region16_simplify_bands(dst);
+}
+
+BOOL region16_intersects_rect(const REGION16* src, const RECTANGLE_16* arg2)
+{
+ const RECTANGLE_16* rect = NULL;
+ const RECTANGLE_16* endPtr = NULL;
+ const RECTANGLE_16* srcExtents = NULL;
+ UINT32 nbRects = 0;
+
+ if (!src || !src->data || !arg2)
+ return FALSE;
+
+ rect = region16_rects(src, &nbRects);
+
+ if (!nbRects)
+ return FALSE;
+
+ srcExtents = region16_extents(src);
+
+ if (nbRects == 1)
+ return rectangles_intersects(srcExtents, arg2);
+
+ if (!rectangles_intersects(srcExtents, arg2))
+ return FALSE;
+
+ for (endPtr = rect + nbRects; (rect < endPtr) && (arg2->bottom > rect->top); rect++)
+ {
+ if (rectangles_intersects(rect, arg2))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL region16_intersect_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect)
+{
+ REGION16_DATA* newItems = NULL;
+ const RECTANGLE_16* srcPtr = NULL;
+ const RECTANGLE_16* endPtr = NULL;
+ const RECTANGLE_16* srcExtents = NULL;
+ RECTANGLE_16* dstPtr = NULL;
+ UINT32 nbRects = 0;
+ UINT32 usedRects = 0;
+ RECTANGLE_16 common;
+ RECTANGLE_16 newExtents;
+ WINPR_ASSERT(src);
+ WINPR_ASSERT(src->data);
+ srcPtr = region16_rects(src, &nbRects);
+
+ if (!nbRects)
+ {
+ region16_clear(dst);
+ return TRUE;
+ }
+
+ srcExtents = region16_extents(src);
+
+ if (nbRects == 1)
+ {
+ BOOL intersects = rectangles_intersection(srcExtents, rect, &common);
+ region16_clear(dst);
+
+ if (intersects)
+ return region16_union_rect(dst, dst, &common);
+
+ return TRUE;
+ }
+
+ newItems = allocateRegion(nbRects);
+
+ if (!newItems)
+ return FALSE;
+
+ dstPtr = (RECTANGLE_16*)(&newItems[1]);
+ usedRects = 0;
+ ZeroMemory(&newExtents, sizeof(newExtents));
+
+ /* accumulate intersecting rectangles, the final region16_simplify_bands() will
+ * do all the bad job to recreate correct rectangles
+ */
+ for (endPtr = srcPtr + nbRects; (srcPtr < endPtr) && (rect->bottom > srcPtr->top); srcPtr++)
+ {
+ if (rectangles_intersection(srcPtr, rect, &common))
+ {
+ *dstPtr = common;
+ usedRects++;
+ dstPtr++;
+
+ if (rectangle_is_empty(&newExtents))
+ {
+ /* Check if the existing newExtents is empty. If it is empty, use
+ * new common directly. We do not need to check common rectangle
+ * because the rectangles_intersection() ensures that it is not empty.
+ */
+ newExtents = common;
+ }
+ else
+ {
+ newExtents.top = MIN(common.top, newExtents.top);
+ newExtents.left = MIN(common.left, newExtents.left);
+ newExtents.bottom = MAX(common.bottom, newExtents.bottom);
+ newExtents.right = MAX(common.right, newExtents.right);
+ }
+ }
+ }
+
+ newItems->nbRects = usedRects;
+ newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16));
+
+ if ((dst->data->size > 0) && (dst->data != &empty_region))
+ free(dst->data);
+
+ dst->data = realloc(newItems, newItems->size);
+
+ if (!dst->data)
+ {
+ free(newItems);
+ return FALSE;
+ }
+
+ dst->extents = newExtents;
+ return region16_simplify_bands(dst);
+}
+
+void region16_uninit(REGION16* region)
+{
+ WINPR_ASSERT(region);
+
+ if (region->data)
+ {
+ if ((region->data->size > 0) && (region->data != &empty_region))
+ free(region->data);
+
+ region->data = NULL;
+ }
+}
diff --git a/libfreerdp/codec/rfx.c b/libfreerdp/codec/rfx.c
new file mode 100644
index 0000000..c83cfd5
--- /dev/null
+++ b/libfreerdp/codec/rfx.c
@@ -0,0 +1,2411 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 Norbert Federa <norbert.federa@thincast.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/sysinfo.h>
+#include <winpr/registry.h>
+
+#include <freerdp/log.h>
+#include <freerdp/settings.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/constants.h>
+#include <freerdp/primitives.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/build-config.h>
+
+#include "rfx_constants.h"
+#include "rfx_types.h"
+#include "rfx_decode.h"
+#include "rfx_encode.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+#include "rfx_rlgr.h"
+
+#include "rfx_sse2.h"
+#include "rfx_neon.h"
+
+#define TAG FREERDP_TAG("codec")
+
+#ifndef RFX_INIT_SIMD
+#define RFX_INIT_SIMD(_rfx_context) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define RFX_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\RemoteFX"
+
+/**
+ * The quantization values control the compression rate and quality. The value
+ * range is between 6 and 15. The higher value, the higher compression rate
+ * and lower quality.
+ *
+ * This is the default values being use by the MS RDP server, and we will also
+ * use it as our default values for the encoder. It can be overrided by setting
+ * the context->num_quants and context->quants member.
+ *
+ * The order of the values are:
+ * LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1
+ */
+static const UINT32 rfx_default_quantization_values[] = { 6, 6, 6, 6, 7, 7, 8, 8, 8, 9 };
+
+static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_TILE* tile);
+
+static void rfx_profiler_create(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+ PROFILER_CREATE(context->priv->prof_rfx_decode_rgb, "rfx_decode_rgb")
+ PROFILER_CREATE(context->priv->prof_rfx_decode_component, "rfx_decode_component")
+ PROFILER_CREATE(context->priv->prof_rfx_rlgr_decode, "rfx_rlgr_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_differential_decode, "rfx_differential_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode")
+ PROFILER_CREATE(context->priv->prof_rfx_ycbcr_to_rgb, "prims->yCbCrToRGB")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_rgb, "rfx_encode_rgb")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_component, "rfx_encode_component")
+ PROFILER_CREATE(context->priv->prof_rfx_rlgr_encode, "rfx_rlgr_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_differential_encode, "rfx_differential_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode")
+ PROFILER_CREATE(context->priv->prof_rfx_rgb_to_ycbcr, "prims->RGBToYCbCr")
+ PROFILER_CREATE(context->priv->prof_rfx_encode_format_rgb, "rfx_encode_format_rgb")
+}
+
+static void rfx_profiler_free(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+ PROFILER_FREE(context->priv->prof_rfx_decode_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_decode_component)
+ PROFILER_FREE(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_FREE(context->priv->prof_rfx_differential_decode)
+ PROFILER_FREE(context->priv->prof_rfx_quantization_decode)
+ PROFILER_FREE(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_FREE(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_encode_rgb)
+ PROFILER_FREE(context->priv->prof_rfx_encode_component)
+ PROFILER_FREE(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_FREE(context->priv->prof_rfx_differential_encode)
+ PROFILER_FREE(context->priv->prof_rfx_quantization_encode)
+ PROFILER_FREE(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_FREE(context->priv->prof_rfx_rgb_to_ycbcr)
+ PROFILER_FREE(context->priv->prof_rfx_encode_format_rgb)
+}
+
+static void rfx_profiler_print(RFX_CONTEXT* context)
+{
+ if (!context || !context->priv)
+ return;
+
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(context->priv->prof_rfx_decode_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_decode_component)
+ PROFILER_PRINT(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_differential_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_quantization_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_PRINT(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_rgb)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_component)
+ PROFILER_PRINT(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_differential_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_quantization_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_PRINT(context->priv->prof_rfx_rgb_to_ycbcr)
+ PROFILER_PRINT(context->priv->prof_rfx_encode_format_rgb)
+ PROFILER_PRINT_FOOTER
+}
+
+static void rfx_tile_init(void* obj)
+{
+ RFX_TILE* tile = (RFX_TILE*)obj;
+ if (tile)
+ {
+ tile->x = 0;
+ tile->y = 0;
+ tile->YLen = 0;
+ tile->YData = NULL;
+ tile->CbLen = 0;
+ tile->CbData = NULL;
+ tile->CrLen = 0;
+ tile->CrData = NULL;
+ }
+}
+
+static void* rfx_decoder_tile_new(const void* val)
+{
+ const size_t size = 4 * 64 * 64;
+ RFX_TILE* tile = NULL;
+ WINPR_UNUSED(val);
+
+ if (!(tile = (RFX_TILE*)winpr_aligned_calloc(1, sizeof(RFX_TILE), 32)))
+ return NULL;
+
+ if (!(tile->data = (BYTE*)winpr_aligned_malloc(size, 16)))
+ {
+ winpr_aligned_free(tile);
+ return NULL;
+ }
+ memset(tile->data, 0xff, size);
+ tile->allocated = TRUE;
+ return tile;
+}
+
+static void rfx_decoder_tile_free(void* obj)
+{
+ RFX_TILE* tile = (RFX_TILE*)obj;
+
+ if (tile)
+ {
+ if (tile->allocated)
+ winpr_aligned_free(tile->data);
+
+ winpr_aligned_free(tile);
+ }
+}
+
+static void* rfx_encoder_tile_new(const void* val)
+{
+ WINPR_UNUSED(val);
+ return winpr_aligned_calloc(1, sizeof(RFX_TILE), 32);
+}
+
+static void rfx_encoder_tile_free(void* obj)
+{
+ winpr_aligned_free(obj);
+}
+
+RFX_CONTEXT* rfx_context_new(BOOL encoder)
+{
+ return rfx_context_new_ex(encoder, 0);
+}
+
+RFX_CONTEXT* rfx_context_new_ex(BOOL encoder, UINT32 ThreadingFlags)
+{
+ HKEY hKey = NULL;
+ LONG status = 0;
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+ DWORD dwValue = 0;
+ SYSTEM_INFO sysinfo;
+ RFX_CONTEXT* context = NULL;
+ wObject* pool = NULL;
+ RFX_CONTEXT_PRIV* priv = NULL;
+ context = (RFX_CONTEXT*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT), 32);
+
+ if (!context)
+ return NULL;
+
+ context->encoder = encoder;
+ context->currentMessage.freeArray = TRUE;
+ context->priv = priv = (RFX_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT_PRIV), 32);
+
+ if (!priv)
+ goto fail;
+
+ priv->log = WLog_Get("com.freerdp.codec.rfx");
+ WLog_OpenAppender(priv->log);
+ priv->TilePool = ObjectPool_New(TRUE);
+
+ if (!priv->TilePool)
+ goto fail;
+
+ pool = ObjectPool_Object(priv->TilePool);
+ pool->fnObjectInit = rfx_tile_init;
+
+ if (context->encoder)
+ {
+ pool->fnObjectNew = rfx_encoder_tile_new;
+ pool->fnObjectFree = rfx_encoder_tile_free;
+ }
+ else
+ {
+ pool->fnObjectNew = rfx_decoder_tile_new;
+ pool->fnObjectFree = rfx_decoder_tile_free;
+ }
+
+ /*
+ * align buffers to 16 byte boundary (needed for SSE/NEON instructions)
+ *
+ * y_r_buffer, cb_g_buffer, cr_b_buffer: 64 * 64 * sizeof(INT16) = 8192 (0x2000)
+ * dwt_buffer: 32 * 32 * 2 * 2 * sizeof(INT16) = 8192, maximum sub-band width is 32
+ *
+ * Additionally we add 32 bytes (16 in front and 16 at the back of the buffer)
+ * in order to allow optimized functions (SEE, NEON) to read from positions
+ * that are actually in front/beyond the buffer. Offset calculations are
+ * performed at the BufferPool_Take function calls in rfx_encode/decode.c.
+ *
+ * We then multiply by 3 to use a single, partioned buffer for all 3 channels.
+ */
+ priv->BufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16);
+
+ if (!priv->BufferPool)
+ goto fail;
+
+ if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS))
+ {
+ priv->UseThreads = TRUE;
+
+ GetNativeSystemInfo(&sysinfo);
+ priv->MinThreadCount = sysinfo.dwNumberOfProcessors;
+ priv->MaxThreadCount = 0;
+ status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, RFX_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ dwSize = sizeof(dwValue);
+
+ if (RegQueryValueEx(hKey, _T("UseThreads"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) ==
+ ERROR_SUCCESS)
+ priv->UseThreads = dwValue ? 1 : 0;
+
+ if (RegQueryValueEx(hKey, _T("MinThreadCount"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ priv->MinThreadCount = dwValue;
+
+ if (RegQueryValueEx(hKey, _T("MaxThreadCount"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ priv->MaxThreadCount = dwValue;
+
+ RegCloseKey(hKey);
+ }
+ }
+ else
+ {
+ priv->UseThreads = FALSE;
+ }
+
+ if (priv->UseThreads)
+ {
+ /* Call primitives_get here in order to avoid race conditions when using primitives_get */
+ /* from multiple threads. This call will initialize all function pointers correctly */
+ /* before any decoding threads are started */
+ primitives_get();
+ priv->ThreadPool = CreateThreadpool(NULL);
+
+ if (!priv->ThreadPool)
+ goto fail;
+
+ InitializeThreadpoolEnvironment(&priv->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&priv->ThreadPoolEnv, priv->ThreadPool);
+
+ if (priv->MinThreadCount)
+ if (!SetThreadpoolThreadMinimum(priv->ThreadPool, priv->MinThreadCount))
+ goto fail;
+
+ if (priv->MaxThreadCount)
+ SetThreadpoolThreadMaximum(priv->ThreadPool, priv->MaxThreadCount);
+ }
+
+ /* initialize the default pixel format */
+ rfx_context_set_pixel_format(context, PIXEL_FORMAT_BGRX32);
+ /* create profilers for default decoding routines */
+ rfx_profiler_create(context);
+ /* set up default routines */
+ context->quantization_decode = rfx_quantization_decode;
+ context->quantization_encode = rfx_quantization_encode;
+ context->dwt_2d_decode = rfx_dwt_2d_decode;
+ context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode;
+ context->dwt_2d_encode = rfx_dwt_2d_encode;
+ context->rlgr_decode = rfx_rlgr_decode;
+ context->rlgr_encode = rfx_rlgr_encode;
+ RFX_INIT_SIMD(context);
+ context->state = RFX_STATE_SEND_HEADERS;
+ context->expectedDataBlockType = WBT_FRAME_BEGIN;
+ return context;
+fail:
+ rfx_context_free(context);
+ return NULL;
+}
+
+void rfx_context_free(RFX_CONTEXT* context)
+{
+ RFX_CONTEXT_PRIV* priv = NULL;
+
+ if (!context)
+ return;
+
+ WINPR_ASSERT(NULL != context);
+
+ priv = context->priv;
+ WINPR_ASSERT(NULL != priv);
+ WINPR_ASSERT(NULL != priv->TilePool);
+ WINPR_ASSERT(NULL != priv->BufferPool);
+
+ /* coverity[address_free] */
+ rfx_message_free(context, &context->currentMessage);
+ winpr_aligned_free(context->quants);
+ rfx_profiler_print(context);
+ rfx_profiler_free(context);
+
+ if (priv)
+ {
+ ObjectPool_Free(priv->TilePool);
+ if (priv->UseThreads)
+ {
+ if (priv->ThreadPool)
+ CloseThreadpool(priv->ThreadPool);
+ DestroyThreadpoolEnvironment(&priv->ThreadPoolEnv);
+ winpr_aligned_free(priv->workObjects);
+ winpr_aligned_free(priv->tileWorkParams);
+#ifdef WITH_PROFILER
+ WLog_VRB(
+ TAG,
+ "WARNING: Profiling results probably unusable with multithreaded RemoteFX codec!");
+#endif
+ }
+
+ BufferPool_Free(priv->BufferPool);
+ winpr_aligned_free(priv);
+ }
+ winpr_aligned_free(context);
+}
+
+static RFX_TILE* rfx_message_get_tile(RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->tiles);
+ WINPR_ASSERT(index < message->numTiles);
+ return message->tiles[index];
+}
+
+static const RFX_RECT* rfx_message_get_rect_const(const RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->rects);
+ WINPR_ASSERT(index < message->numRects);
+ return &message->rects[index];
+}
+
+static RFX_RECT* rfx_message_get_rect(RFX_MESSAGE* message, UINT32 index)
+{
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(message->rects);
+ WINPR_ASSERT(index < message->numRects);
+ return &message->rects[index];
+}
+
+void rfx_context_set_pixel_format(RFX_CONTEXT* context, UINT32 pixel_format)
+{
+ WINPR_ASSERT(context);
+ context->pixel_format = pixel_format;
+ context->bits_per_pixel = FreeRDPGetBitsPerPixel(pixel_format);
+}
+
+UINT32 rfx_context_get_pixel_format(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->pixel_format;
+}
+
+void rfx_context_set_palette(RFX_CONTEXT* context, const BYTE* palette)
+{
+ WINPR_ASSERT(context);
+ context->palette = palette;
+}
+
+const BYTE* rfx_context_get_palette(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->palette;
+}
+
+BOOL rfx_context_reset(RFX_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ if (!context)
+ return FALSE;
+
+ context->width = width;
+ context->height = height;
+ context->state = RFX_STATE_SEND_HEADERS;
+ context->expectedDataBlockType = WBT_FRAME_BEGIN;
+ context->frameIdx = 0;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_sync(RFX_CONTEXT* context, wStream* s)
+{
+ UINT32 magic = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_SYNC;
+
+ /* RFX_SYNC */
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT32(s, magic); /* magic (4 bytes), 0xCACCACCA */
+ if (magic != WF_MAGIC)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid magic number 0x%08" PRIX32 "", magic);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, context->version); /* version (2 bytes), WF_VERSION_1_0 (0x0100) */
+ if (context->version != WF_VERSION_1_0)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid version number 0x%08" PRIX32 "",
+ context->version);
+ return FALSE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_DEBUG, "version 0x%08" PRIX32 "", context->version);
+ context->decodedHeaderBlocks |= RFX_DECODED_SYNC;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_codec_versions(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE numCodecs = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_VERSIONS;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT8(s, numCodecs); /* numCodecs (1 byte), must be set to 0x01 */
+ Stream_Read_UINT8(s, context->codec_id); /* codecId (1 byte), must be set to 0x01 */
+ Stream_Read_UINT16(
+ s, context->codec_version); /* version (2 bytes), must be set to WF_VERSION_1_0 (0x0100) */
+
+ if (numCodecs != 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "numCodes is 0x%02" PRIX8 " (must be 0x01)",
+ numCodecs);
+ return FALSE;
+ }
+
+ if (context->codec_id != 0x01)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec id (0x%02" PRIX32 ")",
+ context->codec_id);
+ return FALSE;
+ }
+
+ if (context->codec_version != WF_VERSION_1_0)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec version (0x%08" PRIX32 ")",
+ context->codec_version);
+ return FALSE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_DEBUG, "id %" PRIu32 " version 0x%" PRIX32 ".",
+ context->codec_id, context->codec_version);
+ context->decodedHeaderBlocks |= RFX_DECODED_VERSIONS;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_channels(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE channelId = 0;
+ BYTE numChannels = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_CHANNELS;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, numChannels); /* numChannels (1 byte), must bet set to 0x01 */
+
+ /* In RDVH sessions, numChannels will represent the number of virtual monitors
+ * configured and does not always be set to 0x01 as [MS-RDPRFX] said.
+ */
+ if (numChannels < 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "no channels announced");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, numChannels, 5ull))
+ return FALSE;
+
+ /* RFX_CHANNELT */
+ Stream_Read_UINT8(s, channelId); /* channelId (1 byte), must be set to 0x00 */
+
+ if (channelId != 0x00)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "channelId:0x%02" PRIX8 ", expected:0x00",
+ channelId);
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, context->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, context->height); /* height (2 bytes) */
+
+ if (!context->width || !context->height)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channel with/height: %" PRIu16 "x%" PRIu16 "", context->width,
+ context->height);
+ return FALSE;
+ }
+
+ /* Now, only the first monitor can be used, therefore the other channels will be ignored. */
+ Stream_Seek(s, 5 * (numChannels - 1));
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "numChannels %" PRIu8 " id %" PRIu8 ", %" PRIu16 "x%" PRIu16 ".", numChannels,
+ channelId, context->width, context->height);
+ context->decodedHeaderBlocks |= RFX_DECODED_CHANNELS;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_context(RFX_CONTEXT* context, wStream* s)
+{
+ BYTE ctxId = 0;
+ UINT16 tileSize = 0;
+ UINT16 properties = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ context->decodedHeaderBlocks &= ~RFX_DECODED_CONTEXT;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5))
+ return FALSE;
+
+ Stream_Read_UINT8(s, ctxId); /* ctxId (1 byte), must be set to 0x00 */
+ Stream_Read_UINT16(s, tileSize); /* tileSize (2 bytes), must be set to CT_TILE_64x64 (0x0040) */
+ Stream_Read_UINT16(s, properties); /* properties (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "ctxId %" PRIu8 " tileSize %" PRIu16 " properties 0x%04" PRIX16 ".", ctxId, tileSize,
+ properties);
+ context->properties = properties;
+ context->flags = (properties & 0x0007);
+
+ if (context->flags == CODEC_MODE)
+ {
+ WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in image mode.");
+ }
+ else
+ {
+ WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in video mode.");
+ }
+
+ switch ((properties & 0x1E00) >> 9)
+ {
+ case CLW_ENTROPY_RLGR1:
+ context->mode = RLGR1;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR1.");
+ break;
+
+ case CLW_ENTROPY_RLGR3:
+ context->mode = RLGR3;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR3.");
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_ERROR, "unknown RLGR algorithm.");
+ return FALSE;
+ }
+
+ context->decodedHeaderBlocks |= RFX_DECODED_CONTEXT;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_frame_begin(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ UINT32 frameIdx = 0;
+ UINT16 numRegions = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_FRAME_BEGIN)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_FRAME_BEGIN");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_REGION;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ return FALSE;
+
+ Stream_Read_UINT32(
+ s, frameIdx); /* frameIdx (4 bytes), if codec is in video mode, must be ignored */
+ Stream_Read_UINT16(s, numRegions); /* numRegions (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "RFX_FRAME_BEGIN: frameIdx: %" PRIu32 " numRegions: %" PRIu16 "", frameIdx,
+ numRegions);
+ return TRUE;
+}
+
+static BOOL rfx_process_message_frame_end(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_FRAME_END)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected, wants WBT_FRAME_END");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_FRAME_BEGIN;
+ WLog_Print(context->priv->log, WLOG_DEBUG, "RFX_FRAME_END");
+ return TRUE;
+}
+
+static BOOL rfx_resize_rects(RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+
+ RFX_RECT* tmpRects =
+ winpr_aligned_recalloc(message->rects, message->numRects, sizeof(RFX_RECT), 32);
+ if (!tmpRects)
+ return FALSE;
+ message->rects = tmpRects;
+ return TRUE;
+}
+
+static BOOL rfx_process_message_region(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ UINT16 regionType = 0;
+ UINT16 numTileSets = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_REGION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_REGION");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_EXTENSION;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 3))
+ return FALSE;
+
+ Stream_Seek_UINT8(s); /* regionFlags (1 byte) */
+ Stream_Read_UINT16(s, message->numRects); /* numRects (2 bytes) */
+
+ if (message->numRects < 1)
+ {
+ /*
+ If numRects is zero the decoder must generate a rectangle with
+ coordinates (0, 0, width, height).
+ See [MS-RDPRFX] (revision >= 17.0) 2.2.2.3.3 TS_RFX_REGION
+ https://msdn.microsoft.com/en-us/library/ff635233.aspx
+ */
+ message->numRects = 1;
+ if (!rfx_resize_rects(message))
+ return FALSE;
+
+ message->rects->x = 0;
+ message->rects->y = 0;
+ message->rects->width = context->width;
+ message->rects->height = context->height;
+ return TRUE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, message->numRects, 8ull))
+ return FALSE;
+
+ if (!rfx_resize_rects(message))
+ return FALSE;
+
+ /* rects */
+ for (UINT16 i = 0; i < message->numRects; i++)
+ {
+ RFX_RECT* rect = rfx_message_get_rect(message, i);
+ /* RFX_RECT */
+ Stream_Read_UINT16(s, rect->x); /* x (2 bytes) */
+ Stream_Read_UINT16(s, rect->y); /* y (2 bytes) */
+ Stream_Read_UINT16(s, rect->width); /* width (2 bytes) */
+ Stream_Read_UINT16(s, rect->height); /* height (2 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "rect %" PRIu16 " (x,y=%" PRIu16 ",%" PRIu16 " w,h=%" PRIu16 " %" PRIu16 ").", i,
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, regionType); /*regionType (2 bytes): MUST be set to CBT_REGION (0xCAC1)*/
+ Stream_Read_UINT16(s, numTileSets); /*numTilesets (2 bytes): MUST be set to 0x0001.*/
+
+ if (regionType != CBT_REGION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid region type 0x%04" PRIX16 "",
+ regionType);
+ return TRUE;
+ }
+
+ if (numTileSets != 0x0001)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid number of tilesets (%" PRIu16 ")",
+ numTileSets);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+typedef struct
+{
+ RFX_TILE* tile;
+ RFX_CONTEXT* context;
+} RFX_TILE_PROCESS_WORK_PARAM;
+
+static void CALLBACK rfx_process_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ RFX_TILE_PROCESS_WORK_PARAM* param = (RFX_TILE_PROCESS_WORK_PARAM*)context;
+ WINPR_ASSERT(param);
+ rfx_decode_rgb(param->context, param->tile, param->tile->data, 64 * 4);
+}
+
+static BOOL rfx_allocate_tiles(RFX_MESSAGE* message, size_t count, BOOL allocOnly)
+{
+ WINPR_ASSERT(message);
+
+ RFX_TILE** tmpTiles = winpr_aligned_recalloc(message->tiles, count, sizeof(RFX_TILE*), 32);
+ if (!tmpTiles && (count != 0))
+ return FALSE;
+
+ message->tiles = tmpTiles;
+ if (!allocOnly)
+ message->numTiles = count;
+ else
+ {
+ WINPR_ASSERT(message->numTiles <= count);
+ }
+ message->allocatedTiles = count;
+
+ return TRUE;
+}
+static BOOL rfx_process_message_tileset(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
+ UINT16* pExpectedBlockType)
+{
+ BOOL rc = 0;
+ size_t close_cnt = 0;
+ BYTE quant = 0;
+ RFX_TILE* tile = NULL;
+ UINT32* quants = NULL;
+ UINT16 subtype = 0;
+ UINT16 numTiles = 0;
+ UINT32 blockLen = 0;
+ UINT32 blockType = 0;
+ UINT32 tilesDataSize = 0;
+ PTP_WORK* work_objects = NULL;
+ RFX_TILE_PROCESS_WORK_PARAM* params = NULL;
+ void* pmem = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->priv);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(pExpectedBlockType);
+
+ if (*pExpectedBlockType != WBT_EXTENSION)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants a tileset");
+ return FALSE;
+ }
+
+ *pExpectedBlockType = WBT_FRAME_END;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 14))
+ return FALSE;
+
+ Stream_Read_UINT16(s, subtype); /* subtype (2 bytes) must be set to CBT_TILESET (0xCAC2) */
+ if (subtype != CBT_TILESET)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid subtype, expected CBT_TILESET.");
+ return FALSE;
+ }
+
+ Stream_Seek_UINT16(s); /* idx (2 bytes), must be set to 0x0000 */
+ Stream_Seek_UINT16(s); /* properties (2 bytes) */
+ Stream_Read_UINT8(s, context->numQuant); /* numQuant (1 byte) */
+ Stream_Seek_UINT8(s); /* tileSize (1 byte), must be set to 0x40 */
+
+ if (context->numQuant < 1)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "no quantization value.");
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, numTiles); /* numTiles (2 bytes) */
+ if (numTiles < 1)
+ {
+ /* Windows Server 2012 (not R2) can send empty tile sets */
+ return TRUE;
+ }
+
+ Stream_Read_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ if (!(pmem =
+ winpr_aligned_recalloc(context->quants, context->numQuant, 10 * sizeof(UINT32), 32)))
+ return FALSE;
+
+ quants = context->quants = (UINT32*)pmem;
+
+ /* quantVals */
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, context->numQuant, 5ull))
+ return FALSE;
+
+ for (size_t i = 0; i < context->numQuant; i++)
+ {
+ /* RFX_CODEC_QUANT */
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ Stream_Read_UINT8(s, quant);
+ *quants++ = (quant & 0x0F);
+ *quants++ = (quant >> 4);
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "quant %d (%" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32
+ " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 ").",
+ i, context->quants[i * 10], context->quants[i * 10 + 1],
+ context->quants[i * 10 + 2], context->quants[i * 10 + 3],
+ context->quants[i * 10 + 4], context->quants[i * 10 + 5],
+ context->quants[i * 10 + 6], context->quants[i * 10 + 7],
+ context->quants[i * 10 + 8], context->quants[i * 10 + 9]);
+ }
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ ObjectPool_Return(context->priv->TilePool, message->tiles[i]);
+ message->tiles[i] = NULL;
+ }
+
+ if (!rfx_allocate_tiles(message, numTiles, FALSE))
+ return FALSE;
+
+ if (context->priv->UseThreads)
+ {
+ work_objects = (PTP_WORK*)winpr_aligned_calloc(message->numTiles, sizeof(PTP_WORK), 32);
+ params = (RFX_TILE_PROCESS_WORK_PARAM*)winpr_aligned_recalloc(
+ NULL, message->numTiles, sizeof(RFX_TILE_PROCESS_WORK_PARAM), 32);
+
+ if (!work_objects)
+ {
+ winpr_aligned_free(params);
+ return FALSE;
+ }
+
+ if (!params)
+ {
+ winpr_aligned_free(work_objects);
+ return FALSE;
+ }
+ }
+
+ /* tiles */
+ close_cnt = 0;
+ rc = FALSE;
+
+ if (Stream_GetRemainingLength(s) >= tilesDataSize)
+ {
+ rc = TRUE;
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ wStream subBuffer;
+ wStream* sub = NULL;
+
+ if (!(tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet failed to get tile from object pool");
+ rc = FALSE;
+ break;
+ }
+
+ message->tiles[i] = tile;
+
+ /* RFX_TILE */
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet packet too small to read tile %d/%" PRIu16 "", i,
+ message->numTiles);
+ rc = FALSE;
+ break;
+ }
+
+ sub = Stream_StaticInit(&subBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s));
+ Stream_Read_UINT16(
+ sub, blockType); /* blockType (2 bytes), must be set to CBT_TILE (0xCAC3) */
+ Stream_Read_UINT32(sub, blockLen); /* blockLen (4 bytes) */
+
+ if (!Stream_SafeSeek(s, blockLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ if ((blockLen < 6 + 13) ||
+ (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, sub, blockLen - 6)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "RfxMessageTileSet not enough bytes to read tile %d/%" PRIu16
+ " with blocklen=%" PRIu32 "",
+ i, message->numTiles, blockLen);
+ rc = FALSE;
+ break;
+ }
+
+ if (blockType != CBT_TILE)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "unknown block type 0x%" PRIX32 ", expected CBT_TILE (0xCAC3).",
+ blockType);
+ rc = FALSE;
+ break;
+ }
+
+ Stream_Read_UINT8(sub, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Read_UINT8(sub, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Read_UINT8(sub, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ if (tile->quantIdxY >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxY %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxY,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+ if (tile->quantIdxCb >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxCb %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCb,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+ if (tile->quantIdxCr >= context->numQuant)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "quantIdxCr %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCr,
+ context->numQuant);
+ rc = FALSE;
+ break;
+ }
+
+ Stream_Read_UINT16(sub, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Read_UINT16(sub, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Read_UINT16(sub, tile->YLen); /* YLen (2 bytes) */
+ Stream_Read_UINT16(sub, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Read_UINT16(sub, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_GetPointer(sub, tile->YData);
+ if (!Stream_SafeSeek(sub, tile->YLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ Stream_GetPointer(sub, tile->CbData);
+ if (!Stream_SafeSeek(sub, tile->CbLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ Stream_GetPointer(sub, tile->CrData);
+ if (!Stream_SafeSeek(sub, tile->CrLen))
+ {
+ rc = FALSE;
+ break;
+ }
+ tile->x = tile->xIdx * 64;
+ tile->y = tile->yIdx * 64;
+
+ if (context->priv->UseThreads)
+ {
+ if (!params)
+ {
+ rc = FALSE;
+ break;
+ }
+
+ params[i].context = context;
+ params[i].tile = message->tiles[i];
+
+ if (!(work_objects[i] =
+ CreateThreadpoolWork(rfx_process_message_tile_work_callback,
+ (void*)&params[i], &context->priv->ThreadPoolEnv)))
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "CreateThreadpoolWork failed.");
+ rc = FALSE;
+ break;
+ }
+
+ SubmitThreadpoolWork(work_objects[i]);
+ close_cnt = i + 1;
+ }
+ else
+ {
+ rfx_decode_rgb(context, tile, tile->data, 64 * 4);
+ }
+ }
+ }
+
+ if (context->priv->UseThreads)
+ {
+ for (size_t i = 0; i < close_cnt; i++)
+ {
+ WaitForThreadpoolWorkCallbacks(work_objects[i], FALSE);
+ CloseThreadpoolWork(work_objects[i]);
+ }
+ }
+
+ winpr_aligned_free(work_objects);
+ winpr_aligned_free(params);
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ if (!(tile = message->tiles[i]))
+ continue;
+
+ tile->YLen = tile->CbLen = tile->CrLen = 0;
+ tile->YData = tile->CbData = tile->CrData = NULL;
+ }
+
+ return rc;
+}
+
+BOOL rfx_process_message(RFX_CONTEXT* context, const BYTE* data, UINT32 length, UINT32 left,
+ UINT32 top, BYTE* dst, UINT32 dstFormat, UINT32 dstStride,
+ UINT32 dstHeight, REGION16* invalidRegion)
+{
+ REGION16 updateRegion = { 0 };
+ wStream inStream = { 0 };
+ BOOL ok = TRUE;
+
+ if (!context || !data || !length)
+ return FALSE;
+
+ WINPR_ASSERT(context->priv);
+ RFX_MESSAGE* message = &context->currentMessage;
+
+ wStream* s = Stream_StaticConstInit(&inStream, data, length);
+
+ while (ok && Stream_GetRemainingLength(s) > 6)
+ {
+ wStream subStreamBuffer = { 0 };
+ size_t extraBlockLen = 0;
+ UINT32 blockLen = 0;
+ UINT32 blockType = 0;
+
+ /* RFX_BLOCKT */
+ Stream_Read_UINT16(s, blockType); /* blockType (2 bytes) */
+ Stream_Read_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ WLog_Print(context->priv->log, WLOG_DEBUG, "blockType 0x%" PRIX32 " blockLen %" PRIu32 "",
+ blockType, blockLen);
+
+ if (blockLen < 6)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "blockLen too small(%" PRIu32 ")", blockLen);
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, blockLen - 6))
+ return FALSE;
+
+ if (blockType > WBT_CONTEXT && context->decodedHeaderBlocks != RFX_DECODED_HEADERS)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "incomplete header blocks processing");
+ return FALSE;
+ }
+
+ if (blockType >= WBT_CONTEXT && blockType <= WBT_EXTENSION)
+ {
+ /* RFX_CODEC_CHANNELT */
+ UINT8 codecId = 0;
+ UINT8 channelId = 0;
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 2))
+ return FALSE;
+
+ extraBlockLen = 2;
+ Stream_Read_UINT8(s, codecId); /* codecId (1 byte) must be set to 0x01 */
+ Stream_Read_UINT8(s, channelId); /* channelId (1 byte) 0xFF or 0x00, see below */
+
+ if (codecId != 0x01)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR, "invalid codecId 0x%02" PRIX8 "",
+ codecId);
+ return FALSE;
+ }
+
+ if (blockType == WBT_CONTEXT)
+ {
+ /* If the blockType is set to WBT_CONTEXT, then channelId MUST be set to 0xFF.*/
+ if (channelId != 0xFF)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channelId 0x%02" PRIX8 " for blockType 0x%08" PRIX32 "",
+ channelId, blockType);
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* For all other values of blockType, channelId MUST be set to 0x00. */
+ if (channelId != 0x00)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "invalid channelId 0x%02" PRIX8 " for blockType WBT_CONTEXT",
+ channelId);
+ return FALSE;
+ }
+ }
+ }
+
+ const size_t blockLenNoHeader = blockLen - 6;
+ if (blockLenNoHeader < extraBlockLen)
+ {
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "blockLen too small(%" PRIu32 "), must be >= 6 + %" PRIu16, blockLen,
+ extraBlockLen);
+ return FALSE;
+ }
+
+ const size_t subStreamLen = blockLenNoHeader - extraBlockLen;
+ wStream* subStream = Stream_StaticInit(&subStreamBuffer, Stream_Pointer(s), subStreamLen);
+ Stream_Seek(s, subStreamLen);
+
+ switch (blockType)
+ {
+ /* Header messages:
+ * The stream MUST start with the header messages and any of these headers can appear
+ * in the stream at a later stage. The header messages can be repeated.
+ */
+ case WBT_SYNC:
+ ok = rfx_process_message_sync(context, subStream);
+ break;
+
+ case WBT_CONTEXT:
+ ok = rfx_process_message_context(context, subStream);
+ break;
+
+ case WBT_CODEC_VERSIONS:
+ ok = rfx_process_message_codec_versions(context, subStream);
+ break;
+
+ case WBT_CHANNELS:
+ ok = rfx_process_message_channels(context, subStream);
+ break;
+
+ /* Data messages:
+ * The data associated with each encoded frame or image is always bracketed by the
+ * TS_RFX_FRAME_BEGIN (section 2.2.2.3.1) and TS_RFX_FRAME_END (section 2.2.2.3.2)
+ * messages. There MUST only be one TS_RFX_REGION (section 2.2.2.3.3) message per
+ * frame and one TS_RFX_TILESET (section 2.2.2.3.4) message per TS_RFX_REGION.
+ */
+
+ case WBT_FRAME_BEGIN:
+ ok = rfx_process_message_frame_begin(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_REGION:
+ ok = rfx_process_message_region(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_EXTENSION:
+ ok = rfx_process_message_tileset(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ case WBT_FRAME_END:
+ ok = rfx_process_message_frame_end(context, message, subStream,
+ &context->expectedDataBlockType);
+ break;
+
+ default:
+ WLog_Print(context->priv->log, WLOG_ERROR, "unknown blockType 0x%" PRIX32 "",
+ blockType);
+ return FALSE;
+ }
+ }
+
+ if (ok)
+ {
+ UINT32 nbUpdateRects = 0;
+ REGION16 clippingRects = { 0 };
+ const RECTANGLE_16* updateRects = NULL;
+ const DWORD formatSize = FreeRDPGetBytesPerPixel(context->pixel_format);
+ const UINT32 dstWidth = dstStride / FreeRDPGetBytesPerPixel(dstFormat);
+ region16_init(&clippingRects);
+
+ WINPR_ASSERT(dstWidth <= UINT16_MAX);
+ WINPR_ASSERT(dstHeight <= UINT16_MAX);
+ for (UINT32 i = 0; i < message->numRects; i++)
+ {
+ RECTANGLE_16 clippingRect = { 0 };
+ const RFX_RECT* rect = &(message->rects[i]);
+
+ WINPR_ASSERT(left + rect->x <= UINT16_MAX);
+ WINPR_ASSERT(top + rect->y <= UINT16_MAX);
+ WINPR_ASSERT(clippingRect.left + rect->width <= UINT16_MAX);
+ WINPR_ASSERT(clippingRect.top + rect->height <= UINT16_MAX);
+
+ clippingRect.left = (UINT16)MIN(left + rect->x, dstWidth);
+ clippingRect.top = (UINT16)MIN(top + rect->y, dstHeight);
+ clippingRect.right = (UINT16)MIN(clippingRect.left + rect->width, dstWidth);
+ clippingRect.bottom = (UINT16)MIN(clippingRect.top + rect->height, dstHeight);
+ region16_union_rect(&clippingRects, &clippingRects, &clippingRect);
+ }
+
+ for (UINT32 i = 0; i < message->numTiles; i++)
+ {
+ RECTANGLE_16 updateRect = { 0 };
+ const RFX_TILE* tile = rfx_message_get_tile(message, i);
+
+ WINPR_ASSERT(left + tile->x <= UINT16_MAX);
+ WINPR_ASSERT(top + tile->y <= UINT16_MAX);
+
+ updateRect.left = (UINT16)left + tile->x;
+ updateRect.top = (UINT16)top + tile->y;
+ updateRect.right = updateRect.left + 64;
+ updateRect.bottom = updateRect.top + 64;
+ region16_init(&updateRegion);
+ region16_intersect_rect(&updateRegion, &clippingRects, &updateRect);
+ updateRects = region16_rects(&updateRegion, &nbUpdateRects);
+
+ for (UINT32 j = 0; j < nbUpdateRects; j++)
+ {
+ const UINT32 stride = 64 * formatSize;
+ const UINT32 nXDst = updateRects[j].left;
+ const UINT32 nYDst = updateRects[j].top;
+ const UINT32 nXSrc = nXDst - updateRect.left;
+ const UINT32 nYSrc = nYDst - updateRect.top;
+ const UINT32 nWidth = updateRects[j].right - updateRects[j].left;
+ const UINT32 nHeight = updateRects[j].bottom - updateRects[j].top;
+
+ if (!freerdp_image_copy(dst, dstFormat, dstStride, nXDst, nYDst, nWidth, nHeight,
+ tile->data, context->pixel_format, stride, nXSrc, nYSrc,
+ NULL, FREERDP_FLIP_NONE))
+ {
+ region16_uninit(&updateRegion);
+ WLog_Print(context->priv->log, WLOG_ERROR,
+ "nbUpdateRectx[%" PRIu32 " (%" PRIu32 ")] freerdp_image_copy failed",
+ j, nbUpdateRects);
+ return FALSE;
+ }
+
+ if (invalidRegion)
+ region16_union_rect(invalidRegion, invalidRegion, &updateRects[j]);
+ }
+
+ region16_uninit(&updateRegion);
+ }
+
+ region16_uninit(&clippingRects);
+ return TRUE;
+ }
+ else
+ {
+ rfx_message_free(context, message);
+ context->currentMessage.freeArray = TRUE;
+ }
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed");
+ return FALSE;
+}
+
+const UINT32* rfx_message_get_quants(const RFX_MESSAGE* message, UINT16* numQuantVals)
+{
+ WINPR_ASSERT(message);
+ if (numQuantVals)
+ *numQuantVals = message->numQuant;
+ return message->quantVals;
+}
+
+const RFX_TILE** rfx_message_get_tiles(const RFX_MESSAGE* message, UINT16* numTiles)
+{
+ WINPR_ASSERT(message);
+ if (numTiles)
+ *numTiles = message->numTiles;
+ return message->tiles;
+}
+
+UINT16 rfx_message_get_tile_count(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->numTiles;
+}
+
+const RFX_RECT* rfx_message_get_rects(const RFX_MESSAGE* message, UINT16* numRects)
+{
+ WINPR_ASSERT(message);
+ if (numRects)
+ *numRects = message->numRects;
+ return message->rects;
+}
+
+UINT16 rfx_message_get_rect_count(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->numRects;
+}
+
+void rfx_message_free(RFX_CONTEXT* context, RFX_MESSAGE* message)
+{
+ if (!message)
+ return;
+
+ winpr_aligned_free(message->rects);
+
+ if (message->tiles)
+ {
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ if (!tile)
+ continue;
+
+ if (tile->YCbCrData)
+ {
+ BufferPool_Return(context->priv->BufferPool, tile->YCbCrData);
+ tile->YCbCrData = NULL;
+ }
+
+ ObjectPool_Return(context->priv->TilePool, (void*)tile);
+ }
+
+ rfx_allocate_tiles(message, 0, FALSE);
+ }
+
+ const BOOL freeArray = message->freeArray;
+ const RFX_MESSAGE empty = { 0 };
+ *message = empty;
+
+ if (!freeArray)
+ winpr_aligned_free(message);
+}
+
+static void rfx_update_context_properties(RFX_CONTEXT* context)
+{
+ UINT16 properties = 0;
+
+ WINPR_ASSERT(context);
+ /* properties in tilesets: note that this has different format from the one in TS_RFX_CONTEXT */
+ properties = 1; /* lt */
+ properties |= (context->flags << 1); /* flags */
+ properties |= (COL_CONV_ICT << 4); /* cct */
+ properties |= (CLW_XFORM_DWT_53_A << 6); /* xft */
+ properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 10); /* et */
+ properties |= (SCALAR_QUANTIZATION << 14); /* qt */
+ context->properties = properties;
+}
+
+static void rfx_write_message_sync(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_SYNC); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT32(s, WF_MAGIC); /* magic (4 bytes) */
+ Stream_Write_UINT16(s, WF_VERSION_1_0); /* version (2 bytes) */
+}
+
+static void rfx_write_message_codec_versions(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CODEC_VERSIONS); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 10); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* numCodecs (1 byte) */
+ Stream_Write_UINT8(s, 1); /* codecs.codecId (1 byte) */
+ Stream_Write_UINT16(s, WF_VERSION_1_0); /* codecs.version (2 bytes) */
+}
+
+static void rfx_write_message_channels(const RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CHANNELS); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* numChannels (1 byte) */
+ Stream_Write_UINT8(s, 0); /* Channel.channelId (1 byte) */
+ Stream_Write_UINT16(s, context->width); /* Channel.width (2 bytes) */
+ Stream_Write_UINT16(s, context->height); /* Channel.height (2 bytes) */
+}
+
+static void rfx_write_message_context(RFX_CONTEXT* context, wStream* s)
+{
+ UINT16 properties = 0;
+ WINPR_ASSERT(context);
+
+ Stream_Write_UINT16(s, WBT_CONTEXT); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, 13); /* CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0xFF); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */
+ Stream_Write_UINT16(s, CT_TILE_64x64); /* tileSize (2 bytes) */
+ /* properties */
+ properties = context->flags; /* flags */
+ properties |= (COL_CONV_ICT << 3); /* cct */
+ properties |= (CLW_XFORM_DWT_53_A << 5); /* xft */
+ properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 9); /* et */
+ properties |= (SCALAR_QUANTIZATION << 13); /* qt */
+ Stream_Write_UINT16(s, properties); /* properties (2 bytes) */
+ rfx_update_context_properties(context);
+}
+
+static BOOL rfx_compose_message_header(RFX_CONTEXT* context, wStream* s)
+{
+ WINPR_ASSERT(context);
+ if (!Stream_EnsureRemainingCapacity(s, 12 + 10 + 12 + 13))
+ return FALSE;
+
+ rfx_write_message_sync(context, s);
+ rfx_write_message_context(context, s);
+ rfx_write_message_codec_versions(context, s);
+ rfx_write_message_channels(context, s);
+ return TRUE;
+}
+
+static size_t rfx_tile_length(const RFX_TILE* tile)
+{
+ WINPR_ASSERT(tile);
+ return 19ull + tile->YLen + tile->CbLen + tile->CrLen;
+}
+
+static BOOL rfx_write_tile(wStream* s, const RFX_TILE* tile)
+{
+ const size_t blockLen = rfx_tile_length(tile);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, CBT_TILE); /* BlockT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* BlockT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_Write(s, tile->YData, tile->YLen); /* YData */
+ Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */
+ Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */
+ return TRUE;
+}
+
+struct S_RFX_TILE_COMPOSE_WORK_PARAM
+{
+ RFX_TILE* tile;
+ RFX_CONTEXT* context;
+};
+
+static void CALLBACK rfx_compose_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance,
+ void* context, PTP_WORK work)
+{
+ RFX_TILE_COMPOSE_WORK_PARAM* param = (RFX_TILE_COMPOSE_WORK_PARAM*)context;
+ WINPR_ASSERT(param);
+ rfx_encode_rgb(param->context, param->tile);
+}
+
+static BOOL computeRegion(const RFX_RECT* rects, size_t numRects, REGION16* region, size_t width,
+ size_t height)
+{
+ const RECTANGLE_16 mainRect = { 0, 0, width, height };
+
+ WINPR_ASSERT(rects);
+ for (size_t i = 0; i < numRects; i++)
+ {
+ const RFX_RECT* rect = &rects[i];
+ RECTANGLE_16 rect16 = { 0 };
+ rect16.left = rect->x;
+ rect16.top = rect->y;
+ rect16.right = rect->x + rect->width;
+ rect16.bottom = rect->y + rect->height;
+
+ if (!region16_union_rect(region, region, &rect16))
+ return FALSE;
+ }
+
+ return region16_intersect_rect(region, region, &mainRect);
+}
+
+#define TILE_NO(v) ((v) / 64)
+
+static BOOL setupWorkers(RFX_CONTEXT* context, size_t nbTiles)
+{
+ WINPR_ASSERT(context);
+
+ RFX_CONTEXT_PRIV* priv = context->priv;
+ WINPR_ASSERT(priv);
+
+ void* pmem = NULL;
+
+ if (!context->priv->UseThreads)
+ return TRUE;
+
+ if (!(pmem = winpr_aligned_recalloc(priv->workObjects, nbTiles, sizeof(PTP_WORK), 32)))
+ return FALSE;
+
+ priv->workObjects = (PTP_WORK*)pmem;
+
+ if (!(pmem = winpr_aligned_recalloc(priv->tileWorkParams, nbTiles,
+ sizeof(RFX_TILE_COMPOSE_WORK_PARAM), 32)))
+ return FALSE;
+
+ priv->tileWorkParams = (RFX_TILE_COMPOSE_WORK_PARAM*)pmem;
+ return TRUE;
+}
+
+static BOOL rfx_ensure_tiles(RFX_MESSAGE* message, size_t count)
+{
+ WINPR_ASSERT(message);
+
+ if (message->numTiles + count <= message->allocatedTiles)
+ return TRUE;
+
+ const size_t alloc = MAX(message->allocatedTiles + 1024, message->numTiles + count);
+ return rfx_allocate_tiles(message, alloc, TRUE);
+}
+
+RFX_MESSAGE* rfx_encode_message(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 w, UINT32 h, size_t s)
+{
+ const UINT32 width = (UINT32)w;
+ const UINT32 height = (UINT32)h;
+ const UINT32 scanline = (UINT32)s;
+ RFX_MESSAGE* message = NULL;
+ PTP_WORK* workObject = NULL;
+ RFX_TILE_COMPOSE_WORK_PARAM* workParam = NULL;
+ BOOL success = FALSE;
+ REGION16 rectsRegion = { 0 };
+ REGION16 tilesRegion = { 0 };
+ RECTANGLE_16 currentTileRect = { 0 };
+ const RECTANGLE_16* regionRect = NULL;
+
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(rects);
+ WINPR_ASSERT(numRects > 0);
+ WINPR_ASSERT(w > 0);
+ WINPR_ASSERT(h > 0);
+ WINPR_ASSERT(s > 0);
+
+ if (!(message = (RFX_MESSAGE*)winpr_aligned_calloc(1, sizeof(RFX_MESSAGE), 32)))
+ return NULL;
+
+ region16_init(&tilesRegion);
+ region16_init(&rectsRegion);
+
+ if (context->state == RFX_STATE_SEND_HEADERS)
+ rfx_update_context_properties(context);
+
+ message->frameIdx = context->frameIdx++;
+
+ if (!context->numQuant)
+ {
+ WINPR_ASSERT(context->quants == NULL);
+ if (!(context->quants =
+ (UINT32*)winpr_aligned_malloc(sizeof(rfx_default_quantization_values), 32)))
+ goto skip_encoding_loop;
+
+ CopyMemory(context->quants, &rfx_default_quantization_values,
+ sizeof(rfx_default_quantization_values));
+ context->numQuant = 1;
+ context->quantIdxY = 0;
+ context->quantIdxCb = 0;
+ context->quantIdxCr = 0;
+ }
+
+ message->numQuant = context->numQuant;
+ message->quantVals = context->quants;
+ const UINT32 bytesPerPixel = (context->bits_per_pixel / 8);
+
+ if (!computeRegion(rects, numRects, &rectsRegion, width, height))
+ goto skip_encoding_loop;
+
+ const RECTANGLE_16* extents = region16_extents(&rectsRegion);
+ WINPR_ASSERT((INT32)extents->right - extents->left > 0);
+ WINPR_ASSERT((INT32)extents->bottom - extents->top > 0);
+ const UINT32 maxTilesX = 1 + TILE_NO(extents->right - 1) - TILE_NO(extents->left);
+ const UINT32 maxTilesY = 1 + TILE_NO(extents->bottom - 1) - TILE_NO(extents->top);
+ const UINT32 maxNbTiles = maxTilesX * maxTilesY;
+
+ if (!rfx_ensure_tiles(message, maxNbTiles))
+ goto skip_encoding_loop;
+
+ if (!setupWorkers(context, maxNbTiles))
+ goto skip_encoding_loop;
+
+ if (context->priv->UseThreads)
+ {
+ workObject = context->priv->workObjects;
+ workParam = context->priv->tileWorkParams;
+ }
+
+ UINT32 regionNbRects = 0;
+ regionRect = region16_rects(&rectsRegion, &regionNbRects);
+
+ if (!(message->rects = winpr_aligned_calloc(regionNbRects, sizeof(RFX_RECT), 32)))
+ goto skip_encoding_loop;
+
+ message->numRects = regionNbRects;
+
+ for (UINT32 i = 0; i < regionNbRects; i++, regionRect++)
+ {
+ RFX_RECT* rfxRect = &message->rects[i];
+ UINT32 startTileX = regionRect->left / 64;
+ UINT32 endTileX = (regionRect->right - 1) / 64;
+ UINT32 startTileY = regionRect->top / 64;
+ UINT32 endTileY = (regionRect->bottom - 1) / 64;
+ rfxRect->x = regionRect->left;
+ rfxRect->y = regionRect->top;
+ rfxRect->width = (regionRect->right - regionRect->left);
+ rfxRect->height = (regionRect->bottom - regionRect->top);
+
+ for (UINT32 yIdx = startTileY, gridRelY = startTileY * 64; yIdx <= endTileY;
+ yIdx++, gridRelY += 64)
+ {
+ UINT32 tileHeight = 64;
+
+ if ((yIdx == endTileY) && (gridRelY + 64 > height))
+ tileHeight = height - gridRelY;
+
+ currentTileRect.top = gridRelY;
+ currentTileRect.bottom = gridRelY + tileHeight;
+
+ for (UINT32 xIdx = startTileX, gridRelX = startTileX * 64; xIdx <= endTileX;
+ xIdx++, gridRelX += 64)
+ {
+ union
+ {
+ const BYTE* cpv;
+ BYTE* pv;
+ } cnv;
+ int tileWidth = 64;
+
+ if ((xIdx == endTileX) && (gridRelX + 64 > width))
+ tileWidth = width - gridRelX;
+
+ currentTileRect.left = gridRelX;
+ currentTileRect.right = gridRelX + tileWidth;
+
+ /* checks if this tile is already treated */
+ if (region16_intersects_rect(&tilesRegion, &currentTileRect))
+ continue;
+
+ RFX_TILE* tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool);
+ if (!tile)
+ goto skip_encoding_loop;
+
+ tile->xIdx = xIdx;
+ tile->yIdx = yIdx;
+ tile->x = gridRelX;
+ tile->y = gridRelY;
+ tile->scanline = scanline;
+ tile->width = tileWidth;
+ tile->height = tileHeight;
+ const UINT32 ax = gridRelX;
+ const UINT32 ay = gridRelY;
+
+ if (tile->data && tile->allocated)
+ {
+ winpr_aligned_free(tile->data);
+ tile->allocated = FALSE;
+ }
+
+ /* Cast away const */
+ cnv.cpv = &data[(ay * scanline) + (ax * bytesPerPixel)];
+ tile->data = cnv.pv;
+ tile->quantIdxY = context->quantIdxY;
+ tile->quantIdxCb = context->quantIdxCb;
+ tile->quantIdxCr = context->quantIdxCr;
+ tile->YLen = tile->CbLen = tile->CrLen = 0;
+
+ if (!(tile->YCbCrData = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1)))
+ goto skip_encoding_loop;
+
+ tile->YData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 0) + 16]);
+ tile->CbData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 1) + 16]);
+ tile->CrData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 2) + 16]);
+
+ if (!rfx_ensure_tiles(message, 1))
+ goto skip_encoding_loop;
+ message->tiles[message->numTiles++] = tile;
+
+ if (context->priv->UseThreads)
+ {
+ workParam->context = context;
+ workParam->tile = tile;
+
+ if (!(*workObject = CreateThreadpoolWork(rfx_compose_message_tile_work_callback,
+ (void*)workParam,
+ &context->priv->ThreadPoolEnv)))
+ {
+ goto skip_encoding_loop;
+ }
+
+ SubmitThreadpoolWork(*workObject);
+ workObject++;
+ workParam++;
+ }
+ else
+ {
+ rfx_encode_rgb(context, tile);
+ }
+
+ if (!region16_union_rect(&tilesRegion, &tilesRegion, &currentTileRect))
+ goto skip_encoding_loop;
+ } /* xIdx */
+ } /* yIdx */
+ } /* rects */
+
+ success = TRUE;
+skip_encoding_loop:
+
+ /* when using threads ensure all computations are done */
+ if (success)
+ {
+ message->tilesDataSize = 0;
+ workObject = context->priv->workObjects;
+
+ for (UINT32 i = 0; i < message->numTiles; i++)
+ {
+ if (context->priv->UseThreads)
+ {
+ if (*workObject)
+ {
+ WaitForThreadpoolWorkCallbacks(*workObject, FALSE);
+ CloseThreadpoolWork(*workObject);
+ }
+
+ workObject++;
+ }
+
+ const RFX_TILE* tile = message->tiles[i];
+ message->tilesDataSize += rfx_tile_length(tile);
+ }
+
+ region16_uninit(&tilesRegion);
+ region16_uninit(&rectsRegion);
+
+ return message;
+ }
+
+ WLog_Print(context->priv->log, WLOG_ERROR, "failed");
+
+ rfx_message_free(context, message);
+ return NULL;
+}
+
+static BOOL rfx_clone_rects(RFX_MESSAGE* dst, const RFX_MESSAGE* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ WINPR_ASSERT(dst->rects == NULL);
+ WINPR_ASSERT(dst->numRects == 0);
+
+ if (src->numRects == 0)
+ return TRUE;
+
+ dst->rects = winpr_aligned_calloc(src->numRects, sizeof(RECTANGLE_16), 32);
+ if (!dst->rects)
+ return FALSE;
+ dst->numRects = src->numRects;
+ for (size_t x = 0; x < src->numRects; x++)
+ {
+ dst->rects[x] = src->rects[x];
+ }
+ return TRUE;
+}
+
+static BOOL rfx_clone_quants(RFX_MESSAGE* dst, const RFX_MESSAGE* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ WINPR_ASSERT(dst->quantVals == NULL);
+ WINPR_ASSERT(dst->numQuant == 0);
+
+ if (src->numQuant == 0)
+ return TRUE;
+
+ /* quantVals are part of context */
+ dst->quantVals = src->quantVals;
+ dst->numQuant = src->numQuant;
+
+ return TRUE;
+}
+
+static RFX_MESSAGE* rfx_split_message(RFX_CONTEXT* context, RFX_MESSAGE* message,
+ size_t* numMessages, size_t maxDataSize)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(numMessages);
+
+ maxDataSize -= 1024; /* reserve enough space for headers */
+ *numMessages = ((message->tilesDataSize + maxDataSize) / maxDataSize) * 4ull;
+
+ RFX_MESSAGE* messages =
+ (RFX_MESSAGE*)winpr_aligned_calloc((*numMessages), sizeof(RFX_MESSAGE), 32);
+ if (!messages)
+ return NULL;
+
+ size_t j = 0;
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ RFX_MESSAGE* msg = &messages[j];
+
+ WINPR_ASSERT(tile);
+ WINPR_ASSERT(msg);
+
+ const size_t tileDataSize = rfx_tile_length(tile);
+
+ if ((msg->tilesDataSize + tileDataSize) > ((UINT32)maxDataSize))
+ j++;
+
+ if (msg->numTiles == 0)
+ {
+ msg->frameIdx = message->frameIdx + j;
+ if (!rfx_clone_quants(msg, message))
+ goto free_messages;
+ if (!rfx_clone_rects(msg, message))
+ goto free_messages;
+ msg->freeArray = TRUE;
+ if (!rfx_allocate_tiles(msg, message->numTiles, TRUE))
+ goto free_messages;
+ }
+
+ msg->tilesDataSize += tileDataSize;
+
+ WINPR_ASSERT(msg->numTiles < msg->allocatedTiles);
+ msg->tiles[msg->numTiles++] = message->tiles[i];
+ message->tiles[i] = NULL;
+ }
+
+ *numMessages = j + 1;
+ context->frameIdx += j;
+ message->numTiles = 0;
+ return messages;
+free_messages:
+
+ for (size_t i = 0; i < j; i++)
+ rfx_allocate_tiles(&messages[i], 0, FALSE);
+
+ winpr_aligned_free(messages);
+ return NULL;
+}
+
+const RFX_MESSAGE* rfx_message_list_get(const RFX_MESSAGE_LIST* messages, size_t idx)
+{
+ WINPR_ASSERT(messages);
+ if (idx >= messages->count)
+ return NULL;
+ WINPR_ASSERT(messages->list);
+ return &messages->list[idx];
+}
+
+void rfx_message_list_free(RFX_MESSAGE_LIST* messages)
+{
+ if (!messages)
+ return;
+ for (size_t x = 0; x < messages->count; x++)
+ rfx_message_free(messages->context, &messages->list[x]);
+ free(messages);
+}
+
+static RFX_MESSAGE_LIST* rfx_message_list_new(RFX_CONTEXT* context, RFX_MESSAGE* messages,
+ size_t count)
+{
+ WINPR_ASSERT(context);
+ RFX_MESSAGE_LIST* msg = calloc(1, sizeof(RFX_MESSAGE_LIST));
+ WINPR_ASSERT(msg);
+
+ msg->context = context;
+ msg->count = count;
+ msg->list = messages;
+ return msg;
+}
+
+RFX_MESSAGE_LIST* rfx_encode_messages(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 width, UINT32 height,
+ UINT32 scanline, size_t* numMessages, size_t maxDataSize)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(numMessages);
+
+ RFX_MESSAGE* message =
+ rfx_encode_message(context, rects, numRects, data, width, height, scanline);
+ if (!message)
+ return NULL;
+
+ RFX_MESSAGE* list = rfx_split_message(context, message, numMessages, maxDataSize);
+ rfx_message_free(context, message);
+ if (!list)
+ return NULL;
+
+ return rfx_message_list_new(context, list, *numMessages);
+}
+
+static BOOL rfx_write_message_tileset(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ const UINT32 blockLen = 22 + (message->numQuant * 5) + message->tilesDataSize;
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_EXTENSION); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT16(s, CBT_TILESET); /* subtype (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* idx (2 bytes) */
+ Stream_Write_UINT16(s, context->properties); /* properties (2 bytes) */
+ Stream_Write_UINT8(s, message->numQuant); /* numQuant (1 byte) */
+ Stream_Write_UINT8(s, 0x40); /* tileSize (1 byte) */
+ Stream_Write_UINT16(s, message->numTiles); /* numTiles (2 bytes) */
+ Stream_Write_UINT32(s, message->tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ UINT32* quantVals = message->quantVals;
+ for (size_t i = 0; i < message->numQuant * 5ul; i++)
+ {
+ WINPR_ASSERT(quantVals);
+ Stream_Write_UINT8(s, quantVals[0] + (quantVals[1] << 4));
+ quantVals += 2;
+ }
+
+ for (size_t i = 0; i < message->numTiles; i++)
+ {
+ RFX_TILE* tile = message->tiles[i];
+ if (!tile)
+ return FALSE;
+
+ if (!rfx_write_tile(s, tile))
+ return FALSE;
+ }
+
+#ifdef WITH_DEBUG_RFX
+ WLog_Print(context->priv->log, WLOG_DEBUG,
+ "numQuant: %" PRIu16 " numTiles: %" PRIu16 " tilesDataSize: %" PRIu32 "",
+ message->numQuant, message->numTiles, message->tilesDataSize);
+#endif
+ return TRUE;
+}
+
+static BOOL rfx_write_message_frame_begin(RFX_CONTEXT* context, wStream* s,
+ const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (!Stream_EnsureRemainingCapacity(s, 14))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_FRAME_BEGIN); /* CodecChannelT.blockType */
+ Stream_Write_UINT32(s, 14); /* CodecChannelT.blockLen */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */
+ Stream_Write_UINT32(s, message->frameIdx); /* frameIdx */
+ Stream_Write_UINT16(s, 1); /* numRegions */
+ return TRUE;
+}
+
+static BOOL rfx_write_message_region(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ const size_t blockLen = 15 + (message->numRects * 8);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_REGION); /* CodecChannelT.blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */
+ Stream_Write_UINT8(s, 1); /* regionFlags (1 byte) */
+ Stream_Write_UINT16(s, message->numRects); /* numRects (2 bytes) */
+
+ for (size_t i = 0; i < message->numRects; i++)
+ {
+ const RFX_RECT* rect = rfx_message_get_rect_const(message, i);
+ WINPR_ASSERT(rect);
+
+ /* Clipping rectangles are relative to destLeft, destTop */
+ Stream_Write_UINT16(s, rect->x); /* x (2 bytes) */
+ Stream_Write_UINT16(s, rect->y); /* y (2 bytes) */
+ Stream_Write_UINT16(s, rect->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, rect->height); /* height (2 bytes) */
+ }
+
+ Stream_Write_UINT16(s, CBT_REGION); /* regionType (2 bytes) */
+ Stream_Write_UINT16(s, 1); /* numTilesets (2 bytes) */
+ return TRUE;
+}
+
+static BOOL rfx_write_message_frame_end(RFX_CONTEXT* context, wStream* s,
+ const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (!Stream_EnsureRemainingCapacity(s, 8))
+ return FALSE;
+
+ Stream_Write_UINT16(s, WBT_FRAME_END); /* CodecChannelT.blockType */
+ Stream_Write_UINT32(s, 8); /* CodecChannelT.blockLen */
+ Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */
+ Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */
+ return TRUE;
+}
+
+BOOL rfx_write_message(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(message);
+
+ if (context->state == RFX_STATE_SEND_HEADERS)
+ {
+ if (!rfx_compose_message_header(context, s))
+ return FALSE;
+
+ context->state = RFX_STATE_SEND_FRAME_DATA;
+ }
+
+ if (!rfx_write_message_frame_begin(context, s, message) ||
+ !rfx_write_message_region(context, s, message) ||
+ !rfx_write_message_tileset(context, s, message) ||
+ !rfx_write_message_frame_end(context, s, message))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL rfx_compose_message(RFX_CONTEXT* context, wStream* s, const RFX_RECT* rects, size_t numRects,
+ const BYTE* data, UINT32 width, UINT32 height, UINT32 scanline)
+{
+ WINPR_ASSERT(context);
+ RFX_MESSAGE* message =
+ rfx_encode_message(context, rects, numRects, data, width, height, scanline);
+ if (!message)
+ return FALSE;
+
+ const BOOL ret = rfx_write_message(context, s, message);
+ rfx_message_free(context, message);
+ return ret;
+}
+
+BOOL rfx_context_set_mode(RFX_CONTEXT* context, RLGR_MODE mode)
+{
+ WINPR_ASSERT(context);
+ context->mode = mode;
+ return TRUE;
+}
+
+RLGR_MODE rfx_context_get_mode(RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->mode;
+}
+
+UINT32 rfx_context_get_frame_idx(const RFX_CONTEXT* context)
+{
+ WINPR_ASSERT(context);
+ return context->frameIdx;
+}
+
+UINT32 rfx_message_get_frame_idx(const RFX_MESSAGE* message)
+{
+ WINPR_ASSERT(message);
+ return message->frameIdx;
+}
+
+static INLINE BOOL rfx_write_progressive_wb_sync(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 12;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_SYNC); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT32(s, 0xCACCACCA); /* magic (4 bytes) */
+ Stream_Write_UINT16(s, 0x0100); /* version (2 bytes) */
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_wb_context(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 10;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_CONTEXT); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */
+ Stream_Write_UINT16(s, 64); /* tileSize (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_region(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ /* RFX_REGION */
+ UINT32 blockLen = 18;
+ UINT32 tilesDataSize = 0;
+ const size_t start = Stream_GetPosition(s);
+
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+
+ blockLen += msg->numRects * 8;
+ blockLen += msg->numQuant * 5;
+ tilesDataSize = msg->numTiles * 22UL;
+ for (UINT16 i = 0; i < msg->numTiles; i++)
+ {
+ const RFX_TILE* tile = msg->tiles[i];
+ WINPR_ASSERT(tile);
+ tilesDataSize += tile->YLen + tile->CbLen + tile->CrLen;
+ }
+ blockLen += tilesDataSize;
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_REGION); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, 64); /* tileSize (1 byte) */
+ Stream_Write_UINT16(s, msg->numRects); /* numRects (2 bytes) */
+ WINPR_ASSERT(msg->numQuant <= UINT8_MAX);
+ Stream_Write_UINT8(s, (UINT8)msg->numQuant); /* numQuant (1 byte) */
+ Stream_Write_UINT8(s, 0); /* numProgQuant (1 byte) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ Stream_Write_UINT16(s, msg->numTiles); /* numTiles (2 bytes) */
+ Stream_Write_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */
+
+ for (UINT16 i = 0; i < msg->numRects; i++)
+ {
+ /* TS_RFX_RECT */
+ const RFX_RECT* r = &msg->rects[i];
+ Stream_Write_UINT16(s, r->x); /* x (2 bytes) */
+ Stream_Write_UINT16(s, r->y); /* y (2 bytes) */
+ Stream_Write_UINT16(s, r->width); /* width (2 bytes) */
+ Stream_Write_UINT16(s, r->height); /* height (2 bytes) */
+ }
+
+ /**
+ * Note: The RFX_COMPONENT_CODEC_QUANT structure differs from the
+ * TS_RFX_CODEC_QUANT ([MS-RDPRFX] section 2.2.2.1.5) structure with respect
+ * to the order of the bands.
+ * 0 1 2 3 4 5 6 7 8 9
+ * RDPRFX: LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1
+ * RDPEGFX: LL3, HL3, LH3, HH3, HL2, LH2, HH2, HL1, LH1, HH1
+ */
+ for (UINT16 i = 0; i < msg->numQuant; i++)
+ {
+ const UINT32* qv = &msg->quantVals[i * 10];
+ /* RFX_COMPONENT_CODEC_QUANT */
+ Stream_Write_UINT8(s, (UINT8)(qv[0] + (qv[2] << 4))); /* LL3 (4-bit), HL3 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[1] + (qv[3] << 4))); /* LH3 (4-bit), HH3 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[5] + (qv[4] << 4))); /* HL2 (4-bit), LH2 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[6] + (qv[8] << 4))); /* HH2 (4-bit), HL1 (4-bit) */
+ Stream_Write_UINT8(s, (UINT8)(qv[7] + (qv[9] << 4))); /* LH1 (4-bit), HH1 (4-bit) */
+ }
+
+ for (UINT16 i = 0; i < msg->numTiles; i++)
+ {
+ const RFX_TILE* tile = msg->tiles[i];
+ if (!rfx_write_progressive_tile_simple(rfx, s, tile))
+ return FALSE;
+ }
+
+ const size_t end = Stream_GetPosition(s);
+ const size_t used = end - start;
+ return (used == blockLen);
+}
+
+static INLINE BOOL rfx_write_progressive_frame_begin(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_MESSAGE* msg)
+{
+ const UINT32 blockLen = 12;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_BEGIN); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT32(s, msg->frameIdx); /* frameIndex (4 bytes) */
+ Stream_Write_UINT16(s, 1); /* regionCount (2 bytes) */
+
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_frame_end(RFX_CONTEXT* rfx, wStream* s)
+{
+ const UINT32 blockLen = 6;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_END); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+
+ return TRUE;
+}
+
+static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s,
+ const RFX_TILE* tile)
+{
+ UINT32 blockLen = 0;
+ WINPR_ASSERT(rfx);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(tile);
+
+ blockLen = 22 + tile->YLen + tile->CbLen + tile->CrLen;
+ if (!Stream_EnsureRemainingCapacity(s, blockLen))
+ return FALSE;
+
+ Stream_Write_UINT16(s, PROGRESSIVE_WBT_TILE_SIMPLE); /* blockType (2 bytes) */
+ Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */
+ Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */
+ Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */
+ Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */
+ Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */
+ Stream_Write_UINT8(s, 0); /* flags (1 byte) */
+ Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */
+ Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* tailLen (2 bytes) */
+ Stream_Write(s, tile->YData, tile->YLen); /* YData */
+ Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */
+ Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */
+
+ return TRUE;
+}
+
+const char* rfx_get_progressive_block_type_string(UINT16 blockType)
+{
+ switch (blockType)
+ {
+ case PROGRESSIVE_WBT_SYNC:
+ return "PROGRESSIVE_WBT_SYNC";
+
+ case PROGRESSIVE_WBT_FRAME_BEGIN:
+ return "PROGRESSIVE_WBT_FRAME_BEGIN";
+
+ case PROGRESSIVE_WBT_FRAME_END:
+ return "PROGRESSIVE_WBT_FRAME_END";
+
+ case PROGRESSIVE_WBT_CONTEXT:
+ return "PROGRESSIVE_WBT_CONTEXT";
+
+ case PROGRESSIVE_WBT_REGION:
+ return "PROGRESSIVE_WBT_REGION";
+
+ case PROGRESSIVE_WBT_TILE_SIMPLE:
+ return "PROGRESSIVE_WBT_TILE_SIMPLE";
+
+ case PROGRESSIVE_WBT_TILE_FIRST:
+ return "PROGRESSIVE_WBT_TILE_FIRST";
+
+ case PROGRESSIVE_WBT_TILE_UPGRADE:
+ return "PROGRESSIVE_WBT_TILE_UPGRADE";
+
+ default:
+ return "PROGRESSIVE_WBT_UNKNOWN";
+ }
+}
+
+BOOL rfx_write_message_progressive_simple(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* msg)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(msg);
+ WINPR_ASSERT(context);
+
+ if (context->mode != RLGR1)
+ {
+ WLog_ERR(TAG, "error, RLGR1 mode is required!");
+ return FALSE;
+ }
+
+ if (!rfx_write_progressive_wb_sync(context, s))
+ return FALSE;
+
+ if (!rfx_write_progressive_wb_context(context, s))
+ return FALSE;
+
+ if (!rfx_write_progressive_frame_begin(context, s, msg))
+ return FALSE;
+
+ if (!rfx_write_progressive_region(context, s, msg))
+ return FALSE;
+
+ if (!rfx_write_progressive_frame_end(context, s))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libfreerdp/codec/rfx_bitstream.h b/libfreerdp/codec/rfx_bitstream.h
new file mode 100644
index 0000000..3e84242
--- /dev/null
+++ b/libfreerdp/codec/rfx_bitstream.h
@@ -0,0 +1,107 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Bit Stream
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_BITSTREAM_H
+#define FREERDP_LIB_CODEC_RFX_BITSTREAM_H
+
+#include <freerdp/codec/rfx.h>
+
+typedef struct
+{
+ BYTE* buffer;
+ int nbytes;
+ int byte_pos;
+ int bits_left;
+} RFX_BITSTREAM;
+
+#define rfx_bitstream_attach(bs, _buffer, _nbytes) \
+ do \
+ { \
+ bs->buffer = (BYTE*)(_buffer); \
+ bs->nbytes = (_nbytes); \
+ bs->byte_pos = 0; \
+ bs->bits_left = 8; \
+ } while (0)
+
+#define rfx_bitstream_get_bits(bs, _nbits, _r) \
+ do \
+ { \
+ int nbits = _nbits; \
+ int b; \
+ UINT16 n = 0; \
+ while (bs->byte_pos < bs->nbytes && nbits > 0) \
+ { \
+ b = nbits; \
+ if (b > bs->bits_left) \
+ b = bs->bits_left; \
+ if (n) \
+ n <<= b; \
+ n |= (bs->buffer[bs->byte_pos] >> (bs->bits_left - b)) & ((1 << b) - 1); \
+ bs->bits_left -= b; \
+ nbits -= b; \
+ if (bs->bits_left == 0) \
+ { \
+ bs->bits_left = 8; \
+ bs->byte_pos++; \
+ } \
+ } \
+ _r = n; \
+ } while (0)
+
+#define rfx_bitstream_put_bits(bs, _bits, _nbits) \
+ do \
+ { \
+ UINT16 bits = (_bits); \
+ int nbits = (_nbits); \
+ int b; \
+ while (bs->byte_pos < bs->nbytes && nbits > 0) \
+ { \
+ b = nbits; \
+ if (b > bs->bits_left) \
+ b = bs->bits_left; \
+ bs->buffer[bs->byte_pos] |= ((bits >> (nbits - b)) & ((1 << b) - 1)) \
+ << (bs->bits_left - b); \
+ bs->bits_left -= b; \
+ nbits -= b; \
+ if (bs->bits_left == 0) \
+ { \
+ bs->bits_left = 8; \
+ bs->byte_pos++; \
+ } \
+ } \
+ } while (0)
+#define rfx_bitstream_flush(bs) \
+ do \
+ { \
+ if (bs->bits_left != 8) \
+ { \
+ int _nbits = 8 - bs->bits_left; \
+ rfx_bitstream_put_bits(bs, 0, _nbits); \
+ } \
+ } while (0)
+
+#define rfx_bitstream_eos(_bs) ((_bs)->byte_pos >= (_bs)->nbytes)
+#define rfx_bitstream_left(_bs) \
+ ((_bs)->byte_pos >= (_bs)->nbytes \
+ ? 0 \
+ : ((_bs)->nbytes - (_bs)->byte_pos - 1) * 8 + (_bs)->bits_left)
+#define rfx_bitstream_get_processed_bytes(_bs) \
+ ((_bs)->bits_left < 8 ? (_bs)->byte_pos + 1 : (_bs)->byte_pos)
+
+#endif /* FREERDP_LIB_CODEC_RFX_BITSTREAM_H */
diff --git a/libfreerdp/codec/rfx_constants.h b/libfreerdp/codec/rfx_constants.h
new file mode 100644
index 0000000..e8405a8
--- /dev/null
+++ b/libfreerdp/codec/rfx_constants.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - API Header
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_CONSTANTS_H
+#define FREERDP_LIB_CODEC_RFX_CONSTANTS_H
+
+#include <freerdp/api.h>
+
+/* sync */
+#define WF_MAGIC 0xCACCACCA
+#define WF_VERSION_1_0 0x0100
+
+/* blockType */
+#define WBT_SYNC 0xCCC0
+#define WBT_CODEC_VERSIONS 0xCCC1
+#define WBT_CHANNELS 0xCCC2
+#define WBT_CONTEXT 0xCCC3
+#define WBT_FRAME_BEGIN 0xCCC4
+#define WBT_FRAME_END 0xCCC5
+#define WBT_REGION 0xCCC6
+#define WBT_EXTENSION 0xCCC7
+#define CBT_REGION 0xCAC1
+#define CBT_TILESET 0xCAC2
+#define CBT_TILE 0xCAC3
+
+#define PROGRESSIVE_WBT_SYNC 0xCCC0
+#define PROGRESSIVE_WBT_FRAME_BEGIN 0xCCC1
+#define PROGRESSIVE_WBT_FRAME_END 0xCCC2
+#define PROGRESSIVE_WBT_CONTEXT 0xCCC3
+#define PROGRESSIVE_WBT_REGION 0xCCC4
+#define PROGRESSIVE_WBT_TILE_SIMPLE 0xCCC5
+#define PROGRESSIVE_WBT_TILE_FIRST 0xCCC6
+#define PROGRESSIVE_WBT_TILE_UPGRADE 0xCCC7
+
+/* tileSize */
+#define CT_TILE_64x64 0x0040
+
+/* properties.flags */
+#define CODEC_MODE 0x02
+
+/* properties.cct */
+#define COL_CONV_ICT 0x1
+
+/* properties.xft */
+#define CLW_XFORM_DWT_53_A 0x1
+
+/* properties.et */
+#define CLW_ENTROPY_RLGR1 0x01
+#define CLW_ENTROPY_RLGR3 0x04
+
+/* properties.qt */
+#define SCALAR_QUANTIZATION 0x1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL const char* rfx_get_progressive_block_type_string(UINT16 blockType);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_CONSTANTS_H */
diff --git a/libfreerdp/codec/rfx_decode.c b/libfreerdp/codec/rfx_decode.c
new file mode 100644
index 0000000..4aa3c3d
--- /dev/null
+++ b/libfreerdp/codec/rfx_decode.c
@@ -0,0 +1,102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 Norbert Federa <norbert.federa@thincast.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/stream.h>
+#include <freerdp/primitives.h>
+
+#include "rfx_types.h"
+#include "rfx_rlgr.h"
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+
+#include "rfx_decode.h"
+
+void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context,
+ const UINT32* WINPR_RESTRICT quantization_values,
+ const BYTE* WINPR_RESTRICT data, size_t size,
+ INT16* WINPR_RESTRICT buffer)
+{
+ INT16* dwt_buffer = NULL;
+ dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_decode_component)
+ PROFILER_ENTER(context->priv->prof_rfx_rlgr_decode)
+ context->rlgr_decode(context->mode, data, size, buffer, 4096);
+ PROFILER_EXIT(context->priv->prof_rfx_rlgr_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_differential_decode)
+ rfx_differential_decode(buffer + 4032, 64);
+ PROFILER_EXIT(context->priv->prof_rfx_differential_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_quantization_decode)
+ context->quantization_decode(buffer, quantization_values);
+ PROFILER_EXIT(context->priv->prof_rfx_quantization_decode)
+ PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_decode)
+ context->dwt_2d_decode(buffer, dwt_buffer);
+ PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_decode)
+ PROFILER_EXIT(context->priv->prof_rfx_decode_component)
+ BufferPool_Return(context->priv->BufferPool, dwt_buffer);
+}
+
+/* rfx_decode_ycbcr_to_rgb code now resides in the primitives library. */
+
+/* stride is bytes between rows in the output buffer. */
+BOOL rfx_decode_rgb(RFX_CONTEXT* context, const RFX_TILE* tile, BYTE* rgb_buffer, UINT32 stride)
+{
+ union
+ {
+ const INT16** cpv;
+ INT16** pv;
+ } cnv;
+ BOOL rc = TRUE;
+ BYTE* pBuffer = NULL;
+ INT16* pSrcDst[3];
+ UINT32* y_quants = NULL;
+ UINT32* cb_quants = NULL;
+ UINT32* cr_quants = NULL;
+ static const prim_size_t roi_64x64 = { 64, 64 };
+ const primitives_t* prims = primitives_get();
+ PROFILER_ENTER(context->priv->prof_rfx_decode_rgb)
+ y_quants = context->quants + (tile->quantIdxY * 10);
+ cb_quants = context->quants + (tile->quantIdxCb * 10);
+ cr_quants = context->quants + (tile->quantIdxCr * 10);
+ pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */
+ rfx_decode_component(context, y_quants, tile->YData, tile->YLen, pSrcDst[0]); /* YData */
+ rfx_decode_component(context, cb_quants, tile->CbData, tile->CbLen, pSrcDst[1]); /* CbData */
+ rfx_decode_component(context, cr_quants, tile->CrData, tile->CrLen, pSrcDst[2]); /* CrData */
+ PROFILER_ENTER(context->priv->prof_rfx_ycbcr_to_rgb)
+
+ cnv.pv = pSrcDst;
+ if (prims->yCbCrToRGB_16s8u_P3AC4R(cnv.cpv, 64 * sizeof(INT16), rgb_buffer, stride,
+ context->pixel_format, &roi_64x64) != PRIMITIVES_SUCCESS)
+ rc = FALSE;
+
+ PROFILER_EXIT(context->priv->prof_rfx_ycbcr_to_rgb)
+ PROFILER_EXIT(context->priv->prof_rfx_decode_rgb)
+ BufferPool_Return(context->priv->BufferPool, pBuffer);
+ return rc;
+}
diff --git a/libfreerdp/codec/rfx_decode.h b/libfreerdp/codec/rfx_decode.h
new file mode 100644
index 0000000..978b192
--- /dev/null
+++ b/libfreerdp/codec/rfx_decode.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Decode
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_DECODE_H
+#define FREERDP_LIB_CODEC_RFX_DECODE_H
+
+#include <winpr/wtypes.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+/* stride is bytes between rows in the output buffer. */
+FREERDP_LOCAL BOOL rfx_decode_rgb(RFX_CONTEXT* WINPR_RESTRICT context,
+ const RFX_TILE* WINPR_RESTRICT tile,
+ BYTE* WINPR_RESTRICT rgb_buffer, UINT32 stride);
+FREERDP_LOCAL void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context,
+ const UINT32* WINPR_RESTRICT quantization_values,
+ const BYTE* WINPR_RESTRICT data, size_t size,
+ INT16* WINPR_RESTRICT buffer);
+#endif /* FREERDP_LIB_CODEC_RFX_DECODE_H */
diff --git a/libfreerdp/codec/rfx_differential.h b/libfreerdp/codec/rfx_differential.h
new file mode 100644
index 0000000..70a1a8c
--- /dev/null
+++ b/libfreerdp/codec/rfx_differential.h
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Differential Encoding
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H
+#define FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+static INLINE void rfx_differential_decode(INT16* buffer, int size)
+{
+ INT16* ptr = buffer;
+ INT16* end = &buffer[size - 1];
+
+ while (ptr != end)
+ {
+ ptr[1] += ptr[0];
+ ptr++;
+ }
+}
+
+static INLINE void rfx_differential_encode(INT16* buffer, int size)
+{
+ INT16 n1 = buffer[0];
+ for (int x = 0; x < size - 1; x++)
+ {
+ INT16* dst = &buffer[x + 1];
+ const INT16 n2 = *dst;
+ *dst -= n1;
+ n1 = n2;
+ }
+}
+
+#endif /* FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H */
diff --git a/libfreerdp/codec/rfx_dwt.c b/libfreerdp/codec/rfx_dwt.c
new file mode 100644
index 0000000..e79e9da
--- /dev/null
+++ b/libfreerdp/codec/rfx_dwt.c
@@ -0,0 +1,218 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - DWT
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rfx_dwt.h"
+
+static void rfx_dwt_2d_decode_block(INT16* buffer, INT16* idwt, size_t subband_width)
+{
+ INT16* dst = NULL;
+ INT16* l = NULL;
+ INT16* h = NULL;
+ INT16* l_dst = NULL;
+ INT16* h_dst = NULL;
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+
+ const size_t total_width = subband_width << 1;
+
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+
+ for (size_t y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ l_dst[0] = ll[0] - ((hl[0] + hl[0] + 1) >> 1);
+ h_dst[0] = lh[0] - ((hh[0] + hh[0] + 1) >> 1);
+ for (size_t n = 1; n < subband_width; n++)
+ {
+ const size_t x = n << 1;
+ l_dst[x] = ll[n] - ((hl[n - 1] + hl[n] + 1) >> 1);
+ h_dst[x] = lh[n] - ((hh[n - 1] + hh[n] + 1) >> 1);
+ }
+
+ /* Odd coefficients */
+ size_t n = 0;
+ for (; n < subband_width - 1; n++)
+ {
+ const size_t x = n << 1;
+ l_dst[x + 1] = (hl[n] << 1) + ((l_dst[x] + l_dst[x + 2]) >> 1);
+ h_dst[x + 1] = (hh[n] << 1) + ((h_dst[x] + h_dst[x + 2]) >> 1);
+ }
+
+ const size_t x = n << 1;
+ l_dst[x + 1] = (hl[n] << 1) + (l_dst[x]);
+ h_dst[x + 1] = (hh[n] << 1) + (h_dst[x]);
+
+ ll += subband_width;
+ hl += subband_width;
+ l_dst += total_width;
+
+ lh += subband_width;
+ hh += subband_width;
+ h_dst += total_width;
+ }
+
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ for (size_t x = 0; x < total_width; x++)
+ {
+ l = idwt + x;
+ h = idwt + x + subband_width * total_width;
+ dst = buffer + x;
+
+ *dst = *l - ((*h * 2 + 1) >> 1);
+
+ for (size_t n = 1; n < subband_width; n++)
+ {
+ l += total_width;
+ h += total_width;
+
+ /* Even coefficients */
+ dst[2 * total_width] = *l - ((*(h - total_width) + *h + 1) >> 1);
+
+ /* Odd coefficients */
+ dst[total_width] = (*(h - total_width) << 1) + ((*dst + dst[2 * total_width]) >> 1);
+
+ dst += 2 * total_width;
+ }
+
+ dst[total_width] = (*h << 1) + ((*dst * 2) >> 1);
+ }
+}
+
+void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ rfx_dwt_2d_decode_block(&buffer[3840], dwt_buffer, 8);
+ rfx_dwt_2d_decode_block(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_decode_block(&buffer[0], dwt_buffer, 32);
+}
+
+static void rfx_dwt_2d_encode_block(INT16* buffer, INT16* dwt, UINT32 subband_width)
+{
+ INT16* src = NULL;
+ INT16* l = NULL;
+ INT16* h = NULL;
+ INT16* l_src = NULL;
+ INT16* h_src = NULL;
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+
+ const UINT32 total_width = subband_width << 1;
+
+ /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */
+ for (UINT32 x = 0; x < total_width; x++)
+ {
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 y = n << 1;
+ l = dwt + n * total_width + x;
+ h = l + subband_width * total_width;
+ src = buffer + y * total_width + x;
+
+ /* H */
+ *h = (src[total_width] -
+ ((src[0] + src[n < subband_width - 1 ? 2 * total_width : 0]) >> 1)) >>
+ 1;
+
+ /* L */
+ *l = src[0] + (n == 0 ? *h : (*(h - total_width) + *h) >> 1);
+ }
+ }
+
+ /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order,
+ * stored in original buffer. */
+ /* The lower part L generates LL(3) and HL(0). */
+ /* The higher part H generates LH(1) and HH(2). */
+
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_src = dwt;
+
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_src = dwt + subband_width * subband_width * 2;
+
+ for (UINT32 y = 0; y < subband_width; y++)
+ {
+ /* L */
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 x = n << 1;
+
+ /* HL */
+ hl[n] =
+ (l_src[x + 1] - ((l_src[x] + l_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1;
+ /* LL */
+ ll[n] = l_src[x] + (n == 0 ? hl[n] : (hl[n - 1] + hl[n]) >> 1);
+ }
+
+ /* H */
+ for (UINT32 n = 0; n < subband_width; n++)
+ {
+ UINT32 x = n << 1;
+
+ /* HH */
+ hh[n] =
+ (h_src[x + 1] - ((h_src[x] + h_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1;
+ /* LH */
+ lh[n] = h_src[x] + (n == 0 ? hh[n] : (hh[n - 1] + hh[n]) >> 1);
+ }
+
+ ll += subband_width;
+ hl += subband_width;
+ l_src += total_width;
+
+ lh += subband_width;
+ hh += subband_width;
+ h_src += total_width;
+ }
+}
+
+void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ rfx_dwt_2d_encode_block(&buffer[0], dwt_buffer, 32);
+ rfx_dwt_2d_encode_block(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_encode_block(&buffer[3840], dwt_buffer, 8);
+}
diff --git a/libfreerdp/codec/rfx_dwt.h b/libfreerdp/codec/rfx_dwt.h
new file mode 100644
index 0000000..bd2104b
--- /dev/null
+++ b/libfreerdp/codec/rfx_dwt.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - DWT
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_DWT_H
+#define FREERDP_LIB_CODEC_RFX_DWT_H
+
+#include <winpr/wtypes.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer);
+FREERDP_LOCAL void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer);
+FREERDP_LOCAL void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* dwt_buffer);
+
+#endif /* FREERDP_LIB_CODEC_RFX_DWT_H */
diff --git a/libfreerdp/codec/rfx_encode.c b/libfreerdp/codec/rfx_encode.c
new file mode 100644
index 0000000..12bc77b
--- /dev/null
+++ b/libfreerdp/codec/rfx_encode.c
@@ -0,0 +1,313 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Encode
+ *
+ * Copyright 2011 Vic Lee
+ * Copyright 2011 Norbert Federa <norbert.federa@thincast.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/collections.h>
+
+#include <freerdp/primitives.h>
+
+#include "rfx_types.h"
+#include "rfx_rlgr.h"
+#include "rfx_differential.h"
+#include "rfx_quantization.h"
+#include "rfx_dwt.h"
+
+#include "rfx_encode.h"
+
+#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v)))
+
+static void rfx_encode_format_rgb(const BYTE* rgb_data, int width, int height, int rowstride,
+ UINT32 pixel_format, const BYTE* palette, INT16* r_buf,
+ INT16* g_buf, INT16* b_buf)
+{
+ int x_exceed = 0;
+ int y_exceed = 0;
+ const BYTE* src = NULL;
+ INT16 r = 0;
+ INT16 g = 0;
+ INT16 b = 0;
+ INT16* r_last = NULL;
+ INT16* g_last = NULL;
+ INT16* b_last = NULL;
+ x_exceed = 64 - width;
+ y_exceed = 64 - height;
+
+ for (int y = 0; y < height; y++)
+ {
+ src = rgb_data + y * rowstride;
+
+ switch (pixel_format)
+ {
+ case PIXEL_FORMAT_BGRX32:
+ case PIXEL_FORMAT_BGRA32:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ src++;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_XBGR32:
+ case PIXEL_FORMAT_ABGR32:
+ for (int x = 0; x < width; x++)
+ {
+ src++;
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGBX32:
+ case PIXEL_FORMAT_RGBA32:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ src++;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_XRGB32:
+ case PIXEL_FORMAT_ARGB32:
+ for (int x = 0; x < width; x++)
+ {
+ src++;
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_BGR24:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *r_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB24:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(*src++);
+ *g_buf++ = (INT16)(*src++);
+ *b_buf++ = (INT16)(*src++);
+ }
+
+ break;
+
+ case PIXEL_FORMAT_BGR16:
+ for (int x = 0; x < width; x++)
+ {
+ *b_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ *r_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ src += 2;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB16:
+ for (int x = 0; x < width; x++)
+ {
+ *r_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5));
+ *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3));
+ *b_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07));
+ src += 2;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_RGB8:
+ if (!palette)
+ break;
+
+ for (int x = 0; x < width; x++)
+ {
+ int shift = 0;
+ BYTE idx = 0;
+ shift = (7 - (x % 8));
+ idx = ((*src) >> shift) & 1;
+ idx |= (((*(src + 1)) >> shift) & 1) << 1;
+ idx |= (((*(src + 2)) >> shift) & 1) << 2;
+ idx |= (((*(src + 3)) >> shift) & 1) << 3;
+ idx *= 3;
+ *r_buf++ = (INT16)palette[idx];
+ *g_buf++ = (INT16)palette[idx + 1];
+ *b_buf++ = (INT16)palette[idx + 2];
+
+ if (shift == 0)
+ src += 4;
+ }
+
+ break;
+
+ case PIXEL_FORMAT_A4:
+ if (!palette)
+ break;
+
+ for (int x = 0; x < width; x++)
+ {
+ int idx = (*src) * 3;
+ *r_buf++ = (INT16)palette[idx];
+ *g_buf++ = (INT16)palette[idx + 1];
+ *b_buf++ = (INT16)palette[idx + 2];
+ src++;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Fill the horizontal region outside of 64x64 tile size with the right-most pixel for best
+ * quality */
+ if (x_exceed > 0)
+ {
+ r = *(r_buf - 1);
+ g = *(g_buf - 1);
+ b = *(b_buf - 1);
+
+ for (int x = 0; x < x_exceed; x++)
+ {
+ *r_buf++ = r;
+ *g_buf++ = g;
+ *b_buf++ = b;
+ }
+ }
+ }
+
+ /* Fill the vertical region outside of 64x64 tile size with the last line. */
+ if (y_exceed > 0)
+ {
+ r_last = r_buf - 64;
+ g_last = g_buf - 64;
+ b_last = b_buf - 64;
+
+ while (y_exceed > 0)
+ {
+ CopyMemory(r_buf, r_last, 64 * sizeof(INT16));
+ CopyMemory(g_buf, g_last, 64 * sizeof(INT16));
+ CopyMemory(b_buf, b_last, 64 * sizeof(INT16));
+ r_buf += 64;
+ g_buf += 64;
+ b_buf += 64;
+ y_exceed--;
+ }
+ }
+}
+
+/* rfx_encode_rgb_to_ycbcr code now resides in the primitives library. */
+
+static void rfx_encode_component(RFX_CONTEXT* context, const UINT32* quantization_values,
+ INT16* data, BYTE* buffer, int buffer_size, int* size)
+{
+ INT16* dwt_buffer = NULL;
+ dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_encode_component)
+ PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_encode)
+ context->dwt_2d_encode(data, dwt_buffer);
+ PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_quantization_encode)
+ context->quantization_encode(data, quantization_values);
+ PROFILER_EXIT(context->priv->prof_rfx_quantization_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_differential_encode)
+ rfx_differential_encode(data + 4032, 64);
+ PROFILER_EXIT(context->priv->prof_rfx_differential_encode)
+ PROFILER_ENTER(context->priv->prof_rfx_rlgr_encode)
+ *size = context->rlgr_encode(context->mode, data, 4096, buffer, buffer_size);
+ PROFILER_EXIT(context->priv->prof_rfx_rlgr_encode)
+ PROFILER_EXIT(context->priv->prof_rfx_encode_component)
+ BufferPool_Return(context->priv->BufferPool, dwt_buffer);
+}
+
+void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile)
+{
+ union
+ {
+ const INT16** cpv;
+ INT16** pv;
+ } cnv;
+ BYTE* pBuffer = NULL;
+ INT16* pSrcDst[3];
+ int YLen = 0;
+ int CbLen = 0;
+ int CrLen = 0;
+ UINT32* YQuant = NULL;
+ UINT32* CbQuant = NULL;
+ UINT32* CrQuant = NULL;
+ primitives_t* prims = primitives_get();
+ static const prim_size_t roi_64x64 = { 64, 64 };
+
+ if (!(pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1)))
+ return;
+
+ YLen = CbLen = CrLen = 0;
+ YQuant = context->quants + (tile->quantIdxY * 10);
+ CbQuant = context->quants + (tile->quantIdxCb * 10);
+ CrQuant = context->quants + (tile->quantIdxCr * 10);
+ pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */
+ pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */
+ pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */
+ PROFILER_ENTER(context->priv->prof_rfx_encode_rgb)
+ PROFILER_ENTER(context->priv->prof_rfx_encode_format_rgb)
+ rfx_encode_format_rgb(tile->data, tile->width, tile->height, tile->scanline,
+ context->pixel_format, context->palette, pSrcDst[0], pSrcDst[1],
+ pSrcDst[2]);
+ PROFILER_EXIT(context->priv->prof_rfx_encode_format_rgb)
+ PROFILER_ENTER(context->priv->prof_rfx_rgb_to_ycbcr)
+
+ cnv.pv = pSrcDst;
+ prims->RGBToYCbCr_16s16s_P3P3(cnv.cpv, 64 * sizeof(INT16), pSrcDst, 64 * sizeof(INT16),
+ &roi_64x64);
+ PROFILER_EXIT(context->priv->prof_rfx_rgb_to_ycbcr)
+ /**
+ * We need to clear the buffers as the RLGR encoder expects it to be initialized to zero.
+ * This allows simplifying and improving the performance of the encoding process.
+ */
+ ZeroMemory(tile->YData, 4096);
+ ZeroMemory(tile->CbData, 4096);
+ ZeroMemory(tile->CrData, 4096);
+ rfx_encode_component(context, YQuant, pSrcDst[0], tile->YData, 4096, &YLen);
+ rfx_encode_component(context, CbQuant, pSrcDst[1], tile->CbData, 4096, &CbLen);
+ rfx_encode_component(context, CrQuant, pSrcDst[2], tile->CrData, 4096, &CrLen);
+ tile->YLen = (UINT16)YLen;
+ tile->CbLen = (UINT16)CbLen;
+ tile->CrLen = (UINT16)CrLen;
+ PROFILER_EXIT(context->priv->prof_rfx_encode_rgb)
+ BufferPool_Return(context->priv->BufferPool, pBuffer);
+}
diff --git a/libfreerdp/codec/rfx_encode.h b/libfreerdp/codec/rfx_encode.h
new file mode 100644
index 0000000..0080645
--- /dev/null
+++ b/libfreerdp/codec/rfx_encode.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Encode
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_ENCODE_H
+#define FREERDP_LIB_CODEC_RFX_ENCODE_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile);
+
+#endif /* FREERDP_LIB_CODEC_RFX_ENCODE_H */
diff --git a/libfreerdp/codec/rfx_neon.c b/libfreerdp/codec/rfx_neon.c
new file mode 100644
index 0000000..f723efd
--- /dev/null
+++ b/libfreerdp/codec/rfx_neon.c
@@ -0,0 +1,536 @@
+/*
+ FreeRDP: A Remote Desktop Protocol Implementation
+ RemoteFX Codec Library - NEON Optimizations
+
+ Copyright 2011 Martin Fleisz <martin.fleisz@thincast.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.
+*/
+
+#include <freerdp/config.h>
+
+#if defined(WITH_NEON)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arm_neon.h>
+#include <winpr/sysinfo.h>
+
+#include "rfx_types.h"
+#include "rfx_neon.h"
+
+/* rfx_decode_YCbCr_to_RGB_NEON code now resides in the primitives library. */
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_quantization_decode_block_NEON(INT16* buffer, const size_t buffer_size, const UINT32 factor)
+{
+ int16x8_t quantFactors = vdupq_n_s16(factor);
+ int16x8_t* buf = (int16x8_t*)buffer;
+ int16x8_t* buf_end = (int16x8_t*)(buffer + buffer_size);
+
+ do
+ {
+ int16x8_t val = vld1q_s16((INT16*)buf);
+ val = vshlq_s16(val, quantFactors);
+ vst1q_s16((INT16*)buf, val);
+ buf++;
+ } while (buf < buf_end);
+}
+
+static void rfx_quantization_decode_NEON(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ rfx_quantization_decode_block_NEON(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block_NEON(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block_NEON(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block_NEON(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block_NEON(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block_NEON(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block_NEON(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block_NEON(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block_NEON(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block_NEON(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_horiz_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h,
+ INT16* WINPR_RESTRICT dst, size_t subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+
+ for (size_t y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ for (size_t n = 0; n < subband_width; n += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t h_n_m = vld1q_s16(h_ptr - 1);
+
+ if (n == 0)
+ {
+ int16_t first = vgetq_lane_s16(h_n_m, 1);
+ h_n_m = vsetq_lane_s16(first, h_n_m, 0);
+ }
+
+ int16x8_t tmp_n = vaddq_s16(h_n, h_n_m);
+ tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1));
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+
+ l_ptr -= subband_width;
+ h_ptr -= subband_width;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < subband_width; n += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8x2_t dst_n;
+ dst_n.val[0] = vld1q_s16(l_ptr);
+ int16x8_t dst_n_p = vld1q_s16(l_ptr + 1);
+
+ if (n == subband_width - 8)
+ {
+ int16_t last = vgetq_lane_s16(dst_n_p, 6);
+ dst_n_p = vsetq_lane_s16(last, dst_n_p, 7);
+ }
+
+ dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]);
+ dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1);
+ dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n);
+ vst2q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ }
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_vert_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h,
+ INT16* WINPR_RESTRICT dst, size_t subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ const size_t total_width = subband_width + subband_width;
+
+ /* Even coefficients */
+ for (size_t n = 0; n < subband_width; n++)
+ {
+ for (size_t x = 0; x < total_width; x += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1));
+
+ if (n == 0)
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ else
+ {
+ int16x8_t h_n_m = vld1q_s16((h_ptr - total_width));
+ tmp_n = vaddq_s16(tmp_n, h_n_m);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+
+ h_ptr = h;
+ dst_ptr = dst + total_width;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < subband_width; n++)
+ {
+ for (size_t x = 0; x < total_width; x += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t dst_n_m = vld1q_s16(dst_ptr - total_width);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8_t tmp_n = dst_n_m;
+
+ if (n == subband_width - 1)
+ tmp_n = vaddq_s16(tmp_n, dst_n_m);
+ else
+ {
+ int16x8_t dst_n_p = vld1q_s16((dst_ptr + total_width));
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vaddq_s16(tmp_n, h_n);
+ vst1q_s16(dst_ptr, dst_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+}
+
+static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__))
+rfx_dwt_2d_decode_block_NEON(INT16* WINPR_RESTRICT buffer, INT16* WINPR_RESTRICT idwt,
+ size_t subband_width)
+{
+ INT16 *hl, *lh, *hh, *ll;
+ INT16 *l_dst, *h_dst;
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+ rfx_dwt_2d_decode_block_horiz_NEON(ll, hl, l_dst, subband_width);
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_decode_block_horiz_NEON(lh, hh, h_dst, subband_width);
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ rfx_dwt_2d_decode_block_vert_NEON(l_dst, h_dst, buffer, subband_width);
+}
+
+static void rfx_dwt_2d_decode_NEON(INT16* buffer, INT16* dwt_buffer)
+{
+ rfx_dwt_2d_decode_block_NEON(buffer + 3840, dwt_buffer, 8);
+ rfx_dwt_2d_decode_block_NEON(buffer + 3072, dwt_buffer, 16);
+ rfx_dwt_2d_decode_block_NEON(buffer, dwt_buffer, 32);
+}
+
+static INLINE void rfx_idwt_extrapolate_horiz_neon(INT16* restrict pLowBand, size_t nLowStep,
+ const INT16* restrict pHighBand,
+ size_t nHighStep, INT16* restrict pDstBand,
+ size_t nDstStep, size_t nLowCount,
+ size_t nHighCount, size_t nDstCount)
+{
+ WINPR_ASSERT(pLowBand);
+ WINPR_ASSERT(pHighBand);
+ WINPR_ASSERT(pDstBand);
+
+ INT16* l_ptr = pLowBand;
+ const INT16* h_ptr = pHighBand;
+ INT16* dst_ptr = pDstBand;
+ size_t batchSize = (nLowCount + nHighCount) >> 1;
+
+ for (size_t y = 0; y < nDstCount; y++)
+ {
+ /* Even coefficients */
+ size_t n = 0;
+ for (; n < batchSize; n += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ int16x8_t h_n_m = vld1q_s16(h_ptr - 1);
+
+ if (n == 0)
+ {
+ int16_t first = vgetq_lane_s16(h_n_m, 1);
+ h_n_m = vsetq_lane_s16(first, h_n_m, 0);
+ }
+ else if (n == 24)
+ h_n = vsetq_lane_s16(0, h_n, 7);
+
+ int16x8_t tmp_n = vaddq_s16(h_n, h_n_m);
+ tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1));
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+ if (n < 32)
+ *l_ptr -= *(h_ptr - 1);
+
+ l_ptr -= batchSize;
+ h_ptr -= batchSize;
+
+ /* Odd coefficients */
+ n = 0;
+ for (; n < batchSize; n += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ int16x8x2_t dst_n;
+ dst_n.val[0] = vld1q_s16(l_ptr);
+ int16x8_t dst_n_p = vld1q_s16(l_ptr + 1);
+
+ if (n == 24)
+ h_n = vsetq_lane_s16(0, h_n, 7);
+
+ dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]);
+ dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1);
+ dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n);
+ vst2q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ if (n == 32)
+ {
+ h_ptr -= 1;
+ l_ptr += 1;
+ }
+ else
+ {
+ *dst_ptr = *l_ptr;
+ l_ptr += 1;
+ dst_ptr += 1;
+ }
+ }
+}
+
+static INLINE void rfx_idwt_extrapolate_vert_neon(const INT16* restrict pLowBand, size_t nLowStep,
+ const INT16* restrict pHighBand, size_t nHighStep,
+ INT16* restrict pDstBand, size_t nDstStep,
+ size_t nLowCount, size_t nHighCount,
+ size_t nDstCount)
+{
+ WINPR_ASSERT(pLowBand);
+ WINPR_ASSERT(pHighBand);
+ WINPR_ASSERT(pDstBand);
+
+ const INT16* l_ptr = pLowBand;
+ const INT16* h_ptr = pHighBand;
+ INT16* dst_ptr = pDstBand;
+ size_t batchSize = (nDstCount >> 3) << 3;
+ size_t forceBandSize = (nLowCount + nHighCount) >> 1;
+
+ /* Even coefficients */
+ for (size_t n = 0; n < forceBandSize; n++)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1);
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16((n == 31) ? (h_ptr - nHighStep) : h_ptr);
+ int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1));
+
+ if (n == 0)
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ else if (n < 31)
+ {
+ int16x8_t h_n_m = vld1q_s16((h_ptr - nHighStep));
+ tmp_n = vaddq_s16(tmp_n, h_n_m);
+ }
+
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t dst_n = vsubq_s16(l_n, tmp_n);
+ vst1q_s16(dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ int16_t h_n = (n == 31) ? *(h_ptr - nHighStep) : *h_ptr;
+ int16_t tmp_n = h_n + 1;
+ if (n == 0)
+ tmp_n += h_n;
+ else if (n < 31)
+ tmp_n += *(h_ptr - nHighStep);
+ tmp_n >>= 1;
+ *dst_ptr = *l_ptr - tmp_n;
+ l_ptr += 1;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+
+ dst_ptr += nDstStep;
+ }
+
+ if (forceBandSize < 32)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ int16x8_t l_n = vld1q_s16(l_ptr);
+ int16x8_t h_n = vld1q_s16(h_ptr - nHighStep);
+ int16x8_t tmp_n = vsubq_s16(l_n, h_n);
+ vst1q_s16(dst_ptr, tmp_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ *dst_ptr = *l_ptr - *(h_ptr - nHighStep);
+ l_ptr += 1;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+ }
+
+ h_ptr = pHighBand;
+ dst_ptr = pDstBand + nDstStep;
+
+ /* Odd coefficients */
+ for (size_t n = 0; n < forceBandSize; n++)
+ {
+ for (size_t x = 0; x < batchSize; x += 8)
+ {
+ // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1);
+ int16x8_t tmp_n = vld1q_s16(dst_ptr - nDstStep);
+ if (n == 31)
+ {
+ int16x8_t dst_n_p = vld1q_s16(l_ptr);
+ l_ptr += 8;
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ }
+ else
+ {
+ int16x8_t dst_n_p = vld1q_s16(dst_ptr + nDstStep);
+ tmp_n = vaddq_s16(tmp_n, dst_n_p);
+ tmp_n = vshrq_n_s16(tmp_n, 1);
+ int16x8_t h_n = vld1q_s16(h_ptr);
+ h_n = vshlq_n_s16(h_n, 1);
+ tmp_n = vaddq_s16(tmp_n, h_n);
+ }
+ vst1q_s16(dst_ptr, tmp_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ if (nDstCount > batchSize)
+ {
+ int16_t tmp_n = *(dst_ptr - nDstStep);
+ if (n == 31)
+ {
+ int16_t dst_n_p = *l_ptr;
+ l_ptr += 1;
+ tmp_n += dst_n_p;
+ tmp_n >>= 1;
+ }
+ else
+ {
+ int16_t dst_n_p = *(dst_ptr + nDstStep);
+ tmp_n += dst_n_p;
+ tmp_n >>= 1;
+ int16_t h_n = *h_ptr;
+ h_n <<= 1;
+ tmp_n += h_n;
+ }
+ *dst_ptr = tmp_n;
+ h_ptr += 1;
+ dst_ptr += 1;
+ }
+
+ dst_ptr += nDstStep;
+ }
+}
+
+static INLINE size_t prfx_get_band_l_count(size_t level)
+{
+ return (64 >> level) + 1;
+}
+
+static INLINE size_t prfx_get_band_h_count(size_t level)
+{
+ if (level == 1)
+ return (64 >> 1) - 1;
+ else
+ return (64 + (1 << (level - 1))) >> level;
+}
+
+static INLINE void rfx_dwt_2d_decode_extrapolate_block_neon(INT16* buffer, INT16* temp,
+ size_t level)
+{
+ size_t nDstStepX;
+ size_t nDstStepY;
+ INT16 *HL, *LH;
+ INT16 *HH, *LL;
+ INT16 *L, *H, *LLx;
+
+ const size_t nBandL = prfx_get_band_l_count(level);
+ const size_t nBandH = prfx_get_band_h_count(level);
+ size_t offset = 0;
+
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+
+ HL = &buffer[offset];
+ offset += (nBandH * nBandL);
+ LH = &buffer[offset];
+ offset += (nBandL * nBandH);
+ HH = &buffer[offset];
+ offset += (nBandH * nBandH);
+ LL = &buffer[offset];
+ nDstStepX = (nBandL + nBandH);
+ nDstStepY = (nBandL + nBandH);
+ offset = 0;
+ L = &temp[offset];
+ offset += (nBandL * nDstStepX);
+ H = &temp[offset];
+ LLx = &buffer[0];
+
+ /* horizontal (LL + HL -> L) */
+ rfx_idwt_extrapolate_horiz_neon(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL);
+
+ /* horizontal (LH + HH -> H) */
+ rfx_idwt_extrapolate_horiz_neon(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH);
+
+ /* vertical (L + H -> LL) */
+ rfx_idwt_extrapolate_vert_neon(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH,
+ nBandL + nBandH);
+}
+
+static void rfx_dwt_2d_extrapolate_decode_neon(INT16* buffer, INT16* temp)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(temp);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3807], temp, 3);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3007], temp, 2);
+ rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[0], temp, 1);
+}
+
+void rfx_init_neon(RFX_CONTEXT* context)
+{
+ if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
+ {
+ DEBUG_RFX("Using NEON optimizations");
+ PROFILER_RENAME(context->priv->prof_rfx_ycbcr_to_rgb, "rfx_decode_YCbCr_to_RGB_NEON");
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_decode,
+ "rfx_quantization_decode_NEON");
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_NEON");
+ context->quantization_decode = rfx_quantization_decode_NEON;
+ context->dwt_2d_decode = rfx_dwt_2d_decode_NEON;
+ context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode_neon;
+ }
+}
+
+#endif // WITH_NEON
diff --git a/libfreerdp/codec/rfx_neon.h b/libfreerdp/codec/rfx_neon.h
new file mode 100644
index 0000000..ecb3ec0
--- /dev/null
+++ b/libfreerdp/codec/rfx_neon.h
@@ -0,0 +1,34 @@
+/*
+ FreeRDP: A Remote Desktop Protocol Implementation
+ RemoteFX Codec Library - NEON Optimizations
+
+ Copyright 2011 Martin Fleisz <martin.fleisz@thincast.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.
+*/
+
+#ifndef FREERDP_LIB_CODEC_RFX_NEON_H
+#define FREERDP_LIB_CODEC_RFX_NEON_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_init_neon(RFX_CONTEXT* context);
+
+#ifndef RFX_INIT_SIMD
+#if defined(WITH_NEON)
+#define RFX_INIT_SIMD(_rfx_context) rfx_init_neon(_rfx_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_NEON_H */
diff --git a/libfreerdp/codec/rfx_quantization.c b/libfreerdp/codec/rfx_quantization.c
new file mode 100644
index 0000000..fb871a2
--- /dev/null
+++ b/libfreerdp/codec/rfx_quantization.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Quantization
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/primitives.h>
+
+#include "rfx_quantization.h"
+
+/*
+ * Band Offset Dimensions Size
+ *
+ * HL1 0 32x32 1024
+ * LH1 1024 32x32 1024
+ * HH1 2048 32x32 1024
+ *
+ * HL2 3072 16x16 256
+ * LH2 3328 16x16 256
+ * HH2 3584 16x16 256
+ *
+ * HL3 3840 8x8 64
+ * LH3 3904 8x8 64
+ * HH3 3968 8x8 64
+ *
+ * LL3 4032 8x8 64
+ */
+
+static void rfx_quantization_decode_block(const primitives_t* WINPR_RESTRICT prims, INT16* buffer,
+ UINT32 buffer_size, UINT32 factor)
+{
+ if (factor == 0)
+ return;
+
+ prims->lShiftC_16s(buffer, factor, buffer, buffer_size);
+}
+
+void rfx_quantization_decode(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ const primitives_t* prims = primitives_get();
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ rfx_quantization_decode_block(prims, &buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block(prims, &buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block(prims, &buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block(prims, &buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block(prims, &buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block(prims, &buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block(prims, &buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block(prims, &buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block(prims, &buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block(prims, &buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static void rfx_quantization_encode_block(INT16* buffer, size_t buffer_size, UINT32 factor)
+{
+ INT16 half = 0;
+
+ if (factor == 0)
+ return;
+
+ half = (1 << (factor - 1));
+ /* Could probably use prims->rShiftC_16s(dst+half, factor, dst, buffer_size); */
+ for (INT16* dst = buffer; buffer_size > 0; dst++, buffer_size--)
+ {
+ *dst = (*dst + half) >> factor;
+ }
+}
+
+void rfx_quantization_encode(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantization_values);
+
+ rfx_quantization_encode_block(buffer, 1024, quantization_values[8] - 6); /* HL1 */
+ rfx_quantization_encode_block(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */
+ rfx_quantization_encode_block(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */
+ rfx_quantization_encode_block(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */
+ rfx_quantization_encode_block(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */
+ rfx_quantization_encode_block(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */
+ rfx_quantization_encode_block(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */
+ rfx_quantization_encode_block(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */
+ rfx_quantization_encode_block(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */
+ rfx_quantization_encode_block(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */
+
+ /* The coefficients are scaled by << 5 at RGB->YCbCr phase, so we round it back here */
+ rfx_quantization_encode_block(buffer, 4096, 5);
+}
diff --git a/libfreerdp/codec/rfx_quantization.h b/libfreerdp/codec/rfx_quantization.h
new file mode 100644
index 0000000..4f7ae7e
--- /dev/null
+++ b/libfreerdp/codec/rfx_quantization.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - Quantization
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_QUANTIZATION_H
+#define FREERDP_LIB_CODEC_RFX_QUANTIZATION_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_quantization_decode(INT16* buffer, const UINT32* quantization_values);
+FREERDP_LOCAL void rfx_quantization_encode(INT16* buffer, const UINT32* quantization_values);
+
+#endif /* FREERDP_LIB_CODEC_RFX_QUANTIZATION_H */
diff --git a/libfreerdp/codec/rfx_rlgr.c b/libfreerdp/codec/rfx_rlgr.c
new file mode 100644
index 0000000..da1b63f
--- /dev/null
+++ b/libfreerdp/codec/rfx_rlgr.c
@@ -0,0 +1,772 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - RLGR
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+/**
+ * This implementation of RLGR refers to
+ * [MS-RDPRFX] 3.1.8.1.7.3 RLGR1/RLGR3 Pseudocode
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+#include <winpr/bitstream.h>
+#include <winpr/intrin.h>
+
+#include "rfx_bitstream.h"
+
+#include "rfx_rlgr.h"
+
+/* Constants used in RLGR1/RLGR3 algorithm */
+#define KPMAX (80) /* max value for kp or krp */
+#define LSGR (3) /* shift count to convert kp to k */
+#define UP_GR (4) /* increase in kp after a zero run in RL mode */
+#define DN_GR (6) /* decrease in kp after a nonzero symbol in RL mode */
+#define UQ_GR (3) /* increase in kp after nonzero symbol in GR mode */
+#define DQ_GR (3) /* decrease in kp after zero symbol in GR mode */
+
+/* Returns the least number of bits required to represent a given value */
+#define GetMinBits(_val, _nbits) \
+ do \
+ { \
+ UINT32 _v = _val; \
+ _nbits = 0; \
+ while (_v) \
+ { \
+ _v >>= 1; \
+ _nbits++; \
+ } \
+ } while (0)
+
+/*
+ * Update the passed parameter and clamp it to the range [0, KPMAX]
+ * Return the value of parameter right-shifted by LSGR
+ */
+#define UpdateParam(_param, _deltaP, _k) \
+ do \
+ { \
+ _param += _deltaP; \
+ if (_param > KPMAX) \
+ _param = KPMAX; \
+ if (_param < 0) \
+ _param = 0; \
+ _k = (_param >> LSGR); \
+ } while (0)
+
+static BOOL g_LZCNT = FALSE;
+
+static INIT_ONCE rfx_rlgr_init_once = INIT_ONCE_STATIC_INIT;
+
+static BOOL CALLBACK rfx_rlgr_init(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ g_LZCNT = IsProcessorFeaturePresentEx(PF_EX_LZCNT);
+ return TRUE;
+}
+
+static INLINE UINT32 lzcnt_s(UINT32 x)
+{
+ if (!x)
+ return 32;
+
+ if (!g_LZCNT)
+ {
+ UINT32 y = 0;
+ UINT32 n = 32;
+ y = x >> 16;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 16);
+ n = n - 16;
+ x = y;
+ }
+ y = x >> 8;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 8);
+ n = n - 8;
+ x = y;
+ }
+ y = x >> 4;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 4);
+ n = n - 4;
+ x = y;
+ }
+ y = x >> 2;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 2);
+ n = n - 2;
+ x = y;
+ }
+ y = x >> 1;
+ if (y != 0)
+ {
+ WINPR_ASSERT(n >= 2);
+ return n - 2;
+ }
+
+ WINPR_ASSERT(n >= x);
+ return n - x;
+ }
+
+ return __lzcnt(x);
+}
+
+int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize,
+ INT16* WINPR_RESTRICT pDstData, UINT32 rDstSize)
+{
+ int vk = 0;
+ size_t run = 0;
+ int cnt = 0;
+ size_t size = 0;
+ int nbits = 0;
+ size_t offset = 0;
+ INT16 mag = 0;
+ UINT32 k = 0;
+ INT32 kp = 0;
+ UINT32 kr = 0;
+ INT32 krp = 0;
+ UINT16 code = 0;
+ UINT32 sign = 0;
+ UINT32 nIdx = 0;
+ UINT32 val1 = 0;
+ UINT32 val2 = 0;
+ INT16* pOutput = NULL;
+ wBitStream* bs = NULL;
+ wBitStream s_bs = { 0 };
+ const SSIZE_T DstSize = rDstSize;
+
+ InitOnceExecuteOnce(&rfx_rlgr_init_once, rfx_rlgr_init, NULL, NULL);
+
+ k = 1;
+ kp = k << LSGR;
+
+ kr = 1;
+ krp = kr << LSGR;
+
+ if ((mode != RLGR1) && (mode != RLGR3))
+ mode = RLGR1;
+
+ if (!pSrcData || !SrcSize)
+ return -1;
+
+ if (!pDstData || !DstSize)
+ return -1;
+
+ pOutput = pDstData;
+
+ bs = &s_bs;
+
+ BitStream_Attach(bs, pSrcData, SrcSize);
+ BitStream_Fetch(bs);
+
+ while ((BitStream_GetRemainingLength(bs) > 0) && ((pOutput - pDstData) < DstSize))
+ {
+ if (k)
+ {
+ /* Run-Length (RL) Mode */
+
+ run = 0;
+
+ /* count number of leading 0s */
+
+ cnt = lzcnt_s(bs->accumulator);
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(bs->accumulator);
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ while (vk--)
+ {
+ const UINT32 add = (1 << k); /* add (1 << k) to run length */
+ run += add;
+
+ /* update k, kp params */
+
+ kp += UP_GR;
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+ }
+
+ /* next k bits contain run length remainder */
+
+ if (BitStream_GetRemainingLength(bs) < k)
+ break;
+
+ bs->mask = ((1 << k) - 1);
+ run += ((bs->accumulator >> (32 - k)) & bs->mask);
+ BitStream_Shift(bs, k);
+
+ /* read sign bit */
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ sign = (bs->accumulator & 0x80000000) ? 1 : 0;
+ BitStream_Shift(bs, 1);
+
+ /* count number of leading 1s */
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ /* next kr bits contain code remainder */
+
+ if (BitStream_GetRemainingLength(bs) < kr)
+ break;
+
+ bs->mask = ((1 << kr) - 1);
+ if (kr > 0)
+ code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask);
+ else
+ code = 0;
+ BitStream_Shift(bs, kr);
+
+ /* add (vk << kr) to code */
+
+ code |= (vk << kr);
+
+ if (!vk)
+ {
+ /* update kr, krp params */
+
+ krp -= 2;
+
+ if (krp < 0)
+ krp = 0;
+
+ kr = krp >> LSGR;
+ }
+ else if (vk != 1)
+ {
+ /* update kr, krp params */
+
+ krp += vk;
+
+ if (krp > KPMAX)
+ krp = KPMAX;
+
+ kr = krp >> LSGR;
+ }
+
+ /* update k, kp params */
+
+ kp -= DN_GR;
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+
+ /* compute magnitude from code */
+
+ if (sign)
+ mag = ((INT16)(code + 1)) * -1;
+ else
+ mag = (INT16)(code + 1);
+
+ /* write to output stream */
+
+ offset = (pOutput - pDstData);
+ size = run;
+
+ if ((offset + size) > rDstSize)
+ size = DstSize - offset;
+
+ if (size)
+ {
+ ZeroMemory(pOutput, size * sizeof(INT16));
+ pOutput += size;
+ }
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ else
+ {
+ /* Golomb-Rice (GR) Mode */
+
+ /* count number of leading 1s */
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk = cnt;
+
+ while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0))
+ {
+ BitStream_Shift32(bs);
+
+ cnt = lzcnt_s(~(bs->accumulator));
+
+ nbits = BitStream_GetRemainingLength(bs);
+
+ if (cnt > nbits)
+ cnt = nbits;
+
+ vk += cnt;
+ }
+
+ BitStream_Shift(bs, (vk % 32));
+
+ if (BitStream_GetRemainingLength(bs) < 1)
+ break;
+
+ BitStream_Shift(bs, 1);
+
+ /* next kr bits contain code remainder */
+
+ if (BitStream_GetRemainingLength(bs) < kr)
+ break;
+
+ bs->mask = ((1 << kr) - 1);
+ if (kr > 0)
+ code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask);
+ else
+ code = 0;
+ BitStream_Shift(bs, kr);
+
+ /* add (vk << kr) to code */
+
+ code |= (vk << kr);
+
+ if (!vk)
+ {
+ /* update kr, krp params */
+
+ krp -= 2;
+
+ if (krp < 0)
+ krp = 0;
+
+ kr = krp >> LSGR;
+ }
+ else if (vk != 1)
+ {
+ /* update kr, krp params */
+
+ krp += vk;
+
+ if (krp > KPMAX)
+ krp = KPMAX;
+
+ kr = krp >> LSGR;
+ }
+
+ if (mode == RLGR1) /* RLGR1 */
+ {
+ if (!code)
+ {
+ /* update k, kp params */
+
+ kp += UQ_GR;
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+
+ mag = 0;
+ }
+ else
+ {
+ /* update k, kp params */
+
+ kp -= DQ_GR;
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+
+ /*
+ * code = 2 * mag - sign
+ * sign + code = 2 * mag
+ */
+
+ if (code & 1)
+ mag = ((INT16)((code + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(code >> 1);
+ }
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ else if (mode == RLGR3) /* RLGR3 */
+ {
+ nIdx = 0;
+
+ if (code)
+ {
+ mag = (UINT32)code;
+ nIdx = 32 - lzcnt_s(mag);
+ }
+
+ if (BitStream_GetRemainingLength(bs) < nIdx)
+ break;
+
+ bs->mask = ((1 << nIdx) - 1);
+ if (nIdx > 0)
+ val1 = ((bs->accumulator >> (32 - nIdx)) & bs->mask);
+ else
+ val1 = 0;
+ BitStream_Shift(bs, nIdx);
+
+ val2 = code - val1;
+
+ if (val1 && val2)
+ {
+ /* update k, kp params */
+
+ kp -= (2 * DQ_GR);
+
+ if (kp < 0)
+ kp = 0;
+
+ k = kp >> LSGR;
+ }
+ else if (!val1 && !val2)
+ {
+ /* update k, kp params */
+
+ kp += (2 * UQ_GR);
+
+ if (kp > KPMAX)
+ kp = KPMAX;
+
+ k = kp >> LSGR;
+ }
+
+ if (val1 & 1)
+ mag = ((INT16)((val1 + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(val1 >> 1);
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+
+ if (val2 & 1)
+ mag = ((INT16)((val2 + 1) >> 1)) * -1;
+ else
+ mag = (INT16)(val2 >> 1);
+
+ if ((pOutput - pDstData) < DstSize)
+ {
+ *pOutput = mag;
+ pOutput++;
+ }
+ }
+ }
+ }
+
+ offset = (pOutput - pDstData);
+
+ if (offset < rDstSize)
+ {
+ size = DstSize - offset;
+ ZeroMemory(pOutput, size * 2);
+ pOutput += size;
+ }
+
+ offset = (pOutput - pDstData);
+
+ if (offset != DstSize)
+ return -1;
+
+ return 1;
+}
+
+/* Returns the next coefficient (a signed int) to encode, from the input stream */
+#define GetNextInput(_n) \
+ do \
+ { \
+ if (data_size > 0) \
+ { \
+ _n = *data++; \
+ data_size--; \
+ } \
+ else \
+ { \
+ _n = 0; \
+ } \
+ } while (0)
+
+/* Emit bitPattern to the output bitstream */
+#define OutputBits(numBits, bitPattern) rfx_bitstream_put_bits(bs, bitPattern, numBits)
+
+/* Emit a bit (0 or 1), count number of times, to the output bitstream */
+#define OutputBit(count, bit) \
+ do \
+ { \
+ UINT16 _b = (bit ? 0xFFFF : 0); \
+ int _c = (count); \
+ for (; _c > 0; _c -= 16) \
+ rfx_bitstream_put_bits(bs, _b, (_c > 16 ? 16 : _c)); \
+ } while (0)
+
+/* Converts the input value to (2 * abs(input) - sign(input)), where sign(input) = (input < 0 ? 1 :
+ * 0) and returns it */
+#define Get2MagSign(input) ((input) >= 0 ? 2 * (input) : -2 * (input)-1)
+
+/* Outputs the Golomb/Rice encoding of a non-negative integer */
+#define CodeGR(krp, val) rfx_rlgr_code_gr(bs, krp, val)
+
+static void rfx_rlgr_code_gr(RFX_BITSTREAM* bs, int* krp, UINT32 val)
+{
+ int kr = *krp >> LSGR;
+
+ /* unary part of GR code */
+
+ UINT32 vk = (val) >> kr;
+ OutputBit(vk, 1);
+ OutputBit(1, 0);
+
+ /* remainder part of GR code, if needed */
+ if (kr)
+ {
+ OutputBits(kr, val & ((1 << kr) - 1));
+ }
+
+ /* update krp, only if it is not equal to 1 */
+ if (vk == 0)
+ {
+ UpdateParam(*krp, -2, kr);
+ }
+ else if (vk > 1)
+ {
+ UpdateParam(*krp, vk, kr);
+ }
+}
+
+int rfx_rlgr_encode(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size,
+ BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size)
+{
+ int k = 0;
+ int kp = 0;
+ int krp = 0;
+ RFX_BITSTREAM* bs = NULL;
+ int processed_size = 0;
+
+ if (!(bs = (RFX_BITSTREAM*)winpr_aligned_calloc(1, sizeof(RFX_BITSTREAM), 32)))
+ return 0;
+
+ rfx_bitstream_attach(bs, buffer, buffer_size);
+
+ /* initialize the parameters */
+ k = 1;
+ kp = 1 << LSGR;
+ krp = 1 << LSGR;
+
+ /* process all the input coefficients */
+ while (data_size > 0)
+ {
+ int input = 0;
+
+ if (k)
+ {
+ int numZeros = 0;
+ int runmax = 0;
+ int sign = 0;
+
+ /* RUN-LENGTH MODE */
+
+ /* collect the run of zeros in the input stream */
+ numZeros = 0;
+ GetNextInput(input);
+ while (input == 0 && data_size > 0)
+ {
+ numZeros++;
+ GetNextInput(input);
+ }
+
+ // emit output zeros
+ runmax = 1 << k;
+ while (numZeros >= runmax)
+ {
+ OutputBit(1, 0); /* output a zero bit */
+ numZeros -= runmax;
+ UpdateParam(kp, UP_GR, k); /* update kp, k */
+ runmax = 1 << k;
+ }
+
+ /* output a 1 to terminate runs */
+ OutputBit(1, 1);
+
+ /* output the remaining run length using k bits */
+ OutputBits(k, numZeros);
+
+ /* note: when we reach here and the last byte being encoded is 0, we still
+ need to output the last two bits, otherwise mstsc will crash */
+
+ /* encode the nonzero value using GR coding */
+ const UINT32 mag =
+ (UINT32)(input < 0 ? -input : input); /* absolute value of input coefficient */
+ sign = (input < 0 ? 1 : 0); /* sign of input coefficient */
+
+ OutputBit(1, sign); /* output the sign bit */
+ CodeGR(&krp, mag ? mag - 1 : 0); /* output GR code for (mag - 1) */
+
+ UpdateParam(kp, -DN_GR, k);
+ }
+ else
+ {
+ /* GOLOMB-RICE MODE */
+
+ if (mode == RLGR1)
+ {
+ UINT32 twoMs = 0;
+
+ /* RLGR1 variant */
+
+ /* convert input to (2*magnitude - sign), encode using GR code */
+ GetNextInput(input);
+ twoMs = Get2MagSign(input);
+ CodeGR(&krp, twoMs);
+
+ /* update k, kp */
+ /* NOTE: as of Aug 2011, the algorithm is still wrongly documented
+ and the update direction is reversed */
+ if (twoMs)
+ {
+ UpdateParam(kp, -DQ_GR, k);
+ }
+ else
+ {
+ UpdateParam(kp, UQ_GR, k);
+ }
+ }
+ else /* mode == RLGR3 */
+ {
+ UINT32 twoMs1 = 0;
+ UINT32 twoMs2 = 0;
+ UINT32 sum2Ms = 0;
+ UINT32 nIdx = 0;
+
+ /* RLGR3 variant */
+
+ /* convert the next two input values to (2*magnitude - sign) and */
+ /* encode their sum using GR code */
+
+ GetNextInput(input);
+ twoMs1 = Get2MagSign(input);
+ GetNextInput(input);
+ twoMs2 = Get2MagSign(input);
+ sum2Ms = twoMs1 + twoMs2;
+
+ CodeGR(&krp, sum2Ms);
+
+ /* encode binary representation of the first input (twoMs1). */
+ GetMinBits(sum2Ms, nIdx);
+ OutputBits(nIdx, twoMs1);
+
+ /* update k,kp for the two input values */
+
+ if (twoMs1 && twoMs2)
+ {
+ UpdateParam(kp, -2 * DQ_GR, k);
+ }
+ else if (!twoMs1 && !twoMs2)
+ {
+ UpdateParam(kp, 2 * UQ_GR, k);
+ }
+ }
+ }
+ }
+
+ rfx_bitstream_flush(bs);
+ processed_size = rfx_bitstream_get_processed_bytes(bs);
+ winpr_aligned_free(bs);
+
+ return processed_size;
+}
diff --git a/libfreerdp/codec/rfx_rlgr.h b/libfreerdp/codec/rfx_rlgr.h
new file mode 100644
index 0000000..93738ce
--- /dev/null
+++ b/libfreerdp/codec/rfx_rlgr.h
@@ -0,0 +1,32 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - RLGR
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_RLGR_H
+#define FREERDP_LIB_CODEC_RFX_RLGR_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL int rfx_rlgr_encode(RLGR_MODE mode, const INT16* data, UINT32 data_size, BYTE* buffer,
+ UINT32 buffer_size);
+
+FREERDP_LOCAL int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* pSrcData, UINT32 SrcSize,
+ INT16* pDstData, UINT32 DstSize);
+
+#endif /* FREERDP_LIB_CODEC_RFX_RLGR_H */
diff --git a/libfreerdp/codec/rfx_sse2.c b/libfreerdp/codec/rfx_sse2.c
new file mode 100644
index 0000000..06cf8f4
--- /dev/null
+++ b/libfreerdp/codec/rfx_sse2.c
@@ -0,0 +1,484 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - SSE2 Optimizations
+ *
+ * Copyright 2011 Stephen Erisman
+ * Copyright 2011 Norbert Federa <norbert.federa@thincast.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winpr/sysinfo.h>
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+#include "rfx_types.h"
+#include "rfx_sse2.h"
+
+#ifdef _MSC_VER
+#define __attribute__(...)
+#endif
+
+#define CACHE_LINE_BYTES 64
+
+#ifndef __clang__
+#define ATTRIBUTES __gnu_inline__, __always_inline__, __artificial__
+#else
+#define ATTRIBUTES __gnu_inline__, __always_inline__
+#endif
+
+#define _mm_between_epi16(_val, _min, _max) \
+ do \
+ { \
+ _val = _mm_min_epi16(_max, _mm_max_epi16(_val, _min)); \
+ } while (0)
+
+static __inline void __attribute__((ATTRIBUTES)) _mm_prefetch_buffer(char* buffer, int num_bytes)
+{
+ __m128i* buf = (__m128i*)buffer;
+
+ for (unsigned int i = 0; i < (num_bytes / sizeof(__m128i));
+ i += (CACHE_LINE_BYTES / sizeof(__m128i)))
+ {
+ _mm_prefetch((char*)(&buf[i]), _MM_HINT_NTA);
+ }
+}
+
+/* rfx_decode_ycbcr_to_rgb_sse2 code now resides in the primitives library. */
+/* rfx_encode_rgb_to_ycbcr_sse2 code now resides in the primitives library. */
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_quantization_decode_block_sse2(INT16* buffer, const size_t buffer_size, const UINT32 factor)
+{
+ __m128i a;
+ __m128i* ptr = (__m128i*)buffer;
+ __m128i* buf_end = (__m128i*)(buffer + buffer_size);
+
+ if (factor == 0)
+ return;
+
+ do
+ {
+ a = _mm_load_si128(ptr);
+ a = _mm_slli_epi16(a, factor);
+ _mm_store_si128(ptr, a);
+ ptr++;
+ } while (ptr < buf_end);
+}
+
+static void rfx_quantization_decode_sse2(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantVals);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_quantization_decode_block_sse2(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */
+ rfx_quantization_decode_block_sse2(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */
+ rfx_quantization_decode_block_sse2(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */
+ rfx_quantization_decode_block_sse2(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */
+ rfx_quantization_decode_block_sse2(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */
+ rfx_quantization_decode_block_sse2(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */
+ rfx_quantization_decode_block_sse2(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */
+ rfx_quantization_decode_block_sse2(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */
+ rfx_quantization_decode_block_sse2(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */
+ rfx_quantization_decode_block_sse2(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_quantization_encode_block_sse2(INT16* buffer, const int buffer_size, const UINT32 factor)
+{
+ __m128i a;
+ __m128i* ptr = (__m128i*)buffer;
+ __m128i* buf_end = (__m128i*)(buffer + buffer_size);
+ __m128i half;
+
+ if (factor == 0)
+ return;
+
+ half = _mm_set1_epi16(1 << (factor - 1));
+
+ do
+ {
+ a = _mm_load_si128(ptr);
+ a = _mm_add_epi16(a, half);
+ a = _mm_srai_epi16(a, factor);
+ _mm_store_si128(ptr, a);
+ ptr++;
+ } while (ptr < buf_end);
+}
+
+static void rfx_quantization_encode_sse2(INT16* buffer,
+ const UINT32* WINPR_RESTRICT quantization_values)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(quantization_values);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_quantization_encode_block_sse2(buffer, 1024, quantization_values[8] - 6); /* HL1 */
+ rfx_quantization_encode_block_sse2(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */
+ rfx_quantization_encode_block_sse2(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */
+ rfx_quantization_encode_block_sse2(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */
+ rfx_quantization_encode_block_sse2(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */
+ rfx_quantization_encode_block_sse2(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */
+ rfx_quantization_encode_block_sse2(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */
+ rfx_quantization_encode_block_sse2(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */
+ rfx_quantization_encode_block_sse2(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */
+ rfx_quantization_encode_block_sse2(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */
+ rfx_quantization_encode_block_sse2(buffer, 4096, 5);
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_horiz_sse2(INT16* l, INT16* h, INT16* dst, int subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ int first = 0;
+ int last = 0;
+ __m128i l_n;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i tmp_n;
+ __m128i dst_n;
+ __m128i dst_n_p;
+ __m128i dst1;
+ __m128i dst2;
+
+ for (int y = 0; y < subband_width; y++)
+ {
+ /* Even coefficients */
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */
+ l_n = _mm_load_si128((__m128i*)l_ptr);
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - 1));
+
+ if (n == 0)
+ {
+ first = _mm_extract_epi16(h_n_m, 1);
+ h_n_m = _mm_insert_epi16(h_n_m, first, 0);
+ }
+
+ tmp_n = _mm_add_epi16(h_n, h_n_m);
+ tmp_n = _mm_add_epi16(tmp_n, _mm_set1_epi16(1));
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_sub_epi16(l_n, tmp_n);
+ _mm_store_si128((__m128i*)l_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ }
+
+ l_ptr -= subband_width;
+ h_ptr -= subband_width;
+
+ /* Odd coefficients */
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ h_n = _mm_slli_epi16(h_n, 1);
+ dst_n = _mm_load_si128((__m128i*)(l_ptr));
+ dst_n_p = _mm_loadu_si128((__m128i*)(l_ptr + 1));
+
+ if (n == subband_width - 8)
+ {
+ last = _mm_extract_epi16(dst_n_p, 6);
+ dst_n_p = _mm_insert_epi16(dst_n_p, last, 7);
+ }
+
+ tmp_n = _mm_add_epi16(dst_n_p, dst_n);
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ tmp_n = _mm_add_epi16(tmp_n, h_n);
+ dst1 = _mm_unpacklo_epi16(dst_n, tmp_n);
+ dst2 = _mm_unpackhi_epi16(dst_n, tmp_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst1);
+ _mm_store_si128((__m128i*)(dst_ptr + 8), dst2);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 16;
+ }
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_vert_sse2(INT16* l, INT16* h, INT16* dst, int subband_width)
+{
+ INT16* l_ptr = l;
+ INT16* h_ptr = h;
+ INT16* dst_ptr = dst;
+ __m128i l_n;
+ __m128i h_n;
+ __m128i tmp_n;
+ __m128i h_n_m;
+ __m128i dst_n;
+ __m128i dst_n_m;
+ __m128i dst_n_p;
+ int total_width = subband_width + subband_width;
+
+ /* Even coefficients */
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */
+ l_n = _mm_load_si128((__m128i*)l_ptr);
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ tmp_n = _mm_add_epi16(h_n, _mm_set1_epi16(1));
+
+ if (n == 0)
+ tmp_n = _mm_add_epi16(tmp_n, h_n);
+ else
+ {
+ h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - total_width));
+ tmp_n = _mm_add_epi16(tmp_n, h_n_m);
+ }
+
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_sub_epi16(l_n, tmp_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst_n);
+ l_ptr += 8;
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+
+ h_ptr = h;
+ dst_ptr = dst + total_width;
+
+ /* Odd coefficients */
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */
+ h_n = _mm_load_si128((__m128i*)h_ptr);
+ dst_n_m = _mm_load_si128((__m128i*)(dst_ptr - total_width));
+ h_n = _mm_slli_epi16(h_n, 1);
+ tmp_n = dst_n_m;
+
+ if (n == subband_width - 1)
+ tmp_n = _mm_add_epi16(tmp_n, dst_n_m);
+ else
+ {
+ dst_n_p = _mm_loadu_si128((__m128i*)(dst_ptr + total_width));
+ tmp_n = _mm_add_epi16(tmp_n, dst_n_p);
+ }
+
+ tmp_n = _mm_srai_epi16(tmp_n, 1);
+ dst_n = _mm_add_epi16(tmp_n, h_n);
+ _mm_store_si128((__m128i*)dst_ptr, dst_n);
+ h_ptr += 8;
+ dst_ptr += 8;
+ }
+
+ dst_ptr += total_width;
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_decode_block_sse2(INT16* buffer, INT16* idwt, int subband_width)
+{
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+ INT16* l_dst = NULL;
+ INT16* h_dst = NULL;
+ _mm_prefetch_buffer((char*)idwt, subband_width * 4 * sizeof(INT16));
+ /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt.
+ */
+ /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */
+ /* The lower part L uses LL(3) and HL(0). */
+ /* The higher part H uses LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ l_dst = idwt;
+ rfx_dwt_2d_decode_block_horiz_sse2(ll, hl, l_dst, subband_width);
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ h_dst = idwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_decode_block_horiz_sse2(lh, hh, h_dst, subband_width);
+ /* Inverse DWT in vertical direction, results are stored in original buffer. */
+ rfx_dwt_2d_decode_block_vert_sse2(l_dst, h_dst, buffer, subband_width);
+}
+
+static void rfx_dwt_2d_decode_sse2(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_dwt_2d_decode_block_sse2(&buffer[3840], dwt_buffer, 8);
+ rfx_dwt_2d_decode_block_sse2(&buffer[3072], dwt_buffer, 16);
+ rfx_dwt_2d_decode_block_sse2(&buffer[0], dwt_buffer, 32);
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_vert_sse2(INT16* src, INT16* l, INT16* h, int subband_width)
+{
+ int total_width = 0;
+ __m128i src_2n;
+ __m128i src_2n_1;
+ __m128i src_2n_2;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i l_n;
+ total_width = subband_width << 1;
+
+ for (int n = 0; n < subband_width; n++)
+ {
+ for (int x = 0; x < total_width; x += 8)
+ {
+ src_2n = _mm_load_si128((__m128i*)src);
+ src_2n_1 = _mm_load_si128((__m128i*)(src + total_width));
+
+ if (n < subband_width - 1)
+ src_2n_2 = _mm_load_si128((__m128i*)(src + 2 * total_width));
+ else
+ src_2n_2 = src_2n;
+
+ /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */
+ h_n = _mm_add_epi16(src_2n, src_2n_2);
+ h_n = _mm_srai_epi16(h_n, 1);
+ h_n = _mm_sub_epi16(src_2n_1, h_n);
+ h_n = _mm_srai_epi16(h_n, 1);
+ _mm_store_si128((__m128i*)h, h_n);
+
+ if (n == 0)
+ h_n_m = h_n;
+ else
+ h_n_m = _mm_load_si128((__m128i*)(h - total_width));
+
+ /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */
+ l_n = _mm_add_epi16(h_n_m, h_n);
+ l_n = _mm_srai_epi16(l_n, 1);
+ l_n = _mm_add_epi16(l_n, src_2n);
+ _mm_store_si128((__m128i*)l, l_n);
+ src += 8;
+ l += 8;
+ h += 8;
+ }
+
+ src += total_width;
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_horiz_sse2(INT16* src, INT16* l, INT16* h, int subband_width)
+{
+ int first = 0;
+ __m128i src_2n;
+ __m128i src_2n_1;
+ __m128i src_2n_2;
+ __m128i h_n;
+ __m128i h_n_m;
+ __m128i l_n;
+
+ for (int y = 0; y < subband_width; y++)
+ {
+ for (int n = 0; n < subband_width; n += 8)
+ {
+ /* The following 3 Set operations consumes more than half of the total DWT processing
+ * time! */
+ src_2n =
+ _mm_set_epi16(src[14], src[12], src[10], src[8], src[6], src[4], src[2], src[0]);
+ src_2n_1 =
+ _mm_set_epi16(src[15], src[13], src[11], src[9], src[7], src[5], src[3], src[1]);
+ src_2n_2 = _mm_set_epi16(n == subband_width - 8 ? src[14] : src[16], src[14], src[12],
+ src[10], src[8], src[6], src[4], src[2]);
+ /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */
+ h_n = _mm_add_epi16(src_2n, src_2n_2);
+ h_n = _mm_srai_epi16(h_n, 1);
+ h_n = _mm_sub_epi16(src_2n_1, h_n);
+ h_n = _mm_srai_epi16(h_n, 1);
+ _mm_store_si128((__m128i*)h, h_n);
+ h_n_m = _mm_loadu_si128((__m128i*)(h - 1));
+
+ if (n == 0)
+ {
+ first = _mm_extract_epi16(h_n_m, 1);
+ h_n_m = _mm_insert_epi16(h_n_m, first, 0);
+ }
+
+ /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */
+ l_n = _mm_add_epi16(h_n_m, h_n);
+ l_n = _mm_srai_epi16(l_n, 1);
+ l_n = _mm_add_epi16(l_n, src_2n);
+ _mm_store_si128((__m128i*)l, l_n);
+ src += 16;
+ l += 8;
+ h += 8;
+ }
+ }
+}
+
+static __inline void __attribute__((ATTRIBUTES))
+rfx_dwt_2d_encode_block_sse2(INT16* buffer, INT16* dwt, int subband_width)
+{
+ INT16* hl = NULL;
+ INT16* lh = NULL;
+ INT16* hh = NULL;
+ INT16* ll = NULL;
+ INT16* l_src = NULL;
+ INT16* h_src = NULL;
+ _mm_prefetch_buffer((char*)dwt, subband_width * 4 * sizeof(INT16));
+ /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */
+ l_src = dwt;
+ h_src = dwt + subband_width * subband_width * 2;
+ rfx_dwt_2d_encode_block_vert_sse2(buffer, l_src, h_src, subband_width);
+ /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order,
+ * stored in original buffer. */
+ /* The lower part L generates LL(3) and HL(0). */
+ /* The higher part H generates LH(1) and HH(2). */
+ ll = buffer + subband_width * subband_width * 3;
+ hl = buffer;
+ lh = buffer + subband_width * subband_width;
+ hh = buffer + subband_width * subband_width * 2;
+ rfx_dwt_2d_encode_block_horiz_sse2(l_src, ll, hl, subband_width);
+ rfx_dwt_2d_encode_block_horiz_sse2(h_src, lh, hh, subband_width);
+}
+
+static void rfx_dwt_2d_encode_sse2(INT16* buffer, INT16* dwt_buffer)
+{
+ WINPR_ASSERT(buffer);
+ WINPR_ASSERT(dwt_buffer);
+
+ _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16));
+ rfx_dwt_2d_encode_block_sse2(buffer, dwt_buffer, 32);
+ rfx_dwt_2d_encode_block_sse2(buffer + 3072, dwt_buffer, 16);
+ rfx_dwt_2d_encode_block_sse2(buffer + 3840, dwt_buffer, 8);
+}
+
+void rfx_init_sse2(RFX_CONTEXT* context)
+{
+ if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE))
+ return;
+
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_sse2")
+ PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode_sse2")
+ context->quantization_decode = rfx_quantization_decode_sse2;
+ context->quantization_encode = rfx_quantization_encode_sse2;
+ context->dwt_2d_decode = rfx_dwt_2d_decode_sse2;
+ context->dwt_2d_encode = rfx_dwt_2d_encode_sse2;
+}
diff --git a/libfreerdp/codec/rfx_sse2.h b/libfreerdp/codec/rfx_sse2.h
new file mode 100644
index 0000000..b0d3998
--- /dev/null
+++ b/libfreerdp/codec/rfx_sse2.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library - SSE2 Optimizations
+ *
+ * Copyright 2011 Stephen Erisman
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_SSE2_H
+#define FREERDP_LIB_CODEC_RFX_SSE2_H
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL void rfx_init_sse2(RFX_CONTEXT* context);
+
+#ifdef WITH_SSE2
+#ifndef RFX_INIT_SIMD
+#define RFX_INIT_SIMD(_rfx_context) rfx_init_sse2(_rfx_context)
+#endif
+#endif
+
+#endif /* FREERDP_LIB_CODEC_RFX_SSE2_H */
diff --git a/libfreerdp/codec/rfx_types.h b/libfreerdp/codec/rfx_types.h
new file mode 100644
index 0000000..5762b30
--- /dev/null
+++ b/libfreerdp/codec/rfx_types.h
@@ -0,0 +1,182 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * RemoteFX Codec Library
+ *
+ * Copyright 2011 Vic Lee
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_LIB_CODEC_RFX_TYPES_H
+#define FREERDP_LIB_CODEC_RFX_TYPES_H
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/pool.h>
+#include <winpr/wlog.h>
+#include <winpr/collections.h>
+
+#include <freerdp/codec/rfx.h>
+#include <freerdp/log.h>
+#include <freerdp/utils/profiler.h>
+
+#define RFX_TAG FREERDP_TAG("codec.rfx")
+#ifdef WITH_DEBUG_RFX
+#define DEBUG_RFX(...) WLog_DBG(RFX_TAG, __VA_ARGS__)
+#else
+#define DEBUG_RFX(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define RFX_DECODED_SYNC 0x00000001
+#define RFX_DECODED_CONTEXT 0x00000002
+#define RFX_DECODED_VERSIONS 0x00000004
+#define RFX_DECODED_CHANNELS 0x00000008
+#define RFX_DECODED_HEADERS 0x0000000F
+
+typedef enum
+{
+ RFX_STATE_INITIAL,
+ RFX_STATE_SERVER_UNINITIALIZED,
+ RFX_STATE_SEND_HEADERS,
+ RFX_STATE_SEND_FRAME_DATA,
+ RFX_STATE_FRAME_DATA_SENT,
+ RFX_STATE_FINAL
+} RFX_STATE;
+
+typedef struct S_RFX_TILE_COMPOSE_WORK_PARAM RFX_TILE_COMPOSE_WORK_PARAM;
+
+typedef struct S_RFX_CONTEXT_PRIV RFX_CONTEXT_PRIV;
+struct S_RFX_CONTEXT_PRIV
+{
+ wLog* log;
+ wObjectPool* TilePool;
+
+ BOOL UseThreads;
+ PTP_WORK* workObjects;
+ RFX_TILE_COMPOSE_WORK_PARAM* tileWorkParams;
+
+ DWORD MinThreadCount;
+ DWORD MaxThreadCount;
+
+ PTP_POOL ThreadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+
+ wBufferPool* BufferPool;
+
+ /* profilers */
+ PROFILER_DEFINE(prof_rfx_decode_rgb)
+ PROFILER_DEFINE(prof_rfx_decode_component)
+ PROFILER_DEFINE(prof_rfx_rlgr_decode)
+ PROFILER_DEFINE(prof_rfx_differential_decode)
+ PROFILER_DEFINE(prof_rfx_quantization_decode)
+ PROFILER_DEFINE(prof_rfx_dwt_2d_decode)
+ PROFILER_DEFINE(prof_rfx_ycbcr_to_rgb)
+
+ PROFILER_DEFINE(prof_rfx_encode_rgb)
+ PROFILER_DEFINE(prof_rfx_encode_component)
+ PROFILER_DEFINE(prof_rfx_rlgr_encode)
+ PROFILER_DEFINE(prof_rfx_differential_encode)
+ PROFILER_DEFINE(prof_rfx_quantization_encode)
+ PROFILER_DEFINE(prof_rfx_dwt_2d_encode)
+ PROFILER_DEFINE(prof_rfx_rgb_to_ycbcr)
+ PROFILER_DEFINE(prof_rfx_encode_format_rgb)
+};
+
+struct S_RFX_MESSAGE
+{
+ UINT32 frameIdx;
+
+ /**
+ * The rects array represents the updated region of the frame. The UI
+ * requires to clip drawing destination base on the union of the rects.
+ */
+ UINT16 numRects;
+ RFX_RECT* rects;
+
+ /**
+ * The tiles array represents the actual frame data. Each tile is always
+ * 64x64. Note that only pixels inside the updated region (represented as
+ * rects described above) are valid. Pixels outside of the region may
+ * contain arbitrary data.
+ */
+ UINT16 numTiles;
+ size_t allocatedTiles;
+ RFX_TILE** tiles;
+
+ UINT16 numQuant;
+ UINT32* quantVals;
+
+ UINT32 tilesDataSize;
+
+ BOOL freeArray;
+};
+
+struct S_RFX_MESSAGE_LIST
+{
+ struct S_RFX_MESSAGE* list;
+ size_t count;
+ RFX_CONTEXT* context;
+};
+
+struct S_RFX_CONTEXT
+{
+ RFX_STATE state;
+
+ BOOL encoder;
+ UINT16 flags;
+ UINT16 properties;
+ UINT16 width;
+ UINT16 height;
+ RLGR_MODE mode;
+ UINT32 version;
+ UINT32 codec_id;
+ UINT32 codec_version;
+ UINT32 pixel_format;
+ BYTE bits_per_pixel;
+
+ /* color palette allocated by the application */
+ const BYTE* palette;
+
+ /* temporary data within a frame */
+ UINT32 frameIdx;
+ BYTE numQuant;
+ UINT32* quants;
+ BYTE quantIdxY;
+ BYTE quantIdxCb;
+ BYTE quantIdxCr;
+
+ /* decoded header blocks */
+ UINT32 decodedHeaderBlocks;
+ UINT16 expectedDataBlockType;
+ struct S_RFX_MESSAGE currentMessage;
+
+ /* routines */
+ void (*quantization_decode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values);
+ void (*quantization_encode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values);
+ void (*dwt_2d_decode)(INT16* buffer, INT16* dwt_buffer);
+ void (*dwt_2d_extrapolate_decode)(INT16* src, INT16* temp);
+ void (*dwt_2d_encode)(INT16* buffer, INT16* dwt_buffer);
+ int (*rlgr_decode)(RLGR_MODE mode, const BYTE* WINPR_RESTRICT data, UINT32 data_size,
+ INT16* WINPR_RESTRICT buffer, UINT32 buffer_size);
+ int (*rlgr_encode)(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size,
+ BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size);
+
+ /* private definitions */
+ RFX_CONTEXT_PRIV* priv;
+};
+
+#endif /* FREERDP_LIB_CODEC_RFX_TYPES_H */
diff --git a/libfreerdp/codec/test/CMakeLists.txt b/libfreerdp/codec/test/CMakeLists.txt
new file mode 100644
index 0000000..4258b50
--- /dev/null
+++ b/libfreerdp/codec/test/CMakeLists.txt
@@ -0,0 +1,37 @@
+
+set(MODULE_NAME "TestFreeRDPCodec")
+set(MODULE_PREFIX "TEST_FREERDP_CODEC")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestFreeRDPRegion.c
+ TestFreeRDPCodecMppc.c
+ TestFreeRDPCodecNCrush.c
+ TestFreeRDPCodecXCrush.c
+ TestFreeRDPCodecZGfx.c
+ TestFreeRDPCodecPlanar.c
+ TestFreeRDPCodecClear.c
+ TestFreeRDPCodecInterleaved.c
+ TestFreeRDPCodecProgressive.c
+ TestFreeRDPCodecRemoteFX.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_definitions(-DCMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
+add_definitions(-DCMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}")
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} freerdp winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test")
+
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecClear.c b/libfreerdp/codec/test/TestFreeRDPCodecClear.c
new file mode 100644
index 0000000..ee4bc3b
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecClear.c
@@ -0,0 +1,91 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/platform.h>
+
+#include <freerdp/codec/clear.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR
+/* [MS-RDPEGFX] 4.1.1.1 Example 1 */
+static const BYTE PREPARE_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00";
+static const BYTE TEST_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00";
+WINPR_PRAGMA_DIAG_POP
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 2 */
+static const BYTE TEST_CLEAR_EXAMPLE_2[] =
+ "\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00"
+ "\x00\x00\x4e\x00\x11\x00\x75\x00\x00\x00\x02\x0e\xff\xff\xff\x00"
+ "\x00\x00\xdb\xff\xff\x00\x3a\x90\xff\xb6\x66\x66\xb6\xff\xb6\x66"
+ "\x00\x90\xdb\xff\x00\x00\x3a\xdb\x90\x3a\x3a\x90\xdb\x66\x00\x00"
+ "\xff\xff\xb6\x64\x64\x64\x11\x04\x11\x4c\x11\x4c\x11\x4c\x11\x4c"
+ "\x11\x4c\x00\x47\x13\x00\x01\x01\x04\x00\x01\x00\x00\x47\x16\x00"
+ "\x11\x02\x00\x47\x29\x00\x11\x01\x00\x49\x0a\x00\x01\x00\x04\x00"
+ "\x01\x00\x00\x4a\x0a\x00\x09\x00\x01\x00\x00\x47\x05\x00\x01\x01"
+ "\x1c\x00\x01\x00\x11\x4c\x11\x4c\x11\x4c\x00\x47\x0d\x4d\x00\x4d";
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 3 */
+static const BYTE TEST_CLEAR_EXAMPLE_3[] =
+ "\x00\xdf\x0e\x00\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\xfe\xfe"
+ "\xfe\xff\x80\x05\xff\xff\xff\x40\xfe\xfe\xfe\x40\x00\x00\x3f\x00"
+ "\x03\x00\x0b\x00\xfe\xfe\xfe\xc5\xd0\xc6\xd0\xc7\xd0\x68\xd4\x69"
+ "\xd4\x6a\xd4\x6b\xd4\x6c\xd4\x6d\xd4\x1a\xd4\x1a\xd4\xa6\xd0\x6e"
+ "\xd4\x6f\xd4\x70\xd4\x71\xd4\x72\xd4\x73\xd4\x74\xd4\x21\xd4\x22"
+ "\xd4\x23\xd4\x24\xd4\x25\xd4\xd9\xd0\xda\xd0\xdb\xd0\xc5\xd0\xc5"
+ "\xd0\xdc\xd0\xc2\xd0\x21\xd4\x22\xd4\x23\xd4\x24\xd4\x25\xd4\xc9"
+ "\xd0\xca\xd0\x5a\xd4\x2b\xd1\x28\xd1\x2c\xd1\x75\xd4\x27\xd4\x28"
+ "\xd4\x29\xd4\x2a\xd4\x1a\xd4\x1a\xd4\x1a\xd4\xb7\xd0\xb8\xd0\xb9"
+ "\xd0\xba\xd0\xbb\xd0\xbc\xd0\xbd\xd0\xbe\xd0\xbf\xd0\xc0\xd0\xc1"
+ "\xd0\xc2\xd0\xc3\xd0\xc4\xd0";
+
+/* [MS-RDPEGFX] 4.1.1.1 Example 4 */
+static const BYTE TEST_CLEAR_EXAMPLE_4[] =
+ "\x01\x0b\x78\x00\x00\x00\x00\x00\x46\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x06\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x0f\xff\xff\xff"
+ "\xff\xff\xff\xff\xff\xff\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+ "\xb6\x66\xff\xff\xff\xff\xff\xff\xff\xb6\x66\xdb\x90\x3a\xff\xff"
+ "\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x91\x47\x91\x48\x91"
+ "\x49\x91\x4a\x91\x1b\x91";
+
+static BOOL test_ClearDecompressExample(UINT32 nr, UINT32 width, UINT32 height,
+ const BYTE* pSrcData, const UINT32 SrcSize)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ BYTE* pDstData = calloc(width * height, 4);
+ CLEAR_CONTEXT* clear = clear_context_new(FALSE);
+
+ if (!clear || !pDstData)
+ goto fail;
+
+ status = clear_decompress(clear, pSrcData, SrcSize, width, height, pDstData,
+ PIXEL_FORMAT_XRGB32, 0, 0, 0, width, height, NULL);
+ printf("clear_decompress example %" PRIu32 " status: %d\n", nr, status);
+ fflush(stdout);
+ rc = (status == 0);
+fail:
+ clear_context_free(clear);
+ free(pDstData);
+ return rc;
+}
+
+int TestFreeRDPCodecClear(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Example 1 needs a filled glyph cache
+ if (!test_ClearDecompressExample(1, 8, 9, TEST_CLEAR_EXAMPLE_1,
+ sizeof(TEST_CLEAR_EXAMPLE_1)))
+ return -1;
+ */
+ if (!test_ClearDecompressExample(2, 78, 17, TEST_CLEAR_EXAMPLE_2, sizeof(TEST_CLEAR_EXAMPLE_2)))
+ return -1;
+
+ if (!test_ClearDecompressExample(3, 64, 24, TEST_CLEAR_EXAMPLE_3, sizeof(TEST_CLEAR_EXAMPLE_3)))
+ return -1;
+
+ if (!test_ClearDecompressExample(4, 7, 15, TEST_CLEAR_EXAMPLE_4, sizeof(TEST_CLEAR_EXAMPLE_4)))
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c
new file mode 100644
index 0000000..e8cbb6a
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c
@@ -0,0 +1,219 @@
+
+#include <freerdp/config.h>
+
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/interleaved.h>
+#include <winpr/crypto.h>
+#include <freerdp/utils/profiler.h>
+
+static BOOL run_encode_decode_single(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder,
+ BITMAP_INTERLEAVED_CONTEXT* decoder
+#if defined(WITH_PROFILER)
+ ,
+ PROFILER* profiler_comp, PROFILER* profiler_decomp
+#endif
+)
+{
+ BOOL rc2 = FALSE;
+ BOOL rc = 0;
+ const UINT32 w = 64;
+ const UINT32 h = 64;
+ const UINT32 x = 0;
+ const UINT32 y = 0;
+ const UINT32 format = PIXEL_FORMAT_RGBX32;
+ const UINT32 bstep = FreeRDPGetBytesPerPixel(format);
+ const size_t step = (w + 13) * 4;
+ const size_t SrcSize = step * h;
+ const float maxDiff = 4.0f * ((bpp < 24) ? 2.0f : 1.0f);
+ UINT32 DstSize = SrcSize;
+ BYTE* pSrcData = calloc(1, SrcSize);
+ BYTE* pDstData = calloc(1, SrcSize);
+ BYTE* tmp = calloc(1, SrcSize);
+
+ if (!pSrcData || !pDstData || !tmp)
+ goto fail;
+
+ winpr_RAND(pSrcData, SrcSize);
+
+ if (!bitmap_interleaved_context_reset(encoder) || !bitmap_interleaved_context_reset(decoder))
+ goto fail;
+
+ PROFILER_ENTER(profiler_comp)
+ rc =
+ interleaved_compress(encoder, tmp, &DstSize, w, h, pSrcData, format, step, x, y, NULL, bpp);
+ PROFILER_EXIT(profiler_comp)
+
+ if (!rc)
+ goto fail;
+
+ PROFILER_ENTER(profiler_decomp)
+ rc = interleaved_decompress(decoder, tmp, DstSize, w, h, bpp, pDstData, format, step, x, y, w,
+ h, NULL);
+ PROFILER_EXIT(profiler_decomp)
+
+ if (!rc)
+ goto fail;
+
+ for (UINT32 i = 0; i < h; i++)
+ {
+ const BYTE* srcLine = &pSrcData[i * step];
+ const BYTE* dstLine = &pDstData[i * step];
+
+ for (UINT32 j = 0; j < w; j++)
+ {
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE dr = 0;
+ BYTE dg = 0;
+ BYTE db = 0;
+ const UINT32 srcColor = FreeRDPReadColor(&srcLine[j * bstep], format);
+ const UINT32 dstColor = FreeRDPReadColor(&dstLine[j * bstep], format);
+ FreeRDPSplitColor(srcColor, format, &r, &g, &b, NULL, NULL);
+ FreeRDPSplitColor(dstColor, format, &dr, &dg, &db, NULL, NULL);
+
+ if (fabsf((float)r - dr) > maxDiff)
+ goto fail;
+
+ if (fabsf((float)g - dg) > maxDiff)
+ goto fail;
+
+ if (fabsf((float)b - db) > maxDiff)
+ goto fail;
+ }
+ }
+
+ rc2 = TRUE;
+fail:
+ free(pSrcData);
+ free(pDstData);
+ free(tmp);
+ return rc2;
+}
+
+static const char* get_profiler_name(BOOL encode, UINT16 bpp)
+{
+ switch (bpp)
+ {
+ case 24:
+ if (encode)
+ return "interleaved_compress 24bpp";
+ else
+ return "interleaved_decompress 24bpp";
+
+ case 16:
+ if (encode)
+ return "interleaved_compress 16bpp";
+ else
+ return "interleaved_decompress 16bpp";
+
+ case 15:
+ if (encode)
+ return "interleaved_compress 15bpp";
+ else
+ return "interleaved_decompress 15bpp";
+
+ default:
+ return "configuration error!";
+ }
+}
+
+static BOOL run_encode_decode(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder,
+ BITMAP_INTERLEAVED_CONTEXT* decoder)
+{
+ BOOL rc = FALSE;
+ PROFILER_DEFINE(profiler_comp)
+ PROFILER_DEFINE(profiler_decomp)
+ PROFILER_CREATE(profiler_comp, get_profiler_name(TRUE, bpp))
+ PROFILER_CREATE(profiler_decomp, get_profiler_name(FALSE, bpp))
+
+ for (UINT32 x = 0; x < 50; x++)
+ {
+ if (!run_encode_decode_single(bpp, encoder, decoder
+#if defined(WITH_PROFILER)
+ ,
+ profiler_comp, profiler_decomp
+#endif
+ ))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ PROFILER_PRINT_HEADER
+ PROFILER_PRINT(profiler_comp)
+ PROFILER_PRINT(profiler_decomp)
+ PROFILER_PRINT_FOOTER
+ PROFILER_FREE(profiler_comp)
+ PROFILER_FREE(profiler_decomp)
+ return rc;
+}
+
+static BOOL TestColorConversion(void)
+{
+ const UINT32 formats[] = { PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_ABGR15,
+ PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_RGB16 };
+
+ /* Check color conversion 15/16 -> 32bit maps to proper values */
+ for (UINT32 x = 0; x < ARRAYSIZE(formats); x++)
+ {
+ const UINT32 dstFormat = PIXEL_FORMAT_RGBA32;
+ const UINT32 format = formats[x];
+ const UINT32 colorLow = FreeRDPGetColor(format, 0, 0, 0, 255);
+ const UINT32 colorHigh = FreeRDPGetColor(format, 255, 255, 255, 255);
+ const UINT32 colorLow32 = FreeRDPConvertColor(colorLow, format, dstFormat, NULL);
+ const UINT32 colorHigh32 = FreeRDPConvertColor(colorHigh, format, dstFormat, NULL);
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+ FreeRDPSplitColor(colorLow32, dstFormat, &r, &g, &b, &a, NULL);
+ if ((r != 0) || (g != 0) || (b != 0))
+ return FALSE;
+
+ FreeRDPSplitColor(colorHigh32, dstFormat, &r, &g, &b, &a, NULL);
+ if ((r != 255) || (g != 255) || (b != 255))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestFreeRDPCodecInterleaved(int argc, char* argv[])
+{
+ BITMAP_INTERLEAVED_CONTEXT* encoder = NULL;
+ BITMAP_INTERLEAVED_CONTEXT* decoder = NULL;
+ int rc = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ encoder = bitmap_interleaved_context_new(TRUE);
+ decoder = bitmap_interleaved_context_new(FALSE);
+
+ if (!encoder || !decoder)
+ goto fail;
+
+ if (!run_encode_decode(24, encoder, decoder))
+ goto fail;
+
+ if (!run_encode_decode(16, encoder, decoder))
+ goto fail;
+
+ if (!run_encode_decode(15, encoder, decoder))
+ goto fail;
+
+ if (!TestColorConversion())
+ goto fail;
+
+ rc = 0;
+fail:
+ bitmap_interleaved_context_free(encoder);
+ bitmap_interleaved_context_free(decoder);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecMppc.c b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c
new file mode 100644
index 0000000..b0d70d7
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c
@@ -0,0 +1,1093 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+
+#include "../mppc.h"
+
+static const BYTE TEST_RDP5_COMPRESSED_DATA[] = {
+ 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xbf, 0xdf, 0xc3, 0x20,
+ 0x80, 0x00, 0x1f, 0x0a, 0x00, 0x00, 0x07, 0x43, 0x4e, 0x00, 0x68, 0x02, 0x00, 0x22, 0x00, 0x34,
+ 0xcb, 0xfb, 0xf8, 0x18, 0x40, 0x01, 0x00, 0x27, 0xe2, 0x90, 0x0f, 0xc3, 0x91, 0xa8, 0x00, 0x08,
+ 0x00, 0x00, 0x68, 0x50, 0x60, 0x65, 0xfc, 0x0e, 0xfe, 0x04, 0x00, 0x08, 0x00, 0x06, 0x0c, 0x00,
+ 0x01, 0x00, 0xf8, 0x40, 0x20, 0x00, 0x00, 0x90, 0x00, 0xcf, 0x95, 0x1f, 0x44, 0x90, 0x00, 0x6e,
+ 0x03, 0xf4, 0x40, 0x21, 0x9f, 0x26, 0x01, 0xbf, 0x88, 0x10, 0x90, 0x00, 0x08, 0x04, 0x00, 0x04,
+ 0x30, 0x03, 0xe4, 0xc7, 0xea, 0x05, 0x1e, 0x87, 0xf8, 0x20, 0x1c, 0x00, 0x10, 0x84, 0x22, 0x1f,
+ 0x71, 0x0d, 0x0e, 0xb9, 0x88, 0x9f, 0x5c, 0xee, 0x41, 0x97, 0xfb, 0xf8, 0x88, 0x68, 0x08, 0x6d,
+ 0xd0, 0x44, 0xfc, 0x34, 0x06, 0xe6, 0x16, 0x21, 0x04, 0x11, 0x0f, 0xb9, 0x85, 0x86, 0x5d, 0x44,
+ 0x4f, 0xae, 0xb7, 0x40, 0xa8, 0xcd, 0x5b, 0xed, 0x02, 0xee, 0xc2, 0x21, 0x40, 0x21, 0x21, 0x23,
+ 0x17, 0xb7, 0x00, 0x60, 0x00, 0x3b, 0xfd, 0xfc, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x34, 0x00, 0x33,
+ 0xc7, 0xe0, 0xc0, 0x0f, 0x07, 0x12, 0x42, 0x01, 0xe8, 0x6c, 0xc7, 0x83, 0x07, 0x8c, 0xd4, 0x30,
+ 0x07, 0x20, 0x01, 0x90, 0xa3, 0xf1, 0xdb, 0xf5, 0xd4, 0x13, 0xc2, 0x4f, 0x0f, 0xe5, 0xe2, 0xc7,
+ 0x87, 0xf2, 0xf0, 0x93, 0xc3, 0xf9, 0x78, 0xb0, 0x1a, 0x03, 0xe1, 0xf1, 0xd0, 0x08, 0x4c, 0x66,
+ 0xac, 0x32, 0x31, 0x70, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0xf0, 0x36, 0x1f, 0xe5, 0xe0,
+ 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x34, 0xf9, 0x94, 0x32,
+ 0x31, 0x74, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xbf, 0x87, 0xdf, 0xef, 0xfe, 0x4b, 0xbf, 0x02, 0xfa,
+ 0xde, 0xa7, 0x79, 0x32, 0x44, 0x7c, 0x20, 0x82, 0x00, 0x5f, 0xef, 0xff, 0x09, 0xe1, 0x05, 0x74,
+ 0x32, 0xea, 0x09, 0xe1, 0x0f, 0x55, 0x83, 0x85, 0x2a, 0xa0, 0x1d, 0x50, 0x0e, 0x0e, 0x0b, 0x01,
+ 0x01, 0x43, 0x06, 0x02, 0xbe, 0x5f, 0x00, 0x00, 0x0c, 0x3d, 0x4d, 0x87, 0xa6, 0x5e, 0xa6, 0xcb,
+ 0xc3, 0xcf, 0x53, 0x65, 0xe9, 0x97, 0xa9, 0xb2, 0xf5, 0x9b, 0xd4, 0xd3, 0xee, 0xcd, 0xc0, 0x7c,
+ 0xae, 0xe0, 0x65, 0x1f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc,
+ 0x26, 0xf0, 0x34, 0xfb, 0xb3, 0xf2, 0x41, 0x30, 0x20, 0x04, 0xa0, 0x80, 0x93, 0xf3, 0xf2, 0x1b,
+ 0xed, 0xf6, 0x0f, 0x04, 0x82, 0x7b, 0xcc, 0x00, 0x65, 0xef, 0x4f, 0x86, 0x02, 0xf7, 0xa7, 0xe0,
+ 0x0a, 0x88, 0x1c, 0x34, 0x02, 0x02, 0x02, 0x60, 0x60, 0x49, 0x40, 0xc1, 0x2f, 0x14, 0xca, 0x60,
+ 0xc1, 0x81, 0x80, 0x07, 0xc3, 0x00, 0x00, 0x39, 0xfa, 0x86, 0x38, 0x93, 0x47, 0x08, 0x27, 0x08,
+ 0xfc, 0xb8, 0x4e, 0x38, 0x47, 0xe5, 0xc2, 0x09, 0xc2, 0x3f, 0x2e, 0x13, 0x8e, 0x11, 0xf3, 0xc3,
+ 0x57, 0x1a, 0x88, 0x7d, 0x44, 0x3c, 0x3c, 0x04, 0x0f, 0xd4, 0x3f, 0x83, 0x8d, 0x82, 0x00, 0x25,
+ 0x04, 0x84, 0xdf, 0xe0, 0x17, 0xf8, 0x04, 0x03, 0xe1, 0x47, 0xc4, 0xaf, 0x9c, 0x00, 0x00, 0x31,
+ 0xf5, 0x4c, 0x71, 0x78, 0x8f, 0x54, 0xfb, 0x1c, 0x97, 0xa4, 0x04, 0x13, 0xd5, 0x2f, 0x77, 0xc7,
+ 0xb8, 0x9e, 0xef, 0xcb, 0xc2, 0x6f, 0x77, 0xe5, 0xee, 0x27, 0xbb, 0xf2, 0xf7, 0xe3, 0xdd, 0xf3,
+ 0xc6, 0xfb, 0x2a, 0x78, 0x6d, 0x3c, 0x34, 0x37, 0xc0, 0xaf, 0x25, 0xc7, 0x81, 0x7d, 0x6e, 0x5d,
+ 0x5c, 0xd6, 0xe3, 0x43, 0xc0, 0x82, 0xd0, 0x95, 0x90, 0xd8, 0xbd, 0xfc, 0x00, 0x09, 0xc0, 0x34,
+ 0x39, 0x46, 0x84, 0x20, 0x40, 0x38, 0xa3, 0x42, 0x12, 0xb0, 0x55, 0xbe, 0x28, 0xc0, 0x70, 0x64,
+ 0x28, 0xc8, 0x48, 0x42, 0x08, 0xb2, 0x1b, 0x46, 0xa6, 0x09, 0x54, 0x2e, 0x5f, 0x73, 0x84, 0xfc,
+ 0x28, 0x4a, 0x73, 0x79, 0xf2, 0x6c, 0x5d, 0x82, 0x82, 0x6e, 0xc2, 0x27, 0xd7, 0x6b, 0xb8, 0x4f,
+ 0xa4, 0xa4, 0x22, 0xee, 0x22, 0x7e, 0x10, 0x03, 0x78, 0x08, 0xf4, 0x94, 0x5e, 0x02, 0x01, 0xef,
+ 0x02, 0x27, 0xd7, 0x8b, 0xc8, 0x3f, 0xa4, 0xa4, 0x1a, 0xf3, 0xd1, 0x84, 0x0c, 0x32, 0x31, 0x75,
+ 0x60, 0x05, 0xe2, 0x30, 0xb7, 0xad, 0x5b, 0x15, 0xd5, 0xc3, 0xc0, 0x00, 0x11, 0x81, 0x81, 0x69,
+ 0x8f, 0x06, 0x0f, 0x14, 0xcf, 0xa6, 0xe8, 0xb1, 0x22, 0x77, 0xeb, 0xd7, 0x45, 0x89, 0xf0, 0xb6,
+ 0x3e, 0x23, 0x06, 0x80, 0xf8, 0x5b, 0x0f, 0x04, 0x83, 0xfc, 0x2d, 0x8f, 0x88, 0xc1, 0xa0, 0x3e,
+ 0x16, 0x1d, 0x00, 0x83, 0x74, 0x58, 0xa0, 0xc0, 0x10, 0xce, 0x8b, 0x17, 0xe0, 0x68, 0xff, 0x20,
+ 0xff, 0x03, 0x63, 0xe5, 0xcf, 0x1f, 0xa0, 0x40, 0x00, 0x00, 0x2a, 0xff, 0xd6, 0xd1, 0xc0, 0xb9,
+ 0xe0, 0x5f, 0x6b, 0x81, 0x73, 0xc9, 0x93, 0xd1, 0x63, 0x50, 0xf0, 0x9b, 0xf0, 0x48, 0x4f, 0xaf,
+ 0xe0, 0x1b, 0xef, 0x82, 0x6f, 0xc2, 0x40, 0xe0, 0xe4, 0x60, 0xa0, 0x69, 0xa1, 0xa1, 0xbe, 0xba,
+ 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x42, 0x00, 0x44, 0x00, 0x88, 0x01, 0x10, 0x02,
+ 0x21, 0x02, 0x22, 0x04, 0x44, 0x08, 0x9c, 0x8f, 0xcd, 0xe0, 0x02, 0x20, 0x88, 0x02, 0x10, 0x40,
+ 0x01, 0xf0, 0x60, 0x44, 0xc0, 0xce, 0xb1, 0x8f, 0xd0, 0x30, 0x00, 0x60, 0x00, 0xa0, 0x00, 0xc4,
+ 0x00, 0xcc, 0x01, 0x98, 0x03, 0x28, 0x03, 0x31, 0x03, 0x33, 0x06, 0x66, 0x07, 0x0e, 0x2c, 0xe3,
+ 0x7b, 0x18, 0x85, 0xc7, 0xd6, 0x51, 0x71, 0x0f, 0x0e, 0xb8, 0x88, 0x9f, 0x5c, 0x6e, 0x41, 0xde,
+ 0xeb, 0x71, 0x20, 0x5c, 0xba, 0xf7, 0xc8, 0x6f, 0xba, 0xc1, 0xf7, 0x30, 0xd0, 0xce, 0xc1, 0x31,
+ 0x74, 0xec, 0x13, 0x41, 0x77, 0x41, 0x13, 0xa0, 0x10, 0xbf, 0x7c, 0x45, 0xd3, 0xa5, 0xbc, 0x55,
+ 0x84, 0xaa, 0x41, 0xc1, 0xc1, 0xe0, 0xe0, 0x29, 0x01, 0x20, 0x81, 0x00, 0x03, 0x80, 0x07, 0xc0,
+ 0x0f, 0xe0, 0x06, 0xbe, 0x16, 0x75, 0xe7, 0x9f, 0xfb, 0x1e, 0x17, 0x90, 0xef, 0x0b, 0xbb, 0x15,
+ 0x03, 0x7c, 0x2b, 0x7e, 0x22, 0x78, 0x56, 0x83, 0xae, 0x77, 0x40, 0xcf, 0xb0, 0xf0, 0x98, 0x28,
+ 0x04, 0x2f, 0xaf, 0x0e, 0x40, 0xfc, 0x01, 0x1c, 0x5c, 0xb1, 0xf2, 0xbf, 0xa5, 0xd7, 0x8f, 0x97,
+ 0xc0, 0xfe, 0x9f, 0x02, 0xe7, 0x24, 0x79, 0xe0, 0x9b, 0xa9, 0xfd, 0x74, 0x3b, 0xaf, 0x2d, 0xf8,
+ 0x4b, 0xd2, 0xf7, 0x84, 0x54, 0x04, 0x2a, 0x02, 0x02, 0x01, 0xe1, 0x1e, 0xf0, 0x87, 0xff, 0x77,
+ 0x07, 0x00, 0x02, 0x00, 0x0d, 0xbd, 0xe1, 0xf0, 0x01, 0x1e, 0xf0, 0xfd, 0x80, 0x4c, 0x24, 0x11,
+ 0x2c, 0x10, 0x24, 0x02, 0x01, 0x40, 0xb0, 0x5c, 0x2c, 0x14, 0x08, 0x07, 0x1b, 0x80, 0x01, 0xa7,
+ 0xbd, 0x3e, 0x00, 0x27, 0xde, 0x9f, 0xb0, 0x85, 0x01, 0xfb, 0xd2, 0x04, 0x0c, 0x1c, 0x2e, 0x0e,
+ 0x06, 0x18, 0x03, 0xd4, 0x00, 0x00, 0x67, 0xef, 0x4f, 0x80, 0x0a, 0xf7, 0xa7, 0xe3, 0x94, 0xe0,
+ 0xe0, 0x10, 0x1b, 0xfd, 0xfc, 0x74, 0x62, 0xe8, 0xc0, 0x1d, 0x62, 0x00, 0x0b, 0x00, 0xb7, 0x70,
+ 0xe6, 0x8a, 0x68, 0x75, 0x38, 0x3c, 0x3c, 0x4c, 0x2f, 0x87, 0xef, 0x01, 0xc7, 0xb2, 0x40, 0x21,
+ 0xa3, 0x23, 0x0a, 0x08, 0x01, 0xa1, 0xa1, 0xe1, 0x80, 0x69, 0x40, 0xe1, 0x00, 0x00, 0x40, 0xd0,
+ 0xea, 0xe5, 0xe1, 0xc0, 0x81, 0x87, 0xed, 0x68, 0x1a, 0x08, 0x94, 0x0c, 0x0c, 0xf1, 0x7c, 0xbe,
+ 0x5f, 0x2f, 0x8f, 0x00, 0x00, 0x0d, 0x1f, 0x68, 0x7a, 0x1a, 0x04, 0x05, 0xce, 0xe6, 0x2a, 0x0c,
+ 0x01, 0xc2, 0x00, 0x40, 0x42, 0x61, 0xc0, 0x49, 0x41, 0x60, 0xa0, 0x80, 0x01, 0xc0, 0x03, 0xe0,
+ 0x07, 0xf0, 0x07, 0xfa, 0x00, 0x07, 0x3b, 0x99, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0xe0, 0x60,
+ 0xee, 0xd0, 0x0e, 0x19, 0x0a, 0x03, 0xa5, 0x7d, 0x05, 0xd0, 0x83, 0x98, 0x5a, 0x96, 0x21, 0x4b,
+ 0x10, 0x10, 0xe6, 0x17, 0xaf, 0xeb, 0xaf, 0x34, 0x3c, 0xc8, 0x0f, 0xf0, 0x64, 0x3f, 0xd0, 0x0f,
+ 0xe0, 0x03, 0xfe, 0x10, 0x02, 0x7d, 0x47, 0x2d, 0x58, 0xfc, 0x35, 0xe0, 0xca, 0x0f, 0x19, 0x0a,
+ 0xf9, 0xf1, 0xe0, 0xb9, 0xc0, 0x81, 0x10, 0x03, 0xe0, 0xbd, 0x4f, 0xea, 0x61, 0xf7, 0xeb, 0xf6,
+ 0x02, 0xd4, 0x7a, 0xf9, 0xff, 0x15, 0x30, 0xfa, 0x88, 0x68, 0x68, 0xd8, 0x80, 0x12, 0x60, 0x50,
+ 0x50, 0xf0, 0x03, 0xfc, 0x01, 0xfe, 0x01, 0x7f, 0xa0, 0x7c, 0x28, 0xbf, 0xd0, 0x3e, 0x64, 0x0f,
+ 0x00, 0x37, 0x00, 0x08, 0x80, 0x20, 0x0b, 0x88, 0x81, 0xa5, 0x04, 0x84, 0x60, 0x40, 0x36, 0x04,
+ 0x1b, 0x8f, 0x88, 0x01, 0x00, 0xa1, 0x80, 0x1e, 0x00, 0x36, 0xfd, 0xb9, 0x12, 0x02, 0x4c, 0x09,
+ 0x08, 0x1e, 0x00, 0x61, 0x80, 0x20, 0x60, 0x44, 0x17, 0xdc, 0x7c, 0x62, 0x00, 0x03, 0x67, 0xdb,
+ 0x81, 0xb1, 0x30, 0x34, 0xb0, 0xa0, 0xaf, 0xa0, 0x80, 0x75, 0x35, 0x20, 0x7c, 0x49, 0xfc, 0x0f,
+ 0xf5, 0x0d, 0x7f, 0x7e, 0x45, 0x00, 0x53, 0x42, 0x82, 0x83, 0xc0, 0x0c, 0x28, 0x1f, 0x72, 0x3e,
+ 0xd3, 0xf5, 0x62, 0xd4, 0x00, 0x22, 0xa8, 0x81, 0xec, 0x67, 0x96, 0x02, 0xa0, 0x49, 0x7d, 0xfd,
+ 0x6b, 0xbf, 0xcc, 0x7c, 0x4a, 0xf8, 0xd0, 0x00, 0x00, 0xcf, 0xd5, 0xd2, 0x23, 0x35, 0x60, 0x01,
+ 0xf1, 0x60, 0x14, 0xc0, 0xb0, 0xbe, 0xb3, 0x02, 0x0f, 0x89, 0x5f, 0x1b, 0x00, 0x02, 0x0b, 0xfd,
+ 0x80, 0x00, 0x01, 0x9b, 0xf3, 0x40, 0x42, 0x10, 0x00, 0xd8, 0xb8, 0x0f, 0xa8, 0x17, 0xfe, 0x59,
+ 0xef, 0x14, 0x61, 0xf2, 0x30, 0x65, 0xfc, 0x51, 0xe2, 0xc1, 0x18, 0xc0, 0x07, 0x5e, 0x68, 0x08,
+ 0xe8, 0x46, 0xf8, 0x95, 0xf1, 0xb0, 0xf9, 0x13, 0x7f, 0xbc, 0x00, 0x00, 0x32, 0x7e, 0xa8, 0xeb,
+ 0xcd, 0x03, 0x20, 0x09, 0xa1, 0x81, 0x97, 0xfb, 0x87, 0x80, 0xb0, 0xf9, 0x19, 0x7c, 0xa8, 0x63,
+ 0xf3, 0xe6, 0x20, 0x22, 0xbd, 0x85, 0x9e, 0x62, 0x00, 0x8b, 0x7c, 0x87, 0x91, 0x00, 0x22, 0xff,
+ 0x21, 0xe2, 0xa0, 0x08, 0xc7, 0xc8, 0x78, 0x20, 0x02, 0x33, 0xf2, 0x1c, 0x10, 0x41, 0xe3, 0x40,
+ 0x69, 0x7c, 0x45, 0x72, 0x62, 0xf0, 0x04, 0x7f, 0x60, 0x68, 0x6f, 0x80, 0x00, 0x08, 0x1f, 0xf7,
+ 0xad, 0x51, 0x03, 0xf3, 0xf8, 0xa0, 0x9d, 0xa8, 0x40, 0x00, 0x23, 0x42, 0x37, 0x46, 0x0f, 0xde,
+ 0xa6, 0x06, 0xd3, 0x3c, 0x33, 0xe1, 0x78, 0xd8, 0x34, 0x32, 0x14, 0x67, 0xdb, 0xd2, 0x38, 0xaf,
+ 0xc7, 0x9c, 0xdf, 0xd0, 0x21, 0xe6, 0xd7, 0x80, 0x40, 0x22, 0x3f, 0x21, 0xe8, 0xd8, 0x12, 0xf9,
+ 0x0f, 0xb4, 0x01, 0x13, 0xf9, 0x0f, 0x46, 0xc0, 0xa7, 0x13, 0x37, 0x1e, 0x67, 0x07, 0x8b, 0x01,
+ 0xfd, 0xfe, 0x0f, 0xf7, 0x7a, 0xf0, 0x16, 0x36, 0x0a, 0x92, 0x08, 0x08, 0xc1, 0x70, 0xb8, 0x30,
+ 0x34, 0xf1, 0xf3, 0x72, 0x27, 0x8f, 0x4b, 0x60, 0x21, 0xc4, 0xdd, 0xe2, 0xdf, 0x0b, 0xca, 0x4f,
+ 0x2e, 0x4f, 0x9c, 0xde, 0x59, 0xe9, 0xf1, 0x55, 0x00, 0x8d, 0xf2, 0x20, 0x53, 0x3c, 0xc4, 0xf6,
+ 0x46, 0x7e, 0x24, 0xee, 0xf2, 0x0c, 0x0d, 0x81, 0x83, 0xf9, 0x98, 0x0e, 0x00, 0x02, 0x10, 0x11,
+ 0x01, 0x08, 0x95, 0x2a, 0xfc, 0x28, 0x95, 0x2a, 0x84, 0x80, 0xbf, 0x81, 0x06, 0x80, 0x0d, 0x00,
+ 0x86, 0xe0, 0x6b, 0xa5, 0xc3, 0xd8, 0x8f, 0x22, 0xa0, 0x3e, 0xe9, 0x8f, 0x90, 0xf2, 0x6b, 0x85,
+ 0x77, 0x57, 0x99, 0x43, 0x5c, 0x66, 0x5f, 0x9e, 0x85, 0x7c, 0x3f, 0x1f, 0xb3, 0xce, 0xc0, 0x0e,
+ 0x64, 0x20, 0x0e, 0x20, 0xdc, 0x7e, 0x18, 0x81, 0x90, 0xa3, 0x13, 0x4e, 0x52, 0x71, 0x81, 0x03,
+ 0xa4, 0x30, 0x30, 0x6c, 0x73, 0x8f, 0xc4, 0x50, 0x60, 0x16, 0x38, 0x03, 0xbf, 0x6f, 0x89, 0x3e,
+ 0x00, 0x77, 0x00, 0xb1, 0xc0, 0x28, 0x3d, 0x73, 0x98, 0x06, 0xfe, 0x00, 0xe9, 0x81, 0xa3, 0xb8,
+ 0x1c, 0x85, 0x20, 0x45, 0x45, 0xe1, 0xa1, 0x23, 0x63, 0xa0, 0x29, 0x61, 0x41, 0x27, 0xf4, 0x03,
+ 0xfa, 0x01, 0x02, 0x05, 0xff, 0xe1, 0x20, 0x34, 0x08, 0x08, 0x04, 0x04, 0x02, 0xff, 0xeb, 0x96,
+ 0x05, 0x24, 0x8e, 0x0a, 0xb1, 0xce, 0xf2, 0x06, 0xc7, 0xb9, 0x01, 0xd7, 0x20, 0x52, 0x04, 0x03,
+ 0xe1, 0x47, 0xc4, 0xa4, 0x0b, 0xfd, 0x03, 0x01, 0xc0, 0x47, 0xe6, 0xc0, 0x2c, 0x7c, 0x09, 0x10,
+ 0x1c, 0x0a, 0xfd, 0x7e, 0xc0, 0xd2, 0x94, 0x7a, 0x1a, 0x06, 0x07, 0xcf, 0x12, 0x2a, 0x8c, 0x1e,
+ 0xe7, 0x07, 0x08, 0x81, 0x81, 0x91, 0x90, 0x72, 0x26, 0x9e, 0x55, 0x44, 0x0e, 0x4d, 0x21, 0x00,
+ 0x08, 0x40, 0x02, 0x20, 0x01, 0x17, 0x2c, 0xd4, 0x22, 0x00, 0x88, 0x80, 0x44, 0x40, 0x23, 0xcd,
+ 0xf8, 0xf1, 0xc8, 0x9b, 0x02, 0x10, 0x0c, 0x02, 0x99, 0x30, 0x00, 0x0a, 0x06, 0x01, 0x4b, 0x18,
+ 0x00, 0x46, 0x00, 0x29, 0x9c, 0xa3, 0x86, 0x60, 0x11, 0x98, 0x05, 0x32, 0x80, 0xcc, 0xc0, 0xf3,
+ 0xc3, 0xb8, 0x7a, 0x21, 0x7d, 0xbe, 0xfa, 0xce, 0x2a, 0x9d, 0xfa, 0xa0, 0x3c, 0x32, 0xfb, 0x7d,
+ 0x13, 0x22, 0x05, 0xeb, 0x0b, 0xbb, 0xb8, 0x00, 0x15, 0xfe, 0xfe, 0x1a, 0x14, 0x7e, 0x1c, 0x00,
+ 0x01, 0x82, 0x3a, 0xa7, 0xd2, 0x6c, 0x11, 0xdd, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x18, 0x23, 0x5a,
+ 0x00, 0x80, 0xb0, 0x47, 0x84, 0x7c, 0xa8, 0x03, 0xa7, 0x82, 0x48, 0x83, 0x01, 0x50, 0x11, 0x2a,
+ 0x37, 0xfb, 0xfc, 0x03, 0x03, 0xd1, 0xa3, 0x35, 0x68, 0xcd, 0x58, 0x40, 0x03, 0xe3, 0x47, 0xc4,
+ 0xaf, 0x8d, 0x1f, 0x42, 0x84, 0x20, 0x81, 0x08, 0x57, 0xfb, 0xff, 0xd0, 0x98, 0x27, 0xc8, 0xaf,
+ 0x99, 0x1f, 0x12, 0x04, 0x3e, 0x84, 0xfe, 0x08, 0x1c, 0xc1, 0x31, 0x58, 0x80, 0x3a, 0xd1, 0x99,
+ 0x8a, 0x40, 0x02, 0x5a, 0x04, 0x00, 0x02, 0x1a, 0x38, 0xf3, 0x08, 0x00, 0x01, 0xda, 0xe3, 0x35,
+ 0x60, 0x5f, 0x88, 0x00, 0x03, 0x6e, 0xbf, 0xdf, 0xc0, 0xbe, 0x20, 0x00, 0x42, 0x80, 0x01, 0x77,
+ 0x9e, 0x80, 0xd0, 0x30, 0x4a, 0x32, 0x81, 0xe3, 0x94, 0x04, 0x21, 0x0a, 0x9c, 0xcc, 0x52, 0x03,
+ 0x7d, 0xa7, 0x0c, 0x51, 0x80, 0x6f, 0xa5, 0xc0, 0x3f, 0x3e, 0x80, 0xa0, 0x22, 0x10, 0x40, 0x68,
+ 0x17, 0x9f, 0x60, 0x1e, 0x9b, 0x09, 0x52, 0x03, 0x2d, 0x03, 0x81, 0x88, 0x41, 0x3c, 0x65, 0x14,
+ 0x98, 0xcd, 0x58, 0x6a, 0x04, 0x21, 0x80, 0x9b, 0x81, 0x45, 0x21, 0x24, 0xe1, 0x8c, 0xf1, 0x9a,
+ 0xb0, 0xa9, 0x38, 0xef, 0xe7, 0x90, 0xdf, 0x98, 0x00, 0x19, 0xa8, 0x18, 0x42, 0x6a, 0xc0, 0x7f,
+ 0xda, 0x00, 0x00, 0x2b, 0x1e, 0x36, 0x7c, 0xaa, 0xa0, 0x00, 0xc0, 0xf8, 0xa0, 0xbe, 0x60, 0x2e,
+ 0xb1, 0x09, 0xab, 0x60, 0x3e, 0x38, 0xf9, 0x6f, 0xa9, 0x3e, 0x08, 0x81, 0xa6, 0x8c, 0x13, 0xae,
+ 0x83, 0x7e, 0x0a, 0xfb, 0x0f, 0x60, 0x86, 0x3e, 0x90, 0x6d, 0xa2, 0x33, 0x56, 0x06, 0xfa, 0xcf,
+ 0xc5, 0x1f, 0x12, 0x38, 0x49, 0x3d, 0x04, 0x03, 0xa6, 0x42, 0x54, 0x82, 0x3e, 0xd3, 0xd1, 0xd0,
+ 0x08, 0x58, 0x06, 0xdc, 0x10, 0x85, 0xe8, 0xf8, 0xf8, 0x94, 0x10, 0x84, 0x21, 0xe7, 0xa3, 0x85,
+ 0xfe, 0xfe, 0xc1, 0xe9, 0x77, 0xa3, 0x27, 0xe7, 0xbd, 0x31, 0x98, 0x17, 0xa1, 0xe2, 0x13, 0xe8,
+ 0x5a, 0xf1, 0x44, 0x7c, 0x4a, 0x00, 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xa3, 0x46, 0x6a, 0xc1,
+ 0x9e, 0x9f, 0x9f, 0x51, 0xc0, 0x55, 0x1a, 0x13, 0x56, 0x0e, 0xf4, 0xa4, 0x85, 0xfd, 0x4c, 0x47,
+ 0x10, 0x0d, 0x70, 0x24, 0x9b, 0xfa, 0x45, 0x41, 0x3a, 0x33, 0xea, 0x28, 0x60, 0x00, 0x80, 0x00,
+ 0xbc, 0x00, 0x80, 0x7b, 0x2e, 0x43, 0x10, 0x0b, 0x00, 0xec, 0x1e, 0x98, 0x8a, 0xb4, 0x26, 0xac,
+ 0x5f, 0xf9, 0x20, 0x03, 0xf2, 0xc1, 0xdf, 0xca, 0x14, 0x40, 0x07, 0x40, 0x1e, 0x00, 0x3d, 0x10,
+ 0xe1, 0x37, 0x90, 0x64, 0x17, 0xec, 0x3d, 0x4c, 0xf5, 0x94, 0x20, 0x15, 0x80, 0xdc, 0x3e, 0x74,
+ 0x7f, 0x87, 0x87, 0xa9, 0xa6, 0x33, 0x56, 0x16, 0xfd, 0xcf, 0xa9, 0x1f, 0x12, 0x23, 0x35, 0x60,
+ 0xaf, 0xa4, 0x04, 0xf5, 0xb0, 0x1f, 0xe4, 0x3d, 0x75, 0x1c, 0x20, 0xeb, 0xd7, 0x19, 0x00, 0xb8,
+ 0x04, 0x21, 0x7a, 0xd3, 0xbe, 0x15, 0xeb, 0x4a, 0xf1, 0x84, 0x78, 0x52, 0x3e, 0x25, 0x03, 0x16,
+ 0x81, 0xc3, 0x7d, 0x59, 0x1f, 0x12, 0x30, 0x50, 0xe3, 0xe1, 0xcf, 0xc5, 0x8f, 0xa1, 0x1c, 0x0e,
+ 0x9e, 0xd0, 0x0d, 0x7b, 0x18, 0x14, 0xcc, 0x21, 0x04, 0x1b, 0x6a, 0x8c, 0xd5, 0x86, 0xe0, 0x31,
+ 0x9a, 0xb0, 0x4f, 0xc8, 0x0b, 0x7c, 0x40, 0x37, 0xc4, 0x5c, 0x22, 0x80, 0x3e, 0x54, 0x71, 0x10,
+ 0xbf, 0x26, 0xf9, 0xa2, 0x1c, 0x0b, 0x82, 0xf0, 0x8f, 0x22, 0x47, 0x8a, 0xab, 0xca, 0xd4, 0x31,
+ 0x08, 0xf1, 0xe6, 0x51, 0x9a, 0xb7, 0xcc, 0x80, 0x7f, 0xc9, 0xc2, 0x13, 0x08, 0xfd, 0x95, 0xfe,
+ 0x23, 0xc0, 0x14, 0x0f, 0x08, 0xe1, 0xb5, 0x5f, 0x4a, 0x38, 0x10, 0x47, 0x1b, 0x17, 0x0a, 0x07,
+ 0x1d, 0x38, 0xe3, 0xcb, 0x42, 0x10, 0x4f, 0x5d, 0x40, 0x3f, 0xf8, 0xe1, 0x0a, 0xe0, 0x45, 0xa8,
+ 0x47, 0xe0, 0x78, 0x23, 0x0f, 0x91, 0x5f, 0x4a, 0x7f, 0xe3, 0xc9, 0x11, 0xe0, 0x4a, 0x09, 0xfe,
+ 0x5a, 0xf0, 0xea, 0x8f, 0x21, 0x57, 0x82, 0xa3, 0xfa, 0x47, 0xc4, 0x8e, 0x0d, 0x8f, 0xcc, 0xfe,
+ 0x11, 0xf1, 0x22, 0x33, 0x56, 0xe1, 0xf9, 0x1f, 0x9a, 0x83, 0x79, 0x2d, 0xe3, 0xf5, 0x23, 0xf6,
+ 0x50, 0x64, 0x17, 0xce, 0x4f, 0x12, 0x58, 0x5f, 0xe0, 0xc4, 0x32, 0x0d, 0xfc, 0xab, 0xd5, 0x54,
+ 0x15, 0x04, 0xfd, 0x91, 0xf1, 0x20, 0x32, 0x0d, 0xe1, 0x48, 0xf8, 0x91, 0xe5, 0x48, 0x09, 0xfc,
+ 0xdb, 0x7b, 0xab, 0x84, 0x22, 0x0d, 0xfd, 0x23, 0xda, 0xd1, 0xf2, 0x20, 0x2a, 0x11, 0xfe, 0x23,
+ 0xe7, 0x4f, 0x8c, 0x2f, 0x80, 0xe7, 0x1f, 0x09, 0x40, 0x2f, 0x00, 0xee, 0x7f, 0xf5, 0x1f, 0x12,
+ 0x3c, 0x0d, 0x40, 0xff, 0xa9, 0xc3, 0x1b, 0x01, 0x42, 0xce, 0x18, 0x5b, 0x52, 0xd9, 0x8a, 0x79,
+ 0xa7, 0xbc, 0xc5, 0x01, 0x08, 0x41, 0x21, 0xb5, 0xfc, 0x1b, 0x93, 0x1e, 0x8f, 0x60, 0x02, 0x98,
+ 0xf8, 0xe0, 0x0c, 0x1c, 0x2e, 0x15, 0x00, 0xe7, 0x61, 0x08, 0x02, 0xfd, 0x16, 0x5c, 0xdb, 0xf2,
+ 0xb8, 0x4f, 0x03, 0xfd, 0x81, 0x8a, 0x88, 0x52, 0x05, 0x20, 0x0e, 0xe9, 0xf9, 0xaa, 0xed, 0x7f,
+ 0xbf, 0xd0, 0x0b, 0x0b, 0x42, 0x60, 0x85, 0xa1, 0x3f, 0x0a, 0x0b, 0x42, 0x40, 0x08, 0xa8, 0x02,
+ 0x04, 0xa9, 0x60, 0x46, 0x00, 0x45, 0x40, 0x5c, 0xa7, 0xa6, 0xfa, 0x5c, 0x07, 0xf0, 0xe0, 0xa4,
+ 0x0f, 0x94, 0xc4, 0x16, 0x82, 0x96, 0x82, 0x94, 0x83, 0x71, 0x76, 0x04, 0x94, 0x8f, 0xa1, 0xf3,
+ 0x40, 0x00, 0x93, 0x85, 0xa2, 0x50, 0xc0, 0x00, 0x28, 0x1c, 0xbb, 0x03, 0x09, 0x12, 0x5e, 0x91,
+ 0xaf, 0x21, 0x42, 0x05, 0x09, 0x6b, 0xe5, 0x59, 0x27, 0xcf, 0x8f, 0x88, 0x24, 0x00, 0x90, 0x7c,
+ 0x60, 0x00, 0x00, 0x17, 0x1a, 0x02, 0x40, 0x2c, 0x03, 0x94, 0x1a, 0xf8, 0x02, 0xa0, 0x80, 0xd2,
+ 0x15, 0xf5, 0x64, 0x00, 0xc0, 0x32, 0x01, 0x83, 0xa4, 0xc0, 0x5e, 0xb2, 0x0e, 0x70, 0x9a, 0x7b,
+ 0x12, 0x23, 0x35, 0x6f, 0x26, 0x43, 0x7f, 0x40, 0x6a, 0x04, 0xe8, 0x14, 0x04, 0xa4, 0xb3, 0x14,
+ 0x81, 0x30, 0x2f, 0x16, 0x84, 0xd0, 0x0c, 0x0b, 0x42, 0x6e, 0x14, 0x00, 0x9a, 0x00, 0x87, 0x76,
+ 0x80, 0x07, 0x98, 0x2c, 0x03, 0x99, 0x9c, 0xf3, 0xbb, 0x7f, 0xb8, 0xa4, 0xdb, 0xde, 0xfc, 0x4a,
+ 0x00, 0x05, 0xa4, 0xc2, 0x6a, 0xc0, 0xed, 0x3d, 0x15, 0xc1, 0x04, 0xe1, 0x30, 0x2e, 0x2c, 0xf1,
+ 0x50, 0x69, 0x84, 0xa9, 0x0f, 0xf8, 0xc2, 0xbe, 0x35, 0xa8, 0x87, 0x50, 0x10, 0x0e, 0x00, 0xe5,
+ 0x1e, 0xc6, 0xa9, 0x55, 0xfe, 0xff, 0x48, 0xf5, 0xe0, 0x53, 0xdc, 0x78, 0x80, 0x10, 0x51, 0x89,
+ 0x52, 0xc0, 0x06, 0xab, 0x03, 0x14, 0x6f, 0xed, 0x85, 0xde, 0x80, 0x03, 0x09, 0x52, 0xe5, 0xff,
+ 0x5e, 0x02, 0xbf, 0x8f, 0x8f, 0xc9, 0xcf, 0xe5, 0xeb, 0xf3, 0x72, 0xbb, 0x80, 0x00, 0xc6, 0x6a,
+ 0xd8, 0x08, 0x95, 0xf4, 0xb2, 0xf9, 0x4f, 0xa1, 0xc1, 0xc2, 0x5a, 0xef, 0xf7, 0xfa, 0x81, 0xdd,
+ 0xbd, 0xef, 0xee, 0xe0, 0xd1, 0xe5, 0x72, 0xc5, 0xcd, 0xf0, 0x2c, 0x00, 0x03, 0xcb, 0x98, 0xf0,
+ 0x7f, 0x52, 0x00
+};
+
+static const BYTE TEST_RDP5_UNCOMPRESSED_DATA[] = {
+ 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xff, 0xff, 0x0c, 0x84,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x0d, 0x38, 0x01, 0xc0, 0x10, 0x01, 0x10,
+ 0x01, 0xcc, 0xff, 0x7f, 0x03, 0x08, 0x00, 0x20, 0x04, 0x05, 0x10, 0x01, 0x40, 0x0a, 0x00, 0x0c,
+ 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x0a,
+ 0x0c, 0x0c, 0xff, 0x03, 0xff, 0x02, 0x00, 0x04, 0x00, 0x03, 0x06, 0x00, 0x00, 0x80, 0x00, 0x80,
+ 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, 0x0c, 0x80, 0x00, 0x80, 0x00, 0x06, 0x00, 0x00, 0x48, 0x00,
+ 0x37, 0x01, 0x02, 0x00, 0x00, 0x01, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x06, 0x01, 0x00, 0x00, 0x04,
+ 0x24, 0x00, 0x02, 0x01, 0x00, 0x01, 0x0c, 0x00, 0x04, 0x24, 0x00, 0x02, 0x00, 0x00, 0x09, 0x0a,
+ 0x3d, 0x0f, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3,
+ 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6,
+ 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x09, 0x18, 0xfb, 0x70, 0x06, 0x00, 0x03,
+ 0xff, 0xff, 0x00, 0x03, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x0c, 0x00, 0x00, 0x80, 0x0c, 0x00, 0x0f,
+ 0x00, 0x01, 0x49, 0x08, 0x07, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x72, 0x00, 0x19,
+ 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01,
+ 0x11, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e,
+ 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18,
+ 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11,
+ 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x0b,
+ 0x01, 0x01, 0x43, 0x06, 0x02, 0xfc, 0xfc, 0x00, 0x00, 0x30, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe,
+ 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08,
+ 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f,
+ 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6,
+ 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01,
+ 0x01, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3,
+ 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01,
+ 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99,
+ 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00,
+ 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84,
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x13, 0x02, 0x00, 0x4a, 0x08,
+ 0x09, 0x3f, 0x3f, 0x21, 0xfd, 0xfd, 0x87, 0x84, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x32, 0x00, 0x19,
+ 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01,
+ 0x11, 0x01, 0x01, 0x01, 0x02, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e,
+ 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18,
+ 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11,
+ 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x13, 0x03, 0x02, 0x4a,
+ 0x06, 0x09, 0x78, 0xcc, 0xcc, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x73, 0x00,
+ 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3,
+ 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84,
+ 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0xd1,
+ 0x0f, 0xd1, 0x0f, 0x0f, 0x01, 0x03, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11,
+ 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19,
+ 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe,
+ 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff,
+ 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f,
+ 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6,
+ 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1b, 0x04, 0x00,
+ 0x4a, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0xff, 0x80, 0x00, 0x00, 0x31, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04,
+ 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11,
+ 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b,
+ 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a,
+ 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x04, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0xcf,
+ 0x0d, 0xcf, 0x0d, 0x0d, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1,
+ 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5,
+ 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4,
+ 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xee, 0x34, 0x3c, 0x08, 0x2d, 0x09, 0x59, 0x0d, 0x97,
+ 0xff, 0x00, 0x02, 0x70, 0x0d, 0x0e, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6,
+ 0x51, 0xc2, 0x12, 0x30, 0x1c, 0x19, 0x0a, 0x32, 0x12, 0x10, 0x84, 0x59, 0x0d, 0xc6, 0xcc, 0x12,
+ 0xd0, 0xf2, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6, 0x51, 0xc2, 0x12, 0x30,
+ 0x1c, 0x19, 0x0a, 0x3f, 0x0a, 0x12, 0xb9, 0xf9, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6,
+ 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01,
+ 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99,
+ 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf5, 0x60, 0x05, 0x00,
+ 0x00, 0x00, 0xef, 0x5a, 0xec, 0x57, 0x57, 0x0f, 0x00, 0x00, 0x46, 0x06, 0x05, 0xcc, 0x78, 0x30,
+ 0x78, 0xcc, 0x00, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0xff, 0xff,
+ 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0x08, 0x42, 0x11, 0x0d, 0x01,
+ 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x99, 0xd6, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8,
+ 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x3a, 0x01, 0x06,
+ 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f,
+ 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7,
+ 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x10, 0x84,
+ 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99, 0xd6, 0x11, 0x0d, 0x01,
+ 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0a, 0xff, 0x0a,
+ 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09,
+ 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08,
+ 0x01, 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07,
+ 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0xff,
+ 0xff, 0x00, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x19, 0x0a, 0x0f, 0x09, 0xfe, 0x09, 0x09, 0x19, 0x18, 0xf5, 0x60, 0x06, 0xff, 0xff,
+ 0x00, 0x09, 0xfe, 0x12, 0x07, 0x07, 0x23, 0x05, 0x03, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x88, 0x01, 0x10, 0x02, 0x20, 0x04, 0x40, 0x08, 0x88,
+ 0x11, 0x10, 0x22, 0x20, 0x44, 0x40, 0x00, 0x00, 0x6f, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00,
+ 0x1f, 0x06, 0x04, 0x4c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x60, 0x00, 0xc0,
+ 0x01, 0x90, 0x03, 0x30, 0x06, 0x60, 0x0c, 0xc0, 0x19, 0x90, 0x33, 0x30, 0x66, 0x60, 0x70, 0x00,
+ 0x19, 0x0a, 0x37, 0xe3, 0x10, 0xf1, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd6, 0x12, 0xd2, 0x0e, 0x0e, 0x0f, 0x07, 0x01, 0x48, 0x09, 0x04, 0x08, 0x00, 0x1c, 0x00,
+ 0x3e, 0x00, 0x7f, 0x00, 0x35, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3,
+ 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x01, 0x07, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99,
+ 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f,
+ 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2,
+ 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84,
+ 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01,
+ 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x0f, 0x08, 0x01, 0x48, 0x09, 0x04, 0x7f, 0x00, 0x3e, 0x00,
+ 0x1c, 0x00, 0x08, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11,
+ 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3,
+ 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x01, 0x08, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11,
+ 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d,
+ 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11,
+ 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99,
+ 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f,
+ 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2,
+ 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84,
+ 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01,
+ 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11,
+ 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19,
+ 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e,
+ 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d,
+ 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11,
+ 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00,
+ 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x09, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0,
+ 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d,
+ 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c,
+ 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19,
+ 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x09, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5,
+ 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6,
+ 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3,
+ 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11,
+ 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff,
+ 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1,
+ 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01,
+ 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff,
+ 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20,
+ 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f,
+ 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f,
+ 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4,
+ 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18,
+ 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x0a, 0x03, 0x4b, 0x04, 0x09,
+ 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x19, 0x0a,
+ 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1,
+ 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11,
+ 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11,
+ 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x0a, 0x19, 0x0a,
+ 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1,
+ 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10,
+ 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d,
+ 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00,
+ 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f,
+ 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2,
+ 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e,
+ 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6,
+ 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10,
+ 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xce, 0x0e, 0x01, 0x01, 0xff, 0xff,
+ 0x1d, 0x18, 0xf4, 0x60, 0x0e, 0xe2, 0x00, 0x0b, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, 0xcd, 0x0e,
+ 0xce, 0x0f, 0x0f, 0x13, 0x0b, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xe0, 0xc0,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d,
+ 0x0d, 0x0f, 0x0c, 0x03, 0x4a, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e, 0xdc, 0xf8, 0x70, 0x20, 0x61,
+ 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x0f, 0x0d, 0x04, 0x4a, 0x06,
+ 0x06, 0x78, 0xfc, 0xfc, 0xfc, 0xfc, 0x78, 0x00, 0x00, 0x68, 0x00, 0x19, 0x0a, 0x3d, 0x0d, 0x02,
+ 0x02, 0x99, 0xd6, 0x19, 0x18, 0xd0, 0x60, 0x0e, 0x10, 0x02, 0x02, 0x13, 0x0e, 0x02, 0x4a, 0x0b,
+ 0x05, 0x04, 0x00, 0x0e, 0x00, 0x1f, 0x00, 0x3f, 0x80, 0x7f, 0xc0, 0x00, 0x00, 0x35, 0x00, 0x19,
+ 0x0a, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0e, 0x19, 0x0a,
+ 0x03, 0xca, 0x0f, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0xcb, 0x10, 0xcb, 0x10, 0x10, 0x11,
+ 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18,
+ 0x54, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x13, 0x0f, 0x02, 0x4a, 0x0b, 0x05, 0x7f, 0xc0, 0x3f,
+ 0x80, 0x1f, 0x00, 0x0e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x01, 0x0f, 0x19,
+ 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0f, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18,
+ 0xf4, 0x20, 0xff, 0xff, 0x00, 0x10, 0x01, 0x10, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xd3, 0x0f, 0xfe, 0xfe, 0xff, 0xff, 0x19, 0x18,
+ 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd3, 0x0f, 0xd1, 0x0d, 0x0d, 0x1b, 0x10, 0x02, 0x4c, 0x0a, 0x0a,
+ 0x1e, 0x00, 0x7f, 0x80, 0x7f, 0x80, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0x7f, 0x80,
+ 0x7f, 0x80, 0x1e, 0x00, 0x6e, 0x00, 0x11, 0x00, 0x40, 0x17, 0x11, 0x03, 0x4a, 0x09, 0x08, 0x01,
+ 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0xc3, 0x00, 0x3c, 0x00, 0x6d,
+ 0x00, 0x11, 0x00, 0x40, 0x17, 0x12, 0x02, 0x4c, 0x09, 0x08, 0x1e, 0x00, 0x61, 0x80, 0x40, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x6c, 0x00, 0x11, 0x00, 0x40, 0x1b,
+ 0x13, 0x03, 0x4b, 0x0a, 0x0a, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+ 0x40, 0x00, 0x80, 0x00, 0x80, 0xc3, 0x00, 0x3c, 0x00, 0x6b, 0x00, 0x11, 0x00, 0x40, 0x1b, 0x14,
+ 0x01, 0x4d, 0x0a, 0x0a, 0x0f, 0x00, 0x30, 0xc0, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x6a, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00,
+ 0x0d, 0x0d, 0x1b, 0x15, 0x02, 0x4b, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+ 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0x00, 0x00, 0x67, 0x00, 0x11, 0x04,
+ 0x40, 0x99, 0xd6, 0x00, 0x1f, 0x16, 0x01, 0x4c, 0x0b, 0x0b, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20,
+ 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, 0xe0,
+ 0x00, 0x00, 0x66, 0x00, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x1b, 0x17, 0x01, 0x4c, 0x0a, 0x0a,
+ 0xff, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x65, 0x00, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x23, 0x18, 0x00, 0x4d,
+ 0x0d, 0x0d, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08,
+ 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x64, 0x00,
+ 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x19, 0x00, 0x4d, 0x0c, 0x0c, 0xff, 0xf0, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00,
+ 0x80, 0x00, 0x80, 0x00, 0x63, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x15,
+ 0x11, 0x04, 0x40, 0x99, 0xd6, 0x00, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17,
+ 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19,
+ 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x1a, 0x03, 0x4b, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e,
+ 0xdc, 0xf8, 0x70, 0x20, 0x62, 0x00, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15,
+ 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40,
+ 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x54, 0x40,
+ 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08,
+ 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10,
+ 0x84, 0x00, 0x01, 0x19, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x11, 0xf4, 0x60, 0x99,
+ 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x33, 0x0d, 0x0d,
+ 0x00, 0x00, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40,
+ 0x99, 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40,
+ 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01,
+ 0x0d, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40, 0x99,
+ 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff,
+ 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00,
+ 0x00, 0x00, 0x0b, 0x1b, 0x05, 0x49, 0x04, 0x04, 0x60, 0xf0, 0xf0, 0x60, 0x69, 0x00, 0x19, 0x0a,
+ 0x01, 0x0d, 0x19, 0x18, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40,
+ 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00,
+ 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18,
+ 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1b, 0x19, 0x0a,
+ 0x03, 0xcc, 0x0d, 0x19, 0x18, 0xf4, 0x60, 0x99, 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01,
+ 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04,
+ 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x00,
+ 0x40, 0x01, 0x1b, 0x03, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x08, 0x08, 0x81, 0x08, 0xaa,
+ 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0x09, 0x01, 0x7f, 0x02, 0x0d, 0x00, 0x1a, 0x01, 0x0d,
+ 0x00, 0x0d, 0x00, 0xf0, 0xff, 0xff, 0x00, 0x99, 0xd6, 0x00, 0x81, 0x19, 0x18, 0x54, 0x40, 0x99,
+ 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04,
+ 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00,
+ 0x40, 0x01, 0x1a, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40,
+ 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00,
+ 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x11,
+ 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04,
+ 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04,
+ 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x31, 0x34, 0xff,
+ 0xff, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x1b, 0x1c, 0x02, 0x4b, 0x09, 0x09,
+ 0xc1, 0x80, 0xe3, 0x80, 0x77, 0x00, 0x3e, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x77, 0x00, 0xe3, 0x80,
+ 0xc1, 0x80, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x03, 0xcc, 0x0d, 0x1d, 0x18, 0xf0, 0x60, 0xa0,
+ 0x45, 0x45, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x1b, 0x1d, 0x01, 0x4b, 0x0a, 0x09, 0x3f, 0xc0, 0x3f,
+ 0xc0, 0x20, 0x40, 0xff, 0x40, 0xff, 0x40, 0x81, 0xc0, 0x81, 0x00, 0x81, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0x32, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x1b, 0x1e, 0x01,
+ 0x4c, 0x0a, 0x0a, 0xff, 0xc0, 0xff, 0xc0, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80,
+ 0x40, 0x80, 0x40, 0x80, 0x40, 0xff, 0xc0, 0x31, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50,
+ 0x40, 0x0d, 0x0d, 0x0b, 0x1f, 0x02, 0x44, 0x07, 0x02, 0xfe, 0xfe, 0x00, 0x00, 0x30, 0x00, 0x19,
+ 0x0a, 0x3d, 0x0d, 0x03, 0x03, 0x99, 0xd6, 0x19, 0x18, 0xd4, 0x60, 0xff, 0xff, 0x00, 0x0e, 0x11,
+ 0x03, 0x03, 0x23, 0x20, 0x00, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00,
+ 0x10, 0x00, 0x88, 0x00, 0x44, 0x00, 0x22, 0x00, 0x11, 0x00, 0x88, 0x80, 0x44, 0x40, 0x22, 0x20,
+ 0x11, 0x10, 0x00, 0x00, 0x78, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x21, 0x00, 0x4c,
+ 0x0c, 0x0c, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x98, 0x00, 0xcc, 0x00,
+ 0x66, 0x00, 0x33, 0x00, 0x99, 0x80, 0xcc, 0xc0, 0x66, 0x60, 0x79, 0x00, 0x19, 0x0a, 0x3d, 0x10,
+ 0xfd, 0xfd, 0xff, 0xff, 0x19, 0x18, 0xd4, 0x60, 0x00, 0x00, 0x00, 0x0f, 0x0c, 0xfd, 0xfd, 0x13,
+ 0x22, 0x05, 0x4b, 0x04, 0x09, 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x77, 0x00, 0x02, 0xff, 0xff, 0x0d, 0x0a, 0x3f, 0x0e, 0x00, 0x00, 0xff, 0x03, 0xff, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x06, 0x02, 0x00, 0x48, 0x00, 0x37,
+ 0x01, 0x02, 0x02, 0x00, 0x09, 0x00, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x03, 0xcf, 0x04, 0xa2, 0x0c,
+ 0x05, 0x40, 0x44, 0xd1, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00,
+ 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00,
+ 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84,
+ 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84,
+ 0xff, 0xff, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x75, 0xc6, 0x66, 0x29, 0x00, 0x09, 0x68, 0x10, 0x00, 0x08, 0x68, 0x10, 0x84,
+ 0x00, 0x20, 0x00, 0x07, 0x6b, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x00, 0x03, 0x6e, 0xff, 0xff,
+ 0x02, 0x6e, 0xff, 0xff, 0x00, 0x10, 0xc0, 0x00, 0xf7, 0xbd, 0x01, 0xc0, 0x00, 0x08, 0x42, 0xc0,
+ 0x00, 0xff, 0xff, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd,
+ 0x2e, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x02, 0x81, 0x10, 0x84, 0x06, 0x82, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x03, 0x83, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6,
+ 0xc9, 0x99, 0xd6, 0x1a, 0x82, 0x10, 0x00, 0x10, 0x00, 0x0a, 0x29, 0x09, 0x27, 0x0c, 0x67, 0x99,
+ 0xd6, 0x15, 0x27, 0x1d, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x67, 0x99, 0xd6, 0x00, 0x19, 0xd0,
+ 0x30, 0x89, 0xd6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x99, 0xd6, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xd8, 0x89,
+ 0xd6, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x1a, 0x68, 0x00, 0x00, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x18, 0x68, 0x00, 0x00, 0x1b, 0x68, 0x99, 0xd6, 0x06, 0x86, 0x99, 0xd6, 0x10,
+ 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x19, 0x6b, 0x99, 0xd6, 0x03, 0xcc, 0x89,
+ 0x52, 0x08, 0x68, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x04, 0x2c, 0x03, 0x6e, 0x08, 0x42, 0x02,
+ 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x10, 0x81, 0x08, 0x42, 0x70,
+ 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66,
+ 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x89, 0x10,
+ 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xc6, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0x0a, 0xc6, 0x89, 0xd6, 0x0e, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x1c, 0x40, 0x35, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x03, 0x00, 0x00, 0x60, 0x00, 0x80, 0x01, 0x78, 0x01, 0x00, 0x82,
+ 0x10, 0x00, 0x10, 0x00, 0x0c, 0x40, 0x2c, 0x03, 0xe0, 0x05, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6,
+ 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x3b, 0x00, 0x00, 0x00, 0x28, 0x80,
+ 0x1d, 0x00, 0x78, 0x00, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x0c, 0x85, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x40, 0x2b, 0x01,
+ 0xf0, 0x00, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x99,
+ 0xd6, 0x16, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a,
+ 0x69, 0x99, 0xd6, 0x04, 0xcc, 0x89, 0x52, 0x07, 0x69, 0x99, 0xd6, 0x08, 0x68, 0x99, 0xd6, 0x03,
+ 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01,
+ 0x70, 0x08, 0x42, 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81,
+ 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89,
+ 0x52, 0x03, 0x8a, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x2d, 0x03, 0x8d, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x06, 0xc6, 0x99, 0xd6, 0x1a, 0xc6, 0x89, 0xd6, 0x0a, 0x66, 0x10,
+ 0x84, 0x1b, 0x6a, 0x99, 0xd6, 0x1b, 0x81, 0x99, 0xd6, 0x09, 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99,
+ 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0xf0, 0x94, 0x01, 0xcc, 0x89, 0x52, 0x00, 0x03, 0x6e, 0xff, 0xff,
+ 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01, 0x70, 0x08, 0x42,
+ 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce,
+ 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x10,
+ 0x2d, 0x03, 0x2d, 0x17, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x00, 0x00, 0x00, 0x00, 0x18, 0x88, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x07, 0x88, 0x10, 0x00, 0x10,
+ 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10,
+ 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99,
+ 0xd6, 0x07, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
+ 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x99, 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x10,
+ 0x84, 0x10, 0x84, 0x99, 0xd6, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99,
+ 0xd6, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x10,
+ 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x0b, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x99, 0xd6, 0x0d, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99,
+ 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0c, 0x85, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x0b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x86, 0x00,
+ 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x84, 0x10, 0x00, 0x10,
+ 0x00, 0x10, 0x00, 0x10, 0x00, 0x0c, 0x86, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x09, 0x86, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00,
+ 0x00, 0x0a, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x86, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10,
+ 0x00, 0x0a, 0x88, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99,
+ 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x6a, 0x99, 0xd6, 0x05, 0x88, 0x10, 0x00, 0x10, 0x00, 0x99,
+ 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10, 0x84, 0x10,
+ 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x07,
+ 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99, 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0x14, 0x2c, 0x00, 0x03, 0x6e,
+ 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0xcb, 0x66, 0x29, 0x84, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x42, 0x09, 0x0d, 0xdf, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x03, 0x15, 0x00, 0xa2, 0x0c,
+ 0x05, 0x40, 0x40, 0x17, 0xff, 0xff, 0x00, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xf0, 0xbc, 0x0f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40,
+ 0xc8, 0x03, 0xf4, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xf6, 0xff, 0xff, 0xc0, 0x2c, 0x2d, 0x09,
+ 0x84, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x00, 0x22, 0xc0, 0x10, 0x25, 0x4b, 0x02,
+ 0x30, 0x02, 0x2a, 0x02, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x03, 0xfd, 0x2e, 0x03, 0xfd,
+ 0x29, 0x03, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x05, 0x2d, 0x05, 0x29, 0x06, 0xc6, 0x99, 0xd6, 0x09,
+ 0x29, 0x1f, 0x43, 0x03, 0x00, 0x00, 0x01, 0x27, 0x0b, 0x44, 0xc3, 0x00, 0x00, 0xc0, 0x68, 0x99,
+ 0xd6, 0x18, 0x48, 0xa5, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc2, 0x5a, 0x00, 0x60,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0xa0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x81, 0x99, 0xd6, 0x48, 0x05, 0x80, 0x05, 0x00, 0x00, 0xfc,
+ 0x01, 0x50, 0x40, 0x69, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c, 0x80, 0x06, 0x00,
+ 0x00, 0x02, 0x6a, 0x99, 0xd6, 0x1c, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x2d,
+ 0x09, 0x2d, 0x09, 0x6f, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x04, 0x6e, 0xff, 0xff, 0x04, 0xc9,
+ 0x66, 0x29, 0x02, 0x60, 0x5e, 0x2d, 0x09, 0xc0, 0x30, 0x2d, 0x09, 0xf0, 0xc0, 0x09, 0xc0, 0x10,
+ 0x08, 0x42, 0x00, 0x00, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x00, 0x02, 0xcd, 0x89, 0x52,
+ 0x03, 0x83, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0xc9, 0xef, 0x7b, 0x81, 0x99, 0xd6, 0x00, 0x05,
+ 0xc9, 0x89, 0xd6, 0x07, 0x69, 0x10, 0x84, 0x00, 0x08, 0x27, 0x09, 0x82, 0xff, 0xff, 0x99, 0xd6,
+ 0xd0, 0x69, 0x89, 0x52, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x51, 0x0e, 0xc0, 0x40, 0x38, 0x03, 0xa8, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xaa,
+ 0xff, 0xff, 0xc8, 0x2d, 0x09, 0x00, 0x14, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x20, 0xc6, 0x25, 0x4b, 0x00, 0x1a, 0xd8, 0x18, 0xc6, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xf7, 0xc0, 0x01, 0x89, 0x52, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7,
+ 0x00, 0x01, 0x99, 0xd6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x84, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x3b, 0xef,
+ 0x7b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x2d, 0x09, 0x00, 0x58, 0xf3, 0x7c,
+ 0x0b, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40, 0xc8,
+};
+
+/**
+ *
+ * for.whom.the.bell.tolls,.the.bell.tolls.for.thee!
+ *
+ * for.whom.the.bell.tolls,<16,15>.<40,4><19,3>e! ([MS-RDPBCGR])
+ *
+ * <16,15> : ".the.bell.tolls"
+ * <40,4> : "for."
+ * <19,3> : "the"
+ *
+ * for.whom.the.bell.tolls,<16,15>.f<40,3><35,3>e! (Microsoft implementation)
+ *
+ * <16,15> : ".the.bell.tolls"
+ * <40,3> : "or."
+ * <19,3> " "the"
+ *
+ * RDP5:
+ * 01100110 Literal 'f'
+ * 01101111 Literal 'o'
+ * 01110010 Literal 'r'
+ * 00101110 Literal '.'
+ * 01110111 Literal 'w'
+ * 01101000 Literal 'h'
+ * 01101111 Literal 'o'
+ * 01101101 Literal 'm'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101000 Literal 'h'
+ * 01100101 Literal 'e'
+ * 00101110 Literal '.'
+ * 01100010 Literal 'b'
+ * 01100101 Literal 'e'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101111 Literal 'o'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 01110011 Literal 's'
+ * 00101100 Literal ','
+ * 11111+010000 CopyOffset 16
+ * 110+111 LengthOfMatch 15
+ * 00101110 Literal '.'
+ * 01100110 Literal 'f'
+ * 11111+101000 CopyOffset 40
+ * 0 LengthOfMatch 3
+ * 11111+100011 CopyOffset 35
+ * 0 LengthOfMatch 3
+ * 01100101 Literal 'e'
+ * 00100001 Literal '!'
+ * 0000000 Trailing Bits
+ *
+ * RDP4:
+ * 01100110 Literal 'f'
+ * 01101111 Literal 'o'
+ * 01110010 Literal 'r'
+ * 00101110 Literal '.'
+ * 01110111 Literal 'w'
+ * 01101000 Literal 'h'
+ * 01101111 Literal 'o'
+ * 01101101 Literal 'm'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101000 Literal 'h'
+ * 01100101 Literal 'e'
+ * 00101110 Literal '.'
+ * 01100010 Literal 'b'
+ * 01100101 Literal 'e'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 00101110 Literal '.'
+ * 01110100 Literal 't'
+ * 01101111 Literal 'o'
+ * 01101100 Literal 'l'
+ * 01101100 Literal 'l'
+ * 01110011 Literal 's'
+ * 00101100 Literal ','
+ * 1111+010000 CopyOffset 16
+ * 110+111 LengthOfMatch 15
+ * 00101110 Literal '.'
+ * 01100110 Literal 'f'
+ * 1111+101000 CopyOffset 40
+ * 0 LengthOfMatch 3
+ * 1111+100011 CopyOffset 35
+ * 0 LengthOfMatch 3
+ * 01100101 Literal 'e'
+ * 00100001 Literal '!'
+ * 00 Trailing Bits
+ */
+
+static const BYTE TEST_MPPC_BELLS[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+/* Flags: 0x0060 Length: 33 */
+
+static const BYTE TEST_MPPC_BELLS_RDP4[] =
+ "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c"
+ "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xf4\x37\x2e\x66\xfa\x1f\x19\x94"
+ "\x84";
+
+/* Flags: 0x0061 Length: 34 */
+
+static const BYTE TEST_MPPC_BELLS_RDP5[] =
+ "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c"
+ "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xfa\x1b\x97\x33\x7e\x87\xe3\x32"
+ "\x90\x80";
+
+static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man "
+ "is a piece of the continent, a part of the main; "
+ "if a clod be washed away by the sea, Europe "
+ "is the less, as well as if a promontory were, as"
+ "well as any manner of thy friends or of thine "
+ "own were; any man's death diminishes me, "
+ "because I am involved in mankind. "
+ "And therefore never send to know for whom "
+ "the bell tolls; it tolls for thee.";
+
+static const BYTE TEST_ISLAND_DATA_RDP5[] =
+ "\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2\xdc\xc8"
+ "\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6\xca\xd8"
+ "\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65\x63\xfc"
+ "\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2\x40\xe0"
+ "\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b\x37\xd7"
+ "\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2\x10\x30"
+ "\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20\x45\x75"
+ "\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69\xcc\x81"
+ "\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2\xb3\x7f"
+ "\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96\xe6\x57"
+ "\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f\xd2\xb6"
+ "\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49\x3b\x99"
+ "\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96\xce\x64"
+ "\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30\xb6\x90"
+ "\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69\x6e\x64"
+ "\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd\xbf\x04"
+ "\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81\x03\xbb"
+ "\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec\xf3\xa7"
+ "\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80";
+
+static int test_MppcCompressBellsRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1;
+ const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1;
+
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBellsRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS_RDP5, DstSize) != 0)
+ {
+ printf("MppcCompressBellsRdp5: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP5, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressBellsRdp4(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS;
+ const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1;
+ MPPC_CONTEXT* mppc = mppc_context_new(0, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBellsRdp4: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS_RDP4, DstSize) != 0)
+ {
+ printf("MppcCompressBellsRdp4: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP4, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBellsRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(1, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1;
+ pSrcData = TEST_MPPC_BELLS_RDP5;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1;
+ expectedSize = sizeof(TEST_MPPC_BELLS) - 1;
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBellsRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0)
+ {
+ printf("MppcDecompressBellsRdp5: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBellsRdp4(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(0, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1;
+ pSrcData = (const BYTE*)TEST_MPPC_BELLS_RDP4;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 0;
+ expectedSize = sizeof(TEST_MPPC_BELLS) - 1;
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBellsRdp4: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0)
+ {
+ printf("MppcDecompressBellsRdp4: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressIslandRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_ISLAND_DATA) - 1;
+ const BYTE* pSrcData = (const BYTE*)TEST_ISLAND_DATA;
+ const UINT32 expectedSize = sizeof(TEST_ISLAND_DATA_RDP5) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ const BYTE* pDstData = NULL;
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressIslandRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_ISLAND_DATA_RDP5, DstSize) != 0)
+ {
+ printf("MppcCompressIslandRdp5: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_ISLAND_DATA_RDP5, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcCompressBufferRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA);
+ const BYTE* pSrcData = (const BYTE*)TEST_RDP5_UNCOMPRESSED_DATA;
+ const UINT32 expectedSize = sizeof(TEST_RDP5_COMPRESSED_DATA);
+ UINT32 DstSize = sizeof(OutputBuffer);
+
+ const BYTE* pDstData = NULL;
+ MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE);
+
+ if (!mppc)
+ return -1;
+
+ status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcCompressBufferRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_RDP5_COMPRESSED_DATA, DstSize) != 0)
+ {
+ printf("MppcCompressBufferRdp5: output mismatch: compressed output does not match "
+ "Microsoft implementation\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+static int test_MppcDecompressBufferRdp5(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ MPPC_CONTEXT* mppc = NULL;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ mppc = mppc_context_new(1, FALSE);
+
+ if (!mppc)
+ return -1;
+
+ SrcSize = sizeof(TEST_RDP5_COMPRESSED_DATA);
+ pSrcData = (const BYTE*)TEST_RDP5_COMPRESSED_DATA;
+ Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1;
+ expectedSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA);
+ status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("MppcDecompressBufferRdp5: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_RDP5_UNCOMPRESSED_DATA, DstSize) != 0)
+ {
+ printf("MppcDecompressBufferRdp5: output mismatch\n");
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ mppc_context_free(mppc);
+ return rc;
+}
+
+int TestFreeRDPCodecMppc(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_MppcCompressIslandRdp5() < 0)
+ return -1;
+
+ if (test_MppcCompressBellsRdp5() < 0)
+ return -1;
+
+ if (test_MppcDecompressBellsRdp5() < 0)
+ return -1;
+
+ if (test_MppcCompressBellsRdp4() < 0)
+ return -1;
+
+ if (test_MppcDecompressBellsRdp4() < 0)
+ return -1;
+
+ if (test_MppcCompressBufferRdp5() < 0)
+ return -1;
+
+ if (test_MppcDecompressBufferRdp5() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c
new file mode 100644
index 0000000..689b378
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c
@@ -0,0 +1,122 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "../ncrush.h"
+
+static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+static const BYTE TEST_BELLS_NCRUSH[] =
+ "\xfb\x1d\x7e\xe4\xda\xc7\x1d\x70\xf8\xa1\x6b\x1f\x7d\xc0\xbe\x6b"
+ "\xef\xb5\xef\x21\x87\xd0\xc5\xe1\x85\x71\xd4\x10\x16\xe7\xda\xfb"
+ "\x1d\x7e\xe4\xda\x47\x1f\xb0\xef\xbe\xbd\xff\x2f";
+
+static BOOL test_NCrushCompressBells(void)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pDstData = NULL;
+ BYTE OutputBuffer[65536] = { 0 };
+ const UINT32 SrcSize = sizeof(TEST_BELLS_DATA) - 1;
+ const BYTE* pSrcData = TEST_BELLS_DATA;
+ const UINT32 expectedSize = sizeof(TEST_BELLS_NCRUSH) - 1;
+ UINT32 DstSize = sizeof(OutputBuffer);
+ NCRUSH_CONTEXT* ncrush = ncrush_context_new(TRUE);
+
+ if (!ncrush)
+ return rc;
+
+ status = ncrush_compress(ncrush, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", status, Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("NCrushCompressBells: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "\n",
+ DstSize, expectedSize);
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_BELLS_NCRUSH, DstSize) != 0)
+ {
+ printf("NCrushCompressBells: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ ncrush_context_free(ncrush);
+ return rc;
+}
+
+static BOOL test_NCrushDecompressBells(void)
+{
+ BOOL rc = FALSE;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ UINT32 expectedSize = 0;
+ const BYTE* pDstData = NULL;
+ NCRUSH_CONTEXT* ncrush = ncrush_context_new(FALSE);
+
+ if (!ncrush)
+ return rc;
+
+ SrcSize = sizeof(TEST_BELLS_NCRUSH) - 1;
+ pSrcData = (const BYTE*)TEST_BELLS_NCRUSH;
+ Flags = PACKET_COMPRESSED | 2;
+ expectedSize = sizeof(TEST_BELLS_DATA) - 1;
+ status = ncrush_decompress(ncrush, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("NCrushDecompressBells: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_BELLS_DATA, DstSize) != 0)
+ {
+ printf("NCrushDecompressBells: output mismatch\n");
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ ncrush_context_free(ncrush);
+ return rc;
+}
+
+int TestFreeRDPCodecNCrush(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_NCrushCompressBells())
+ return -1;
+
+ if (!test_NCrushDecompressBells())
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c
new file mode 100644
index 0000000..ba4c5c6
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c
@@ -0,0 +1,5785 @@
+
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/planar.h>
+
+/**
+ * Experimental Case 01: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_01[16384] =
+ "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x16\x12"
+ "\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF"
+ "\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x18\x16\x14\xFF\x1C\x1A\x16\xFF\x1A\x18\x14"
+ "\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF"
+ "\x1B\x19\x16\xFF\x1A\x18\x15\xFF\x1A\x18\x13\xFF\x19\x17\x13\xFF\x1B\x19\x15\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF"
+ "\x1A\x19\x15\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1B\x1B\x16\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x1B\x19\x14\xFF"
+ "\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17"
+ "\xFF\x1B\x1A\x15\xFF\x18\x17\x12\xFF"
+ "\x1B\x1A\x16\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x1E\x1D\x18\xFF\x19\x18\x14\xFF\x1C\x1B\x17"
+ "\xFF\x1C\x1A\x16\xFF\x1B\x1A\x18\xFF"
+ "\x1D\x1B\x16\xFF\x1E\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1A\x1A\x14\xFF\x1B\x1B\x15"
+ "\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17\xFF"
+ "\x1C\x19\x15\xFF\x1D\x1C\x17\xFF\x1A\x19\x14\xFF\x1C\x1B\x17\xFF\x1E\x1D\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1B\x1C\x16\xFF\x1D\x1C\x18\xFF"
+ "\x1C\x1C\x16\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1A\x18\x14\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1C\x17\xFF\x1E\x1B\x19\xFF"
+ "\x1F\x1C\x18\xFF\x1D\x1C\x18\xFF\x20\x1F\x19\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF\x1F\x1E\x1A"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1C\x18\xFF"
+ "\x1F\x1D\x18\xFF\x1E\x1C\x18\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF\x1D\x1C\x18\xFF\x1F\x1F\x19"
+ "\xFF\x1D\x1C\x18\xFF\x1B\x1B\x16\xFF"
+ "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1D\x1C\x16\xFF\x1B\x1A\x15\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1E\x1A\xFF\x22\x20\x1C\xFF"
+ "\x22\x20\x1B\xFF\x20\x1F\x1A\xFF\x1F\x1F\x1A\xFF\x1E\x1E\x1A\xFF\x1D\x1C\x18\xFF\x1F\x1D\x19"
+ "\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF"
+ "\x20\x1F\x1A\xFF\x1D\x1D\x18\xFF\x1E\x1D\x18\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x1E\x1D\x18"
+ "\xFF\x20\x20\x1B\xFF\x20\x1F\x1B\xFF"
+ "\x1F\x1D\x18\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19\xFF\x1D\x1B\x18\xFF\x1D\x1B\x16\xFF\x1E\x1C\x17"
+ "\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14"
+ "\xFF\x17\x17\x13\xFF\x19\x18\x15\xFF"
+ "\x15\x15\x11\xFF\x14\x13\x10\xFF\x4E\x4A\x3D\xFF\x21\x20\x1A\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x21\x20\x1A\xFF\x24\x23\x1F\xFF\x21\x20\x1A\xFF\x2A\x29\x23\xFF\x21\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x21\x20\x1A"
+ "\xFF\x22\x20\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1C"
+ "\xFF\x26\x25\x21\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1A\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x24\x24\x1E\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x21\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x24\x23\x1E\xFF\x26\x25\x21\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x23\x22\x1E\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x27\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x29\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1E"
+ "\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x27\x26\x21\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x4E\x49\x3C"
+ "\xFF\x1A\x18\x14\xFF\x1A\x18\x15\xFF"
+ "\x1B\x1B\x16\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1D\x19\xFF\x1F\x1D\x19"
+ "\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x26\x20"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F\xFF\x28\x25\x20"
+ "\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF"
+ "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x1E"
+ "\xFF\x26\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1E\xFF\x27\x24\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x28\x26\x20\xFF\x27\x26\x1F"
+ "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x24\x21\x1C\xFF\x26\x23\x1E\xFF"
+ "\x27\x24\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x22\x20\x1A\xFF\x24\x24\x1D\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x27\x25\x20\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x22\x21\x1C"
+ "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x29\x2F\x26\xFF\x3C\x70\x55"
+ "\xFF\x54\xC5\x92\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x56\xCC\x97\xFF\x4B\xA4\x7B\xFF\x2F\x47\x37\xFF\x26\x26\x20\xFF\x25\x23\x1D\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x25\x25\x1D\xFF"
+ "\x27\x2D\x26\xFF\x39\x68\x50\xFF\x54\xC0\x8F\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x49\x9F\x76\xFF\x2D\x42\x33\xFF\x24\x25\x20\xFF\x23\x22\x1D"
+ "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF"
+ "\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x20\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x25\x1F\xFF\x26\x24\x1E\xFF"
+ "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x26\x23\x1F\xFF\x23\x20\x1C\xFF\x28\x26\x1F\xFF\x25\x24\x1F"
+ "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF"
+ "\x23\x23\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1E\xFF\x27\x24\x1F\xFF\x23\x21\x1B\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x27\x26\x21\xFF\x25\x22\x1D"
+ "\xFF\x22\x20\x1C\xFF\x25\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x25\x1F\xFF\x24\x22\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1D"
+ "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF"
+ "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x21\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x23\x1F\x1B\xFF"
+ "\x21\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x20\x1C\xFF\x26\x24\x1E"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x21\x21\x1A\xFF\x21\x21\x1C\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x21\x21\x1C"
+ "\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF"
+ "\x25\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x22\x1C\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x22\x21\x1D\xFF\x21\x21\x1C\xFF\x23\x22\x1D\xFF\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x24\x23\x1D"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x22\x22\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x21\x1C"
+ "\xFF\x21\x21\x1B\xFF\x24\x24\x1E\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x25\x24\x1E"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x23\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x22\x1C\xFF\x22\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x21\x1F\x1B\xFF\x21\x1F\x1A\xFF\x20\x20\x19\xFF\x24\x22\x1B\xFF\x23\x22\x1C"
+ "\xFF\x27\x25\x1E\xFF\x25\x24\x1E\xFF"
+ "\x26\x29\x23\xFF\x2F\x45\x37\xFF\x2B\x3E\x2F\xFF\x27\x26\x20\xFF\x21\x1F\x1A\xFF\x21\x20\x1C"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x1F\x1E\x1A\xFF\x24\x22\x1C\xFF\x27\x23\x1F"
+ "\xFF\x24\x22\x1E\xFF\x21\x20\x1C\xFF"
+ "\x1F\x1E\x19\xFF\x21\x1F\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x21\x1F\x1A"
+ "\xFF\x21\x1F\x1C\xFF\x21\x20\x1B\xFF"
+ "\x24\x21\x1C\xFF\x1F\x1D\x19\xFF\x22\x20\x1A\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x21\x21\x1A"
+ "\xFF\x20\x20\x1A\xFF\x21\x20\x1C\xFF"
+ "\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x1F\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1D"
+ "\xFF\x20\x1F\x1C\xFF\x22\x21\x1C\xFF"
+ "\x15\x14\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x14\x13\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF"
+ "\x12\x10\x0E\xFF\x11\x10\x0E\xFF\x10\x0F\x0D\xFF\x14\x12\x10\xFF\x14\x12\x0E\xFF\x14\x13\x0F"
+ "\xFF\x15\x13\x10\xFF\x12\x10\x0D\xFF"
+ "\x12\x11\x0E\xFF\x12\x11\x0E\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10"
+ "\xFF\x16\x15\x12\xFF\x13\x12\x0E\xFF"
+ "\x12\x11\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF\x15\x15\x11\xFF\x13\x12\x0F"
+ "\xFF\x13\x11\x0E\xFF\x13\x12\x0F\xFF"
+ "\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x12\x12\x0F"
+ "\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF"
+ "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x15\x14\x0F"
+ "\xFF\x14\x13\x10\xFF\x14\x12\x10\xFF"
+ "\x15\x14\x10\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x11\x10\x0D\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0E\xFF\x14\x12\x0F\xFF"
+ "\x16\x15\x10\xFF\x14\x13\x0F\xFF\x12\x10\x0E\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x15\x14\x10"
+ "\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF"
+ "\x16\x15\x11\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF"
+ "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x16\x14\x12"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF"
+ "\x14\x13\x10\xFF\x15\x14\x12\xFF\x16\x14\x11\xFF\x15\x13\x0F\xFF\x14\x13\x10\xFF\x15\x13\x10"
+ "\xFF\x18\x17\x13\xFF\x15\x13\x11\xFF"
+ "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x11\xFF\x13\x12\x0F\xFF\x17\x15\x12"
+ "\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF"
+ "\x17\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF\x15\x14\x10\xFF\x15\x14\x12"
+ "\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF"
+ "\x16\x15\x11\xFF\x19\x18\x14\xFF\x18\x16\x13\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x15\x13\x11"
+ "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x11\xFF\x18\x17\x14\xFF\x17\x15\x12\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x16\xFF\x16\x15\x11\xFF"
+ "\x16\x14\x12\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x14\x13\x11\xFF\x17\x16\x12\xFF\x19\x19\x14"
+ "\xFF\x16\x15\x12\xFF\x19\x18\x14\xFF"
+ "\x18\x17\x14\xFF\x18\x18\x13\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x15\x13\xFF\x18\x17\x14"
+ "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF"
+ "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x15\x13\x12"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF"
+ "\x14\x13\x0F\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x10\xFF\x17\x16\x12\xFF\x19\x18\x13\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1B\x18\x15\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1A\x19\x15"
+ "\xFF\x1B\x19\x15\xFF\x1A\x19\x14\xFF"
+ "\x16\x15\x11\xFF\x1A\x19\x15\xFF\x1A\x18\x15\xFF\x17\x16\x12\xFF\x19\x19\x14\xFF\x19\x18\x14"
+ "\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF"
+ "\x19\x18\x14\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x19\x19\x14"
+ "\xFF\x19\x19\x14\xFF\x19\x18\x14\xFF"
+ "\x1D\x1B\x17\xFF\x1A\x19\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x17\x14"
+ "\xFF\x19\x18\x14\xFF\x19\x18\x13\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1C\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x12\xFF\x18\x17\x12\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x17\x16\x12\xFF\x16\x16\x11\xFF\x1A\x18\x14"
+ "\xFF\x1B\x19\x15\xFF\x1E\x1C\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1A\x15\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x1C\x1A\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1B\x1A\x15"
+ "\xFF\x1C\x1C\x17\xFF\x1C\x1C\x17\xFF"
+ "\x1B\x19\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x15\xFF\x1D\x1C\x17\xFF\x1C\x1A\x16\xFF\x1C\x1C\x17"
+ "\xFF\x1C\x1B\x16\xFF\x1D\x1C\x17\xFF"
+ "\x1D\x1B\x17\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF\x1E\x1E\x18\xFF\x1E\x1D\x18"
+ "\xFF\x1D\x1B\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1E\x1C\x18\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x1D\x1C\x17\xFF\x1C\x19\x15\xFF"
+ "\x1D\x1B\x17\xFF\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x22\x20\x1B\xFF\x1E\x1D\x18"
+ "\xFF\x1D\x1C\x18\xFF\x1C\x1C\x17\xFF"
+ "\x1B\x1B\x15\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1B\x17\xFF\x1B\x1B\x16\xFF"
+ "\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1C\x1C\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1B\x17\xFF\x1E\x1B\x17\xFF"
+ "\x1D\x1B\x17\xFF\x1D\x1C\x17\xFF\x1E\x1E\x19\xFF\x1E\x1D\x19\xFF\x1C\x1C\x18\xFF\x20\x1F\x19"
+ "\xFF\x1F\x1D\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF\x1A\x19\x15"
+ "\xFF\x20\x1F\x1A\xFF\x1E\x1D\x18\xFF"
+ "\x1E\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1D\x1C\x18\xFF\x21\x20\x1B\xFF"
+ "\x1E\x1E\x19\xFF\x20\x1F\x1A\xFF\x20\x1E\x1A\xFF\x20\x1F\x1A\xFF\x20\x20\x1A\xFF\x21\x20\x1A"
+ "\xFF\x23\x21\x1B\xFF\x21\x21\x1B\xFF"
+ "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x18\xFF\x1D\x1C\x17\xFF\x1E\x1C\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF\x1A\x1A\x15\xFF\x19\x19\x15\xFF\x18\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF"
+ "\x15\x14\x11\xFF\x15\x14\x11\xFF\x51\x4E\x41\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x21\x20\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1D"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x29\x28\x23"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1E"
+ "\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1E"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x29\x27\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x29\x28\x23"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D"
+ "\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF"
+ "\x1D\x1B\x17\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF\x1F\x1D\x18\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19"
+ "\xFF\x21\x1E\x1A\xFF\x22\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x25\x22\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1E\xFF\x29\x26\x21\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x28\x27\x21\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x28\x26\x1F\xFF"
+ "\x27\x24\x1F\xFF\x29\x26\x20\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x24\x1E\xFF\x29\x26\x21\xFF\x28\x26\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x25\x1D\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x2A\x26\x21\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x20\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF\x25\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1E"
+ "\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x26\x22\x1D\xFF\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x29\x28\x22\xFF\x25\x22\x1C\xFF\x27\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x29\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x28\x2B\x23\xFF\x35\x57\x43"
+ "\xFF\x4E\xB1\x84\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x51\xBB\x8A\xFF\x36\x60\x49\xFF\x25\x2A\x23\xFF\x27\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x28\x2A\x23\xFF\x33\x50\x3D\xFF\x4E\xAC\x80\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8D\xFF\x34\x58\x43\xFF\x26\x28\x21\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x22\x22\x1C\xFF\x22\x22\x1B\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x24\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x24\x21\x1C\xFF"
+ "\x24\x23\x1D\xFF\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x23\x1D"
+ "\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F\xFF\x23\x22\x1D"
+ "\xFF\x21\x21\x1C\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1C"
+ "\xFF\x25\x22\x1D\xFF\x23\x21\x1B\xFF"
+ "\x25\x22\x1D\xFF\x24\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF"
+ "\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x24\x1D"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x22\x22\x1C"
+ "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF"
+ "\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x25\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1D\xFF\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1C"
+ "\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF"
+ "\x22\x22\x1C\xFF\x24\x21\x1D\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1D"
+ "\xFF\x24\x21\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x22\x21\x1C\xFF\x22\x20\x1B\xFF\x24\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x19\xFF\x26\x23\x1E\xFF\x25\x23\x1C"
+ "\xFF\x23\x22\x1C\xFF\x21\x22\x1B\xFF"
+ "\x22\x22\x1D\xFF\x27\x2C\x24\xFF\x26\x2A\x23\xFF\x23\x24\x1C\xFF\x23\x24\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x22\x22\x1B\xFF"
+ "\x24\x22\x1D\xFF\x21\x1F\x1A\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1E"
+ "\xFF\x23\x22\x1D\xFF\x1F\x1F\x19\xFF"
+ "\x22\x21\x1B\xFF\x23\x20\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x28\x25\x1E\xFF\x25\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x1F\x1E\x19\xFF"
+ "\x22\x22\x1C\xFF\x20\x1F\x1B\xFF\x22\x20\x1C\xFF\x20\x20\x1B\xFF\x21\x1F\x1B\xFF\x22\x21\x1C"
+ "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1D\xFF"
+ "\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x18\x17\x13\xFF\x13\x12\x0E\xFF\x14\x12\x10"
+ "\xFF\x16\x13\x10\xFF\x14\x14\x0E\xFF"
+ "\x12\x10\x0E\xFF\x0F\x0E\x0B\xFF\x11\x10\x0D\xFF\x15\x13\x11\xFF\x16\x14\x10\xFF\x14\x12\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF"
+ "\x14\x13\x10\xFF\x11\x10\x0F\xFF\x14\x14\x10\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10"
+ "\xFF\x19\x18\x14\xFF\x15\x14\x10\xFF"
+ "\x12\x10\x0E\xFF\x10\x10\x0C\xFF\x10\x0F\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x14\x12\x10"
+ "\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF"
+ "\x12\x10\x0F\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x14\x13\x10"
+ "\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF"
+ "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x0F"
+ "\xFF\x14\x14\x0F\xFF\x12\x11\x0D\xFF"
+ "\x12\x11\x0E\xFF\x15\x13\x11\xFF\x15\x14\x11\xFF\x15\x14\x11\xFF\x12\x12\x0F\xFF\x14\x14\x0F"
+ "\xFF\x14\x13\x11\xFF\x14\x13\x10\xFF"
+ "\x17\x16\x12\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10"
+ "\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF"
+ "\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x11\x10\x0C\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x14\x11\xFF\x18\x17\x13\xFF"
+ "\x13\x11\x10\xFF\x14\x12\x10\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x12\x12\x10\xFF\x15\x13\x10"
+ "\xFF\x12\x12\x0F\xFF\x0F\x0F\x0D\xFF"
+ "\x13\x11\x10\xFF\x14\x13\x10\xFF\x17\x14\x11\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF\x12\x11\x0F"
+ "\xFF\x14\x13\x0F\xFF\x13\x11\x10\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x15\x14\x11\xFF\x13\x11\x0E\xFF\x13\x11\x10"
+ "\xFF\x14\x13\x11\xFF\x12\x11\x0F\xFF"
+ "\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x16\x12\xFF\x18\x16\x13\xFF\x14\x13\x11\xFF\x14\x13\x10"
+ "\xFF\x17\x15\x14\xFF\x15\x14\x10\xFF"
+ "\x18\x17\x13\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF\x16\x14\x12"
+ "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF"
+ "\x14\x12\x11\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x19\x16\x12"
+ "\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF"
+ "\x15\x13\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x18\x17\x12\xFF\x15\x14\x11"
+ "\xFF\x16\x14\x11\xFF\x17\x16\x12\xFF"
+ "\x19\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x18\x18\x14\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x10\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x18\x17\x14"
+ "\xFF\x18\x16\x13\xFF\x1B\x19\x15\xFF"
+ "\x17\x15\x11\xFF\x14\x15\x11\xFF\x15\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x17\x12\xFF\x14\x13\x0F\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x16\x14\x11\xFF\x1B\x1A\x16\xFF"
+ "\x1A\x19\x15\xFF\x1A\x19\x16\xFF\x19\x19\x15\xFF\x17\x15\x13\xFF\x19\x18\x14\xFF\x19\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF"
+ "\x1F\x1D\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x17\x13\xFF\x1A\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x18\x16\x13\xFF"
+ "\x19\x17\x13\xFF\x1D\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x1C\x1A\x16\xFF\x1B\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x19\x17\x13\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17"
+ "\xFF\x1A\x17\x14\xFF\x19\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1D\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1C\x1B\x17\xFF\x1C\x1B\x16\xFF"
+ "\x1D\x1C\x18\xFF\x18\x16\x12\xFF\x19\x17\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF\x1C\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A\xFF"
+ "\x18\x17\x13\xFF\x18\x16\x12\xFF\x1B\x19\x14\xFF\x1D\x1D\x17\xFF\x19\x1A\x15\xFF\x1F\x1E\x19"
+ "\xFF\x1F\x1D\x19\xFF\x20\x1E\x19\xFF"
+ "\x1A\x19\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF\x20\x1D\x1A\xFF\x1F\x1F\x19"
+ "\xFF\x1E\x1D\x19\xFF\x1B\x19\x16\xFF"
+ "\x1C\x1B\x16\xFF\x1B\x1B\x16\xFF\x1D\x1C\x17\xFF\x1F\x1D\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15"
+ "\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF"
+ "\x1D\x1C\x16\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF\x1D\x1D\x17\xFF\x21\x1E\x1A\xFF\x1D\x1D\x18"
+ "\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF"
+ "\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x1D\x1C\x18"
+ "\xFF\x1F\x1D\x19\xFF\x1F\x1C\x18\xFF"
+ "\x1D\x1C\x16\xFF\x1E\x1D\x19\xFF\x1D\x1C\x18\xFF\x1D\x1A\x16\xFF\x1E\x1F\x19\xFF\x1F\x1E\x1A"
+ "\xFF\x1E\x1C\x19\xFF\x20\x1E\x1A\xFF"
+ "\x1E\x1D\x18\xFF\x1E\x1C\x16\xFF\x1C\x1C\x17\xFF\x1D\x1D\x17\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x22\x20\x1B\xFF"
+ "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF\x20\x1E\x1A\xFF\x1E\x1E\x18\xFF\x1F\x1D\x19"
+ "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x17\xFF"
+ "\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1F\x1D\x19"
+ "\xFF\x21\x1F\x1A\xFF\x23\x20\x1B\xFF"
+ "\x1F\x1F\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x1C\x1C\x18\xFF\x1D\x1D\x19"
+ "\xFF\x22\x1F\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1D\x1A\x17\xFF\x1C\x1B\x18\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16"
+ "\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17\xFF"
+ "\x1A\x1B\x17\xFF\x1D\x1B\x16\xFF\x1C\x1B\x17\xFF\x19\x18\x15\xFF\x17\x16\x13\xFF\x17\x16\x13"
+ "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF"
+ "\x16\x16\x12\xFF\x15\x15\x11\xFF\x50\x4C\x40\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x21\x20\x1A"
+ "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x27\x21"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x20\x1B\xFF\x26\x25\x20"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x26\x24\x20\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF"
+ "\x25\x24\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF"
+ "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x29\x28\x22"
+ "\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x20\xFF\x22\x21\x1B\xFF\x27\x25\x20"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D"
+ "\xFF\x19\x18\x15\xFF\x1B\x1A\x16\xFF"
+ "\x1D\x1B\x16\xFF\x1D\x1D\x17\xFF\x1D\x1C\x18\xFF\x1E\x1E\x19\xFF\x20\x1D\x19\xFF\x22\x1F\x1B"
+ "\xFF\x22\x20\x1B\xFF\x24\x21\x1D\xFF"
+ "\x22\x1F\x1B\xFF\x24\x22\x1D\xFF\x25\x22\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D"
+ "\xFF\x25\x22\x1D\xFF\x27\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1F\xFF\x28\x27\x20"
+ "\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x27\x26\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF"
+ "\x27\x25\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x23\x22\x1D\xFF\x28\x25\x20\xFF\x29\x27\x20"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x23\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x25\x22\x1D\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x1E\xFF\x24\x23\x1D\xFF"
+ "\x24\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x26\x1D\xFF\x29\x26\x21\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x25\x1E\xFF\x27\x27\x21\xFF\x27\x24\x21\xFF\x28\x26\x1F"
+ "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x21\x1D\xFF\x26\x23\x1E\xFF\x29\x29\x22\xFF\x2E\x3E\x31"
+ "\xFF\x48\x9C\x74\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x3F\x7F\x5F\xFF\x28\x33\x28\xFF\x27\x27\x20\xFF\x27\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x22\x1E\xFF\x24\x23\x1F\xFF"
+ "\x25\x27\x1F\xFF\x2B\x38\x2C\xFF\x46\x94\x6F\xFF\x57\xCB\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x3E\x7D\x5E\xFF\x28\x2E\x26\xFF\x24\x24\x1F"
+ "\xFF\x23\x22\x1D\xFF\x26\x23\x1E\xFF"
+ "\x27\x25\x20\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x22\x20\x1C"
+ "\xFF\x24\x22\x1D\xFF\x24\x24\x1E\xFF"
+ "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x24\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x21\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF"
+ "\x26\x25\x1F\xFF\x26\x23\x1F\xFF\x23\x22\x1D\xFF\x24\x21\x1C\xFF\x26\x24\x1F\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x22\x1B\xFF\x25\x23\x1C"
+ "\xFF\x26\x24\x1E\xFF\x22\x21\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x21\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x25\x1E"
+ "\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF"
+ "\x23\x20\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF\x25\x22\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1E"
+ "\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x1F\x1A\xFF\x21\x20\x1B"
+ "\xFF\x25\x21\x1D\xFF\x25\x22\x1E\xFF"
+ "\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x20\x1C\xFF\x25\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x21\x22\x1A\xFF"
+ "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1A\xFF\x25\x22\x1D"
+ "\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B"
+ "\xFF\x22\x20\x1B\xFF\x23\x23\x1D\xFF"
+ "\x26\x24\x1F\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x1F\x1A\xFF\x21\x20\x1B\xFF\x21\x20\x1B"
+ "\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x24\x21\x1C\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x25\x23\x1D\xFF"
+ "\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x26\x24\x1C\xFF\x26\x26\x20\xFF\x28\x25\x20"
+ "\xFF\x23\x22\x1D\xFF\x20\x20\x1B\xFF"
+ "\x23\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF\x21\x21\x1C\xFF\x25\x23\x1D\xFF\x24\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x20\x20\x1A\xFF"
+ "\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x22\x21\x1C\xFF\x25\x23\x1D\xFF\x23\x21\x1D"
+ "\xFF\x22\x20\x1B\xFF\x22\x22\x1D\xFF"
+ "\x25\x22\x1D\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x21\x1C\xFF\x21\x20\x1B"
+ "\xFF\x20\x20\x1B\xFF\x25\x22\x1D\xFF"
+ "\x22\x20\x1C\xFF\x26\x24\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x1F\x1B\xFF\x24\x22\x1E"
+ "\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF"
+ "\x0F\x0F\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x11\x10\x0D\xFF\x12\x11\x0E"
+ "\xFF\x13\x12\x0F\xFF\x11\x0F\x0E\xFF"
+ "\x12\x11\x0E\xFF\x12\x11\x0F\xFF\x13\x11\x0E\xFF\x12\x11\x0D\xFF\x19\x18\x14\xFF\x16\x15\x12"
+ "\xFF\x12\x11\x0E\xFF\x11\x10\x0F\xFF"
+ "\x14\x13\x0F\xFF\x16\x14\x10\xFF\x13\x12\x0E\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF\x13\x13\x0F"
+ "\xFF\x12\x12\x0E\xFF\x13\x11\x0F\xFF"
+ "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0E\xFF\x16\x14\x11\xFF\x18\x19\x12\xFF\x14\x12\x0F"
+ "\xFF\x14\x10\x0F\xFF\x12\x10\x0F\xFF"
+ "\x12\x10\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11\xFF\x18\x18\x12\xFF\x14\x13\x0F\xFF\x13\x12\x0E"
+ "\xFF\x15\x14\x11\xFF\x14\x12\x11\xFF"
+ "\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x10\xFF\x12\x11\x0F\xFF\x14\x13\x0F"
+ "\xFF\x15\x14\x10\xFF\x19\x19\x14\xFF"
+ "\x14\x14\x0F\xFF\x12\x12\x0D\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x11\x10\x0E\xFF\x13\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF"
+ "\x10\x0F\x0B\xFF\x11\x10\x0E\xFF\x15\x14\x10\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF"
+ "\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x11\x10\x0C\xFF"
+ "\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0F\xFF\x12\x11\x0D\xFF\x14\x11\x0D\xFF\x16\x13\x0F"
+ "\xFF\x15\x13\x12\xFF\x16\x15\x11\xFF"
+ "\x13\x13\x0F\xFF\x13\x12\x0F\xFF\x16\x15\x12\xFF\x16\x15\x11\xFF\x12\x10\x0F\xFF\x14\x13\x0F"
+ "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x13\xFF\x16\x14\x11\xFF\x17\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x15\x14\x11\xFF"
+ "\x17\x16\x13\xFF\x11\x10\x0E\xFF\x13\x12\x0E\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF"
+ "\x13\x11\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0E"
+ "\xFF\x17\x16\x11\xFF\x18\x17\x12\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x13\x0F"
+ "\xFF\x14\x13\x0E\xFF\x14\x13\x0F\xFF"
+ "\x18\x17\x13\xFF\x16\x16\x12\xFF\x12\x12\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x15\x13\x11"
+ "\xFF\x17\x15\x12\xFF\x18\x17\x13\xFF"
+ "\x18\x17\x13\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x13\x13\x0E"
+ "\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF"
+ "\x15\x13\x11\xFF\x18\x17\x14\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF\x18\x16\x12"
+ "\xFF\x1B\x18\x14\xFF\x17\x15\x11\xFF"
+ "\x14\x13\x0F\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF"
+ "\x17\x16\x13\xFF\x15\x14\x10\xFF\x16\x15\x10\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x1B\x1A\x16"
+ "\xFF\x18\x17\x13\xFF\x18\x18\x12\xFF"
+ "\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x1B\x1A\x15\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF"
+ "\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1B\x19\x16\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF"
+ "\x1B\x19\x15\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x18\x17\x15\xFF\x1E\x1E\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF"
+ "\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x1A\x19\x14\xFF\x1C\x1A\x15\xFF\x1A\x19\x14\xFF\x19\x1A\x15\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1A\x16\xFF\x1E\x1C\x18\xFF"
+ "\x1F\x1D\x19\xFF\x1A\x18\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x19\x17\xFF\x1A\x19\x14"
+ "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x15\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1E\x1C\x17\xFF\x1B\x1B\x15"
+ "\xFF\x1C\x1B\x17\xFF\x1B\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1C\x1A\x17"
+ "\xFF\x1D\x1B\x18\xFF\x1B\x1B\x15\xFF"
+ "\x20\x1E\x19\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17"
+ "\xFF\x1E\x1D\x19\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x19\x19\x15\xFF\x19\x17\x13\xFF\x19\x18\x14\xFF\x1F\x1E\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1D\x19\xFF\x1F\x1F\x1A\xFF"
+ "\x1D\x1B\x17\xFF\x1C\x1A\x16\xFF\x1A\x18\x16\xFF\x18\x17\x13\xFF\x19\x19\x14\xFF\x1E\x1C\x17"
+ "\xFF\x1B\x19\x15\xFF\x1C\x1B\x17\xFF"
+ "\x1F\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x1A\x19\x16\xFF\x1E\x1B\x17\xFF\x20\x1E\x1A"
+ "\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x17\xFF"
+ "\x1C\x19\x15\xFF\x20\x1E\x1A\xFF\x22\x20\x1B\xFF\x1F\x1C\x17\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x1C\x1B\x18\xFF\x1C\x1C\x17\xFF\x1F\x1E\x1A\xFF\x1D\x1B\x17"
+ "\xFF\x1E\x1E\x19\xFF\x1D\x1B\x17\xFF"
+ "\x20\x1F\x1A\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17\xFF\x20\x1D\x19\xFF\x1F\x1E\x19\xFF\x21\x21\x1A"
+ "\xFF\x1F\x1D\x18\xFF\x1C\x1C\x18\xFF"
+ "\x1D\x1D\x18\xFF\x20\x20\x1A\xFF\x21\x21\x1B\xFF\x22\x20\x1B\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19"
+ "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF"
+ "\x20\x1F\x1A\xFF\x22\x20\x1C\xFF\x21\x1F\x1B\xFF\x1F\x1F\x19\xFF\x1E\x1E\x18\xFF\x1C\x1B\x17"
+ "\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1D\x1D\x17\xFF\x1F\x1E\x1A\xFF\x1E\x1F\x17\xFF\x1E\x1D\x18"
+ "\xFF\x1F\x1E\x19\xFF\x20\x20\x1A\xFF"
+ "\x1F\x1E\x19\xFF\x1F\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1D\x1A\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17"
+ "\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF"
+ "\x1E\x1C\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF\x1C\x1B\x18\xFF\x1A\x18\x15\xFF\x18\x18\x14"
+ "\xFF\x17\x17\x13\xFF\x17\x17\x13\xFF"
+ "\x16\x16\x12\xFF\x15\x14\x11\xFF\x4E\x4A\x3D\xFF\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x21\x20\x1A"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1A\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF"
+ "\x28\x27\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21"
+ "\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1C\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x21\x20\x1A\xFF"
+ "\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x20\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x28\x26\x22"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x2A\x29\x23\xFF\x25\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x2B\x2A\x24\xFF"
+ "\x26\x25\x21\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x29\x28\x22\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x22"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23"
+ "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x27\x22"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x27\x26\x21\xFF"
+ "\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF"
+ "\x26\x24\x20\xFF\x27\x25\x20\xFF\x29\x28\x22\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x4F\x4A\x3D"
+ "\xFF\x1A\x19\x15\xFF\x19\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1D\x1C\x18\xFF\x21\x1E\x19\xFF\x21\x1F\x1B"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x23\x20\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1C\xFF\x25\x22\x1D\xFF\x24\x24\x1D\xFF\x25\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1D\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x23\x23\x1D\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x28\x26\x20\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1E\xFF"
+ "\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x28\x22\xFF\x28\x25\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x29\x27\x20\xFF\x2A\x27\x22\xFF\x29\x26\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x28\x24\x1F\xFF\x29\x25\x20\xFF"
+ "\x27\x26\x20\xFF\x23\x23\x1D\xFF\x26\x24\x1E\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x24\x1E\xFF\x28\x28\x21\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x28\x26\x21\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x24\x24\x1D"
+ "\xFF\x27\x26\x1E\xFF\x27\x26\x20\xFF"
+ "\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x25\x1F\xFF"
+ "\x28\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x23\x23\x1D\xFF\x25\x24\x20\xFF\x26\x25\x20"
+ "\xFF\x29\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x24\x22\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1F\xFF\x28\x2F\x25"
+ "\xFF\x3E\x78\x5B\xFF\x54\xC4\x91\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x4C\xA5\x7B\xFF\x30\x46\x37\xFF\x27\x27\x21\xFF\x27\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x29\x26\x21"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x27\x26\x21\xFF\x28\x2C\x26\xFF\x39\x6C\x52\xFF\x53\xC1\x8F\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x4F\xB5\x85\xFF\x29\x38\x2D\xFF\x27\x28\x21"
+ "\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x24\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x22\x1D\xFF\x22\x21\x1C\xFF"
+ "\x25\x22\x1D\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x22\x1E\xFF\x25\x25\x1E\xFF"
+ "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x22\x1C"
+ "\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF"
+ "\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x26\x24\x1F\xFF\x27\x27\x1E\xFF"
+ "\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x22\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1C"
+ "\xFF\x23\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x20\x20\x1A\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x25\x25\x1E"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1A\xFF"
+ "\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x22\x1C\xFF\x22\x21\x1C"
+ "\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF"
+ "\x24\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x26\x24\x1F\xFF\x22\x20\x1B\xFF\x22\x22\x1C"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x23\x1E\xFF\x22\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x25\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x1E\x1A\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x22\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x23\x1C\xFF\x22\x20\x1B\xFF\x23\x22\x1C"
+ "\xFF\x25\x23\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x21\x1F\x1A\xFF\x23\x23\x1D\xFF\x22\x22\x1C"
+ "\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF"
+ "\x27\x24\x1F\xFF\x21\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x21\x20\x1A"
+ "\xFF\x22\x20\x1B\xFF\x20\x20\x1B\xFF"
+ "\x22\x22\x1C\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x20\x1B\xFF"
+ "\x22\x20\x1B\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x20\x1A\xFF\x25\x21\x1C\xFF\x24\x23\x1D"
+ "\xFF\x20\x21\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1C\xFF\x1F\x1F\x19\xFF\x20\x1F\x1A\xFF\x1F\x20\x1A\xFF\x23\x20\x1C\xFF\x22\x21\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF"
+ "\x1F\x1F\x1B\xFF\x21\x22\x1D\xFF\x20\x1F\x1B\xFF\x21\x21\x1B\xFF\x21\x1E\x1A\xFF\x22\x20\x1C"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF"
+ "\x23\x22\x1D\xFF\x25\x21\x1D\xFF\x21\x20\x1B\xFF\x20\x1F\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1C\xFF"
+ "\x14\x13\x10\xFF\x12\x11\x0E\xFF\x12\x12\x0E\xFF\x12\x11\x0E\xFF\x10\x10\x0D\xFF\x10\x10\x0C"
+ "\xFF\x10\x0F\x0B\xFF\x14\x13\x10\xFF"
+ "\x11\x0F\x0E\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F\xFF\x11\x10\x0E"
+ "\xFF\x12\x11\x0F\xFF\x10\x0F\x0E\xFF"
+ "\x15\x14\x0F\xFF\x12\x11\x0D\xFF\x13\x11\x0E\xFF\x16\x15\x11\xFF\x15\x15\x11\xFF\x16\x15\x11"
+ "\xFF\x13\x12\x10\xFF\x13\x11\x0F\xFF"
+ "\x0F\x0F\x0D\xFF\x0F\x0E\x0C\xFF\x10\x0E\x0C\xFF\x14\x11\x10\xFF\x14\x13\x0E\xFF\x13\x12\x0E"
+ "\xFF\x14\x12\x11\xFF\x12\x10\x0E\xFF"
+ "\x0F\x0E\x0D\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x12\x11\x0D\xFF\x14\x12\x0F"
+ "\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF"
+ "\x14\x12\x0E\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0E\xFF\x13\x11\x0F\xFF\x14\x13\x10"
+ "\xFF\x16\x14\x10\xFF\x15\x14\x10\xFF"
+ "\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10"
+ "\xFF\x15\x13\x10\xFF\x13\x11\x0F\xFF"
+ "\x12\x12\x0E\xFF\x10\x0F\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x15\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x15\x13\x10\xFF"
+ "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x15\x10"
+ "\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF"
+ "\x11\x10\x0F\xFF\x14\x12\x0F\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x15\x13\x10\xFF\x15\x14\x10"
+ "\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF\x16\x15\x13\xFF\x16\x15\x11"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x12\x10\x0E\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x18\x17\x13"
+ "\xFF\x17\x16\x12\xFF\x15\x13\x10\xFF"
+ "\x17\x16\x13\xFF\x13\x11\x10\xFF\x16\x14\x11\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F"
+ "\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x10\xFF\x16\x15\x10\xFF\x15\x13\x11\xFF\x16\x15\x11\xFF\x17\x15\x12"
+ "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF"
+ "\x15\x14\x10\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x14\x13\x0F\xFF\x15\x14\x10"
+ "\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF"
+ "\x17\x15\x11\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF\x18\x16\x12\xFF\x14\x13\x10\xFF\x18\x17\x13"
+ "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x17\x16\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF"
+ "\x18\x16\x12\xFF\x16\x15\x12\xFF\x16\x14\x12\xFF\x18\x16\x13\xFF\x15\x14\x10\xFF\x17\x15\x11"
+ "\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x1B\x1A\x16\xFF\x16\x15\x11\xFF\x19\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x1C\x1C\x17\xFF"
+ "\x17\x16\x12\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x17\x15\x13\xFF\x16\x15\x12\xFF\x1A\x19\x14"
+ "\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x15\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF"
+ "\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x18\x17\x14\xFF\x1B\x1A\x16\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF"
+ "\x17\x16\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1E\x1E\x19\xFF\x1D\x1D\x18"
+ "\xFF\x1A\x19\x16\xFF\x19\x18\x14\xFF";
+
+/**
+ * Experimental Case 02: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_02[16384] =
+ "\x1C\x1C\x17\xFF\x1D\x1B\x18\xFF\x1B\x19\x15\xFF\x19\x18\x13\xFF\x19\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x17\x17\x13\xFF\x19\x17\x14\xFF"
+ "\x15\x14\x11\xFF\x13\x13\x10\xFF\x4F\x4B\x3E\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x21\x21\x1A\xFF"
+ "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x22\x20\x1A\xFF\x21\x20\x1A\xFF"
+ "\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF"
+ "\x26\x25\x21\xFF\x21\x20\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF"
+ "\x23\x23\x1D\xFF\x2A\x29\x23\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1D\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D"
+ "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x23\x22\x1D"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x29\x29\x23\xFF\x21\x20\x1A\xFF\x25\x24\x20"
+ "\xFF\x26\x24\x20\xFF\x21\x21\x1B\xFF"
+ "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x29\x28\x22\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF"
+ "\x25\x25\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x23\x22\x1E"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF"
+ "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF"
+ "\x24\x23\x1E\xFF\x2A\x29\x23\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x23\x1E"
+ "\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x21\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF"
+ "\x23\x23\x1E\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1F\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x25\x24\x1F"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF\x27\x25\x20\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x26\x25\x21\xFF\x22\x23\x1C\xFF"
+ "\x23\x23\x1C\xFF\x24\x25\x1E\xFF\x27\x2B\x25\xFF\x2B\x2F\x27\xFF\x25\x2C\x23\xFF\x26\x2E\x24"
+ "\xFF\x26\x2F\x25\xFF\x2A\x35\x2B\xFF"
+ "\x2E\x39\x2E\xFF\x28\x34\x2A\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF\x2A\x35\x2C\xFF\x27\x32\x27"
+ "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF"
+ "\x2A\x36\x2C\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x2E\x39\x2E\xFF\x28\x33\x28\xFF\x28\x33\x2A"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x28\x33\x28\xFF\x2D\x37\x2D\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x54\x51\x43"
+ "\xFF\x37\x82\x60\xFF\x36\x7A\x5A\xFF"
+ "\x34\x71\x54\xFF\x32\x68\x4D\xFF\x30\x5D\x46\xFF\x2D\x53\x3F\xFF\x2B\x45\x35\xFF\x26\x35\x29"
+ "\xFF\x25\x2D\x23\xFF\x26\x2C\x25\xFF"
+ "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF"
+ "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58"
+ "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF"
+ "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1F\xFF\x29\x27\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x2A\x27\x22\xFF\x2B\x28\x22"
+ "\xFF\x2B\x28\x21\xFF\x25\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x23\x1E\xFF\x28\x25\x20\xFF\x25\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x25\x25\x1E\xFF"
+ "\x26\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x1E\xFF\x25\x23\x1C\xFF"
+ "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x22\x1D\xFF\x27\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1E\xFF\x25\x22\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x20"
+ "\xFF\x26\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x23\x22\x1C\xFF\x25\x23\x1D\xFF\x25\x22\x1E"
+ "\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x27\x21\xFF\x2A\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x28\x29\x23\xFF"
+ "\x2F\x46\x36\xFF\x4B\xA5\x7B\xFF\x57\xCD\x97\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x4D\xAC\x7F"
+ "\xFF\x32\x50\x3D\xFF\x26\x29\x22\xFF"
+ "\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x27\x21\xFF\x2B\x36\x2B\xFF"
+ "\x4B\xA7\x7D\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x50\xB9\x89\xFF\x32\x54\x40\xFF\x27\x2A\x23\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF\x24\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x25\x24\x1E\xFF\x23\x22\x1C\xFF"
+ "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x24\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF"
+ "\x26\x23\x1F\xFF\x24\x21\x1D\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C"
+ "\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1C\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x24\x21\x1D\xFF\x23\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x21\x1F\x1B"
+ "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1E\xFF\x24\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF"
+ "\x20\x20\x1B\xFF\x25\x25\x1E\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x22\x20\x1B"
+ "\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x20\x1F\x1A\xFF\x24\x21\x1C\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF"
+ "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x24\x22\x1C\xFF\x25\x23\x1D\xFF\x23\x22\x1D"
+ "\xFF\x20\x20\x1A\xFF\x20\x1F\x19\xFF"
+ "\x1F\x1F\x1A\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x1F\x1B\xFF\x21\x21\x1A\xFF\x22\x22\x1C"
+ "\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF"
+ "\x21\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x1F\x1B\xFF\x24\x21\x1C"
+ "\xFF\x25\x22\x1D\xFF\x22\x22\x1C\xFF"
+ "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x1E\x19\xFF\x21\x1F\x1B\xFF\x22\x20\x1B"
+ "\xFF\x23\x21\x1C\xFF\x22\x20\x1B\xFF"
+ "\x22\x21\x1C\xFF\x20\x20\x1C\xFF\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF\x21\x21\x1B"
+ "\xFF\x21\x21\x1B\xFF\x24\x22\x1F\xFF"
+ "\x12\x11\x0F\xFF\x11\x10\x0C\xFF\x11\x10\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x11\x0E"
+ "\xFF\x12\x11\x0F\xFF\x16\x15\x11\xFF"
+ "\x17\x15\x12\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0D\xFF\x10\x10\x0D"
+ "\xFF\x13\x13\x0F\xFF\x13\x11\x0F\xFF"
+ "\x13\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x10\x0E\x0E\xFF\x11\x0F\x0E\xFF\x13\x11\x10"
+ "\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF"
+ "\x15\x13\x12\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x0F\xFF\x15\x13\x11"
+ "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF"
+ "\x14\x13\x11\xFF\x13\x13\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D"
+ "\xFF\x11\x10\x0D\xFF\x12\x10\x0D\xFF"
+ "\x12\x10\x0E\xFF\x14\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x17\x15\x12"
+ "\xFF\x15\x14\x12\xFF\x16\x15\x11\xFF"
+ "\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x16\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x15\x14\x11"
+ "\xFF\x14\x12\x11\xFF\x13\x12\x10\xFF"
+ "\x15\x13\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF"
+ "\x16\x15\x11\xFF\x19\x17\x13\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x14\x13\x10"
+ "\xFF\x15\x13\x10\xFF\x13\x11\x10\xFF"
+ "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x16\x14\x11\xFF\x12\x11\x0F\xFF\x14\x13\x10"
+ "\xFF\x17\x17\x12\xFF\x18\x18\x12\xFF"
+ "\x18\x17\x13\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x15\x13\x10\xFF\x15\x13\x11"
+ "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF"
+ "\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x15\x13\x11\xFF\x14\x13\x10"
+ "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF"
+ "\x15\x14\x10\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x16\x16\x10\xFF\x16\x15\x11"
+ "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF"
+ "\x15\x14\x10\xFF\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF\x17\x16\x12\xFF\x17\x15\x14"
+ "\xFF\x13\x12\x0F\xFF\x17\x15\x12\xFF"
+ "\x1A\x18\x14\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x12\x11\x0E"
+ "\xFF\x15\x13\x12\xFF\x17\x16\x13\xFF"
+ "\x15\x14\x12\xFF\x18\x17\x13\xFF\x18\x16\x14\xFF\x12\x11\x10\xFF\x15\x14\x10\xFF\x18\x16\x13"
+ "\xFF\x17\x15\x12\xFF\x19\x18\x15\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x17\x14\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x12"
+ "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF"
+ "\x18\x16\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF\x17\x16\x13\xFF\x18\x17\x13"
+ "\xFF\x18\x16\x14\xFF\x18\x17\x14\xFF"
+ "\x16\x15\x11\xFF\x1C\x1A\x16\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1A\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x16\x15\x11"
+ "\xFF\x16\x14\x11\xFF\x16\x15\x11\xFF"
+ "\x1A\x19\x15\xFF\x15\x14\x11\xFF\x19\x17\x13\xFF\x1E\x1E\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15"
+ "\xFF\x1C\x1A\x16\xFF\x17\x16\x13\xFF"
+ "\x16\x14\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF"
+ "\x16\x15\x11\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x13\x12\x0E\xFF\x16\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x15\x11\xFF\x19\x18\x14\xFF\x1A\x19\x15"
+ "\xFF\x1B\x1A\x15\xFF\x22\x22\x1D\xFF"
+ "\x1F\x1D\x18\xFF\x1B\x18\x14\xFF\x1C\x1B\x16\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16"
+ "\xFF\x1B\x1A\x15\xFF\x1A\x19\x15\xFF"
+ "\x1A\x18\x14\xFF\x1C\x1B\x16\xFF\x1C\x1A\x16\xFF\x1C\x19\x15\xFF\x1B\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1A\x16\xFF"
+ "\x17\x16\x11\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1A\x18\x14"
+ "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF"
+ "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF"
+ "\x1B\x19\x15\xFF\x1C\x1B\x17\xFF\x1C\x1B\x18\xFF\x1A\x1A\x16\xFF\x1E\x1C\x18\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1C\x18\xFF\x1F\x1E\x19\xFF"
+ "\x1C\x1B\x17\xFF\x1B\x1B\x17\xFF\x1A\x19\x15\xFF\x1E\x1C\x18\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1C\x1A\x18\xFF\x1F\x1C\x19\xFF"
+ "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1B\x17\xFF\x1E\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x1F\x1D\x18\xFF\x1C\x1B\x17\xFF\x20\x1F\x19"
+ "\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF"
+ "\x1C\x1A\x16\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1F\x1E\x19\xFF\x21\x1E\x1A\xFF\x1D\x1B\x17"
+ "\xFF\x1D\x1D\x18\xFF\x1E\x1E\x18\xFF"
+ "\x1E\x1E\x18\xFF\x1F\x1D\x19\xFF\x1E\x1C\x17\xFF\x1E\x1D\x18\xFF\x1E\x1C\x18\xFF\x1F\x1E\x19"
+ "\xFF\x1D\x1C\x18\xFF\x1F\x1D\x1A\xFF"
+ "\x1F\x1E\x18\xFF\x1E\x1D\x18\xFF\x1F\x1E\x19\xFF\x22\x22\x1C\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18"
+ "\xFF\x20\x1F\x1C\xFF\x1E\x1E\x17\xFF"
+ "\x1D\x1C\x18\xFF\x1D\x1D\x18\xFF\x20\x21\x1B\xFF\x21\x20\x1B\xFF\x1D\x1C\x18\xFF\x1B\x19\x15"
+ "\xFF\x1D\x1B\x17\xFF\x20\x1D\x1B\xFF"
+ "\x1F\x1E\x19\xFF\x1E\x1D\x18\xFF\x20\x1F\x1A\xFF\x1F\x1F\x19\xFF\x22\x20\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x1E\x1D\x19\xFF\x22\x1F\x1B\xFF"
+ "\x1E\x1E\x17\xFF\x1C\x1C\x16\xFF\x1B\x1C\x15\xFF\x1D\x1B\x18\xFF\x1E\x1C\x19\xFF\x20\x1E\x19"
+ "\xFF\x21\x1E\x1A\xFF\x1F\x1D\x18\xFF"
+ "\x1A\x1A\x15\xFF\x19\x17\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14"
+ "\xFF\x18\x17\x14\xFF\x18\x18\x14\xFF"
+ "\x19\x17\x14\xFF\x15\x16\x12\xFF\x52\x4E\x42\xFF\x22\x21\x1B\xFF\x22\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21"
+ "\xFF\x23\x22\x1D\xFF\x24\x23\x1E\xFF"
+ "\x21\x20\x1A\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B"
+ "\xFF\x26\x25\x21\xFF\x21\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1F\xFF\x25\x24\x20\xFF"
+ "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x25\x24\x20\xFF"
+ "\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x26\x25\x20\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x21\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20"
+ "\xFF\x25\x24\x1F\xFF\x24\x22\x1D\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x21\x1C\xFF\x23\x23\x1E\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x20"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x29\x28\x23\xFF\x25\x25\x20\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C"
+ "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x26\x22\xFF\x26\x26\x22"
+ "\xFF\x26\x27\x21\xFF\x26\x2A\x23\xFF"
+ "\x26\x2E\x26\xFF\x26\x2F\x25\xFF\x27\x31\x27\xFF\x27\x31\x27\xFF\x28\x33\x28\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x29\x34\x2A\xFF"
+ "\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x2A\x36\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x2A\x35\x2C"
+ "\xFF\x27\x32\x27\xFF\x2D\x37\x2D\xFF"
+ "\x28\x33\x28\xFF\x28\x34\x2A\xFF\x28\x34\x2A\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF"
+ "\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x2D\x37\x2D"
+ "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x4F\x4D\x3F"
+ "\xFF\x3A\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x40\x98\x71\xFF\x42\x9A\x72\xFF\x42\x9A\x72\xFF\x42\x97\x70\xFF\x41\x93\x6D"
+ "\xFF\x3E\x87\x64\xFF\x3B\x75\x56\xFF"
+ "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22"
+ "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF"
+ "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92"
+ "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF"
+ "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C"
+ "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF"
+ "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x29\x26\x20\xFF\x28\x25\x20\xFF\x27\x26\x1E\xFF\x26\x24\x1E\xFF\x28\x26\x21\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x23\x1E"
+ "\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF"
+ "\x26\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x27\x24\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x24\x23\x1D\xFF\x21\x21\x1C\xFF"
+ "\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x24\x24\x1D\xFF"
+ "\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x27\x1F\xFF"
+ "\x29\x26\x21\xFF\x26\x25\x1F\xFF\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1D"
+ "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x25\x21\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x26\x26\x20\xFF"
+ "\x2A\x37\x2D\xFF\x42\x87\x66\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8E"
+ "\xFF\x37\x66\x4D\xFF\x28\x2C\x25\xFF"
+ "\x26\x26\x21\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x22\x1C\xFF\x23\x21\x1C"
+ "\xFF\x28\x27\x20\xFF\x2B\x31\x27\xFF"
+ "\x3E\x79\x5B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x54\xC4\x91\xFF\x3E\x79\x5B\xFF\x29\x30\x28\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E"
+ "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF"
+ "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x1F\x1A\xFF\x23\x21\x1C"
+ "\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF"
+ "\x27\x26\x21\xFF\x27\x24\x1F\xFF\x24\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x20\x1B"
+ "\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x22\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1D"
+ "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF"
+ "\x23\x21\x1C\xFF\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x27\x25\x20\xFF\x23\x22\x1D\xFF\x26\x22\x1F"
+ "\xFF\x23\x20\x1D\xFF\x24\x21\x1D\xFF"
+ "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x20\x1F\x1A\xFF\x20\x1E\x19\xFF\x23\x22\x1D\xFF\x23\x22\x1B"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x25\x22\x1E"
+ "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x20\x20\x1A\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x22\x1F\x1A\xFF\x24\x21\x1C"
+ "\xFF\x23\x20\x1C\xFF\x20\x1F\x1A\xFF"
+ "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x24\x22\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x22\x1C"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x23\x22\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C"
+ "\xFF\x22\x20\x1B\xFF\x21\x21\x1B\xFF"
+ "\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x20\x1E\x19\xFF\x21\x1E\x1A\xFF\x23\x22\x1D\xFF\x26\x24\x1E"
+ "\xFF\x23\x23\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1C\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x24\x22\x1D\xFF\x24\x22\x1C\xFF\x22\x20\x1C"
+ "\xFF\x22\x20\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x20\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D\xFF\x26\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1A"
+ "\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF"
+ "\x22\x1F\x1A\xFF\x27\x25\x1F\xFF\x23\x22\x1D\xFF\x22\x23\x1B\xFF\x25\x22\x1D\xFF\x27\x24\x1F"
+ "\xFF\x24\x22\x1D\xFF\x23\x23\x1D\xFF"
+ "\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x26\x23\x1C\xFF\x24\x21\x1D\xFF\x22\x20\x1B"
+ "\xFF\x20\x1E\x1B\xFF\x1D\x1C\x18\xFF"
+ "\x20\x20\x1A\xFF\x20\x1F\x1A\xFF\x1F\x1D\x19\xFF\x20\x1F\x1A\xFF\x23\x22\x1D\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF"
+ "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x12\x10\x0E\xFF\x12\x11\x0F\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF"
+ "\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x13\x11\x0E\xFF\x17\x13\x10"
+ "\xFF\x13\x12\x0E\xFF\x0E\x0D\x0A\xFF"
+ "\x0E\x0E\x0C\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D\xFF\x13\x11\x0F\xFF\x0F\x10\x0E\xFF\x11\x10\x0F"
+ "\xFF\x13\x11\x10\xFF\x13\x12\x10\xFF"
+ "\x11\x10\x0D\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x10\x0F\x0D\xFF\x13\x11\x0F"
+ "\xFF\x14\x13\x10\xFF\x13\x12\x10\xFF"
+ "\x15\x13\x0F\xFF\x14\x12\x0F\xFF\x12\x10\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x10"
+ "\xFF\x13\x11\x0E\xFF\x12\x11\x0F\xFF"
+ "\x11\x10\x0C\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF"
+ "\x12\x11\x0E\xFF\x13\x12\x0E\xFF\x15\x13\x10\xFF\x12\x11\x0D\xFF\x17\x16\x12\xFF\x17\x16\x13"
+ "\xFF\x12\x10\x0F\xFF\x14\x13\x0F\xFF"
+ "\x13\x13\x0F\xFF\x14\x14\x11\xFF\x14\x14\x11\xFF\x13\x12\x0E\xFF\x15\x13\x12\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF"
+ "\x18\x17\x13\xFF\x14\x13\x10\xFF\x17\x16\x13\xFF\x17\x16\x12\xFF\x16\x15\x12\xFF\x14\x12\x10"
+ "\xFF\x15\x15\x11\xFF\x12\x11\x0D\xFF"
+ "\x13\x10\x0F\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x13\x10\xFF\x11\x0F\x0D\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0E\xFF"
+ "\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x13\x11\x10"
+ "\xFF\x11\x10\x0D\xFF\x15\x14\x10\xFF"
+ "\x18\x16\x14\xFF\x17\x17\x12\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x11\xFF\x15\x14\x10\xFF"
+ "\x19\x17\x15\xFF\x1B\x1B\x16\xFF\x19\x18\x13\xFF\x16\x15\x13\xFF\x14\x13\x0F\xFF\x15\x14\x10"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x16\x14\x12\xFF\x15\x14\x10\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x18\x17\x13\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF"
+ "\x19\x17\x14\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x15\x13"
+ "\xFF\x18\x17\x12\xFF\x1B\x1A\x16\xFF"
+ "\x18\x15\x11\xFF\x14\x13\x0F\xFF\x17\x15\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x17\x15\x11"
+ "\xFF\x14\x13\x0F\xFF\x17\x16\x12\xFF"
+ "\x18\x17\x13\xFF\x16\x14\x12\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x16\x15\x12"
+ "\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF"
+ "\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x15\x13\x11\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF"
+ "\x19\x18\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x1C\x1A\x16\xFF\x1B\x19\x15"
+ "\xFF\x1A\x19\x15\xFF\x16\x14\x10\xFF"
+ "\x14\x13\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14"
+ "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x1B\x18\x16\xFF\x1C\x19\x16\xFF\x18\x17\x13\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF"
+ "\x1B\x18\x14\xFF\x19\x16\x14\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x19\x17\x13\xFF\x19\x18\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF"
+ "\x16\x15\x12\xFF\x16\x16\x12\xFF\x18\x18\x13\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1A\x17\x13"
+ "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x17\x16\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF"
+ "\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF"
+ "\x1D\x1C\x17\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x18\x14"
+ "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x1A\x1A\x16\xFF\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1B\x1A\x17\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17"
+ "\xFF\x1D\x1B\x17\xFF\x1A\x19\x15\xFF"
+ "\x18\x19\x14\xFF\x1B\x19\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x16\xFF\x1C\x1B\x17\xFF\x1C\x1C\x17"
+ "\xFF\x1D\x1C\x16\xFF\x1E\x1D\x18\xFF"
+ "\x20\x1E\x19\xFF\x1E\x1B\x17\xFF\x1B\x19\x14\xFF\x1D\x1B\x17\xFF\x1F\x1E\x1A\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1D\x17\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x15\xFF\x1F\x1F\x1A\xFF\x20\x1D\x19\xFF\x1E\x1C\x18"
+ "\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16\xFF"
+ "\x1C\x1B\x17\xFF\x1C\x1C\x17\xFF\x1E\x1F\x19\xFF\x1F\x1D\x18\xFF\x1D\x1B\x19\xFF\x1D\x1C\x17"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x1A\x17\xFF\x1D\x1D\x18\xFF\x1D\x1C\x17\xFF\x1E\x1D\x18\xFF\x1C\x1C\x17\xFF\x1E\x1C\x18"
+ "\xFF\x20\x1E\x1A\xFF\x1F\x1E\x19\xFF"
+ "\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1A\x19\x15\xFF\x1C\x1B\x16"
+ "\xFF\x1E\x1D\x19\xFF\x22\x21\x1B\xFF"
+ "\x20\x1F\x19\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x20\x20\x1A\xFF\x1C\x1A\x16"
+ "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF"
+ "\x20\x1F\x1A\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x21\x21\x1B\xFF\x1F\x1E\x19\xFF\x1D\x1D\x18"
+ "\xFF\x1C\x1B\x16\xFF\x1E\x1D\x18\xFF"
+ "\x1D\x1C\x18\xFF\x20\x20\x1A\xFF\x1E\x1D\x18\xFF\x1E\x1E\x19\xFF\x22\x20\x1C\xFF\x22\x1F\x1A"
+ "\xFF\x1E\x1C\x17\xFF\x20\x1D\x18\xFF"
+ "\x20\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x18\xFF\x1F\x1C\x17"
+ "\xFF\x1A\x19\x15\xFF\x1C\x1B\x16\xFF"
+ "\x1D\x1B\x18\xFF\x1C\x1B\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x15\xFF\x1C\x19\x15\xFF\x1B\x1B\x17"
+ "\xFF\x1A\x19\x15\xFF\x16\x16\x13\xFF"
+ "\x18\x15\x12\xFF\x14\x13\x10\xFF\x4F\x4B\x3F\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x21\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x20\x1A\xFF"
+ "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF\x23\x22\x1D\xFF\x26\x25\x20"
+ "\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF"
+ "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x20\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x20\x1B"
+ "\xFF\x21\x20\x1B\xFF\x21\x20\x1B\xFF"
+ "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF\x21\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x21\x20\x1A\xFF\x21\x20\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x25\x20\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF"
+ "\x28\x26\x21\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x27\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x20\xFF\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x24\x1E"
+ "\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1D"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20"
+ "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x22\x1E\xFF\x26\x25\x20"
+ "\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF"
+ "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x25\x24\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x29\x27\x22\xFF"
+ "\x29\x26\x21\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF"
+ "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x27\x26\x22\xFF\x22\x22\x1C\xFF\x29\x29\x23\xFF\x23\x26\x1F\xFF\x29\x2F\x27\xFF\x26\x2F\x25"
+ "\xFF\x27\x31\x27\xFF\x27\x32\x27\xFF"
+ "\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x34\x2A\xFF\x2D\x37\x2D\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF"
+ "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x35\x2B"
+ "\xFF\x2A\x35\x2C\xFF\x29\x34\x2B\xFF"
+ "\x29\x34\x2A\xFF\x29\x34\x2A\xFF\x29\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28"
+ "\xFF\x27\x32\x27\xFF\x29\x35\x2B\xFF"
+ "\x2B\x36\x2C\xFF\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x29\x34\x2A"
+ "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF"
+ "\x2A\x35\x2B\xFF\x2E\x39\x2E\xFF\x2A\x35\x2B\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x4F\x4D\x3F"
+ "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x49\xAE\x81"
+ "\xFF\x4B\xB2\x84\xFF\x4C\xB6\x87\xFF"
+ "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39"
+ "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF"
+ "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B"
+ "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF"
+ "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22"
+ "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF"
+ "\x28\x27\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1D\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x25\x24\x1D\xFF\x26\x26\x20\xFF"
+ "\x25\x22\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x24\x1D"
+ "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x29\x28\x20\xFF"
+ "\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF"
+ "\x22\x23\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x27\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x24\x1E\xFF\x24\x24\x1E"
+ "\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x23\x21\x1C\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x23\x21\x1C"
+ "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x1F\xFF\x28\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x25\x20\xFF\x24\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF"
+ "\x27\x2D\x26\xFF\x39\x6A\x50\xFF\x53\xC1\x90\xFF\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x41\x85\x65\xFF\x2A\x35\x2C\xFF"
+ "\x27\x27\x21\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF\x25\x22\x1D\xFF\x26\x22\x1F\xFF\x24\x23\x1E"
+ "\xFF\x24\x24\x1E\xFF\x22\x21\x1C\xFF"
+ "\x20\x20\x1A\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x27\x1F\xFF\x27\x2A\x24\xFF"
+ "\x31\x52\x3E\xFF\x51\xBA\x8A\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x48\x9C\x75\xFF\x2D\x3E\x31\xFF\x27\x28\x22\xFF\x25\x22\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x25\x22\x1E\xFF\x25\x22\x1E\xFF"
+ "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x22\x1E\xFF\x22\x22\x1D\xFF\x24\x23\x1D\xFF\x21\x21\x1B"
+ "\xFF\x1F\x1E\x19\xFF\x23\x20\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x23\x1D\xFF\x24\x23\x1D"
+ "\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF"
+ "\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x23\x23\x1D"
+ "\xFF\x25\x22\x1D\xFF\x28\x24\x20\xFF"
+ "\x24\x21\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x24\x21\x1C"
+ "\xFF\x24\x21\x1C\xFF\x21\x20\x1A\xFF"
+ "\x22\x21\x1B\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1F\xFF\x23\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x24\x24\x1E\xFF\x23\x23\x1D\xFF\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1C\xFF\x21\x1D\x1A\xFF"
+ "\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x21\x20\x1B\xFF\x21\x1F\x1B\xFF\x21\x1F\x1B\xFF\x25\x23\x1E"
+ "\xFF\x24\x21\x1D\xFF\x24\x23\x1D\xFF"
+ "\x26\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x24\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x21\x21\x1C\xFF\x22\x21\x1C\xFF\x23\x22\x1C\xFF\x21\x1F\x1B\xFF\x23\x20\x1C"
+ "\xFF\x24\x23\x1D\xFF\x21\x21\x1B\xFF"
+ "\x22\x22\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x22\x22\x1C\xFF\x23\x23\x1D"
+ "\xFF\x23\x23\x1D\xFF\x24\x21\x1C\xFF"
+ "\x1F\x1E\x1A\xFF\x21\x20\x1A\xFF\x1F\x1D\x18\xFF\x1E\x1D\x19\xFF\x22\x22\x1C\xFF\x21\x21\x1B"
+ "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF"
+ "\x24\x23\x1D\xFF\x21\x20\x1A\xFF\x21\x1F\x1A\xFF\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x23\x21\x1D\xFF\x24\x21\x1C\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x21\x1B"
+ "\xFF\x23\x21\x1B\xFF\x24\x21\x1C\xFF"
+ "\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1D\xFF\x20\x1F\x1B\xFF"
+ "\x25\x23\x1E\xFF\x23\x23\x1E\xFF\x23\x21\x1C\xFF\x28\x25\x20\xFF\x21\x1F\x1B\xFF\x20\x1E\x1A"
+ "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1C\xFF\x23\x21\x1D\xFF"
+ "\x12\x11\x0E\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x15\x13\x12\xFF\x15\x14\x11"
+ "\xFF\x15\x15\x10\xFF\x11\x0F\x0E\xFF"
+ "\x11\x0E\x0D\xFF\x11\x10\x0E\xFF\x12\x10\x0E\xFF\x11\x0E\x0D\xFF\x10\x0E\x0D\xFF\x12\x10\x0E"
+ "\xFF\x11\x0F\x0D\xFF\x11\x10\x0E\xFF"
+ "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x13\x12\x0F\xFF\x10\x10\x0C\xFF\x12\x11\x0E"
+ "\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF"
+ "\x11\x0F\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x13\x11\x10"
+ "\xFF\x12\x11\x0F\xFF\x11\x11\x0E\xFF"
+ "\x11\x11\x0E\xFF\x16\x14\x11\xFF\x13\x11\x0F\xFF\x13\x12\x10\xFF\x16\x14\x10\xFF\x15\x13\x10"
+ "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF"
+ "\x14\x13\x0F\xFF\x16\x14\x11\xFF\x14\x12\x0F\xFF\x15\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x10"
+ "\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF"
+ "\x13\x11\x0F\xFF\x14\x12\x10\xFF\x14\x12\x11\xFF\x13\x13\x10\xFF\x16\x15\x11\xFF\x15\x15\x10"
+ "\xFF\x13\x13\x0E\xFF\x17\x15\x11\xFF"
+ "\x10\x10\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x13\x12\x0F\xFF\x15\x14\x11\xFF\x15\x14\x10"
+ "\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF"
+ "\x16\x14\x10\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x12\x10\xFF\x14\x13\x10\xFF\x14\x13\x10"
+ "\xFF\x13\x12\x0F\xFF\x13\x11\x0E\xFF"
+ "\x17\x15\x11\xFF\x18\x16\x13\xFF\x18\x17\x13\xFF\x12\x11\x0E\xFF\x15\x14\x10\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x14\x12\x0F\xFF"
+ "\x18\x17\x13\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x15\x14\x11"
+ "\xFF\x14\x13\x10\xFF\x13\x13\x0E\xFF"
+ "\x15\x14\x11\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x10"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x13\xFF"
+ "\x16\x14\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x15\x14\x11"
+ "\xFF\x17\x15\x13\xFF\x15\x14\x11\xFF"
+ "\x16\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF"
+ "\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF\x13\x12\x0F"
+ "\xFF\x13\x12\x0E\xFF\x13\x11\x0F\xFF"
+ "\x15\x14\x11\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x1A\x19\x15\xFF\x18\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x13\x13\x0F\xFF\x17\x15\x11\xFF\x18\x16\x14\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x19\x17\x13"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11"
+ "\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x14\x11\xFF\x19\x18\x14\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1B\x1A\x16\xFF\x16\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF"
+ "\x15\x14\x10\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF"
+ "\x19\x18\x14\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x19\x18\x14"
+ "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF"
+ "\x1A\x18\x14\xFF\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x13\x13\x11\xFF\x19\x19\x15\xFF\x19\x18\x14"
+ "\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF"
+ "\x1A\x18\x14\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1B\x1C\x16"
+ "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF"
+ "\x18\x17\x13\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15"
+ "\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF"
+ "\x1A\x18\x14\xFF\x19\x19\x15\xFF\x1A\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x17\x13\xFF\x19\x19\x15"
+ "\xFF\x1B\x19\x15\xFF\x19\x17\x13\xFF"
+ "\x1A\x1A\x15\xFF\x1A\x19\x14\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x19\x17\x14\xFF\x19\x17\x13"
+ "\xFF\x1A\x19\x15\xFF\x1C\x1A\x16\xFF"
+ "\x1B\x19\x15\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13"
+ "\xFF\x1A\x19\x14\xFF\x1C\x1B\x16\xFF"
+ "\x1E\x1B\x17\xFF\x1A\x1A\x15\xFF\x1C\x1A\x16\xFF\x1B\x1A\x15\xFF\x1C\x1B\x17\xFF\x1B\x1B\x16"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1D\x1B\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x17\xFF\x1A\x18\x16\xFF\x1B\x19\x15\xFF\x1B\x1B\x16"
+ "\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF"
+ "\x20\x1F\x19\xFF\x1B\x1A\x17\xFF\x1B\x1A\x16\xFF\x19\x19\x15\xFF\x1D\x1C\x18\xFF\x20\x20\x19"
+ "\xFF\x1F\x1F\x18\xFF\x1D\x1C\x18\xFF"
+ "\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1A\x19\x14\xFF\x19\x18\x14\xFF\x1C\x1A\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1E\x1E\x19\xFF\x1F\x1E\x18\xFF"
+ "\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x1C\x17\xFF\x1F\x1E\x19\xFF\x1F\x1F\x19\xFF\x1F\x1C\x18"
+ "\xFF\x1E\x1C\x17\xFF\x1C\x1C\x17\xFF"
+ "\x1D\x1B\x16\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16"
+ "\xFF\x1D\x1B\x18\xFF\x1E\x1C\x18\xFF"
+ "\x1C\x1C\x17\xFF\x1F\x1F\x19\xFF\x1D\x1C\x17\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1D\x1C\x18"
+ "\xFF\x1D\x1D\x18\xFF\x21\x20\x1B\xFF"
+ "\x21\x1F\x1C\xFF\x1E\x1D\x19\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x17"
+ "\xFF\x1F\x1D\x18\xFF\x20\x1F\x19\xFF"
+ "\x20\x1D\x19\xFF\x1C\x1B\x16\xFF\x20\x1F\x1A\xFF\x22\x1F\x1B\xFF\x21\x1F\x1A\xFF\x1E\x1C\x18"
+ "\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF"
+ "\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF\x1F\x1E\x1A"
+ "\xFF\x21\x1F\x1A\xFF\x1E\x1D\x19\xFF"
+ "\x20\x1F\x1B\xFF\x1E\x1E\x18\xFF\x1D\x1D\x18\xFF\x20\x1E\x1A\xFF\x20\x1E\x1A\xFF\x20\x1E\x19"
+ "\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF"
+ "\x1C\x1B\x17\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x20\x1E\x1A\xFF\x1D\x1B\x16\xFF\x1C\x1C\x16"
+ "\xFF\x1C\x1D\x18\xFF\x1D\x1D\x18\xFF"
+ "\x1B\x19\x16\xFF\x1B\x1B\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x18"
+ "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF"
+ "\x15\x13\x10\xFF\x14\x13\x10\xFF\x4F\x4B\x3E\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF"
+ "\x29\x28\x23\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x20\x1B\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x23\x22\x1D"
+ "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x21\x20\x1A\xFF\x28\x26\x21\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x25\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x23\x21\x1C\xFF\x23\x22\x1D\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x22\x1D"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF"
+ "\x28\x26\x21\xFF\x24\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x21\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF"
+ "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF"
+ "\x22\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B"
+ "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E"
+ "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF"
+ "\x24\x23\x1E\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF"
+ "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x28\x22"
+ "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x22"
+ "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF"
+ "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x22\x21\x1C\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF"
+ "\x29\x28\x23\xFF\x23\x23\x1E\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x25\x25\x20\xFF\x27\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x24\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x28\x26\x21\xFF"
+ "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20"
+ "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x26\x21"
+ "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x24\x1F\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x23\x1E"
+ "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF"
+ "\x28\x26\x21\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C"
+ "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF"
+ "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x26\x21"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF"
+ "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C"
+ "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF"
+ "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF"
+ "\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20"
+ "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF"
+ "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B"
+ "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF"
+ "\x27\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF"
+ "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x28\x27\x21"
+ "\xFF\x22\x22\x1B\xFF\x24\x24\x1F\xFF"
+ "\x23\x26\x1F\xFF\x2A\x2E\x28\xFF\x25\x2D\x24\xFF\x27\x31\x27\xFF\x29\x34\x2A\xFF\x27\x32\x27"
+ "\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF"
+ "\x29\x34\x2A\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF"
+ "\x27\x32\x27\xFF\x2B\x36\x2D\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x29\x34\x2A"
+ "\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF"
+ "\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27"
+ "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF"
+ "\x28\x33\x28\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27"
+ "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF"
+ "\x29\x34\x2A\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x4F\x4D\x3F"
+ "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF"
+ "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x4A\xAF\x81"
+ "\xFF\x4C\xB3\x84\xFF\x4D\xB7\x87\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C"
+ "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF"
+ "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39"
+ "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF"
+ "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E"
+ "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x28\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF\x26\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x27\x25\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1D\xFF\x25\x24\x1D\xFF\x28\x26\x1F"
+ "\xFF\x28\x27\x21\xFF\x26\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x24\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x26\x1F\xFF"
+ "\x29\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F"
+ "\xFF\x25\x25\x1E\xFF\x25\x24\x1E\xFF"
+ "\x25\x24\x1E\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E"
+ "\xFF\x23\x23\x1E\xFF\x25\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1F\xFF\x25\x22\x1D\xFF\x27\x24\x1E\xFF\x27\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x26\x24\x1E\xFF"
+ "\x26\x24\x1E\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x24\x21\x1E\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1F\xFF\x25\x22\x1C\xFF"
+ "\x26\x29\x22\xFF\x32\x53\x3F\xFF\x4E\xAF\x82\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4A\xA4\x7A\xFF\x2C\x43\x35\xFF"
+ "\x28\x28\x22\xFF\x26\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1F\xFF\x25\x23\x1C\xFF\x25\x22\x1D"
+ "\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF"
+ "\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x22\x1D"
+ "\xFF\x24\x23\x1D\xFF\x24\x25\x1F\xFF"
+ "\x2C\x40\x30\xFF\x47\x98\x71\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCC\x98\xFF\x56\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x4E\xB2\x84\xFF\x32\x56\x42\xFF\x25\x2A\x22\xFF\x25\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1E\xFF\x25\x23\x1D"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1E\xFF\x24\x22\x1E\xFF\x23\x20\x1B"
+ "\xFF\x23\x22\x1C\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x25\x24\x1E\xFF\x22\x20\x1B\xFF"
+ "\x23\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF"
+ "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x24\x22\x1C"
+ "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF"
+ "\x25\x23\x1E\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D\xFF\x21\x21\x1B"
+ "\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF"
+ "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x23\x21\x1D\xFF\x21\x21\x1C\xFF\x25\x23\x1E"
+ "\xFF\x28\x25\x20\xFF\x23\x20\x1B\xFF"
+ "\x23\x21\x1C\xFF\x25\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x20\x1C"
+ "\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x21\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x23\x22\x1D"
+ "\xFF\x23\x22\x1D\xFF\x25\x24\x1E\xFF"
+ "\x24\x21\x1D\xFF\x27\x24\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1C"
+ "\xFF\x24\x21\x1D\xFF\x25\x23\x1F\xFF"
+ "\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D"
+ "\xFF\x22\x1F\x1A\xFF\x22\x21\x1B\xFF"
+ "\x20\x1E\x1A\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x22\x1C\xFF\x23\x21\x1C"
+ "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF"
+ "\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x1F\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C"
+ "\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF"
+ "\x20\x1F\x1A\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1B\xFF\x21\x1F\x1B\xFF\x22\x1F\x1B"
+ "\xFF\x24\x21\x1D\xFF\x25\x23\x1D\xFF"
+ "\x25\x22\x1D\xFF\x21\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x22\x21\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF"
+ "\x25\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1B\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C"
+ "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF"
+ "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x25\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1C"
+ "\xFF\x24\x22\x1E\xFF\x22\x21\x1D\xFF"
+ "\x18\x18\x13\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x13\x12\x0E\xFF\x12\x10\x0D\xFF\x12\x11\x0D"
+ "\xFF\x16\x15\x10\xFF\x14\x12\x10\xFF"
+ "\x12\x10\x10\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x10\x0F\x0C"
+ "\xFF\x10\x0E\x0D\xFF\x10\x0E\x0D\xFF"
+ "\x13\x12\x0E\xFF\x10\x0F\x0D\xFF\x10\x0F\x0C\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0E"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF"
+ "\x12\x10\x0E\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x12\x10\x0E"
+ "\xFF\x13\x11\x0F\xFF\x14\x12\x10\xFF"
+ "\x13\x10\x0D\xFF\x17\x15\x12\xFF\x17\x17\x13\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x14\x13\x0F"
+ "\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF"
+ "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x1E\x1E\x18\xFF\x17\x15\x13\xFF\x16\x15\x11"
+ "\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF"
+ "\x14\x12\x10\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10"
+ "\xFF\x14\x14\x10\xFF\x15\x13\x0F\xFF"
+ "\x15\x14\x10\xFF\x12\x11\x0E\xFF\x12\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11"
+ "\xFF\x12\x11\x0E\xFF\x16\x14\x0F\xFF"
+ "\x15\x14\x10\xFF\x11\x10\x0C\xFF\x12\x11\x0D\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F"
+ "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF"
+ "\x13\x12\x10\xFF\x14\x12\x11\xFF\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x17\x16\x12"
+ "\xFF\x15\x14\x11\xFF\x15\x15\x0F\xFF"
+ "\x17\x16\x12\xFF\x1A\x18\x15\xFF\x17\x16\x13\xFF\x14\x13\x0F\xFF\x11\x0F\x0E\xFF\x12\x11\x0F"
+ "\xFF\x14\x13\x10\xFF\x16\x15\x11\xFF"
+ "\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10\xFF\x12\x11\x0E\xFF\x16\x15\x11"
+ "\xFF\x17\x16\x12\xFF\x15\x13\x11\xFF"
+ "\x16\x15\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF"
+ "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF\x17\x16\x12"
+ "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF"
+ "\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x17\x16\x12\xFF\x16\x15\x11"
+ "\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF"
+ "\x14\x13\x0F\xFF\x15\x13\x11\xFF\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x19\x18\x15\xFF\x17\x16\x13"
+ "\xFF\x15\x14\x11\xFF\x1B\x1A\x16\xFF"
+ "\x13\x12\x0E\xFF\x17\x15\x11\xFF\x16\x15\x11\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x18\x16\x13"
+ "\xFF\x18\x16\x13\xFF\x17\x16\x12\xFF"
+ "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF"
+ "\x17\x16\x12\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF"
+ "\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1B\x17\xFF\x18\x17\x13"
+ "\xFF\x1C\x1A\x16\xFF\x19\x16\x12\xFF"
+ "\x16\x15\x11\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x18\x17\x13"
+ "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF"
+ "\x19\x18\x14\xFF\x18\x16\x12\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12"
+ "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF"
+ "\x17\x17\x13\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF\x1B\x1B\x16\xFF\x18\x18\x14\xFF\x18\x18\x14"
+ "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF"
+ "\x1B\x19\x15\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1C\x1A\x16\xFF\x1B\x1A\x16\xFF\x1A\x19\x15"
+ "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x19\x15\xFF\x1A\x18\x14\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16"
+ "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF"
+ "\x1B\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x16\x12\xFF\x19\x18\x13"
+ "\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF"
+ "\x1B\x1A\x16\xFF\x20\x1D\x19\xFF\x1D\x1B\x17\xFF\x18\x18\x13\xFF\x1E\x1B\x17\xFF\x1B\x19\x15"
+ "\xFF\x1B\x1A\x16\xFF\x1A\x19\x14\xFF"
+ "\x19\x17\x14\xFF\x1C\x19\x16\xFF\x1B\x19\x15\xFF\x19\x17\x15\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x19\x19\x14\xFF\x1A\x1A\x15\xFF"
+ "\x1B\x1A\x15\xFF\x1E\x1C\x17\xFF\x1B\x1A\x15\xFF\x1B\x1A\x15\xFF\x1D\x1D\x17\xFF\x1E\x1D\x17"
+ "\xFF\x1C\x1B\x16\xFF\x17\x15\x11\xFF"
+ "\x19\x19\x15\xFF\x1C\x19\x15\xFF\x1D\x1B\x17\xFF\x1D\x1B\x18\xFF\x19\x18\x14\xFF\x1B\x1A\x16"
+ "\xFF\x1C\x1B\x17\xFF\x18\x17\x13\xFF"
+ "\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1B\x1A\x15\xFF\x17\x17\x12\xFF\x1A\x19\x15\xFF\x1A\x19\x15"
+ "\xFF\x17\x17\x13\xFF\x18\x17\x13\xFF"
+ "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1A\x19\x14\xFF\x1A\x18\x16\xFF\x1D\x1A\x16\xFF\x1C\x1A\x16"
+ "\xFF\x1A\x1A\x16\xFF\x1D\x1C\x17\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x18\x14\xFF\x1D\x1B\x17"
+ "\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF"
+ "\x1F\x1D\x19\xFF\x1E\x1E\x19\xFF\x1F\x1F\x1A\xFF\x1C\x1B\x17\xFF\x1E\x1E\x18\xFF\x1D\x1B\x18"
+ "\xFF\x1D\x1C\x17\xFF\x1D\x1C\x18\xFF"
+ "\x1A\x1A\x18\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1D\x17\xFF\x1E\x1D\x19\xFF\x1E\x1D\x18"
+ "\xFF\x1B\x1A\x15\xFF\x1F\x1E\x19\xFF"
+ "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1E\x1D\x18\xFF\x1E\x1D\x19\xFF\x1C\x1B\x16"
+ "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF"
+ "\x1D\x1A\x17\xFF\x1F\x1D\x19\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18"
+ "\xFF\x1C\x1B\x17\xFF\x1D\x1D\x17\xFF"
+ "\x1D\x1C\x16\xFF\x1F\x1D\x18\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A"
+ "\xFF\x24\x21\x1D\xFF\x21\x21\x1B\xFF"
+ "\x24\x22\x1D\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1C\x17\xFF\x20\x1F\x1B\xFF\x1F\x1E\x19"
+ "\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF"
+ "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1D\x18\xFF\x1E\x1B\x18\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17"
+ "\xFF\x1D\x1D\x19\xFF\x1E\x1E\x18\xFF";
+
+/**
+ * Experimental Case 03: 64x64 (32bpp)
+ */
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03[16384] =
+ "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF"
+ "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58"
+ "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF"
+ "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF"
+ "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22"
+ "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF"
+ "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92"
+ "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF"
+ "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C"
+ "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF"
+ "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF"
+ "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39"
+ "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF"
+ "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B"
+ "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF"
+ "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22"
+ "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C"
+ "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF"
+ "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39"
+ "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF"
+ "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E"
+ "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x53\xC5\x91\xFF\x54\xC7\x92"
+ "\xFF\x55\xC8\x95\xFF\x53\xC3\x92\xFF"
+ "\x50\xBA\x89\xFF\x51\xBD\x8C\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x99\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x95\xFF\x52\xBD\x8D\xFF\x4E\xB0\x82"
+ "\xFF\x47\x93\x6E\xFF\x38\x65\x4C\xFF"
+ "\x2D\x3A\x2F\xFF\x2C\x33\x2A\xFF\x26\x2A\x23\xFF\x27\x28\x20\xFF\x28\x27\x21\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF\x24\x24\x1D\xFF\x24\x22\x1D"
+ "\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x27\x25\x1E"
+ "\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x92"
+ "\xFF\x55\xC8\x95\xFF\x54\xC8\x94\xFF"
+ "\x55\xCB\x96\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x52\xBE\x8D\xFF\x45\x90\x6C\xFF\x38\x62\x4B\xFF\x2F\x42\x34\xFF\x29\x30\x27\xFF\x27\x28\x22"
+ "\xFF\x28\x27\x21\xFF\x25\x22\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1E"
+ "\xFF\x25\x24\x1E\xFF\x27\x24\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x27\x23\x1F\xFF"
+ "\x27\x25\x1E\xFF\x26\x25\x1F\xFF\x2A\x27\x21\xFF\x2C\x29\x24\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x29\x26\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x53\xC7\x93"
+ "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF"
+ "\x56\xCD\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x52\xC0\x8E\xFF\x49\x9D\x76\xFF\x3A\x6A\x51\xFF\x2E\x3F\x31"
+ "\xFF\x29\x2E\x24\xFF\x28\x27\x21\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1E\xFF\x27\x25\x20\xFF\x27\x27\x21\xFF\x25\x24\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x25\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x25\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x53\xC7\x93"
+ "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCB\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x96\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xC1\x8E\xFF\x4C\xA8\x7C\xFF\x45\x92\x6D"
+ "\xFF\x42\x86\x64\xFF\x47\x99\x72\xFF"
+ "\x51\xB9\x89\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xBF\x8E\xFF\x45\x8F\x6B"
+ "\xFF\x30\x47\x38\xFF\x28\x2E\x25\xFF"
+ "\x27\x26\x1E\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x29\x26\x20"
+ "\xFF\x28\x25\x1F\xFF\x26\x25\x1F\xFF"
+ "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF"
+ "\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1D\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x26\x24\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF"
+ "\x56\xCA\x96\xFF\x50\xB6\x87\xFF\x42\x83\x62\xFF\x30\x48\x39\xFF\x2A\x3B\x2E\xFF\x2B\x39\x2E"
+ "\xFF\x2D\x3B\x2F\xFF\x2D\x3B\x2F\xFF"
+ "\x2E\x40\x32\xFF\x35\x58\x45\xFF\x48\x9B\x74\xFF\x54\xC5\x92\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4C\xA9\x7D\xFF\x34\x52\x3E\xFF"
+ "\x27\x2B\x22\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF\x28\x25\x1E\xFF\x26\x23\x1E"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x25\x23\x1E"
+ "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF"
+ "\x27\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1E"
+ "\xFF\x25\x23\x1D\xFF\x26\x25\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF"
+ "\x4C\xA8\x7D\xFF\x37\x5D\x47\xFF\x2D\x3D\x31\xFF\x2E\x3D\x30\xFF\x34\x5F\x49\xFF\x3B\x71\x55"
+ "\xFF\x3D\x79\x5B\xFF\x3A\x6C\x52\xFF"
+ "\x33\x50\x3D\xFF\x2F\x3D\x30\xFF\x2F\x47\x37\xFF\x3E\x7A\x5D\xFF\x54\xC5\x92\xFF\x56\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x48\x98\x72\xFF"
+ "\x2C\x37\x2C\xFF\x29\x2A\x23\xFF\x27\x26\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x29\x27\x21\xFF\x25\x25\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF"
+ "\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x26\x21\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1E\xFF\x24\x23\x1D\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x53\xC3\x91\xFF"
+ "\x35\x59\x45\xFF\x2D\x3B\x2F\xFF\x32\x50\x3E\xFF\x46\x92\x6D\xFF\x51\xBB\x8B\xFF\x54\xC4\x91"
+ "\xFF\x55\xC7\x93\xFF\x53\xC1\x8F\xFF"
+ "\x4E\xAE\x81\xFF\x3F\x75\x59\xFF\x2D\x40\x32\xFF\x2D\x3D\x31\xFF\x43\x8C\x68\xFF\x56\xCC\x97"
+ "\xFF\x56\xCD\x97\xFF\x56\xC8\x94\xFF"
+ "\x39\x65\x4D\xFF\x2A\x30\x26\xFF\x28\x28\x22\xFF\x27\x25\x1E\xFF\x2A\x27\x22\xFF\x28\x26\x21"
+ "\xFF\x29\x26\x21\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x26\x25\x20"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x1F\xFF"
+ "\x27\x24\x20\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x97"
+ "\xFF\x55\xC7\x93\xFF\x3B\x70\x56\xFF"
+ "\x2E\x3B\x2F\xFF\x36\x59\x44\xFF\x4E\xAE\x81\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97"
+ "\xFF\x55\xC9\x94\xFF\x54\xC4\x91\xFF"
+ "\x56\xCB\x96\xFF\x56\xCA\x96\xFF\x43\x8A\x69\xFF\x2E\x41\x33\xFF\x31\x4C\x3A\xFF\x4A\xA1\x79"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF"
+ "\x4E\xB0\x81\xFF\x2F\x45\x37\xFF\x28\x2B\x24\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x28\x26\x20\xFF\x27\x24\x20\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF"
+ "\x28\x26\x21\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x20\xFF\x27\x24\x1F\xFF\x27\x24\x20"
+ "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x4E\xAF\x82\xFF\x32\x50\x3E\xFF"
+ "\x2F\x42\x35\xFF\x45\x90\x6C\xFF\x56\xCA\x96\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x4E\xB0\x82"
+ "\xFF\x42\x84\x63\xFF\x3D\x74\x57\xFF"
+ "\x44\x8B\x69\xFF\x50\xB7\x88\xFF\x53\xC0\x8F\xFF\x36\x64\x4C\xFF\x2D\x3C\x2F\xFF\x3E\x76\x59"
+ "\xFF\x54\xC7\x92\xFF\x57\xCD\x97\xFF"
+ "\x54\xC3\x91\xFF\x3D\x74\x58\xFF\x2A\x31\x28\xFF\x27\x26\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x26\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x26\x1F\xFF\x23\x23\x1D\xFF"
+ "\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x27\x24\x1E\xFF\x29\x26\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x49\x98\x72\xFF\x2E\x44\x36\xFF"
+ "\x33\x51\x3F\xFF\x4F\xB4\x86\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xC0\x8F\xFF\x3D\x73\x57"
+ "\xFF\x2F\x3E\x32\xFF\x2C\x38\x2C\xFF"
+ "\x2F\x42\x35\xFF\x41\x83\x62\xFF\x55\xC8\x94\xFF\x43\x8F\x6A\xFF\x2E\x3A\x2F\xFF\x39\x66\x4E"
+ "\xFF\x52\xBB\x8B\xFF\x56\xCD\x97\xFF"
+ "\x56\xCB\x97\xFF\x48\x9C\x75\xFF\x2E\x42\x34\xFF\x28\x29\x22\xFF\x28\x27\x21\xFF\x28\x26\x1F"
+ "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x20\xFF\x2A\x27\x21\xFF\x27\x26\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x28\x22"
+ "\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x41\x80\x61\xFF\x2B\x39\x2E\xFF"
+ "\x33\x58\x44\xFF\x54\xC7\x93\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC9\x94\xFF\x46\x96\x70"
+ "\xFF\x34\x5B\x46\xFF\x32\x51\x3E\xFF"
+ "\x38\x67\x4D\xFF\x49\xA3\x79\xFF\x56\xCD\x97\xFF\x4C\xAC\x80\xFF\x2F\x3D\x31\xFF\x36\x58\x44"
+ "\xFF\x4E\xAF\x82\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x52\xBF\x8E\xFF\x39\x69\x50\xFF\x2A\x30\x26\xFF\x26\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x28\x26\x1F\xFF\x25\x24\x1D\xFF"
+ "\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x44\x8C\x69\xFF\x2D\x40\x31\xFF"
+ "\x33\x55\x41\xFF\x52\xBE\x8D\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xC9\x95"
+ "\xFF\x50\xBA\x8A\xFF\x4D\xAD\x80\xFF"
+ "\x52\xC0\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF\x4A\xA5\x7A\xFF\x2E\x3B\x2F\xFF\x36\x5A\x45"
+ "\xFF\x4F\xB1\x83\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x42\x8A\x68\xFF\x2B\x3B\x2E\xFF\x28\x28\x20\xFF\x27\x24\x1E"
+ "\xFF\x28\x25\x1F\xFF\x28\x25\x1F\xFF"
+ "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x56\xCD\x97\xFF\x56\xCD\x98"
+ "\xFF\x49\x9E\x77\xFF\x31\x47\x38\xFF"
+ "\x2F\x4A\x39\xFF\x4B\xA9\x7E\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCE\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x40\x80\x61\xFF\x2E\x3B\x2F\xFF\x38\x6A\x50"
+ "\xFF\x52\xBF\x8E\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4D\xAC\x80\xFF\x2F\x4B\x3A\xFF\x28\x2A\x21\xFF\x27\x26\x1F"
+ "\xFF\x28\x25\x1F\xFF\x29\x26\x21\xFF"
+ "\x26\x23\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x53\xC1\x8F"
+ "\xFF\x57\xC7\x94\xFF\x57\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97"
+ "\xFF\x51\xBA\x89\xFF\x37\x5E\x47\xFF"
+ "\x2D\x39\x2E\xFF\x39\x6B\x52\xFF\x52\xBE\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCB\x97\xFF\x4C\xA8\x7C\xFF\x31\x4D\x3B\xFF\x2F\x3F\x31\xFF\x41\x86\x65"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xC9\x96\xFF\x39\x63\x4D\xFF\x27\x2D\x26\xFF\x26\x27\x1F"
+ "\xFF\x27\x26\x20\xFF\x24\x23\x1D\xFF"
+ "\x24\x22\x1D\xFF\x26\x24\x1D\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x23\x1F\xFF\x25\x23\x1E\xFF"
+ "\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x27\x25\x1F"
+ "\xFF\x26\x23\x1E\xFF\x27\x24\x20\xFF"
+ "\x4F\xBA\x8A\xFF\x50\xBD\x8C\xFF\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x49\x9E\x76"
+ "\xFF\x43\x8B\x68\xFF\x50\xB5\x87\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCD\x97\xFF\x48\x9A\x72\xFF"
+ "\x2C\x3B\x2F\xFF\x30\x42\x34\xFF\x3F\x7C\x5D\xFF\x51\xBB\x8B\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xC9\x95\xFF\x4C\xA9\x7D\xFF\x37\x5F\x49\xFF\x2C\x3A\x2E\xFF\x36\x5D\x47\xFF\x50\xB4\x86"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCC\x99\xFF\x57\xCC\x98\xFF\x57\xCD\x98\xFF\x44\x8C\x69\xFF\x29\x30\x27\xFF\x27\x27\x20"
+ "\xFF\x27\x26\x1F\xFF\x25\x23\x1E\xFF"
+ "\x23\x22\x1C\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x4C\xAA\x7F"
+ "\xFF\x35\x5E\x49\xFF\x35\x59\x45\xFF"
+ "\x46\x99\x72\xFF\x55\xC8\x94\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x55\xC8\x94\xFF"
+ "\x3B\x6C\x51\xFF\x2C\x3A\x2E\xFF\x2E\x3F\x31\xFF\x38\x5F\x49\xFF\x46\x94\x6F\xFF\x4E\xB1\x83"
+ "\xFF\x50\xB9\x88\xFF\x4C\xAA\x7F\xFF"
+ "\x41\x80\x61\xFF\x32\x50\x3E\xFF\x2C\x3A\x2E\xFF\x2B\x40\x32\xFF\x4B\xA7\x7C\xFF\x57\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4F\xB6\x86\xFF\x2D\x38\x2D\xFF\x29\x2A\x23"
+ "\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF\x26\x24\x1F"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x55\xC7\x94"
+ "\xFF\x40\x7B\x5D\xFF\x2C\x3B\x2E\xFF"
+ "\x2B\x37\x2C\xFF\x35\x5B\x46\xFF\x48\x9E\x76\xFF\x52\xBF\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x54\xC4\x91\xFF\x44\x89\x67\xFF\x31\x4D\x3C\xFF\x29\x37\x2C\xFF\x2C\x39\x2D\xFF\x2C\x3A\x2E"
+ "\xFF\x2D\x3C\x2E\xFF\x2C\x3B\x2E\xFF"
+ "\x2C\x39\x2D\xFF\x2E\x41\x32\xFF\x38\x60\x4B\xFF\x4B\xA2\x79\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCC\x97\xFF\x33\x52\x3F\xFF\x28\x2C\x23"
+ "\xFF\x28\x27\x1F\xFF\x27\x24\x1F\xFF"
+ "\x23\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x27\x26\x21\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF"
+ "\x4F\xBC\x8B\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x4A\xA4\x7A\xFF\x30\x4B\x39\xFF"
+ "\x2A\x30\x28\xFF\x2A\x32\x28\xFF\x2E\x40\x32\xFF\x38\x67\x4E\xFF\x48\x9B\x74\xFF\x53\xC0\x8F"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x55\xC8\x94\xFF\x4A\xA5\x7A\xFF\x3F\x7D\x5E\xFF\x37\x62\x4C\xFF\x31\x4C\x3A"
+ "\xFF\x31\x49\x39\xFF\x35\x57\x42\xFF"
+ "\x3A\x6B\x52\xFF\x44\x8D\x6A\xFF\x50\xBB\x8B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x4F\xAC\x80\xFF\x2F\x4A\x39\xFF\x28\x2A\x23"
+ "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF"
+ "\x50\xBC\x8C\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x54\xC6\x93\xFF\x41\x82\x62\xFF"
+ "\x2D\x3C\x30\xFF\x2B\x34\x29\xFF\x29\x2F\x26\xFF\x27\x30\x27\xFF\x2F\x42\x34\xFF\x38\x63\x4C"
+ "\xFF\x48\x98\x72\xFF\x55\xC6\x93\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC7\x94\xFF\x52\xBC\x8C\xFF\x4F\xB2\x84"
+ "\xFF\x4E\xB0\x83\xFF\x53\xB7\x88\xFF"
+ "\x53\xC0\x8E\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x97\xFF\x52\xBC\x8C\xFF\x40\x81\x62\xFF\x32\x4E\x3D\xFF\x29\x2C\x25\xFF\x29\x27\x21"
+ "\xFF\x29\x27\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x26\x20\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x29\x26\x20\xFF"
+ "\x27\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x50\xBD\x8C\xFF\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x54\xC6\x93\xFF\x54\xC7\x93"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF"
+ "\x52\xBA\x8B\xFF\x42\x85\x64\xFF\x36\x59\x46\xFF\x2F\x40\x33\xFF\x2B\x31\x28\xFF\x2B\x31\x27"
+ "\xFF\x2C\x36\x2B\xFF\x31\x4B\x3A\xFF"
+ "\x41\x82\x61\xFF\x4C\xA8\x7E\xFF\x52\xBC\x8C\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97"
+ "\xFF\x54\xC8\x93\xFF\x51\xB8\x89\xFF"
+ "\x3F\x82\x62\xFF\x2F\x47\x38\xFF\x2A\x34\x29\xFF\x26\x2B\x22\xFF\x27\x27\x21\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x20\xFF\x27\x24\x20\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF"
+ "\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x50\xBA\x8A\xFF\x46\x94\x6F\xFF\x38\x64\x4B\xFF\x2D\x3F\x32"
+ "\xFF\x29\x33\x29\xFF\x29\x32\x27\xFF"
+ "\x2A\x35\x29\xFF\x30\x43\x34\xFF\x38\x61\x4A\xFF\x40\x81\x61\xFF\x49\x9F\x78\xFF\x4E\xB0\x83"
+ "\xFF\x52\xC0\x8E\xFF\x56\xCB\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xC9\x95\xFF\x4F\xB7\x86\xFF\x48\x9B\x74"
+ "\xFF\x3F\x7E\x5F\xFF\x35\x5A\x44\xFF"
+ "\x2A\x36\x2B\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2D\x3D\x30\xFF\x28\x29\x21\xFF\x25\x25\x1E"
+ "\xFF\x26\x25\x20\xFF\x26\x26\x1E\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x24\x23\x1D"
+ "\xFF\x28\x24\x1F\xFF\x25\x22\x1E\xFF"
+ "\x25\x22\x1E\xFF\x24\x25\x1F\xFF\x27\x25\x20\xFF\x29\x27\x21\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC8\x95\xFF\x55\xCA\x96\xFF"
+ "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x51\xBD\x8C\xFF\x49\xA2\x79"
+ "\xFF\x3D\x72\x57\xFF\x31\x47\x37\xFF"
+ "\x2D\x36\x2C\xFF\x2B\x34\x29\xFF\x2A\x33\x28\xFF\x29\x34\x29\xFF\x2F\x43\x35\xFF\x31\x4D\x3C"
+ "\xFF\x35\x5A\x45\xFF\x3A\x67\x50\xFF"
+ "\x3D\x73\x57\xFF\x3B\x6B\x52\xFF\x39\x66\x4D\xFF\x37\x61\x4A\xFF\x33\x53\x3F\xFF\x2E\x42\x34"
+ "\xFF\x29\x35\x2A\xFF\x2A\x34\x2A\xFF"
+ "\x2B\x3B\x2F\xFF\x37\x5F\x49\xFF\x46\x95\x6F\xFF\x47\x97\x70\xFF\x28\x2E\x25\xFF\x25\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x22\x1D\xFF\x28\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF"
+ "\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x57\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x54\xC7\x94\xFF"
+ "\x4A\x9E\x77\xFF\x3E\x75\x58\xFF\x35\x57\x42\xFF\x2E\x40\x32\xFF\x28\x33\x28\xFF\x29\x33\x28"
+ "\xFF\x28\x32\x27\xFF\x29\x32\x28\xFF"
+ "\x29\x31\x27\xFF\x2A\x32\x2A\xFF\x2C\x34\x2A\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2F\x42\x35"
+ "\xFF\x35\x55\x42\xFF\x3E\x74\x58\xFF"
+ "\x4E\xB2\x85\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x45\x90\x6B\xFF\x2B\x32\x28\xFF\x25\x25\x20"
+ "\xFF\x27\x24\x20\xFF\x29\x25\x20\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x23\x1D\xFF\x25\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x24\x1F\xFF\x22\x21\x1C\xFF\x25\x21\x1D\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x25\x25\x1F\xFF\x27\x26\x20\xFF"
+ "\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x93\xFF\x55\xC8\x95"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x50\xB8\x89\xFF\x48\x9C\x74\xFF\x41\x83\x62\xFF\x3E\x76\x59"
+ "\xFF\x38\x66\x4D\xFF\x34\x56\x42\xFF"
+ "\x31\x4A\x38\xFF\x33\x51\x3F\xFF\x38\x5E\x48\xFF\x3B\x6B\x51\xFF\x41\x83\x62\xFF\x48\x9B\x74"
+ "\xFF\x4F\xB5\x86\xFF\x55\xC9\x95\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC5\x92\xFF\x38\x66\x4E\xFF\x2A\x2E\x26\xFF\x28\x26\x21"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x20\xFF\x22\x23\x1D\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF\x27\x27\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95"
+ "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCB\x96\xFF\x54\xC5\x92"
+ "\xFF\x52\xBE\x8D\xFF\x51\xB7\x88\xFF"
+ "\x4F\xB2\x84\xFF\x4F\xB5\x87\xFF\x52\xBB\x8B\xFF\x53\xC1\x8F\xFF\x55\xCA\x95\xFF\x56\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x49\x9C\x75\xFF\x2F\x44\x35\xFF\x26\x27\x23\xFF\x26\x25\x1E"
+ "\xFF\x29\x26\x20\xFF\x26\x26\x1E\xFF"
+ "\x26\x22\x1D\xFF\x26\x23\x1F\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D"
+ "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x26\x24\x1D\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x55\xCA\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x51\xBC\x8B\xFF\x43\x8C\x69\xFF\x30\x48\x39\xFF\x2A\x2D\x27\xFF\x24\x25\x20\xFF\x24\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF"
+ "\x27\x25\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF"
+ "\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x52\xBE\x8D\xFF\x48\x9A\x73\xFF"
+ "\x38\x63\x4D\xFF\x2C\x39\x2E\xFF\x27\x2B\x24\xFF\x27\x26\x21\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x25\x24\x1D\xFF\x25\x26\x1D\xFF"
+ "\x25\x24\x1F\xFF\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x26\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x20"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95"
+ "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF"
+ "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x58\xCC\x99\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98\xFF\x52\xBE\x8D\xFF\x43\x8C\x68"
+ "\xFF\x35\x5D\x47\xFF\x2E\x40\x32\xFF"
+ "\x27\x2C\x26\xFF\x28\x2A\x23\xFF\x29\x29\x22\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x23\x1D"
+ "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF"
+ "\x25\x22\x1C\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF"
+ "\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x92\xFF\x55\xC8\x94\xFF\x55\xC9\x95\xFF\x54\xC9\x95"
+ "\xFF\x54\xC9\x95\xFF\x55\xCA\x95\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x99\xFF\x56\xCD\x97"
+ "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCE\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF"
+ "\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x54\xC7\x93\xFF"
+ "\x51\xBC\x8B\xFF\x4E\xAF\x83\xFF\x46\x92\x6D\xFF\x39\x67\x4E\xFF\x2D\x3B\x30\xFF\x2B\x32\x29"
+ "\xFF\x29\x2D\x24\xFF\x28\x28\x22\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF"
+ "\x2A\x27\x22\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x23\x1C\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF"
+ "\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x53\xC8\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x94\xFF\x52\xBD\x8C"
+ "\xFF\x4C\xAD\x80\xFF\x4B\xA7\x7D\xFF"
+ "\x48\xA1\x77\xFF\x48\xA0\x77\xFF\x4B\xA7\x7D\xFF\x4F\xB0\x83\xFF\x51\xB9\x8A\xFF\x52\xC0\x8E"
+ "\xFF\x55\xC6\x92\xFF\x56\xCB\x96\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCB\x97\xFF\x54\xC4\x91\xFF\x50\xBA\x8B\xFF\x4E\xAF\x81\xFF\x49\x9F\x77"
+ "\xFF\x43\x8B\x68\xFF\x3F\x78\x5B\xFF"
+ "\x37\x61\x4A\xFF\x30\x48\x38\xFF\x2A\x35\x2B\xFF\x2A\x30\x27\xFF\x28\x2B\x22\xFF\x27\x27\x20"
+ "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x24\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF"
+ "\x26\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x1F"
+ "\xFF\x27\x26\x1F\xFF\x26\x24\x1E\xFF"
+ "\x54\xC6\x92\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x44\x8C\x69\xFF\x37\x5E\x49"
+ "\xFF\x32\x4D\x3C\xFF\x31\x49\x38\xFF"
+ "\x2D\x42\x33\xFF\x2D\x44\x33\xFF\x2E\x47\x37\xFF\x32\x4E\x3C\xFF\x35\x54\x42\xFF\x34\x58\x43"
+ "\xFF\x34\x5C\x45\xFF\x38\x63\x4C\xFF"
+ "\x3C\x6F\x55\xFF\x40\x7B\x5C\xFF\x43\x87\x65\xFF\x45\x91\x6D\xFF\x44\x8E\x6B\xFF\x43\x8A\x68"
+ "\xFF\x43\x86\x65\xFF\x41\x81\x61\xFF"
+ "\x3D\x75\x59\xFF\x38\x67\x4E\xFF\x36\x5C\x47\xFF\x33\x55\x42\xFF\x33\x4E\x3C\xFF\x2F\x41\x34"
+ "\xFF\x2A\x34\x2A\xFF\x2B\x31\x27\xFF"
+ "\x27\x2C\x23\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x27\x26\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF"
+ "\x29\x26\x21\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x25\x25\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x26\x21\xFF"
+ "\x27\x25\x20\xFF\x25\x25\x1E\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x20\xFF\x25\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF\x25\x23\x1C\xFF\x28\x25\x20\xFF\x26\x25\x1F"
+ "\xFF\x28\x27\x1F\xFF\x27\x25\x1F\xFF"
+ "\x53\xC7\x92\xFF\x53\xC8\x94\xFF\x53\xC3\x91\xFF\x3E\x7B\x5D\xFF\x2B\x35\x2D\xFF\x25\x2B\x22"
+ "\xFF\x23\x26\x1E\xFF\x25\x26\x20\xFF"
+ "\x24\x24\x1E\xFF\x27\x27\x1F\xFF\x28\x28\x22\xFF\x28\x29\x23\xFF\x27\x29\x23\xFF\x27\x29\x23"
+ "\xFF\x2A\x2C\x25\xFF\x2A\x2D\x25\xFF"
+ "\x27\x2D\x25\xFF\x27\x2D\x25\xFF\x27\x2D\x24\xFF\x29\x30\x27\xFF\x29\x2F\x28\xFF\x28\x2F\x26"
+ "\xFF\x29\x30\x27\xFF\x29\x2F\x26\xFF"
+ "\x28\x2C\x23\xFF\x2A\x2D\x24\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x27\x27\x21\xFF\x26\x26\x21"
+ "\xFF\x27\x26\x1F\xFF\x29\x26\x21\xFF"
+ "\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x27\x21\xFF\x29\x26\x21\xFF\x27\x24\x20\xFF\x25\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x28\x25\x21\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x26\x20\xFF\x26\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F"
+ "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF\x25\x23\x1F"
+ "\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF"
+ "\x53\xC8\x94\xFF\x54\xC8\x95\xFF\x49\xA4\x7A\xFF\x30\x49\x39\xFF\x26\x29\x23\xFF\x24\x25\x1F"
+ "\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF"
+ "\x25\x24\x1D\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF"
+ "\x26\x25\x1E\xFF\x28\x26\x20\xFF\x28\x27\x20\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF\x28\x26\x21"
+ "\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF"
+ "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x26\x1F\xFF\x25\x25\x20\xFF\x24\x23\x1D"
+ "\xFF\x25\x24\x1D\xFF\x27\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x26\x23\x1F\xFF\x27\x25\x1F\xFF\x27\x26\x21\xFF\x29\x27\x22\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1D\xFF\x26\x25\x1E\xFF"
+ "\x27\x26\x20\xFF\x2A\x27\x21\xFF\x29\x27\x21\xFF\x27\x25\x1F\xFF\x25\x22\x1F\xFF\x24\x22\x1D"
+ "\xFF\x25\x23\x1D\xFF\x28\x26\x21\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1F"
+ "\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF"
+ "\x26\x23\x1F\xFF\x28\x25\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x26\x24\x1F\xFF"
+ "\x55\xC8\x94\xFF\x54\xC8\x94\xFF\x40\x80\x60\xFF\x29\x36\x2A\xFF\x26\x26\x20\xFF\x24\x23\x1D"
+ "\xFF\x24\x22\x1D\xFF\x27\x23\x1F\xFF"
+ "\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x27\x20\xFF\x29\x27\x21\xFF\x26\x24\x1F\xFF\x24\x24\x1E"
+ "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF"
+ "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x27\x24\x20"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x25\x1F"
+ "\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x27\x20\xFF\x28\x27\x21\xFF\x24\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x27\x26\x20\xFF\x28\x26\x20\xFF"
+ "\x26\x24\x1F\xFF\x26\x26\x1F\xFF\x29\x27\x21\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x26\x1D\xFF"
+ "\x54\xC9\x94\xFF\x52\xC1\x8E\xFF\x3A\x6C\x52\xFF\x29\x30\x26\xFF\x25\x25\x1F\xFF\x24\x22\x1D"
+ "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x29\x26\x22\xFF\x2A\x27\x21\xFF\x28\x26\x20\xFF\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x20"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF\x29\x26\x21\xFF\x28\x25\x21\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x26\x21\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x55\xC9\x94\xFF\x52\xBE\x8C\xFF\x38\x68\x4F\xFF\x29\x2E\x25\xFF\x24\x24\x1D\xFF\x26\x23\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x26\x26\x20\xFF"
+ "\x29\x27\x21\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF"
+ "\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F"
+ "\xFF\x28\x26\x20\xFF\x28\x26\x20\xFF"
+ "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E"
+ "\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF"
+ "\x28\x25\x1F\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF"
+ "\x55\xCA\x95\xFF\x51\xBB\x8A\xFF\x38\x66\x4D\xFF\x28\x2D\x25\xFF\x24\x22\x1C\xFF\x26\x23\x1D"
+ "\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x27\x21\xFF\x26\x24\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x25\x25\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x26\x24\x20\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x26\x20\xFF\x2A\x28\x22\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1E\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x23\x1E"
+ "\xFF\x25\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x20\xFF\x27\x24\x20\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF"
+ "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x25\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x56\xCB\x96\xFF\x53\xC0\x8F\xFF\x39\x6A\x50\xFF\x28\x2D\x25\xFF\x28\x25\x20\xFF\x28\x25\x1F"
+ "\xFF\x28\x26\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x2A\x27\x21\xFF\x2A\x27\x22"
+ "\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF"
+ "\x28\x26\x21\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x26\x24\x1F\xFF\x27\x26\x20"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x26\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x27\x24\x20\xFF"
+ "\x25\x25\x1F\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x27\x25\x1D\xFF\x26\x25\x1E"
+ "\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x27\x26\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x24\x1F"
+ "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF"
+ "\x28\x24\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x25\x1E\xFF\x25\x23\x1E"
+ "\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF"
+ "\x56\xCB\x96\xFF\x55\xC6\x92\xFF\x3B\x71\x56\xFF\x27\x2F\x26\xFF\x26\x25\x1F\xFF\x24\x23\x1D"
+ "\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF"
+ "\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF"
+ "\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x26\x25\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1F\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x25\x1F\xFF\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF"
+ "\x27\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x26\x26\x1F"
+ "\xFF\x26\x25\x1E\xFF\x28\x26\x1F\xFF"
+ "\x26\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF"
+ "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x43\x87\x66\xFF\x2C\x39\x2D\xFF\x25\x25\x1D\xFF\x25\x22\x1D"
+ "\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF"
+ "\x27\x25\x1F\xFF\x27\x24\x20\xFF\x28\x24\x20\xFF\x28\x27\x22\xFF\x26\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x27\x24\x1F\xFF\x23\x20\x1B\xFF"
+ "\x23\x23\x1C\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1E\xFF\x26\x24\x1E"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x2A\x28\x21"
+ "\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF"
+ "\x29\x27\x21\xFF\x29\x27\x20\xFF\x29\x26\x21\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x29\x26\x20\xFF\x27\x25\x1E\xFF"
+ "\x28\x23\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x28\x25\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1D\xFF\x25\x22\x1E\xFF\x27\x24\x1F"
+ "\xFF\x29\x27\x20\xFF\x26\x24\x1E\xFF"
+ "\x25\x24\x1F\xFF\x28\x26\x20\xFF\x28\x25\x1E\xFF\x25\x24\x1E\xFF\x28\x26\x1E\xFF\x27\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x55\xCC\x96\xFF\x56\xCC\x97\xFF\x4B\xA6\x7A\xFF\x2E\x47\x36\xFF\x25\x25\x20\xFF\x28\x26\x21"
+ "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF"
+ "\x25\x25\x1F\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x25\x24\x1E\xFF\x25\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF"
+ "\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F"
+ "\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF"
+ "\x25\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF\x27\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF"
+ "\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E"
+ "\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x28\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x20\xFF\x24\x22\x1D\xFF"
+ "\x29\x26\x21\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x1F\xFF\x25\x23\x1E\xFF"
+ "\x55\xCC\x97\xFF\x56\xCC\x97\xFF\x51\xBE\x8C\xFF\x32\x52\x3F\xFF\x27\x2A\x22\xFF\x28\x26\x21"
+ "\xFF\x27\x24\x20\xFF\x27\x25\x20\xFF"
+ "\x26\x24\x1F\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF"
+ "\x26\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x29\x22\xFF\x25\x25\x1F\xFF\x27\x24\x20"
+ "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF"
+ "\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20"
+ "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x25\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x26\x25\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x2A\x27\x22\xFF\x29\x26\x21\xFF\x25\x24\x1E"
+ "\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x26\x25\x1E\xFF\x25\x23\x1D\xFF"
+ "\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x27\x25\x1E"
+ "\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xCA\x95\xFF\x39\x6C\x51\xFF\x29\x2F\x26\xFF\x29\x28\x22"
+ "\xFF\x28\x25\x20\xFF\x29\x27\x22\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1E\xFF\x26\x24\x1F"
+ "\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF"
+ "\x25\x25\x1F\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x27\x24\x1F\xFF\x26\x25\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x27\x26\x1F"
+ "\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x26\x25\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x29\x27\x21\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x1E\xFF\x28\x25\x1E\xFF"
+ "\x27\x26\x21\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x26\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1E"
+ "\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF"
+ "\x24\x23\x1D\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1D"
+ "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF"
+ "\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4A\xA3\x79\xFF\x29\x35\x2B\xFF\x27\x29\x23"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x26\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x29\x26\x21"
+ "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF"
+ "\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF\x26\x24\x1F"
+ "\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x27\x20"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x24\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x26\x23\x1F\xFF\x28\x26\x20\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1E\xFF\x2A\x26\x21\xFF"
+ "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1D\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x93\xFF\x32\x53\x40\xFF\x28\x2E\x25"
+ "\xFF\x25\x25\x20\xFF\x24\x23\x1E\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x26\x1F\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F"
+ "\xFF\x27\x27\x21\xFF\x26\x25\x20\xFF"
+ "\x26\x25\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20"
+ "\xFF\x29\x27\x22\xFF\x27\x25\x20\xFF"
+ "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x24\x23\x1D\xFF\x29\x26\x21\xFF\x27\x25\x20"
+ "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x29\x25\x20\xFF\x26\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x23\x1E\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x22\x1D"
+ "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF"
+ "\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF"
+ "\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x26\x25\x1F"
+ "\xFF\x25\x24\x1E\xFF\x25\x23\x1C\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x43\x8E\x6A\xFF\x2B\x3C\x2F"
+ "\xFF\x29\x32\x28\xFF\x28\x30\x26\xFF"
+ "\x2A\x2F\x28\xFF\x29\x2F\x26\xFF\x29\x2D\x25\xFF\x2A\x2E\x26\xFF\x28\x2B\x23\xFF\x27\x2A\x24"
+ "\xFF\x28\x2B\x25\xFF\x27\x2A\x23\xFF"
+ "\x27\x29\x21\xFF\x27\x29\x22\xFF\x27\x28\x20\xFF\x29\x27\x22\xFF\x26\x25\x1F\xFF\x2A\x28\x23"
+ "\xFF\x28\x28\x21\xFF\x24\x24\x1D\xFF"
+ "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF"
+ "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x25\x24\x1F\xFF\x25\x23\x1E\xFF"
+ "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E"
+ "\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x20\xFF\x26\x25\x20\xFF\x26\x23\x1E"
+ "\xFF\x28\x25\x20\xFF\x25\x22\x1D\xFF"
+ "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF"
+ "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x54\xC5\x93\xFF\x49\xA2\x78"
+ "\xFF\x47\x94\x6F\xFF\x44\x8D\x6A\xFF"
+ "\x42\x87\x64\xFF\x40\x7F\x5F\xFF\x3F\x79\x5B\xFF\x3D\x71\x57\xFF\x37\x68\x4F\xFF\x39\x65\x4D"
+ "\xFF\x38\x60\x4B\xFF\x36\x5E\x49\xFF"
+ "\x37\x5A\x45\xFF\x32\x51\x3E\xFF\x30\x48\x38\xFF\x2F\x41\x33\xFF\x2A\x35\x2A\xFF\x2C\x31\x2A"
+ "\xFF\x2B\x31\x29\xFF\x29\x2E\x26\xFF"
+ "\x2B\x2D\x27\xFF\x28\x2A\x23\xFF\x28\x2A\x23\xFF\x26\x27\x21\xFF\x26\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x24\x1E\xFF\x28\x27\x1F\xFF"
+ "\x27\x26\x1D\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF"
+ "\x29\x26\x21\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x26\x1E\xFF\x28\x26\x21\xFF\x26\x24\x1E"
+ "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF"
+ "\x26\x26\x1E\xFF\x28\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x27\x26\x1F\xFF\x28\x26\x20\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97"
+ "\xFF\x55\xC9\x95\xFF\x54\xC6\x93\xFF"
+ "\x53\xBF\x8D\xFF\x4F\xB4\x85\xFF\x4C\xA8\x7D\xFF\x48\x9C\x74\xFF\x44\x8D\x6A\xFF\x41\x82\x62"
+ "\xFF\x3D\x76\x59\xFF\x3B\x6A\x51\xFF"
+ "\x36\x5A\x46\xFF\x30\x4B\x3B\xFF\x2C\x3D\x30\xFF\x2B\x35\x2B\xFF\x2B\x32\x2A\xFF\x2A\x2F\x27"
+ "\xFF\x2A\x2C\x24\xFF\x2A\x2B\x24\xFF"
+ "\x28\x29\x22\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1E\xFF\x26\x23\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF"
+ "\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x20\xFF"
+ "\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x28\x26\x1F\xFF\x28\x26\x1F\xFF"
+ "\x28\x26\x20\xFF\x28\x26\x1F\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x27\x26\x1F\xFF\x28\x26\x20"
+ "\xFF\x29\x25\x20\xFF\x27\x25\x20\xFF"
+ "\x56\xCC\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96"
+ "\xFF\x54\xC5\x92\xFF\x53\xC0\x8F\xFF"
+ "\x51\xB9\x89\xFF\x50\xB3\x85\xFF\x4D\xAC\x80\xFF\x49\xA1\x78\xFF\x43\x8E\x6A\xFF\x3D\x76\x59"
+ "\xFF\x34\x56\x42\xFF\x2E\x3D\x30\xFF"
+ "\x2D\x36\x2C\xFF\x2A\x31\x28\xFF\x26\x2A\x23\xFF\x25\x28\x21\xFF\x28\x29\x22\xFF\x27\x26\x1F"
+ "\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF"
+ "\x28\x25\x20\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20"
+ "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF"
+ "\x24\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E"
+ "\xFF\x25\x23\x1D\xFF\x29\x26\x20\xFF"
+ "\x28\x26\x1F\xFF\x29\x26\x21\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF"
+ "\x56\xCE\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x58\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCC\x97\xFF\x56\xC6\x91\xFF"
+ "\x4B\xA9\x7D\xFF\x42\x8C\x69\xFF\x3A\x6D\x54\xFF\x36\x5B\x46\xFF\x2F\x49\x38\xFF\x2C\x3D\x2F"
+ "\xFF\x29\x32\x27\xFF\x28\x2E\x26\xFF"
+ "\x27\x2B\x23\xFF\x27\x28\x22\xFF\x24\x25\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1E"
+ "\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF"
+ "\x27\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1D"
+ "\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF"
+ "\x27\x26\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E"
+ "\xFF\x22\x21\x1C\xFF\x26\x24\x1F\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x52\xC0\x8E\xFF\x4C\xAB\x7E\xFF\x46\x97\x70"
+ "\xFF\x41\x82\x62\xFF\x3C\x6E\x53\xFF"
+ "\x34\x54\x41\xFF\x2C\x3C\x31\xFF\x28\x30\x27\xFF\x27\x2C\x23\xFF\x27\x29\x22\xFF\x28\x27\x21"
+ "\xFF\x26\x25\x1F\xFF\x27\x27\x20\xFF"
+ "\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E"
+ "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x28\x26\x21\xFF\x26\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCD\x97\xFF\x56\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x55\xCA\x95\xFF\x53\xC2\x90\xFF"
+ "\x50\xB6\x88\xFF\x4B\xA7\x7C\xFF\x41\x86\x64\xFF\x35\x5A\x45\xFF\x2B\x38\x2D\xFF\x29\x31\x28"
+ "\xFF\x28\x2C\x25\xFF\x26\x28\x22\xFF"
+ "\x26\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1F"
+ "\xFF\x29\x26\x20\xFF\x29\x26\x21\xFF"
+ "\x2B\x29\x22\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF\x29\x27\x20\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCC\x97\xFF\x52\xBB\x89\xFF\x46\x91\x6C"
+ "\xFF\x38\x68\x4F\xFF\x31\x4F\x3D\xFF"
+ "\x2C\x3A\x30\xFF\x2A\x30\x28\xFF\x27\x2A\x24\xFF\x26\x28\x21\xFF\x25\x26\x1E\xFF\x27\x25\x1F"
+ "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF"
+ "\x25\x24\x1D\xFF\x24\x24\x1D\xFF\x27\x25\x1F\xFF\x28\x26\x1F\xFF\x23\x23\x1D\xFF\x26\x23\x1E"
+ "\xFF\x28\x26\x20\xFF\x27\x25\x1F\xFF"
+ "\x56\xCB\x96\xFF\x55\xCA\x95\xFF\x55\xC9\x94\xFF\x56\xCB\x96\xFF\x58\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x55\xC8\x94\xFF\x4F\xB4\x86\xFF"
+ "\x46\x93\x6E\xFF\x3F\x76\x59\xFF\x34\x57\x42\xFF\x2B\x3B\x2E\xFF\x29\x30\x27\xFF\x2B\x2D\x24"
+ "\xFF\x28\x28\x22\xFF\x26\x25\x1F\xFF"
+ "\x25\x25\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1E\xFF\x24\x22\x1D\xFF\x26\x23\x1E"
+ "\xFF\x28\x24\x1F\xFF\x25\x25\x1E\xFF"
+ "\x39\x64\x4C\xFF\x38\x62\x4B\xFF\x37\x61\x4A\xFF\x37\x62\x4B\xFF\x39\x68\x4F\xFF\x3B\x71\x55"
+ "\xFF\x3F\x7B\x5B\xFF\x40\x81\x60\xFF"
+ "\x44\x8C\x69\xFF\x47\x94\x6F\xFF\x49\x9C\x75\xFF\x4A\xA3\x7A\xFF\x4C\xAB\x7F\xFF\x50\xB5\x86"
+ "\xFF\x53\xC3\x90\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x54\xC5\x92\xFF\x50\xB8\x88\xFF\x4B\xA5\x7A\xFF\x3F\x79\x5B\xFF\x31\x4C\x3C"
+ "\xFF\x29\x34\x29\xFF\x27\x2E\x25\xFF"
+ "\x26\x26\x22\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x27\x27\x1F\xFF\x23\x21\x1C\xFF\x29\x26\x20"
+ "\xFF\x27\x26\x21\xFF\x27\x25\x1F\xFF"
+ "\x2A\x2C\x24\xFF\x29\x2B\x24\xFF\x26\x28\x23\xFF\x2A\x2C\x25\xFF\x28\x2C\x24\xFF\x27\x2B\x23"
+ "\xFF\x26\x2B\x23\xFF\x28\x2F\x25\xFF"
+ "\x27\x2E\x25\xFF\x2A\x31\x28\xFF\x29\x33\x2A\xFF\x29\x34\x2A\xFF\x2D\x37\x2C\xFF\x2D\x38\x2D"
+ "\xFF\x2C\x3B\x2E\xFF\x2F\x43\x35\xFF"
+ "\x35\x5B\x47\xFF\x3C\x70\x55\xFF\x41\x83\x62\xFF\x47\x95\x6E\xFF\x4B\xA7\x7B\xFF\x4E\xAF\x82"
+ "\xFF\x50\xB6\x87\xFF\x52\xBC\x8C\xFF"
+ "\x54\xC5\x92\xFF\x56\xCB\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x54\xC6\x92"
+ "\xFF\x4A\xA1\x78\xFF\x3D\x6E\x54\xFF"
+ "\x32\x4A\x39\xFF\x2E\x35\x2B\xFF\x27\x29\x22\xFF\x24\x24\x1E\xFF\x28\x27\x1F\xFF\x26\x26\x20"
+ "\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF"
+ "\x26\x26\x20\xFF\x25\x25\x1F\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x20\xFF\x26\x25\x1F"
+ "\xFF\x26\x26\x1F\xFF\x25\x25\x1F\xFF"
+ "\x26\x25\x1F\xFF\x24\x24\x1F\xFF\x28\x28\x21\xFF\x2A\x2A\x23\xFF\x26\x26\x20\xFF\x26\x28\x21"
+ "\xFF\x26\x29\x21\xFF\x2A\x2C\x24\xFF"
+ "\x27\x2B\x24\xFF\x29\x2E\x25\xFF\x29\x30\x27\xFF\x2B\x33\x2A\xFF\x2B\x37\x2D\xFF\x31\x44\x35"
+ "\xFF\x34\x53\x40\xFF\x38\x61\x4A\xFF"
+ "\x3C\x74\x58\xFF\x43\x88\x66\xFF\x4A\x9D\x76\xFF\x4E\xB1\x83\xFF\x54\xC4\x91\xFF\x56\xCD\x97"
+ "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x56\xC9\x94\xFF"
+ "\x4C\xA7\x7C\xFF\x41\x81\x61\xFF\x35\x58\x44\xFF\x2D\x39\x2F\xFF\x2B\x30\x28\xFF\x28\x2A\x22"
+ "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x24\x1D\xFF\x28\x26\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E"
+ "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF"
+ "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x29\x26\x21\xFF\x2A\x28\x20\xFF\x28\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x27\x25\x1E\xFF\x26\x26\x1F\xFF"
+ "\x26\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x26\x1E\xFF\x26\x27\x21\xFF\x27\x28\x22\xFF\x29\x28\x22"
+ "\xFF\x28\x2A\x23\xFF\x29\x2C\x24\xFF"
+ "\x2A\x31\x27\xFF\x2A\x35\x2A\xFF\x2F\x42\x35\xFF\x34\x50\x3E\xFF\x38\x61\x4A\xFF\x3C\x74\x56"
+ "\xFF\x46\x92\x6D\xFF\x4D\xAF\x82\xFF"
+ "\x53\xC9\x94\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCE\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x51\xB8\x89\xFF\x48\x9B\x74\xFF\x38\x5E\x48\xFF\x2C\x39\x2D"
+ "\xFF\x28\x2F\x26\xFF\x27\x2A\x22\xFF"
+ "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F"
+ "\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF"
+ "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF"
+ "\x26\x24\x1F\xFF\x25\x24\x1D\xFF\x25\x22\x1C\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x29\x27\x22\xFF\x27\x27\x21\xFF\x28\x28\x22\xFF\x2B\x2C\x26\xFF\x29\x2B\x23\xFF\x28\x2C\x25"
+ "\xFF\x29\x31\x27\xFF\x2B\x36\x2C\xFF"
+ "\x2D\x3F\x31\xFF\x34\x5A\x46\xFF\x3F\x7D\x5E\xFF\x4A\xA0\x77\xFF\x51\xB8\x88\xFF\x55\xC5\x92"
+ "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x4E\xB2\x85"
+ "\xFF\x3F\x7D\x5E\xFF\x33\x55\x42\xFF"
+ "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20"
+ "\xFF\x27\x25\x1F\xFF\x25\x24\x1D\xFF"
+ "\x27\x24\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F"
+ "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF"
+ "\x27\x25\x20\xFF\x29\x26\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x20"
+ "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF"
+ "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF\x28\x26\x20"
+ "\xFF\x27\x27\x20\xFF\x27\x29\x21\xFF"
+ "\x28\x2A\x24\xFF\x28\x2C\x25\xFF\x2A\x31\x29\xFF\x2C\x38\x2D\xFF\x35\x57\x43\xFF\x3E\x74\x59"
+ "\xFF\x46\x91\x6E\xFF\x4E\xAD\x81\xFF"
+ "\x55\xC7\x93\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF"
+ "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98"
+ "\xFF\x56\xCB\x96\xFF\x51\xB7\x88\xFF";
+
+static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03_RLE[11160] =
+ "\x30\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24"
+ "\x22\x21\x20\x20\x20\x1D\x1E\x20\x1E"
+ "\x1F\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20"
+ "\x1E\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C"
+ "\xF0\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C"
+ "\x16\x12\x0C\x06\x08\x06\x00\x02\x00"
+ "\xF0\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00"
+ "\x00\x03\x01\x02\x00\x02\x01\x00\x04"
+ "\x02\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C"
+ "\x94\xBE\xBE\x9A\x6C\x48\x34\x1C\x10"
+ "\x08\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00"
+ "\x01\x02\x00\x06\x02\x04\x01\x01\x03"
+ "\x03\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02"
+ "\xF0\x1A\x44\x78\x92\x8C\x82\x6E\x52"
+ "\x2E\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00"
+ "\xD0\x02\x00\x02\x00\x02\x02\x02\x00"
+ "\x00\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82"
+ "\x92\x86\x50\x1C\x12\x0A\x04\x04\x02"
+ "\x01\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02"
+ "\x04\x00\x02\x00\x01\xE5\x00\x00\x02"
+ "\x02\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84"
+ "\x50\x28\x0C\x06\x04\x03\x03\x00\xF0"
+ "\x04\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04"
+ "\x04\x5C\x02\x02\x00\x02\x00\x14\x01"
+ "\xF0\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02"
+ "\x01\x02\x01\x04\x02\x01\x00\x02\x01"
+ "\x80\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01"
+ "\x00\x02\x00\x01\x13\x37\x55\x67\x4B"
+ "\x1D\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00"
+ "\x01\x04\x02\x00\x00\x00\x01\x00\x00"
+ "\x03\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A"
+ "\x8A\x32\x08\x04\x04\x04\x01\x03\x04"
+ "\x01\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00"
+ "\x00\x02\x17\x00\xF0\x01\x00\x00\x00"
+ "\x31\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02"
+ "\x00\x02\x00\x00\xA3\x00\x06\x00\x00"
+ "\x00\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57"
+ "\x53\x01\xF0\x00\x44\x42\x06\x06\x00"
+ "\x04\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0"
+ "\x01\x01\x07\x75\x2B\x2A\x86\x54\x1A"
+ "\x0C\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83"
+ "\x01\x03\x02\x01\x00\x01\x02\x00\x30"
+ "\x02\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F"
+ "\x09\x00\x20\x42\x08\x02\x00\x01\x00"
+ "\x00\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14"
+ "\x34\x04\x00\x0D\x55\x61\x55\x67\x4B"
+ "\x0A\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00"
+ "\x00\x02\x00\x00\x01\x00\x00\x02\x00"
+ "\x11\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04"
+ "\x13\x11\x02\x02\x32\x38\x08\x03\x01"
+ "\x01\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01"
+ "\x00\x00\x02\x02\x02\x00\x00\x10\x06"
+ "\x05\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02"
+ "\x00\x02\x01\x05\x03\x02\x02\x03\x01"
+ "\x00\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D"
+ "\x00\x02\x02\x06\x1A\x2E\x16\x02\xF0"
+ "\x02\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00"
+ "\x02\x02\x06\x04\x01\x01\x00\x05\x0C"
+ "\xF0\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00"
+ "\x01\x37\x4B\x04\x2A\x12\x00\x00\x00"
+ "\xF0\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01"
+ "\x06\x33\x00\x01\x00\xF0\x01\x00\x00"
+ "\x00\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05"
+ "\x33\x65\x19\x2C\x42\x02\x00\x02\x00"
+ "\x04\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02"
+ "\x01\x39\x00\x02\x00\xF0\x06\x12\x3D"
+ "\x83\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A"
+ "\x22\x00\x00\x01\x00\xF0\x00\x3A\x0C"
+ "\x06\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02"
+ "\x2A\x28\x2D\x8B\x9B\x43\x15\x01\x00"
+ "\x00\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01"
+ "\x22\x24\x00\x00\x02\x03\x01\x03\x03"
+ "\x00\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16"
+ "\x07\x3B\x87\x7D\x45\x11\x00\x00\x0E"
+ "\x5A\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0"
+ "\x04\x02\x00\x00\x06\x00\x03\x01\x01"
+ "\x02\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0"
+ "\x85\x4B\x09\x00\x08\x3A\x6C\x80\x94"
+ "\x94\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00"
+ "\x02\x90\x02\x01\x02\x01\x01\x04\x01"
+ "\x00\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15"
+ "\x02\x18\xF0\x28\x2A\x20\x12\x04\x01"
+ "\x00\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01"
+ "\x00\x02\x50\x04\x01\x00\x00\x00\x77"
+ "\x02\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F"
+ "\xF0\x29\x13\x01\x02\x02\x00\x05\x23"
+ "\x45\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01"
+ "\x02\x02\x01\x07\x28\x02\x00\xF0\x04"
+ "\x1C\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33"
+ "\x08\x3E\x86\x80\x08\x02\x01\xF0\x02"
+ "\x02\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03"
+ "\xF0\x18\x3C\x82\xBA\x96\x5E\x34\x12"
+ "\x19\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00"
+ "\xE0\x02\x03\x01\x00\x01\x02\x02\x03"
+ "\x05\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E"
+ "\x84\x74\x62\x4C\x34\x22\x2A\xF0\x3C"
+ "\x4E\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03"
+ "\x02\x02\x00\x04\x00\x02\x00\x04\x23"
+ "\x02\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00"
+ "\x00\x39\x31\x05\x05\x00\x01\x01\x00"
+ "\x00\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C"
+ "\x14\x1E\x28\x22\x1A\x12\x06\x00\x01"
+ "\x00\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01"
+ "\x02\x02\x04\x02\x23\x00\x02\x1F\x00"
+ "\x0F\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04"
+ "\x04\x01\x05\x02\x00\x02\x05\x01\x29"
+ "\x02\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81"
+ "\x4D\x15\x03\x01\x03\xF0\x01\x02\x06"
+ "\x00\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00"
+ "\x00\x01\x01\x02\x00\x00\x00\x02\x01"
+ "\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45"
+ "\x1F\x0B\x07\x03\x00\x06\x04\x00\x00"
+ "\x06\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01"
+ "\x11\x29\x2F\x3F\x3F\x33\x29\x1D\x11"
+ "\x0B\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03"
+ "\x01\x03\x03\x01\x00\x00\x02\x00\x05"
+ "\x03\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85"
+ "\x87\x89\x87\x87\x8B\x8D\x8F\x95\x99"
+ "\xF0\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D"
+ "\x00\xF0\x04\x04\x04\x02\x00\x00\x01"
+ "\x02\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D"
+ "\x77\x4D\x3B\x2F\x29\x27\x29\x31\x3D"
+ "\x3F\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07"
+ "\x07\x01\x05\x01\x03\x01\x01\x02\x93"
+ "\x00\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13"
+ "\x05\x01\x05\x01\x00\x03\x07\x07\x05"
+ "\x09\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02"
+ "\x04\x06\x02\x01\x01\x00\x02\x00\x03"
+ "\x01\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05"
+ "\x03\x00\x04\x00\x00\x00\x04\x00\x03"
+ "\x01\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00"
+ "\x05\x05\x00\x04\x02\x03\x01\x05\x01"
+ "\x00\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0"
+ "\x00\x0B\x1B\x07\x01\x00\x02\x01\x02"
+ "\x00\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0"
+ "\x04\x02\x00\x04\x01\x01\x02\x01\x02"
+ "\x01\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40"
+ "\x00\x02\x00\x04\xF0\x00\x03\x05\x01"
+ "\x03\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01"
+ "\x00\x01\x01\x01\xF0\x00\x00\x02\x03"
+ "\x01\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03"
+ "\x03\x01\x01\x00\x10\x01\xF0\x02\x03"
+ "\x03\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01"
+ "\x00\x04\x01\x00\x02\x02\x83\x00\x04"
+ "\x00\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00"
+ "\x00\x01\x00\x02\x00\xF0\x02\x0A\x06"
+ "\x00\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06"
+ "\x00\x02\x02\x03\x00\xF0\x00\x05\x01"
+ "\x03\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00"
+ "\x03\x02\x01\x00\x00\x40\x00\x03\x01"
+ "\x04\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01"
+ "\x02\x01\x01\x00\x01\x00\xB3\x02\x01"
+ "\x00\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01"
+ "\x00\x00\x60\x02\x02\x00\x04\x00\x03"
+ "\xF0\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01"
+ "\x00\xF0\x03\x01\x01\x00\x00\x02\x02"
+ "\x04\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02"
+ "\x02\x63\x00\x00\x02\x01\x01\x00\xF0"
+ "\x00\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02"
+ "\x00\x01\x01\x04\x02\x01\x00\x03\xF0"
+ "\x01\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04"
+ "\x06\x02\x00\x01\x04\x00\x02\x04\x13"
+ "\x00\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04"
+ "\x00\x02\x04\x02\x01\x01\x02\x00\x00"
+ "\x02\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02"
+ "\x02\x00\x03\x01\x03\x00\x05\x03\x01"
+ "\x00\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0"
+ "\x00\x01\x01\x01\x07\x01\x01\x01\x00"
+ "\x02\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63"
+ "\x04\x04\x02\x04\x02\x01\xA0\x00\x04"
+ "\x01\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04"
+ "\x00\xF0\x03\x00\x01\x00\x06\x04\x00"
+ "\x01\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01"
+ "\x01\x63\x03\x02\x00\x03\x00\x02\xA0"
+ "\x00\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00"
+ "\x01\x03\x06\xF0\x06\x02\x02\x00\x03"
+ "\x03\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02"
+ "\x00\x01\x03\xF0\x01\x00\x06\x00\x03"
+ "\x01\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10"
+ "\x0A\x0C\x0C\x08\x0A\x08\x06\x02\x06"
+ "\xF0\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00"
+ "\x01\x00\x03\x00\x03\x00\x02\x00\x03"
+ "\xF0\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E"
+ "\x88\x78\x72\x6C\x62\x58\x52\x4C\x4C"
+ "\x48\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05"
+ "\x01\x01\x00\x06\x02\x00\x02\x06\x00"
+ "\x00\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A"
+ "\x40\x52\x5C\x68\x72\x7A\x82\x92\x94"
+ "\x94\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00"
+ "\x04\x02\x00\x02\x02\x01\x00\x00\x04"
+ "\x05\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39"
+ "\x00\x01\x00\xF0\x01\x02\x06\x0A\x16"
+ "\x26\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02"
+ "\x01\x00\x02\xF0\x01\x01\x02\x02\x02"
+ "\x01\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00"
+ "\x01\x00\xF0\x01\x02\x04\x0C\x10\x1E"
+ "\x26\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03"
+ "\x02\x02\xF0\x02\x03\x01\x01\x01\x02"
+ "\x01\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00"
+ "\x00\x02\xF0\x0E\x34\x5E\x84\x90\x8C"
+ "\x82\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03"
+ "\x01\x05\x30\x02\x08\x00\x03\x3A\x01"
+ "\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16"
+ "\x0E\x0C\x04\x02\x33\x00\x00\x02\x90"
+ "\x04\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00"
+ "\x01\x01\x00\xF0\x01\x01\x00\x00\x02"
+ "\x00\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01"
+ "\x05\x09\x05\x60\x01\x01\x05\x03\x02"
+ "\x01\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00"
+ "\x94\x02\x00\x00\x00\x02\x02\x00\x01"
+ "\x00\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02"
+ "\x06\x02\x01\x00\x00\x01\x01\xF0\x93"
+ "\x93\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00"
+ "\x13\x01\xF0\x00\x01\x01\x00\x00\x00"
+ "\x08\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D"
+ "\x4B\x55\x63\x6F\x75\x87\x8D\x95\x9F"
+ "\xA5\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01"
+ "\x00\x14\x01\xF0\x00\x0C\x20\x3A\x78"
+ "\xAC\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11"
+ "\x11\x0D\x17\x17\x19\xF0\x21\x45\x5F"
+ "\x75\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00"
+ "\x02\x00\xF0\x02\x01\x00\x02\x02\x0C"
+ "\x40\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00"
+ "\x05\x00\x01\x05\xF0\x09\x09\x0B\x11"
+ "\x11\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00"
+ "\x00\x00\x02\x34\x00\x02\x00\x90\x08"
+ "\x36\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01"
+ "\x04\x33\x01\x00\x03\xF0\x05\x07\x09"
+ "\x09\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01"
+ "\x00\x80\x02\x06\x1E\x46\x9C\xB0\x70"
+ "\x40\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02"
+ "\x01\x02\x02\x01\x05\x03\x03\x0B\x05"
+ "\x09\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00"
+ "\x02\x04\x26\x70\x8C\x34\x2A\x25\x23"
+ "\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25\x25\x23\x23\x25"
+ "\x24\x24\x25\x84\x26\x27\x25\x24\x25"
+ "\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23\x21\xF0\x4E\x24"
+ "\x1C\x12\x0C\x0A\x08\x06\x08\x26\x4E"
+ "\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02\x02\xF0\x03\x03"
+ "\x02\x02\x01\x00\x00\x02\x00\x02\x07"
+ "\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02\x00\x40\x04\x04"
+ "\x02\x06\xF0\xD2\xF8\xCC\x9A\x5E\x40"
+ "\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98\x66\x4A\x2C\x14"
+ "\x0C\x08\xF0\x0A\x08\x01\x03\x01\x07"
+ "\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04\x04\x01\x01\x01"
+ "\x00\x00\x40\x07\x00\x04\x04\xC4\x02"
+ "\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2\xB2\x9C\x70\x3E"
+ "\x1C\x0A\x04\x06\x00\x02\x75\x00\x03"
+ "\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06\x00\x03\x10\x03"
+ "\x04\x8C\x12\x40\x70\x96\xAE\x6C\x02"
+ "\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02\x04\x01\x05\x03"
+ "\x02\x01\x03\x02\x05\x05\x03\x01\x00"
+ "\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A\x22\x1C\x00\x03"
+ "\xF0\x0A\x20\x38\x72\xCE\xF7\xBA\x70"
+ "\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03\x01\x03\x00\x04"
+ "\x70\x00\x06\x0A\x03\x01\x02\x04\x04"
+ "\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6\x74\x2E\x0E\x0A"
+ "\x02\x00\x01\x01\xF0\x00\x01\x00\x00"
+ "\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23\x01\x00\x47\x01"
+ "\x00\x01\x00\x23\x01\x00\xF0\x01\x01"
+ "\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01\x04\x02\x02\x01"
+ "\x02\x00\x05\x01\x01\x02\x90\x00\x00"
+ "\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9\xB1\x95\xBB\xF1"
+ "\xE5\x63\x0F\x1C\x7C\xC4\xF0\x48\x0A"
+ "\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01\x00\x00\x01\x00"
+ "\x02\x04\x99\x02\x00\x00\x00\x02\x02"
+ "\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F\xF0\x00\x46\x8C"
+ "\x18\x08\x00\x05\x00\x04\x02\x02\x00"
+ "\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13\x9D\x43\x26\xAA"
+ "\xB8\xA6\x9C\xAA\xBC\x70\x0D\x79\x71"
+ "\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04\x04\x06\x02\x00"
+ "\x03\x01\x00\x41\xF0\x01\x00\x09\xA5"
+ "\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A\x06\x03\x03\x01"
+ "\x00\x01\x01\x00\xE0\x01\x03\x00\x00"
+ "\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03\x37\x89\x9F\x7F"
+ "\x25\x6C\x46\x1F\xF0\x55\x09\x00\x26"
+ "\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02\x04\x02\x00\x00"
+ "\x01\x41\xF0\x02\x00\x2D\x17\x1E\x48"
+ "\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06\x04\x02\x03\x01"
+ "\x04\x04\x93\x02\x03\x04\x04\x01\x01"
+ "\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12\x46\x3A\x32\x4A"
+ "\xF0\x40\x0A\x3A\x06\x1B\x17\x00\x04"
+ "\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01\x03\x05\x01\x00"
+ "\x10\x02\x11\xF0\x01\x00\x02\x01\x00"
+ "\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00\x00\x1A\x42\x16"
+ "\x06\x02\x00\xF0\x00\x05\x01\x00\x02"
+ "\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02\x00\x24\x0E\x15"
+ "\x29\x00\x02\x02\x08\x24\x3E\xF0\x1C"
+ "\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00\x01\x03\x03\x02"
+ "\x00\x00\x02\x02\x04\x00\x00\x20\x00"
+ "\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00\x02\x02\x01\x03"
+ "\x49\xF0\x65\x08\x38\x1A\x00\x00\x00"
+ "\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00\x00\x04\x01\x04"
+ "\xF0\x01\x01\x00\x01\x01\x01\x00\x01"
+ "\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00\xE3\x07\x43\x91"
+ "\x25\x3C\x5C\x02\x00\x01\x01\x08\x52"
+ "\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93\x02\x02\x00\x02"
+ "\x00\x00\x00\x02\x00\xF0\x08\x18\x59"
+ "\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91\xB1\x49\x0C\x94"
+ "\x30\x00\x00\x02\x02\xF0\x00\x54\x10"
+ "\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00\x00\x04\x35\x02"
+ "\x02\x00\xF0\x02\x3A\x3A\x3B\xC3\xD9"
+ "\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4\x4C\x02\x00\xF0"
+ "\x2C\x34\x04\x04\x02\x03\x01\x03\x03"
+ "\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C\x52\x20\x0D\x51"
+ "\xBB\xAF\x61\x19\x00\x00\x12\x7E\xB0"
+ "\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02\x00\x02\x04\x04"
+ "\x02\x01\x02\x06\x70\x02\x03\x00\x01"
+ "\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D\x00\xC4\x0A\x4E"
+ "\x94\xB4\xCC\xCE\xC0\xAA\x78\x24\x02"
+ "\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00\x00\x01\x01\x04"
+ "\x01\x01\x00\x68\x02\x02\x02\x00\x02"
+ "\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36\x3A\x2C\x18\x06"
+ "\x01\x00\x00\x00\x09\x29\x95\xE9\x99"
+ "\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06\x01\x00\x00\x02"
+ "\x78\x02\x00\x02\x00\x00\x02\x00\xF0"
+ "\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02\x02\x00\x07\x2B"
+ "\x63\x93\xBB\x97\x25\x06\x24\x04\xF0"
+ "\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02\x01\x98\x02\x02"
+ "\x00\x02\x00\x00\x00\x02\x00\xF0\x04"
+ "\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF\xC7\xB1\x91\x4B"
+ "\x0A\x56\xBC\xB4\x0A\x00\x01\x64\x01"
+ "\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02\x00\x02\x00\x02"
+ "\x00\xF0\x20\x54\xB6\xFF\xD0\x82\x48"
+ "\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08\x00\x00\x00\x03"
+ "\x01\xE0\x04\x01\x00\x02\x00\x02\x02"
+ "\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA\xC2\xB8\xA0\x86"
+ "\x68\x48\x32\x3E\xF0\x54\x6E\x98\xB2"
+ "\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06\x00\x06\x04\x00"
+ "\x01\x63\x02\x02\x00\x00\x02\x00\x2E"
+ "\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0\x00\x00\x51\x43"
+ "\x0D\x01\x02\x02\x05\x03\x00\x02\x01"
+ "\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00\x05\xF0\x04\x10"
+ "\x1C\x2A\x36\x30\x24\x18\x06\x00\x01"
+ "\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03\x02\x60\x02\x00"
+ "\x02\x01\x02\x04\x9F\x02\x00\x02\x02"
+ "\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08\x01\x01\x02\xD0"
+ "\x01\x03\x02\x00\x02\x06\x00\x03\x00"
+ "\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x01\x00\x00\x00"
+ "\x1B\x7F\xC1\xB3\x6D\x1D\x03\x00\x01"
+ "\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02\x02\x00\xA6\x00"
+ "\x02\x00\x00\x02\x00\x01\x01\x02\x00"
+ "\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F\xF0\x2F\x0B\x09"
+ "\x05\x01\x06\x06\x02\x00\x06\x02\x01"
+ "\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00\x01\x17\x37\x45"
+ "\x55\x57\x49\x39\x27\x19\x0D\x37\x03"
+ "\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0\x03\x01\x05\x01"
+ "\x01\x03\x01\x02\x05\x03\x01\x04\x02"
+ "\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD\xBF\xBB\xBD\xB7"
+ "\xBF\xC3\xC9\xCF\xD3\xF0\xCF\xBB\xA3"
+ "\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15\x05\x01\x01\x01"
+ "\x04\x02\x04\x02\x00\xF0\x02\x02\x00"
+ "\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02\x00\x09\x99\xAD"
+ "\x65\x4D\x45\x3B\x39\x3D\x49\x55\x5D"
+ "\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B\x15\x0B\x0D\x09"
+ "\x01\x07\x03\x01\x02\x01\x00\x04\x02"
+ "\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0\x02\x00\x3D\x63"
+ "\x17\x0B\x07\x07\x00\x03\x03\x07\x09"
+ "\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0\x03\x01\x01\x01"
+ "\x02\x04\x06\x02\x00\x00\x02\x04\x00"
+ "\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40\x06\x04\x02\x01"
+ "\xF0\x00\x00\x47\x25\x05\x03\x00\x02"
+ "\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01\xF0\x04\x02\x01"
+ "\x00\x04\x00\x03\x07\x01\x02\x01\x03"
+ "\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04\x50\x00\x00\x00"
+ "\x03\x04\xF0\x02\x0D\x27\x0B\x01\x01"
+ "\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00\x01\x00\x02\x02"
+ "\x02\x00\xF0\x00\x02\x01\x02\x03\x01"
+ "\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01\x03\x01\x04\x01"
+ "\x03\x00\x40\x01\x00\x00\x03\xF0\x00"
+ "\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01\x01\x00\x01\x03"
+ "\x00\x02\x02\x00\x01\x00\x00\xF0\x02"
+ "\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00\x02\x02\x02\x00"
+ "\x02\x01\x03\x05\x01\x03\x00\xF0\x02"
+ "\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04\x01\x03\x02\x04"
+ "\x00\x00\x04\x01\x01\x02\x02\xF0\x00"
+ "\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02\x03\x01\x04\x00"
+ "\x04\x04\x06\x02\x02\x02\x01\x40\x02"
+ "\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04\xF0\x06\x00\x00"
+ "\x03\x02\x02\x02\x00\x04\x02\x03\x00"
+ "\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06\xF0\x04\x04\x01"
+ "\x03\x04\x01\x07\x04\x03\x02\x01\x00"
+ "\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01\x00\x04\x05\x03"
+ "\x03\xA5\x01\x04\x01\x01\x03\x04\x00"
+ "\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02\xF0\x02\x03\x04"
+ "\x03\x05\x00\x04\x05\x00\x01\x00\x00"
+ "\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00\x01\x02\x00\x01"
+ "\x00\xF0\x09\x05\x02\x00\x06\x01\x01"
+ "\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01\x03\x00\x00\x00"
+ "\x03\xF0\x03\x00\x01\x02\x02\x00\x00"
+ "\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00\x08\x04\x01\x00"
+ "\x03\x00\x03\x00\x02\x02\xF0\x08\x00"
+ "\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05\x05\x01\x01\x01"
+ "\x02\x02\x01\x00\x00\x01\xF0\x01\x00"
+ "\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0\x00\x00\x30\x16"
+ "\x0A\x00\x03\x00\x01\x08\x00\x03\x00"
+ "\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01\x07\x02\x04\x01"
+ "\x00\x00\xF0\x03\x02\x02\x04\x06\x06"
+ "\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00\x04\xF0\x00\x00"
+ "\x18\x34\x0A\x04\x02\x04\x02\x01\x00"
+ "\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02\x02\x06\x04\x04"
+ "\x00\x02\x00\x00\x00\x04\x02\xF0\x01"
+ "\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02\x03\x01\x00\xF0"
+ "\x00\x02\x06\x6E\x0C\x02\x00\x03\x02"
+ "\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01\x03\x04\x02\xF0"
+ "\x01\x00\x05\x00\x03\x00\x00\x01\x01"
+ "\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04\x00\x00\x00\x40"
+ "\x02\x02\x00\x03\xF0\x02\x00\x00\x4A"
+ "\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02\x04\x00\x01\x00"
+ "\xF0\x03\x00\x01\x02\x02\x01\x01\x02"
+ "\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70\x04\x02\x01\x01"
+ "\x02\x00\x00\x03\xF0\x0A\x76\x1C\x1A"
+ "\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01\x00\x00\x00\x07"
+ "\x03\x00\x04\x02\xF0\x00\x06\x04\x00"
+ "\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02\x07\x01\x01\x00"
+ "\x02\x00\x01\x01\x10\x02\x04\xF0\x6E"
+ "\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12\x14\x14\x0A\x0A"
+ "\x08\x08\x04\x00\x04\x04\x01\xF0\x01"
+ "\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01\x02\x00\x04\x02"
+ "\x03\x02\x02\x04\x02\x06\x04\x04\xF0"
+ "\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2\x8A\x78\x5A\x42"
+ "\x26\x1C\x18\x14\x10\x08\x06\x04\xF0"
+ "\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00\x02\x00\x02\x04"
+ "\x02\x06\x01\x02\x00\x02\x01\x01\x39"
+ "\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE\xF0\xD8\xB8\x8E"
+ "\x54\x24\x1A\x16\x0A\x08\x08\x06\x06"
+ "\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00\x70\x00\x02\x03"
+ "\x01\x03\x01\x01\x6C\x04\x00\x00\x00"
+ "\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0\xB6\x86\x66\x40"
+ "\x2E\x16\x10\x0C\x04\x02\x03\x00\x01"
+ "\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10\x00\x34\x01\x02"
+ "\x00\x23\x01\x00\x2E\x02\x00\xF0\x02"
+ "\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04\x02\x01\x00\x00"
+ "\x00\x01\x02\x00\x00\x00\x03\x60\x01"
+ "\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0"
+ "\x04\x18\x44\x6C\x90\xA8\xC4\xD6\xAC"
+ "\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08\x02\x00\x04\x03"
+ "\x25\x02\x00\x25\x01\x00\x34\x01\x01"
+ "\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0\x78\xF0\x4E\x28"
+ "\x16\x0C\x08\x04\x00\x03\x05\x09\x01"
+ "\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01\x02\x00\x02\x24"
+ "\x78\xC0\xCA\xB2\x8C\x5A\x26\x14\x10"
+ "\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3\x97\x81\x71\x61"
+ "\x53\x43\x2F\x13\x5D\x01\x02\x02\x02"
+ "\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E\x18\x90\x12\x02"
+ "\x00\x00\x04\x01\x06\x04\x00\xF0\x6F"
+ "\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93\x6F\x4B\x3B\x2D"
+ "\x21\x0F\x03\x00\x29\x01\x00\x14\x01"
+ "\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0\x0B\x0B\x09\x11"
+ "\x0B\x0B\x09\x13\x11\x19\x15\x13\x21"
+ "\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6\x00\x00\x02\x00"
+ "\x00\x00\x02\x02\x00\x00\x02\x00\xF0"
+ "\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01\x01\x06\x06\x03"
+ "\x01\x05\x01\x03\x00\x03\x03\x01\x03"
+ "\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75\x3B\x07\x00\x01"
+ "\x01\x00\x00\x02\x02\x00\x00\x00\x02"
+ "\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0\x01\x01\x05\x03"
+ "\x01\x00\x04\x04\x04\x02\x01\x05\x01"
+ "\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4\xC1\xF1\xEC\xE5"
+ "\x9D\x57\x29\x0F\x01\x00\xF0\x01\x01"
+ "\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56\xF0\x03\x00\x04"
+ "\x02\x04\x02\x00\x03\x01\x01\x03\x01"
+ "\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B\xF0\x13\x19\x29"
+ "\x5B\x97\xCF\xC1\xA1\x75\x3F\x0B\x02"
+ "\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47"
+ "\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26"
+ "\x27\x27\xF0\x27\x25\x24\x26\x28\x26\x27\x28\x28\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27"
+ "\x25\x26\x24\x25\x27\x28\x27\x28\x27"
+ "\x27\x25\xB0\x26\x27\x27\x26\x27\x27\x27\x26\x25\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04"
+ "\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A"
+ "\x3E\x3C\x30\x20\x12\x0A\x08\x06\x04\x06\x02\x01\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00"
+ "\x00\x00\x03\x01\x00\x02\x04\xF0\x02"
+ "\x04\x00\x01\x01\x03\x00\x00\x00\x03\x00\x01\x00\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38"
+ "\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00"
+ "\x02\x06\x0A\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00"
+ "\x01\x07\x05\x02\x04\x00\x00\x02\x02"
+ "\x08\x06\x02\xF0\x01\x09\x03\x04\x00\x04\x03\x08\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04"
+ "\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E"
+ "\x2A\x16\x06\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04"
+ "\xF0\x00\x00\x05\x03\x03\x01\x02\x00"
+ "\x01\x03\x00\x04\x00\x04\x04\xE0\x02\x02\x00\x04\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04"
+ "\x96\x02\x14\x22\x2A\x34\x20\x02\x02"
+ "\x00\xF0\x01\x02\x00\x00\x00\x0A\x14\x20\x2A\x32\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02"
+ "\x04\x04\x03\x09\x05\x01\x01\x03\x02"
+ "\x05\xF0\x07\x03\x04\x03\x03\x03\x01\x00\x04\x02\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01"
+ "\x00\x00\x02\x0A\x08\x00\x01\x00\xF0"
+ "\x03\x01\x00\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06"
+ "\x02\x00\x00\x02\x01\x00\x03\x01\xA0"
+ "\x00\x02\x02\x01\x08\x0A\x00\x01\x02\x0A\x04\x78\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01"
+ "\x01\x00\xF0\x02\x0A\x22\x34\x34\x22"
+ "\x0E\x02\x06\x08\x02\x03\x01\x03\x01\xF3\x04\x00\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03"
+ "\x07\x03\x04\x23\x01\x00\x28\x01\x00"
+ "\xF0\x02\x00\x00\x00\x01\x02\x00\x01\x09\x15\x23\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00"
+ "\x00\x02\x02\x06\x04\x06\x02\x01\x03"
+ "\xF0\x01\x02\x01\x00\x01\x04\x02\x04\x02\x01\x03\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01"
+ "\x02\x02\x00\x00\x01\x0B\x27\x43\x43"
+ "\x33\x29\x33\x45\x41\xE3\x1D\x05\x08\x24\x38\x18\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04"
+ "\x00\x07\x01\x00\x07\x00\x01\x00\x01"
+ "\x00\x00\x04\x7D\x02\x00\x00\x00\x02\x02\x00\xF0\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31"
+ "\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03"
+ "\x09\x01\x00\x00\x01\x04\x00\x00\x06\x02\x02\xA0\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71"
+ "\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30"
+ "\x32\x36\x20\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03"
+ "\xB0\x00\x06\x06\x04\x04\x07\x00\x02"
+ "\x02\x04\x02\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23"
+ "\x17\x00\x02\x2A\x0A\x00\x03\x05\x01"
+ "\x01\x02\x01\x01\xE0\x01\x05\x00\x00\x01\x03\x02\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11"
+ "\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23"
+ "\x0B\x20\x10\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02"
+ "\x02\x05\x05\x04\x02\x06\x01\x41\xF0"
+ "\x02\x00\x09\x07\x08\x14\x02\x00\x05\x21\x25\x21\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16"
+ "\x08\x02\x02\x04\x01\x02\x02\x06\xE0"
+ "\x00\x01\x04\x04\x01\x04\x04\x02\x00\x03\x02\x00\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A"
+ "\x01\x01\x04\x12\x0A\x0C\x12\x10\x02"
+ "\x12\xF0\x02\x05\x07\x00\x02\x14\x16\x04\x03\x03\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00"
+ "\x03\x05\x05\x01\x03\x05\x00\x04\x31"
+ "\xF0\x02\x00\x00\x06\x04\x00\x03\x02\x00\x02\x1E\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02"
+ "\x00\x08\x12\x02\x04\x02\x02\x00\x07"
+ "\xF0\x05\x02\x02\x03\x01\x01\x06\x04\x02\x02\x00\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01"
+ "\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C"
+ "\x12\xF0\x0A\x02\x00\x13\x00\x04\x06\x00\x00\x02\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01"
+ "\x00\x00\x02\x00\x00\x00\x06\x06\x00"
+ "\x01\x20\x00\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15"
+ "\x1D\x02\xF0\x12\x08\x00\x01\x01\x12"
+ "\x14\x01\x01\x01\x09\x03\x01\x00\x00\xC0\x02\x01\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06"
+ "\xF0\x01\x00\x01\x00\x00\x00\x01\x13"
+ "\x27\x0D\x00\x00\x01\x01\x00\x83\x00\x0A\x22\x01\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E"
+ "\x02\x00\x02\x02\x02\x16\x04\x02\x00"
+ "\xF0\x02\x01\x00\x00\x00\x01\x00\x02\x02\x04\x00\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06"
+ "\x1B\x35\x21\x03\x00\x00\x02\x02\x02"
+ "\x1A\x1E\x07\x21\xC4\x31\x21\x11\x0D\x15\x29\x33\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02"
+ "\x06\x02\x04\x00\x04\x00\x03\x01\x03"
+ "\x01\x60\x01\x01\x05\x01\x00\x00\x0C\xF0\x02\x12\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32"
+ "\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29"
+ "\x07\x18\x40\x18\x00\xF0\x10\x0C\x01\x02\x02\x05\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04"
+ "\x08\x02\x00\x01\x02\x58\x00\x02\x02"
+ "\x02\x00\xF0\x04\x14\x08\x01\x15\x33\x33\x1B\x07\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12"
+ "\x1C\x2C\x30\x16\x00\x01\x01\x00\x01"
+ "\x00\x01\xF0\x0F\x07\x00\x01\x00\x04\x04\x00\x06\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04"
+ "\x02\x01\x24\x02\x00\x26\x02\x00\xF0"
+ "\x14\x22\x06\x02\x09\x21\x31\x35\x1D\x03\x00\x04\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02"
+ "\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00"
+ "\x00\x03\x01\x01\x05\x00\x00\x04\x80\x01\x04\x00\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04"
+ "\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47"
+ "\x2B\x15\x07\x00\x0A\xF0\x10\x12\x08\x06\x02\x01\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03"
+ "\x05\x05\x01\x04\x02\x04\x04\x00\x03"
+ "\x02\x05\x01\x01\x02\x50\x04\x02\x00\x02\x02\x0E\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F"
+ "\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01"
+ "\x02\x02\x00\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01"
+ "\x01\x05\x02\x02\x01\x02\x02\x03\x3E"
+ "\x02\x02\x00\xF0\x02\x0E\x20\x32\x38\x28\x10\x06\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39"
+ "\x3D\x37\x33\x2B\x15\x02\x18\x36\x34"
+ "\x00\x00\x02\xF0\x02\x02\x01\x03\x01\x04\x04\x00\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44"
+ "\x00\x00\x02\x00\x25\x02\x00\x23\x02"
+ "\x00\xF0\x0A\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28"
+ "\x46\x3E\x20\x03\x06\x00\x00\x04\x05"
+ "\x00\xE0\x04\x01\x05\x00\x03\x04\x00\x03\x05\x05\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0"
+ "\x02\x02\x00\x00\x06\x1A\x30\x36\x34"
+ "\x32\x2A\x20\x16\x10\x12\xF0\x18\x20\x2C\x32\x34\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0"
+ "\x00\x01\x05\x04\x02\x02\x01\x01\x06"
+ "\x02\x00\x02\x00\x04\x02\x2F\x02\x00\x08\xF0\x02\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28"
+ "\x1C\x10\x04\xF0\x00\x00\x17\x11\x07"
+ "\x03\x02\x01\x02\x00\x02\x0A\x00\x03\x01\x90\x04\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02"
+ "\x00\x09\xF0\x02\x02\x06\x08\x0A\x10"
+ "\x0E\x08\x06\x02\x00\x01\x00\x0B\x27\xF0\x31\x09\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04"
+ "\x05\x01\x70\x00\x00\x01\x00\x00\x04"
+ "\x01\x9F\x00\x00\x02\x00\x02\x00\x00\x02\x00\x09\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02"
+ "\x02\x00\x00\x03\x01\x02\x00\x01\xB0"
+ "\x01\x00\x04\x04\x01\x03\x00\x02\x00\x07\x00\x29\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02"
+ "\x02\x02\x00\x02\x02\x07\x25\x39\x33"
+ "\x21\x07\x04\x00\x03\xF0\x00\x02\x04\x04\x00\x02\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03"
+ "\x02\x04\x02\x93\x00\x02\x00\x02\x00"
+ "\x01\x01\x01\x00\xA4\x01\x01\x01\x00\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D"
+ "\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0"
+ "\x01\x01\x02\x06\x02\x00\x02\x06\x02\x02\x00\x00\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04"
+ "\x03\xF0\x00\x00\x01\x01\x00\x03\x0F"
+ "\x13\x1B\x1B\x15\x0F\x09\x07\x01\x24\x01\x00\xF0\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29"
+ "\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00"
+ "\x00\x03\x03\x01\x00\x02\x00\x01\x05\x05\x01\xD0\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01"
+ "\x03\x02\xF0\x02\x00\x02\x00\x21\x35"
+ "\x33\x33\x35\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39"
+ "\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07"
+ "\x02\x00\x01\x01\x04\x04\x02\x02\x00\xF0\x03\x00\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01"
+ "\x03\x01\x40\x04\x01\x02\x02\xF0\x01"
+ "\x01\x01\x2B\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33"
+ "\x2F\x29\x1B\x19\x15\x17\x11\x83\x05"
+ "\x03\x01\x05\x03\x00\x03\x01\xF0\x02\x04\x02\x02\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04"
+ "\x80\x02\x02\x06\x02\x05\x01\x03\x01"
+ "\xF0\x00\x02\x13\x1B\x09\x01\x00\x00\x02\x02\x00\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03"
+ "\x00\x03\x05\x01\x07\x05\x01\x03\xF0"
+ "\x01\x00\x02\x02\x04\x02\x00\x00\x01\x04\x00\x03\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00"
+ "\x00\x01\x01\x02\x04\x00\x02\x02\x20"
+ "\x00\x00\xF0\x04\x00\x11\x0D\x00\x00\x02\x04\x00\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01"
+ "\x00\x02\x00\x02\x02\x04\x00\x00\x08"
+ "\x04\x00\x04\x02\xF0\x03\x03\x05\x00\x02\x00\x03\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00"
+ "\x00\x09\x02\x00\x04\x00\x03\x02\x00"
+ "\x01\x00\x00\x00\xF0\x01\x03\x0B\x00\x01\x00\x04\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04"
+ "\x02\x01\x08\x02\x03\x01\x02\x01\x00"
+ "\x00\x04\x06\x01\xF0\x00\x02\x00\x02\x00\x03\x02\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00"
+ "\x03\x01\x00\x00\x01\x00\x05\x01\x01"
+ "\x02\x02\x01\x04\x40\x04\x02\x00\x02\xF0\x02\x00\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01"
+ "\x01\x00\xF0\x03\x00\x05\x01\x03\x00"
+ "\x01\x03\x00\x00\x01\x01\x01\x00\x00\xF0\x02\x00\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04"
+ "\x02\x01\xF0\x01\x00\x02\x04\x00\x02"
+ "\x00\x04\x02\x01\x01\x03\x01\x03\x03\x40\x00\x02\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01"
+ "\x02\x00\x03\x00\x06\x04\x00\xF0\x03"
+ "\x03\x02\x02\x01\x07\x00\x04\x00\x03\x04\x00\x03\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00"
+ "\x00\x00\x04\x01\x03\x05\x07\xF0\x00"
+ "\x01\x02\x01\x01\x01\x04\x02\x04\x04\x06\x00\x04\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02"
+ "\x00\x08\x04\x06\x04\xF0\x00\x02\x04"
+ "\x04\x0A\x00\x01\x01\x00\x04\x02\x00\x02\x06\x03\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01"
+ "\x03\x01\x01\x00\x05\xF0\x01\x05\x02"
+ "\x08\x02\x06\x00\x00\x02\x01\x05\x04\x00\x00\x03\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00"
+ "\x04\x04\x01\x03\x07\x03\x00\x07\x01"
+ "\x00\x02\x09\x05\x03\xF0\x03\x04\x00\x01\x00\x04\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01"
+ "\x01\x00\x00\x00\x02\x04\x01\x02\x02"
+ "\x04\x00\x04\x02\x02\xF0\x00\x03\x02\x01\x01\x00\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02"
+ "\x04\x00\x01\xF0\x00\x02\x10\x0A\x01"
+ "\x02\x02\x04\x06\x02\x02\x00\x02\x03\x00\x64\x07\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04"
+ "\x04\x02\x06\x08\x06\x00\x04\x06\x04"
+ "\x01\xF0\x02\x04\x02\x00\x05\x02\x04\x00\x00\x02\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03"
+ "\x02\x00\x02\x02\xF0\x01\x00\x10\x04"
+ "\x00\x06\x04\x01\x03\x03\x01\x00\x01\x00\x00\xF0\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04"
+ "\x02\x01\x01\x05\xF0\x03\x05\x05\x00"
+ "\x05\x07\x01\x00\x03\x02\x03\x01\x02\x02\x02\xF0\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03"
+ "\x08\x00\x01\x04\x40\x03\x02\x02\x01"
+ "\xF0\x00\x00\x0C\x08\x04\x00\x03\x01\x02\x08\x01\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01"
+ "\x02\x06\x04\x02\x01\x02\x00\x02\x02"
+ "\xF0\x00\x02\x01\x07\x00\x08\x03\x01\x02\x03\x00\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02"
+ "\x04\x05\x03\x01\x02\x05\x07\x01\x00"
+ "\x40\x00\x01\x00\x04\xF0\x02\x00\x08\x0E\x04\x02\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00"
+ "\x01\x05\x03\x07\x02\x00\x01\x00\x02"
+ "\x01\x01\x00\x00\x01\xF0\x00\x02\x02\x04\x02\x00\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06"
+ "\x06\x08\x02\x02\x01\x03\x01\x00\x01"
+ "\x02\x03\x02\x00\x00\x40\x00\x03\x03\x02\xF0\x02\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04"
+ "\x02\x06\x00\xF0\x03\x00\x00\x02\x08"
+ "\x00\x01\x01\x00\x02\x04\x01\x03\x06\x04\xF0\x00\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00"
+ "\x02\x03\x03\xF0\x03\x00\x01\x00\x01"
+ "\x00\x00\x04\x00\x02\x08\x08\x02\x00\x03\x40\x04\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02"
+ "\x00\x03\x03\x00\x03\x04\x02\x02\x04"
+ "\xF0\x02\x05\x01\x02\x04\x02\x01\x00\x02\x01\x00\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00"
+ "\x00\x00\x02\x02\x03\x03\x01\x00\x02"
+ "\xF0\x01\x03\x01\x04\x02\x01\x02\x03\x00\x04\x04\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02"
+ "\x22\x06\x08\x08\x04\x02\x04\x06\x02"
+ "\x00\x02\xF0\x02\x02\x00\x01\x06\x02\x06\x01\x05\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00"
+ "\x04\x04\x01\x02\x01\x01\x00\x03\x01"
+ "\x03\x01\xF0\x00\x03\x03\x04\x02\x02\x01\x03\x02\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00"
+ "\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38"
+ "\x30\x2E\x2C\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00"
+ "\xF0\x02\x04\x02\x05\x03\x01\x07\x03"
+ "\x00\x02\x06\x00\x02\x08\x08\xF0\x02\x04\x00\x03\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02"
+ "\x40\x00\x01\x02\x06\xF0\x02\x00\x02"
+ "\x02\x06\x1C\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16"
+ "\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02"
+ "\x04\x00\x02\x08\x02\x02\x02\x01\x02\x00\x01\x05\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02"
+ "\x06\x00\x04\x03\x00\x40\x00\x04\x04"
+ "\x01\x66\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x04\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40"
+ "\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06"
+ "\x00\x03\x00\x02\x04\x01\x00\x02\xF0\x01\x01\x02\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02"
+ "\x00\x70\x02\x00\x01\x00\x01\x05\x01"
+ "\x31\xF0\x01\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02"
+ "\x04\x01\x03\x05\x03\x00\x00\x01\x02"
+ "\x06\xF0\x02\x03\x01\x03\x03\x00\x03\x01\x03\x02\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01"
+ "\x01\x00\x2E\x02\x00\xF0\x01\x02\x00"
+ "\x02\x16\x2A\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00"
+ "\x02\x02\x00\x02\x01\x60\x05\x03\x07"
+ "\x04\x0C\x00\x04\x2A\x02\x00\x53\x01\x00\x02\x02\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22"
+ "\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04"
+ "\x01\x03\xF0\x02\x01\x00\x04\x06\x06\x04\x06\x04\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00"
+ "\x00\x01\x01\x00\x37\x01\x01\x00\x23"
+ "\x01\x00\xF0\x02\x00\x00\x02\x02\x00\x04\x08\x0E\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02"
+ "\x00\x03\x01\x03\x05\x0B\x07\x00\x00"
+ "\x0B\x03\x20\x00\x01\x64\x01\x03\x03\x01\x02\x00\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0"
+ "\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34"
+ "\x2A\x1A\x0A\x08\x08\x02\x90\x00\x00\x08\x02\x01\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37"
+ "\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07"
+ "\x23\x01\x02\x1F\x00\x13\x01\xF0\x00\x01\x01\x02\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02"
+ "\x90\x02\x02\x03\x03\x00\x01\x06\x01"
+ "\x04\xF0\x1D\x1D\x21\x19\x21\x27\x31\x2F\x39\x39\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21"
+ "\x17\x11\x0D\x09\x05\x01\x00\x29\x01"
+ "\x00\xF0\x03\x00\x01\x01\x01\x00\x06\x0E\x16\x2E\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01"
+ "\x03\xF0\x07\x07\x03\x0B\x01\x01\x00"
+ "\x05\x01\x0B\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05"
+ "\x01\xC6\x00\x00\x02\x00\x00\x00\x02"
+ "\x02\x00\x00\x02\x00\xF0\x02\x01\x01\x00\x02\x06\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04"
+ "\xF0\x02\x02\x08\x08\x03\x01\x01\x02"
+ "\x00\x02\x02\x00\x04\x04\x02\xF0\x07\x01\x03\x07\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33"
+ "\xE4\x21\x13\x07\x00\x01\x01\x00\x00"
+ "\x02\x02\x00\x00\x00\x02\xF0\x00\x02\x02\x02\x00\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10"
+ "\x00\xF0\x00\x01\x05\x01\x02\x02\x06"
+ "\x00\x02\x02\x07\x09\x03\x01\x00\xF0\x01\x00\x03\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D"
+ "\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B"
+ "\x03\x01\x00\x00\x02\x02\x00\x01\x84\x01\x00\x00\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44"
+ "\x2E\x18\xF0\x03\x02\x04\x02\x02\x02"
+ "\x01\x01\x00\x02\x00\x04\x01\x00\x00\xF0\x04\x02\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07"
+ "\x05\x00\xF0\x03\x07\x09\x17\x29\x3B"
+ "\x37\x2D\x1F\x11\x03\x00\x00\x00\x02\x24\x02\x00\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E"
+ "\x3C";
+
+static const BYTE TEST_64X64_RED_PLANE[4096] =
+ "\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\x3E\x30\x29\x26\x24\x22\x21\x20"
+ "\x20\x20\x1D\x1E\x20\x1E\x1F\x20\x20"
+ "\x1F\x20\x21\x22\x1E\x1F\x1F\x1F\x1F\x1F\x1F\x1F\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F"
+ "\x20\x1E\x1F\x1F\x1F\x1E\x1D\x1E\x1C"
+ "\x3F\x2C\x28\x24\x21\x22\x20\x21\x25\x56\x94\x96\x97\x92\x8C\x86\x7F\x71\x61\x4D\x38\x2C\x29"
+ "\x26\x23\x21\x21\x20\x1F\x1F\x1E\x1E"
+ "\x20\x20\x20\x20\x1F\x20\x1F\x20\x1E\x1E\x1D\x1F\x1E\x1E\x21\x20\x1F\x1F\x1F\x1F\x1E\x1F\x1D"
+ "\x1F\x1F\x1F\x20\x1E\x1E\x1F\x1F\x1F"
+ "\x89\x83\x70\x5B\x44\x39\x2F\x27\x2C\x5B\x95\x96\x98\x98\x98\x98\x97\x97\x97\x97\x97\x8B\x76"
+ "\x5C\x47\x3B\x2F\x28\x23\x22\x21\x21"
+ "\x20\x1F\x1E\x1F\x20\x20\x20\x20\x1F\x1F\x21\x21\x1F\x1E\x1F\x1F\x1F\x1E\x20\x1F\x21\x20\x1F"
+ "\x1E\x1E\x1D\x1E\x1E\x1E\x1F\x21\x1E"
+ "\x8A\x8D\x8E\x8F\x8B\x7C\x6B\x5B\x4B\x66\x96\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x90\x81\x70\x5F\x4C\x39\x2B\x24"
+ "\x21\x21\x1E\x1E\x1F\x1E\x20\x1F\x1E\x20\x21\x21\x1D\x1D\x1E\x1E\x21\x21\x20\x20\x21\x21\x1F"
+ "\x1F\x1F\x1E\x1E\x1E\x20\x1F\x1F\x1F"
+ "\x8A\x8D\x8E\x8F\x91\x92\x95\x92\x89\x8C\x97\x97\x99\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x95\x8D\x82\x6E\x4C"
+ "\x2F\x2A\x23\x20\x21\x1F\x1F\x20\x1F\x1F\x1E\x1D\x1D\x1D\x1D\x1F\x1F\x1E\x1E\x20\x20\x1E\x1E"
+ "\x1E\x1E\x1E\x1F\x20\x20\x20\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x91\x92\x95\x94\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x97\x97\x97"
+ "\x8D\x6C\x4B\x34\x27\x22\x21\x1E\x1D\x1F\x20\x1F\x1F\x1E\x1E\x20\x1F\x1F\x1E\x20\x1F\x1F\x1E"
+ "\x1F\x1E\x1F\x21\x24\x1F\x1F\x20\x20"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x97\x97"
+ "\x97\x97\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x8E\x76\x51\x31\x24\x21\x1F\x1F\x1E\x1E\x1F\x1F\x1F\x1F\x20\x1E\x20\x21\x1E\x1F\x1F"
+ "\x1E\x1F\x20\x20\x20\x1D\x1E\x1F\x20"
+ "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x96"
+ "\x97\x98\x97\x97\x8E\x7C\x6D\x64\x72"
+ "\x89\x96\x98\x98\x8E\x6B\x38\x25\x1E\x1E\x1F\x20\x1F\x20\x1F\x1F\x1F\x1F\x1F\x1E\x1E\x1E\x21"
+ "\x1F\x1F\x20\x20\x1F\x1D\x1E\x1D\x1E"
+ "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x99\x98\x97"
+ "\x98\x96\x87\x62\x39\x2E\x2E\x2F\x2F"
+ "\x32\x45\x74\x92\x98\x98\x7D\x3E\x22\x20\x21\x22\x1E\x1E\x21\x1E\x1F\x1F\x20\x1E\x20\x1E\x1D"
+ "\x1E\x1F\x1E\x1E\x1F\x1E\x1E\x1D\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x7D\x47\x31\x30\x49\x55\x5B\x52"
+ "\x3D\x30\x37\x5D\x92\x98\x97\x72\x2C\x23\x1F\x1E\x20\x1F\x21\x1F\x1F\x1F\x20\x21\x20\x1E\x1D"
+ "\x1D\x1E\x1E\x21\x20\x1F\x1F\x1E\x1D"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x91\x45\x2F\x3E\x6D\x8B\x91\x93\x8F"
+ "\x81\x59\x32\x31\x68\x97\x97\x94\x4D\x26\x22\x1E\x22\x21\x21\x1E\x1F\x20\x20\x21\x1E\x20\x1E"
+ "\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x93"
+ "\x56\x2F\x44\x81\x97\x98\x97\x94\x91"
+ "\x96\x96\x69\x33\x3A\x79\x97\x97\x81\x37\x24\x1E\x20\x20\x20\x20\x1E\x20\x1F\x1F\x1F\x1F\x1E"
+ "\x1E\x21\x1E\x1E\x20\x1F\x20\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x82"
+ "\x3E\x35\x6C\x96\x98\x96\x82\x63\x57"
+ "\x69\x88\x8F\x4C\x2F\x59\x92\x97\x91\x58\x28\x1F\x20\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1F"
+ "\x1D\x1E\x1F\x1E\x20\x1F\x1F\x1E\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x72"
+ "\x36\x3F\x86\x98\x98\x8F\x57\x32\x2C"
+ "\x35\x62\x94\x6A\x2F\x4E\x8B\x97\x97\x75\x34\x22\x21\x1F\x1F\x20\x20\x21\x20\x1E\x21\x22\x1F"
+ "\x1D\x1F\x1F\x1E\x1F\x1F\x1F\x1F\x1E"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x97\x97\x97\x97\x98\x61"
+ "\x2E\x44\x93\x98\x97\x94\x70\x46\x3E"
+ "\x4D\x79\x97\x80\x31\x44\x82\x98\x98\x8E\x50\x26\x1F\x1E\x1E\x20\x21\x20\x1E\x1F\x21\x20\x1F"
+ "\x1D\x1E\x1F\x1D\x1F\x1D\x1D\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x69"
+ "\x31\x41\x8D\x98\x97\x97\x95\x8A\x80"
+ "\x8D\x97\x97\x7A\x2F\x45\x83\x98\x98\x97\x68\x2E\x20\x1E\x1F\x1F\x1E\x1E\x1F\x20\x1F\x1F\x1F"
+ "\x20\x1F\x1E\x1D\x1F\x1F\x1E\x1F\x20"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x97\x97\x98\x98\x98\x98\x99\x97\x98\x77"
+ "\x38\x39\x7E\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x61\x2F\x50\x8E\x98\x98\x98\x80\x3A\x21\x1F\x1F\x21\x1F\x20\x1F\x20\x1E\x1F\x1F"
+ "\x20\x20\x1F\x20\x21\x1E\x1D\x1F\x1D"
+ "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x96\x8F\x94\x97\x98\x98\x97\x97\x97\x97\x89"
+ "\x47\x2E\x52\x8D\x97\x98\x98\x97\x98"
+ "\x98\x97\x7C\x3B\x31\x65\x97\x98\x98\x98\x96\x4D\x26\x1F\x20\x1D\x1D\x1D\x20\x20\x1F\x1F\x1F"
+ "\x1E\x1F\x1E\x20\x21\x1F\x1F\x1E\x20"
+ "\x8A\x8C\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x94\x76\x68\x87\x98\x98\x98\x98\x97\x97\x97"
+ "\x72\x2F\x34\x5D\x8B\x98\x98\x97\x98"
+ "\x95\x7D\x49\x2E\x47\x86\x98\x98\x99\x98\x98\x69\x27\x20\x1F\x1E\x1C\x1E\x20\x20\x1E\x1E\x1F"
+ "\x1D\x1F\x1E\x1F\x1E\x20\x20\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x7F\x49\x45\x72\x94\x98\x98\x98\x98\x98"
+ "\x94\x51\x2E\x31\x49\x6F\x83\x88\x7F"
+ "\x61\x3E\x2E\x32\x7C\x97\x98\x98\x98\x98\x98\x86\x2D\x23\x1F\x1E\x1F\x20\x21\x20\x1F\x1F\x1E"
+ "\x1D\x1E\x1E\x1F\x1D\x1E\x1F\x1F\x1F"
+ "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x98\x94\x5D\x2E\x2C\x46\x76\x8D\x97\x98\x98"
+ "\x98\x91\x67\x3C\x2C\x2D\x2E\x2E\x2E"
+ "\x2D\x32\x4B\x79\x98\x98\x98\x98\x98\x97\x97\x97\x3F\x23\x1F\x1F\x1D\x1F\x1F\x1E\x1F\x1F\x1F"
+ "\x1F\x1D\x1F\x21\x20\x1F\x1F\x1F\x1F"
+ "\x8B\x8D\x8F\x91\x92\x93\x94\x95\x96\x96\x96\x97\x98\x98\x7A\x39\x28\x28\x32\x4E\x74\x8F\x98"
+ "\x98\x98\x94\x7A\x5E\x4C\x3A\x39\x42"
+ "\x52\x6A\x8B\x97\x98\x98\x98\x98\x98\x98\x96\x80\x39\x23\x20\x1F\x1E\x20\x20\x1F\x21\x20\x1F"
+ "\x1F\x20\x1F\x1F\x1F\x1E\x20\x1F\x20"
+ "\x8C\x8D\x8F\x91\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x93\x62\x30\x29\x26\x27\x34\x4C\x72"
+ "\x93\x98\x98\x97\x94\x8C\x84\x83\x88"
+ "\x8E\x95\x98\x98\x98\x98\x98\x98\x97\x8C\x62\x3D\x25\x21\x20\x1F\x1E\x1F\x20\x20\x1F\x20\x20"
+ "\x20\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F"
+ "\x8C\x8E\x90\x91\x93\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x97\x8B\x64\x46\x33\x28\x27\x2B"
+ "\x3A\x61\x7E\x8C\x95\x98\x98\x98\x98"
+ "\x97\x97\x97\x98\x98\x97\x93\x89\x62\x38\x29\x22\x21\x1F\x1F\x1E\x20\x20\x20\x21\x1F\x1F\x20"
+ "\x1E\x1E\x20\x1F\x20\x1F\x1F\x1F\x1F"
+ "\x8D\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x96\x8A\x6F\x4B\x32\x29"
+ "\x27\x29\x34\x4A\x61\x78\x83\x8E\x97"
+ "\x98\x98\x97\x95\x86\x74\x5F\x44\x2B\x2A\x2C\x30\x21\x1E\x20\x1E\x1F\x1F\x1F\x20\x1F\x1D\x1F"
+ "\x1E\x1E\x1F\x20\x21\x1E\x20\x20\x1E"
+ "\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x8C\x79\x57"
+ "\x37\x2C\x29\x28\x29\x35\x3C\x45\x50"
+ "\x57\x52\x4D\x4A\x3F\x34\x2A\x2A\x2F\x49\x6F\x70\x25\x1F\x1F\x1F\x20\x1F\x1E\x1F\x20\x1F\x20"
+ "\x1E\x1E\x1E\x20\x21\x1D\x1F\x1F\x1F"
+ "\x8E\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x98\x97\x98"
+ "\x94\x77\x58\x42\x32\x28\x28\x27\x28"
+ "\x27\x2A\x2A\x2A\x2C\x35\x42\x58\x85\x97\x97\x6B\x28\x20\x20\x20\x1E\x1F\x1F\x1D\x1F\x1F\x1F"
+ "\x1F\x1F\x1C\x1D\x1F\x1E\x1F\x1F\x20"
+ "\x8F\x90\x92\x93\x93\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x96\x89\x74\x62\x59\x4D\x42"
+ "\x38\x3F\x48\x51\x62\x74\x86\x95\x98\x98\x92\x4E\x26\x21\x20\x1F\x1E\x1F\x20\x1D\x20\x20\x1F"
+ "\x1F\x1D\x1D\x1E\x1F\x20\x1F\x20\x20"
+ "\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x96\x92\x8D\x88"
+ "\x84\x87\x8B\x8F\x95\x98\x98\x98\x98\x98\x75\x35\x23\x1E\x20\x1E\x1D\x1F\x20\x20\x1F\x1D\x1E"
+ "\x20\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1F"
+ "\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x98\x98\x98\x97\x98\x8B\x69\x39\x27\x20\x1E\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1F\x1F"
+ "\x1E\x1D\x1F\x20\x1E\x20\x1F\x21\x20"
+ "\x90\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x97\x97"
+ "\x98\x98\x98\x98\x97\x97\x8D\x73\x4D\x2E\x24\x21\x1E\x1E\x1D\x1D\x1F\x1E\x1E\x1E\x1E\x20\x20"
+ "\x20\x1F\x1E\x1D\x1F\x20\x20\x1E\x1F"
+ "\x91\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98"
+ "\x98\x97\x97\x98\x98\x98\x98\x98\x98"
+ "\x99\x98\x99\x98\x8D\x68\x47\x32\x26\x23\x22\x20\x1C\x1D\x1E\x20\x1F\x1F\x1E\x20\x1D\x1E\x1E"
+ "\x1E\x1C\x1E\x1D\x1E\x1F\x20\x1E\x1E"
+ "\x92\x93\x92\x94\x95\x95\x95\x95\x97\x97\x97\x98\x99\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x99\x97\x97\x97\x98\x97\x97\x97\x93"
+ "\x8B\x83\x6D\x4E\x30\x29\x24\x22\x20\x1F\x20\x20\x1F\x1F\x1E\x20\x22\x20\x20\x20\x1C\x1E\x1F"
+ "\x1E\x1F\x1F\x20\x20\x1E\x20\x21\x1E"
+ "\x92\x93\x93\x94\x94\x8C\x80\x7D\x77\x77\x7D\x83\x8A\x8E\x92\x96\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x97\x91\x8B\x81\x77\x68\x5B"
+ "\x4A\x38\x2B\x27\x22\x20\x20\x20\x1F\x1D\x1E\x1F\x1F\x1F\x1F\x20\x1F\x1E\x20\x20\x1F\x1E\x1F"
+ "\x20\x1E\x1E\x1D\x1E\x1F\x1F\x1F\x1E"
+ "\x92\x94\x94\x94\x69\x49\x3C\x38\x33\x33\x37\x3C\x42\x43\x45\x4C\x55\x5C\x65\x6D\x6B\x68\x65"
+ "\x61\x59\x4E\x47\x42\x3C\x34\x2A\x27"
+ "\x23\x23\x22\x20\x22\x20\x20\x20\x21\x1F\x20\x20\x1F\x1F\x1E\x21\x20\x1E\x1F\x20\x20\x1F\x20"
+ "\x1F\x1F\x1E\x1C\x1C\x20\x1F\x1F\x1F"
+ "\x92\x94\x91\x5D\x2D\x22\x1E\x20\x1E\x1F\x22\x23\x23\x23\x25\x25\x25\x25\x24\x27\x28\x26\x27"
+ "\x26\x23\x24\x23\x22\x21\x21\x1F\x21"
+ "\x20\x1F\x1E\x1F\x1F\x1F\x1E\x1F\x20\x20\x21\x21\x20\x1F\x1F\x21\x1F\x1F\x20\x20\x21\x1F\x20"
+ "\x1F\x1F\x20\x1F\x1F\x1D\x1F\x20\x1F"
+ "\x94\x95\x7A\x39\x23\x1F\x1D\x1D\x1D\x1F\x20\x1F\x1F\x20\x20\x1F\x1E\x20\x20\x1F\x20\x21\x20"
+ "\x20\x20\x1F\x20\x1F\x20\x1D\x1D\x1F"
+ "\x1E\x1F\x1F\x21\x22\x20\x1D\x1E\x20\x21\x21\x1F\x1F\x1D\x1D\x21\x1F\x1F\x1F\x20\x21\x1F\x20"
+ "\x1F\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F"
+ "\x94\x94\x60\x2A\x20\x1D\x1D\x1F\x1D\x1F\x20\x21\x1F\x1E\x1F\x1F\x1F\x20\x20\x1E\x1F\x20\x20"
+ "\x1D\x20\x1F\x20\x20\x1F\x1F\x1E\x1F"
+ "\x1F\x1F\x1F\x1E\x1F\x20\x1F\x1F\x1E\x20\x1E\x1E\x1F\x1F\x1F\x20\x1F\x1F\x20\x21\x1E\x1F\x20"
+ "\x20\x1F\x1F\x21\x1F\x1F\x1F\x1F\x1D"
+ "\x94\x8E\x52\x26\x1F\x1D\x1E\x1E\x1E\x1F\x1F\x20\x1F\x1F\x1F\x20\x22\x21\x20\x20\x1F\x20\x20"
+ "\x1F\x1F\x1F\x20\x21\x21\x20\x20\x20"
+ "\x1F\x21\x1E\x1D\x20\x1F\x20\x1E\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1D\x1E"
+ "\x20\x20\x1F\x1F\x1E\x1F\x20\x1F\x1F"
+ "\x94\x8C\x4F\x25\x1D\x1F\x20\x1E\x1D\x1E\x1E\x1E\x1F\x1F\x20\x20\x21\x20\x20\x1F\x1F\x1E\x1E"
+ "\x20\x1E\x1E\x20\x20\x20\x1F\x20\x20"
+ "\x20\x1F\x1D\x1E\x20\x20\x1F\x1F\x20\x20\x20\x20\x1F\x1E\x1E\x1E\x1F\x20\x20\x1F\x1F\x1E\x1E"
+ "\x1E\x1E\x1E\x1E\x1E\x1F\x20\x1F\x1E"
+ "\x95\x8A\x4D\x25\x1C\x1D\x1D\x1E\x1E\x1E\x1F\x1F\x20\x21\x1F\x1D\x1F\x20\x21\x1F\x1C\x1E\x1F"
+ "\x1F\x1E\x20\x1F\x20\x21\x20\x20\x22"
+ "\x20\x20\x20\x20\x1E\x20\x1F\x1F\x20\x1E\x1E\x1C\x1C\x1E\x1F\x1E\x20\x1F\x1F\x20\x20\x1F\x1F"
+ "\x21\x1E\x20\x1E\x1E\x1E\x20\x20\x1E"
+ "\x96\x8F\x50\x25\x20\x1F\x1F\x1F\x20\x20\x20\x1F\x21\x22\x21\x21\x1F\x1F\x1F\x1F\x1E\x1F\x20"
+ "\x1F\x21\x20\x20\x21\x1F\x20\x20\x1F"
+ "\x1F\x1E\x1F\x1E\x1D\x1E\x1F\x20\x1F\x1E\x1D\x1D\x1D\x1E\x20\x1F\x1F\x1F\x1E\x1E\x21\x1F\x1F"
+ "\x1F\x1F\x1F\x1E\x1E\x1E\x1E\x1F\x20"
+ "\x96\x92\x56\x26\x1F\x1D\x1F\x20\x1D\x1F\x20\x21\x1E\x20\x1F\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1F"
+ "\x1F\x20\x20\x20\x21\x1F\x21\x1F\x1F"
+ "\x1F\x1D\x1F\x1F\x1E\x1E\x1F\x21\x20\x1F\x1E\x1F\x20\x1F\x1E\x1F\x1E\x1E\x1E\x20\x1E\x1E\x1F"
+ "\x1E\x1F\x1F\x1F\x1F\x1E\x20\x1F\x1E"
+ "\x96\x96\x66\x2D\x1D\x1D\x1F\x21\x1F\x20\x20\x22\x1F\x1E\x1F\x1B\x1C\x20\x1F\x21\x1E\x1E\x1F"
+ "\x1F\x20\x1E\x1F\x20\x1F\x21\x20\x20"
+ "\x21\x20\x21\x1F\x20\x20\x20\x1E\x20\x1F\x1F\x1E\x1E\x1F\x20\x1E\x1E\x1F\x1E\x1D\x1E\x1F\x20"
+ "\x1E\x1F\x20\x1E\x1E\x1E\x20\x1F\x1E"
+ "\x96\x97\x7A\x36\x20\x21\x21\x20\x1F\x1E\x1F\x20\x1E\x1F\x20\x1F\x1D\x1D\x1E\x20\x1F\x1F\x1F"
+ "\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1E"
+ "\x1F\x21\x1E\x1D\x1F\x1F\x20\x20\x1E\x1E\x1F\x1F\x1E\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x21\x20\x20"
+ "\x1D\x21\x20\x1F\x20\x1E\x20\x1F\x1E"
+ "\x97\x97\x8C\x3F\x22\x21\x20\x20\x1F\x21\x1F\x1E\x1F\x1F\x20\x1F\x20\x20\x20\x22\x1F\x20\x21"
+ "\x1F\x1E\x1F\x21\x1F\x1F\x20\x1F\x1E"
+ "\x1D\x1D\x1F\x1F\x1E\x1F\x20\x1F\x1E\x1F\x20\x22\x21\x1E\x1C\x1D\x1F\x20\x20\x1F\x1F\x1F\x1E"
+ "\x1D\x1E\x1E\x1E\x20\x1E\x1E\x1F\x1F"
+ "\x97\x97\x95\x51\x26\x22\x20\x22\x20\x1F\x1F\x20\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1E\x1F\x20"
+ "\x1F\x1F\x1E\x20\x20\x1E\x1F\x20\x1F"
+ "\x1F\x20\x20\x1F\x1F\x20\x20\x1F\x21\x20\x1F\x21\x21\x20\x1E\x1E\x21\x21\x1F\x1E\x1E\x1E\x1E"
+ "\x1F\x1D\x1D\x1E\x1F\x1E\x1D\x1F\x20"
+ "\x97\x98\x98\x79\x2B\x23\x1F\x1E\x1F\x20\x21\x20\x20\x21\x1E\x1D\x1F\x1E\x1F\x21\x20\x1F\x1F"
+ "\x1F\x20\x20\x20\x1E\x21\x20\x1F\x1F"
+ "\x1D\x1E\x1F\x1F\x1F\x1F\x1F\x1E\x1F\x20\x20\x20\x20\x1E\x1F\x1E\x1F\x21\x20\x1F\x1F\x1F\x1E"
+ "\x21\x20\x1E\x1F\x1E\x20\x1F\x1F\x1D"
+ "\x98\x98\x98\x93\x40\x25\x20\x1E\x20\x21\x1F\x20\x1F\x1F\x21\x20\x20\x1F\x1F\x1F\x1E\x20\x22"
+ "\x20\x1F\x20\x20\x1D\x21\x20\x1F\x1E"
+ "\x1E\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E\x21\x20\x1F\x1E\x1D\x1F\x21\x1F\x1F\x1F\x20\x20\x1F\x1F"
+ "\x20\x20\x20\x1F\x1D\x1F\x1F\x1E\x1C"
+ "\x98\x98\x98\x97\x6A\x2F\x28\x26\x28\x26\x25\x26\x23\x24\x25\x23\x21\x22\x20\x22\x1F\x23\x21"
+ "\x1D\x1E\x20\x20\x1E\x1E\x1E\x1F\x1F"
+ "\x1F\x20\x21\x20\x20\x1F\x1F\x1E\x1E\x1F\x20\x1D\x1E\x1E\x1F\x1F\x20\x20\x1F\x20\x20\x1E\x20"
+ "\x1D\x1F\x20\x1F\x1E\x1F\x1F\x1E\x1E"
+ "\x98\x98\x98\x97\x93\x78\x6F\x6A\x64\x5F\x5B\x57\x4F\x4D\x4B\x49\x45\x3E\x38\x33\x2A\x2A\x29"
+ "\x26\x27\x23\x23\x21\x20\x1F\x1E\x1F"
+ "\x1D\x1F\x1F\x1D\x1D\x1E\x1E\x1E\x21\x20\x20\x1E\x21\x1E\x1F\x1F\x1E\x20\x1F\x1F\x1F\x1F\x20"
+ "\x1E\x20\x1E\x21\x20\x20\x1F\x1F\x20"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x95\x93\x8D\x85\x7D\x74\x6A\x62\x59"
+ "\x51\x46\x3B\x30\x2B\x2A\x27\x24\x24"
+ "\x22\x20\x1F\x1F\x1E\x1E\x1F\x1F\x20\x20\x20\x20\x1E\x1F\x1F\x20\x1F\x1F\x1F\x1F\x20\x1F\x1F"
+ "\x1F\x20\x1F\x20\x20\x1F\x20\x20\x20"
+ "\x98\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x97\x96\x92"
+ "\x8F\x89\x85\x80\x78\x6A\x59\x42\x30"
+ "\x2C\x28\x23\x21\x22\x1F\x20\x1E\x20\x21\x1F\x1F\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1E\x1D"
+ "\x20\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F"
+ "\x98\x97\x98\x98\x97\x98\x98\x97\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98"
+ "\x97\x98\x98\x98\x97\x97\x97\x97\x91"
+ "\x7D\x69\x54\x46\x38\x2F\x27\x26\x23\x22\x1E\x1D\x1F\x1E\x1E\x20\x20\x20\x1E\x1E\x1E\x1D\x1E"
+ "\x1F\x1E\x20\x21\x20\x21\x1E\x1C\x1F"
+ "\x98\x98\x98\x98\x97\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x96\x8E\x7E\x70\x62\x53\x41\x31\x27\x23\x22\x21\x1F\x20\x20\x20\x1F\x1E\x1E\x1E\x1F"
+ "\x1F\x21\x1E\x1F\x1F\x1E\x1F\x20\x1F"
+ "\x98\x98\x98\x97\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x97\x98\x98\x98\x98\x98\x98\x98"
+ "\x97\x98\x98\x97\x97\x97\x95\x90\x88\x7C\x64\x45\x2D\x28\x25\x22\x21\x20\x1F\x1F\x1F\x1F\x20"
+ "\x21\x22\x20\x20\x20\x20\x20\x1F\x20"
+ "\x98\x97\x97\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x98\x98\x97\x97\x97\x97\x98\x98\x98\x98"
+ "\x98\x97\x98\x98\x98\x97\x97\x98\x98"
+ "\x98\x98\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x89\x6C\x4F\x3D\x30\x28\x24\x21\x1E\x1F\x1F"
+ "\x1E\x1D\x1D\x1F\x1F\x1D\x1E\x20\x1F"
+ "\x96\x95\x94\x96\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x94\x86\x6E\x59\x42\x2E\x27\x24\x22"
+ "\x1F\x1E\x20\x20\x1E\x1D\x1E\x1F\x1E"
+ "\x4C\x4B\x4A\x4B\x4F\x55\x5B\x60\x69\x6F\x75\x7A\x7F\x86\x90\x97\x98\x98\x98\x98\x97\x97\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x98\x97"
+ "\x98\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x98\x98\x98\x98\x98\x97\x92\x88\x7A\x5B\x3C\x29"
+ "\x25\x22\x20\x20\x1F\x1C\x20\x21\x1F"
+ "\x24\x24\x23\x25\x24\x23\x23\x25\x25\x28\x2A\x2A\x2C\x2D\x2E\x35\x47\x55\x62\x6E\x7B\x82\x87"
+ "\x8C\x92\x97\x98\x98\x98\x97\x97\x98"
+ "\x97\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x98\x98\x97\x97\x92\x78"
+ "\x54\x39\x2B\x22\x1E\x1F\x20\x1F\x1E"
+ "\x20\x1F\x1E\x1E\x20\x1F\x1F\x1F\x1F\x1F\x21\x23\x20\x21\x21\x24\x24\x25\x27\x2A\x2D\x35\x40"
+ "\x4A\x58\x66\x76\x83\x91\x97\x97\x98"
+ "\x98\x98\x98\x98\x98\x98\x97\x97\x98\x97\x97\x97\x97\x97\x97\x97\x98\x97\x98\x98\x98\x98\x98"
+ "\x94\x7C\x61\x44\x2F\x28\x22\x20\x20"
+ "\x1F\x1D\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1E\x21\x20\x20\x20\x1E\x1F\x1F\x1F\x1E\x21\x22\x22\x23"
+ "\x24\x27\x2A\x35\x3E\x4A\x56\x6D\x82"
+ "\x94\x98\x97\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
+ "\x98\x97\x94\x89\x74\x48\x2D\x26\x22"
+ "\x1F\x1E\x1E\x1F\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1F\x1F\x20\x1E\x1F\x1D\x1C\x1F\x20\x1F\x1F"
+ "\x1F\x22\x21\x22\x26\x23\x25\x27\x2C"
+ "\x31\x46\x5E\x77\x88\x92\x97\x98\x98\x97\x97\x98\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x97\x98\x97\x96\x85\x5E\x42"
+ "\x1E\x1E\x1F\x20\x20\x20\x1F\x1D\x1E\x1F\x1D\x1F\x1F\x1F\x1F\x1F\x20\x21\x21\x20\x1F\x20\x20"
+ "\x1E\x1F\x1F\x20\x20\x20\x20\x20\x21"
+ "\x24\x25\x29\x2D\x43\x59\x6E\x81\x93\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98"
+ "\x98\x98\x98\x98\x98\x98\x98\x96\x88";
+
+static const BYTE TEST_64X64_RED_PLANE_RLE[3739] =
+ "\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24\x22"
+ "\x21\x20\x20\x20\x1D\x1E\x20\x1E\x1F"
+ "\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20\x1E"
+ "\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C\xF0"
+ "\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C\x16"
+ "\x12\x0C\x06\x08\x06\x00\x02\x00\xF0"
+ "\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00\x00"
+ "\x03\x01\x02\x00\x02\x01\x00\x04\x02"
+ "\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C\x94"
+ "\xBE\xBE\x9A\x6C\x48\x34\x1C\x10\x08"
+ "\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00\x01"
+ "\x02\x00\x06\x02\x04\x01\x01\x03\x03"
+ "\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02\xF0"
+ "\x1A\x44\x78\x92\x8C\x82\x6E\x52\x2E"
+ "\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00\xD0"
+ "\x02\x00\x02\x00\x02\x02\x02\x00\x00"
+ "\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82\x92"
+ "\x86\x50\x1C\x12\x0A\x04\x04\x02\x01"
+ "\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02\x04"
+ "\x00\x02\x00\x01\xE5\x00\x00\x02\x02"
+ "\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84\x50"
+ "\x28\x0C\x06\x04\x03\x03\x00\xF0\x04"
+ "\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04\x04"
+ "\x5C\x02\x02\x00\x02\x00\x14\x01\xF0"
+ "\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02\x01"
+ "\x02\x01\x04\x02\x01\x00\x02\x01\x80"
+ "\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01\x00"
+ "\x02\x00\x01\x13\x37\x55\x67\x4B\x1D"
+ "\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00\x01"
+ "\x04\x02\x00\x00\x00\x01\x00\x00\x03"
+ "\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A\x8A"
+ "\x32\x08\x04\x04\x04\x01\x03\x04\x01"
+ "\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00\x00"
+ "\x02\x17\x00\xF0\x01\x00\x00\x00\x31"
+ "\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02\x00"
+ "\x02\x00\x00\xA3\x00\x06\x00\x00\x00"
+ "\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57\x53"
+ "\x01\xF0\x00\x44\x42\x06\x06\x00\x04"
+ "\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0\x01"
+ "\x01\x07\x75\x2B\x2A\x86\x54\x1A\x0C"
+ "\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83\x01"
+ "\x03\x02\x01\x00\x01\x02\x00\x30\x02"
+ "\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F\x09"
+ "\x00\x20\x42\x08\x02\x00\x01\x00\x00"
+ "\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14\x34"
+ "\x04\x00\x0D\x55\x61\x55\x67\x4B\x0A"
+ "\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00\x00"
+ "\x02\x00\x00\x01\x00\x00\x02\x00\x11"
+ "\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04\x13"
+ "\x11\x02\x02\x32\x38\x08\x03\x01\x01"
+ "\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01\x00"
+ "\x00\x02\x02\x02\x00\x00\x10\x06\x05"
+ "\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02\x00"
+ "\x02\x01\x05\x03\x02\x02\x03\x01\x00"
+ "\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D\x00"
+ "\x02\x02\x06\x1A\x2E\x16\x02\xF0\x02"
+ "\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00\x02"
+ "\x02\x06\x04\x01\x01\x00\x05\x0C\xF0"
+ "\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00\x01"
+ "\x37\x4B\x04\x2A\x12\x00\x00\x00\xF0"
+ "\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01\x06"
+ "\x33\x00\x01\x00\xF0\x01\x00\x00\x00"
+ "\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05\x33"
+ "\x65\x19\x2C\x42\x02\x00\x02\x00\x04"
+ "\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02\x01"
+ "\x39\x00\x02\x00\xF0\x06\x12\x3D\x83"
+ "\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A\x22"
+ "\x00\x00\x01\x00\xF0\x00\x3A\x0C\x06"
+ "\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02\x2A"
+ "\x28\x2D\x8B\x9B\x43\x15\x01\x00\x00"
+ "\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01\x22"
+ "\x24\x00\x00\x02\x03\x01\x03\x03\x00"
+ "\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16\x07"
+ "\x3B\x87\x7D\x45\x11\x00\x00\x0E\x5A"
+ "\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0\x04"
+ "\x02\x00\x00\x06\x00\x03\x01\x01\x02"
+ "\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0\x85"
+ "\x4B\x09\x00\x08\x3A\x6C\x80\x94\x94"
+ "\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00\x02"
+ "\x90\x02\x01\x02\x01\x01\x04\x01\x00"
+ "\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15\x02"
+ "\x18\xF0\x28\x2A\x20\x12\x04\x01\x00"
+ "\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01\x00"
+ "\x02\x50\x04\x01\x00\x00\x00\x77\x02"
+ "\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F\xF0"
+ "\x29\x13\x01\x02\x02\x00\x05\x23\x45"
+ "\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01\x02"
+ "\x02\x01\x07\x28\x02\x00\xF0\x04\x1C"
+ "\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33\x08"
+ "\x3E\x86\x80\x08\x02\x01\xF0\x02\x02"
+ "\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03\xF0"
+ "\x18\x3C\x82\xBA\x96\x5E\x34\x12\x19"
+ "\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00\xE0"
+ "\x02\x03\x01\x00\x01\x02\x02\x03\x05"
+ "\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E\x84"
+ "\x74\x62\x4C\x34\x22\x2A\xF0\x3C\x4E"
+ "\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03\x02"
+ "\x02\x00\x04\x00\x02\x00\x04\x23\x02"
+ "\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00\x00"
+ "\x39\x31\x05\x05\x00\x01\x01\x00\x00"
+ "\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C\x14"
+ "\x1E\x28\x22\x1A\x12\x06\x00\x01\x00"
+ "\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01\x02"
+ "\x02\x04\x02\x23\x00\x02\x1F\x00\x0F"
+ "\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04\x04"
+ "\x01\x05\x02\x00\x02\x05\x01\x29\x02"
+ "\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81\x4D"
+ "\x15\x03\x01\x03\xF0\x01\x02\x06\x00"
+ "\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00\x00"
+ "\x01\x01\x02\x00\x00\x00\x02\x01\x00"
+ "\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45\x1F"
+ "\x0B\x07\x03\x00\x06\x04\x00\x00\x06"
+ "\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01\x11"
+ "\x29\x2F\x3F\x3F\x33\x29\x1D\x11\x0B"
+ "\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03\x01"
+ "\x03\x03\x01\x00\x00\x02\x00\x05\x03"
+ "\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85\x87"
+ "\x89\x87\x87\x8B\x8D\x8F\x95\x99\xF0"
+ "\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D\x00"
+ "\xF0\x04\x04\x04\x02\x00\x00\x01\x02"
+ "\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D\x77"
+ "\x4D\x3B\x2F\x29\x27\x29\x31\x3D\x3F"
+ "\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07\x07"
+ "\x01\x05\x01\x03\x01\x01\x02\x93\x00"
+ "\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13\x05"
+ "\x01\x05\x01\x00\x03\x07\x07\x05\x09"
+ "\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02\x04"
+ "\x06\x02\x01\x01\x00\x02\x00\x03\x01"
+ "\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05\x03"
+ "\x00\x04\x00\x00\x00\x04\x00\x03\x01"
+ "\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00\x05"
+ "\x05\x00\x04\x02\x03\x01\x05\x01\x00"
+ "\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0\x00"
+ "\x0B\x1B\x07\x01\x00\x02\x01\x02\x00"
+ "\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0\x04"
+ "\x02\x00\x04\x01\x01\x02\x01\x02\x01"
+ "\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40\x00"
+ "\x02\x00\x04\xF0\x00\x03\x05\x01\x03"
+ "\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01\x00"
+ "\x01\x01\x01\xF0\x00\x00\x02\x03\x01"
+ "\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03\x03"
+ "\x01\x01\x00\x10\x01\xF0\x02\x03\x03"
+ "\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01\x00"
+ "\x04\x01\x00\x02\x02\x83\x00\x04\x00"
+ "\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00\x00"
+ "\x01\x00\x02\x00\xF0\x02\x0A\x06\x00"
+ "\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06\x00"
+ "\x02\x02\x03\x00\xF0\x00\x05\x01\x03"
+ "\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00\x03"
+ "\x02\x01\x00\x00\x40\x00\x03\x01\x04"
+ "\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01\x02"
+ "\x01\x01\x00\x01\x00\xB3\x02\x01\x00"
+ "\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01\x00"
+ "\x00\x60\x02\x02\x00\x04\x00\x03\xF0"
+ "\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01\x00"
+ "\xF0\x03\x01\x01\x00\x00\x02\x02\x04"
+ "\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02\x02"
+ "\x63\x00\x00\x02\x01\x01\x00\xF0\x00"
+ "\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02\x00"
+ "\x01\x01\x04\x02\x01\x00\x03\xF0\x01"
+ "\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04\x06"
+ "\x02\x00\x01\x04\x00\x02\x04\x13\x00"
+ "\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04\x00"
+ "\x02\x04\x02\x01\x01\x02\x00\x00\x02"
+ "\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02\x02"
+ "\x00\x03\x01\x03\x00\x05\x03\x01\x00"
+ "\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0\x00"
+ "\x01\x01\x01\x07\x01\x01\x01\x00\x02"
+ "\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63\x04"
+ "\x04\x02\x04\x02\x01\xA0\x00\x04\x01"
+ "\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04\x00"
+ "\xF0\x03\x00\x01\x00\x06\x04\x00\x01"
+ "\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01\x01"
+ "\x63\x03\x02\x00\x03\x00\x02\xA0\x00"
+ "\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00\x01"
+ "\x03\x06\xF0\x06\x02\x02\x00\x03\x03"
+ "\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02\x00"
+ "\x01\x03\xF0\x01\x00\x06\x00\x03\x01"
+ "\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10\x0A"
+ "\x0C\x0C\x08\x0A\x08\x06\x02\x06\xF0"
+ "\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00\x01"
+ "\x00\x03\x00\x03\x00\x02\x00\x03\xF0"
+ "\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E\x88"
+ "\x78\x72\x6C\x62\x58\x52\x4C\x4C\x48"
+ "\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05\x01"
+ "\x01\x00\x06\x02\x00\x02\x06\x00\x00"
+ "\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A\x40"
+ "\x52\x5C\x68\x72\x7A\x82\x92\x94\x94"
+ "\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00\x04"
+ "\x02\x00\x02\x02\x01\x00\x00\x04\x05"
+ "\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39\x00"
+ "\x01\x00\xF0\x01\x02\x06\x0A\x16\x26"
+ "\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02\x01"
+ "\x00\x02\xF0\x01\x01\x02\x02\x02\x01"
+ "\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00\x01"
+ "\x00\xF0\x01\x02\x04\x0C\x10\x1E\x26"
+ "\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03\x02"
+ "\x02\xF0\x02\x03\x01\x01\x01\x02\x01"
+ "\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00\x00"
+ "\x02\xF0\x0E\x34\x5E\x84\x90\x8C\x82"
+ "\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03\x01"
+ "\x05\x30\x02\x08\x00\x03\x3A\x01\x02"
+ "\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16\x0E"
+ "\x0C\x04\x02\x33\x00\x00\x02\x90\x04"
+ "\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00\x01"
+ "\x01\x00\xF0\x01\x01\x00\x00\x02\x00"
+ "\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01\x05"
+ "\x09\x05\x60\x01\x01\x05\x03\x02\x01"
+ "\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00\x94"
+ "\x02\x00\x00\x00\x02\x02\x00\x01\x00"
+ "\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02\x06"
+ "\x02\x01\x00\x00\x01\x01\xF0\x93\x93"
+ "\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00\x13"
+ "\x01\xF0\x00\x01\x01\x00\x00\x00\x08"
+ "\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D\x4B"
+ "\x55\x63\x6F\x75\x87\x8D\x95\x9F\xA5"
+ "\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01\x00"
+ "\x14\x01\xF0\x00\x0C\x20\x3A\x78\xAC"
+ "\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11\x11"
+ "\x0D\x17\x17\x19\xF0\x21\x45\x5F\x75"
+ "\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00\x02"
+ "\x00\xF0\x02\x01\x00\x02\x02\x0C\x40"
+ "\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00\x05"
+ "\x00\x01\x05\xF0\x09\x09\x0B\x11\x11"
+ "\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00\x00"
+ "\x00\x02\x34\x00\x02\x00\x90\x08\x36"
+ "\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01\x04"
+ "\x33\x01\x00\x03\xF0\x05\x07\x09\x09"
+ "\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01\x00"
+ "\x80\x02\x06\x1E\x46\x9C\xB0\x70\x40"
+ "\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02\x01"
+ "\x02\x02\x01\x05\x03\x03\x0B\x05\x09"
+ "\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00\x02"
+ "\x04\x26\x70\x8C";
+
+static const BYTE TEST_64X64_GREEN_PLANE[4096] =
+ "\x2A\x25\x23\x23\x23\x23\x23\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x26"
+ "\x25\x25\x23\x23\x25\x24\x24\x25\x25"
+ "\x25\x25\x26\x27\x25\x24\x25\x25\x27\x24\x24\x24\x24\x24\x25\x25\x24\x25\x26\x25\x23\x24\x24"
+ "\x25\x23\x24\x24\x24\x24\x22\x23\x21"
+ "\x51\x37\x31\x2C\x29\x28\x27\x27\x2F\x73\xC9\xCC\xCB\xC5\xBD\xB5\xAA\x99\x80\x66\x48\x38\x33"
+ "\x2E\x29\x27\x27\x26\x25\x25\x23\x23"
+ "\x26\x26\x25\x27\x25\x25\x25\x26\x23\x24\x23\x25\x24\x23\x26\x25\x25\x25\x25\x24\x23\x25\x23"
+ "\x24\x24\x24\x25\x24\x26\x24\x24\x24"
+ "\xBA\xB3\x97\x79\x58\x48\x3A\x31\x37\x79\xCA\xCC\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xBC\x9D"
+ "\x7A\x5C\x4C\x3D\x30\x2B\x29\x28\x27"
+ "\x25\x24\x24\x23\x25\x26\x25\x25\x23\x24\x27\x27\x24\x24\x24\x24\x25\x25\x25\x25\x26\x27\x25"
+ "\x23\x23\x23\x25\x24\x22\x24\x26\x26"
+ "\xBB\xBE\xC0\xC2\xBC\xA7\x90\x78\x63\x87\xCB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xC2\xAD\x96\x7E\x63\x48\x36\x2C"
+ "\x27\x27\x24\x24\x25\x24\x25\x24\x22\x25\x26\x26\x23\x23\x23\x23\x27\x26\x25\x26\x26\x26\x24"
+ "\x24\x24\x24\x23\x22\x25\x24\x24\x24"
+ "\xBB\xBE\xC0\xC2\xC5\xC7\xC8\xC3\xBA\xBD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xC8\xBD\xB0\x93\x65"
+ "\x3A\x33\x2A\x28\x27\x24\x23\x25\x24\x24\x23\x24\x24\x22\x21\x24\x24\x23\x23\x25\x26\x25\x25"
+ "\x23\x23\x25\x24\x24\x26\x25\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC8\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC"
+ "\xBE\x90\x62\x42\x30\x28\x27\x22\x23\x24\x25\x25\x25\x25\x24\x24\x25\x25\x24\x26\x24\x24\x23"
+ "\x23\x25\x25\x27\x29\x24\x24\x25\x26"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xC0\x9D\x6A\x3F\x2E\x27\x24\x24\x24\x24\x25\x24\x24\x24\x25\x24\x25\x27\x24\x25\x25"
+ "\x24\x25\x25\x26\x25\x23\x23\x25\x26"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCB\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC"
+ "\xCC\xCD\xCC\xCC\xC1\xA8\x92\x86\x99"
+ "\xB9\xCB\xCD\xCD\xBF\x8F\x47\x2E\x26\x25\x24\x25\x24\x26\x25\x25\x24\x25\x25\x24\x23\x24\x26"
+ "\x24\x25\x27\x25\x24\x24\x23\x23\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCA\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCA\xB6\x83\x48\x3B\x39\x3B\x3B"
+ "\x40\x58\x9B\xC5\xCD\xCD\xA9\x52\x2B\x26\x26\x27\x25\x23\x26\x24\x24\x25\x25\x23\x25\x23\x22"
+ "\x23\x25\x24\x24\x24\x24\x22\x23\x25"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC"
+ "\xCD\xA8\x5D\x3D\x3D\x5F\x71\x79\x6C"
+ "\x50\x3D\x47\x7A\xC5\xCD\xCC\x98\x37\x2A\x26\x24\x25\x25\x27\x25\x24\x24\x25\x26\x25\x23\x23"
+ "\x22\x23\x23\x26\x26\x24\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC"
+ "\xC3\x59\x3B\x50\x92\xBB\xC4\xC7\xC1"
+ "\xAE\x75\x40\x3D\x8C\xCC\xCD\xC8\x65\x30\x28\x25\x27\x26\x26\x25\x24\x25\x25\x26\x24\x25\x25"
+ "\x25\x24\x23\x24\x25\x24\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xC7"
+ "\x70\x3B\x59\xAE\xCC\xCD\xCC\xC9\xC4"
+ "\xCB\xCA\x8A\x41\x4C\xA1\xCC\xCD\xB0\x45\x2B\x23\x25\x25\x26\x24\x23\x25\x24\x24\x24\x25\x24"
+ "\x23\x26\x25\x23\x24\x24\x24\x23\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xAF"
+ "\x50\x42\x90\xCA\xCD\xCB\xB0\x84\x74"
+ "\x8B\xB7\xC0\x64\x3C\x76\xC7\xCD\xC3\x74\x31\x26\x25\x25\x26\x26\x24\x25\x25\x25\x24\x26\x26"
+ "\x23\x23\x26\x24\x26\x25\x24\x23\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x98"
+ "\x44\x51\xB4\xCD\xCD\xC0\x73\x3E\x38"
+ "\x42\x83\xC8\x8F\x3A\x66\xBB\xCD\xCB\x9C\x42\x29\x27\x26\x24\x25\x26\x27\x26\x23\x26\x28\x25"
+ "\x22\x24\x25\x24\x26\x25\x24\x24\x23"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\x80"
+ "\x39\x58\xC7\xCD\xCC\xC9\x96\x5B\x51"
+ "\x67\xA3\xCD\xAC\x3D\x58\xAF\xCD\xCD\xBF\x69\x30\x25\x23\x25\x25\x26\x25\x25\x25\x26\x26\x26"
+ "\x24\x24\x25\x23\x24\x22\x23\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCD\x8C"
+ "\x40\x55\xBE\xCD\xCC\xCC\xC9\xBA\xAD"
+ "\xC0\xCC\xCD\xA5\x3B\x5A\xB1\xCD\xCD\xCC\x8A\x3B\x28\x24\x25\x25\x23\x24\x25\x26\x25\x26\x24"
+ "\x25\x25\x24\x24\x24\x24\x23\x24\x25"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x9E"
+ "\x47\x4A\xA9\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCE\xCD\xCD\x80\x3B\x6A\xBF\xCD\xCD\xCD\xAC\x4B\x2A\x26\x25\x26\x23\x26\x25\x25\x23\x24\x25"
+ "\x25\x25\x25\x25\x26\x24\x23\x24\x22"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCA\xC1\xC7\xCC\xCD\xCD\xCD\xCD\xCC\xCD\xBA"
+ "\x5E\x39\x6B\xBE\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCB\xA8\x4D\x3F\x86\xCC\xCD\xCD\xCD\xC9\x63\x2D\x27\x26\x23\x22\x24\x26\x25\x25\x25\x23"
+ "\x23\x24\x23\x26\x26\x24\x25\x23\x24"
+ "\xBA\xBD\xC0\xC2\xC4\xC6\xC8\xC8\xCA\xCB\xCB\xCC\xC8\x9E\x8B\xB5\xCD\xCD\xCD\xCD\xCC\xCC\xCD"
+ "\x9A\x3B\x42\x7C\xBB\xCD\xCD\xCD\xCD"
+ "\xC9\xA9\x5F\x3A\x5D\xB4\xCD\xCD\xCC\xCC\xCD\x8C\x30\x27\x26\x23\x22\x23\x25\x25\x23\x23\x25"
+ "\x23\x24\x24\x25\x23\x25\x25\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC4\xC6\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xAA\x5E\x59\x99\xC8\xCD\xCD\xCD\xCD\xCD"
+ "\xC8\x6C\x3A\x3F\x5F\x94\xB1\xB9\xAA"
+ "\x80\x50\x3A\x40\xA7\xCC\xCD\xCD\xCD\xCD\xCD\xB6\x38\x2A\x25\x23\x24\x26\x26\x25\x26\x24\x23"
+ "\x22\x24\x24\x24\x23\x24\x24\x24\x24"
+ "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xC7\x7B\x3B\x37\x5B\x9E\xBF\xCC\xCD\xCD"
+ "\xCD\xC4\x89\x4D\x37\x39\x3A\x3C\x3B"
+ "\x39\x41\x60\xA2\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\x52\x2C\x27\x24\x22\x25\x24\x23\x24\x25\x25"
+ "\x24\x22\x24\x26\x25\x24\x23\x23\x24"
+ "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xCD\xA4\x4B\x30\x32\x40\x67\x9B\xC0\xCD"
+ "\xCD\xCD\xC8\xA5\x7D\x62\x4C\x49\x57"
+ "\x6B\x8D\xBB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCA\xAC\x4A\x2A\x25\x25\x23\x25\x25\x25\x26\x26\x24"
+ "\x25\x25\x25\x24\x25\x23\x25\x25\x25"
+ "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xC6\x82\x3C\x34\x2F\x30\x42\x63\x98"
+ "\xC6\xCD\xCD\xCC\xC7\xBC\xB2\xB0\xB7"
+ "\xC0\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\x81\x4E\x2C\x27\x27\x25\x23\x24\x26\x25\x25\x26\x25"
+ "\x26\x25\x25\x23\x24\x25\x24\x24\x25"
+ "\xBD\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCC\xBA\x85\x59\x40\x31\x31\x36"
+ "\x4B\x82\xA8\xBC\xC9\xCD\xCD\xCD\xCD"
+ "\xCC\xCC\xCC\xCD\xCD\xCD\xC8\xB8\x82\x47\x34\x2B\x27\x25\x26\x25\x24\x24\x26\x26\x25\x25\x25"
+ "\x23\x23\x25\x25\x27\x24\x24\x24\x26"
+ "\xBE\xC0\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCB\xBA\x94\x64\x3F\x33"
+ "\x32\x35\x43\x61\x81\x9F\xB0\xC0\xCB"
+ "\xCD\xCD\xCC\xC9\xB7\x9B\x7E\x5A\x36\x34\x37\x3D\x29\x25\x25\x26\x24\x25\x24\x25\x24\x23\x24"
+ "\x22\x22\x25\x25\x27\x23\x25\x25\x25"
+ "\xBF\xC1\xC3\xC5\xC6\xC8\xC8\xCA\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xBD\xA2\x72"
+ "\x47\x36\x34\x33\x34\x43\x4D\x5A\x67"
+ "\x73\x6B\x66\x61\x53\x42\x35\x34\x3B\x5F\x95\x97\x2E\x25\x24\x25\x25\x25\x24\x24\x25\x24\x25"
+ "\x23\x23\x24\x25\x26\x22\x25\x25\x25"
+ "\xC0\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xCD"
+ "\xC7\x9E\x75\x57\x40\x33\x33\x32\x32"
+ "\x31\x32\x34\x34\x37\x42\x55\x74\xB2\xCC\xCC\x90\x32\x25\x24\x25\x23\x24\x26\x23\x25\x25\x25"
+ "\x24\x24\x21\x21\x24\x23\x25\x25\x26"
+ "\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCA\xB8\x9C\x83\x76\x66\x56"
+ "\x4A\x51\x5E\x6B\x83\x9B\xB5\xC9\xCD\xCD\xC5\x66\x2E\x26\x25\x25\x25\x25\x25\x23\x25\x25\x25"
+ "\x24\x23\x22\x24\x24\x26\x27\x25\x25"
+ "\xC1\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCB\xC5\xBE\xB7"
+ "\xB2\xB5\xBB\xC1\xCA\xCD\xCD\xCD\xCD\xCD\x9C\x44\x27\x25\x26\x26\x22\x23\x25\x24\x24\x23\x25"
+ "\x25\x24\x24\x25\x24\x24\x25\x25\x24"
+ "\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xBC\x8C\x48\x2D\x25\x24\x24\x22\x25\x26\x24\x24\x25\x24\x25"
+ "\x24\x22\x25\x26\x24\x25\x24\x26\x26"
+ "\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC"
+ "\xCD\xCD\xCD\xCD\xCC\xCC\xBE\x9A\x63\x39\x2B\x26\x23\x24\x24\x26\x24\x25\x25\x23\x23\x25\x25"
+ "\x25\x25\x25\x24\x24\x25\x24\x23\x24"
+ "\xC4\xC5\xC7\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCD\xBE\x8C\x5D\x40\x2C\x2A\x29\x26\x22\x23\x24\x25\x24\x24\x26\x25\x24\x23\x23"
+ "\x23\x22\x23\x23\x24\x23\x25\x24\x24"
+ "\xC4\xC6\xC7\xC8\xC9\xC9\xC9\xCA\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCC\xCC\xCD\xCD\xCC\xCC\xC7"
+ "\xBC\xAF\x92\x67\x3B\x32\x2D\x28\x26\x25\x26\x25\x25\x26\x25\x25\x27\x25\x25\x25\x23\x23\x24"
+ "\x23\x24\x25\x25\x25\x23\x25\x26\x24"
+ "\xC5\xC7\xC8\xC8\xC8\xBD\xAD\xA7\xA1\xA0\xA7\xB0\xB9\xC0\xC6\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCB\xC4\xBA\xAF\x9F\x8B\x78"
+ "\x61\x48\x35\x30\x2B\x27\x26\x25\x24\x24\x23\x24\x24\x24\x24\x26\x24\x23\x24\x27\x24\x23\x24"
+ "\x24\x24\x24\x22\x24\x25\x25\x26\x24"
+ "\xC6\xC8\xC8\xC8\x8C\x5E\x4D\x49\x42\x44\x47\x4E\x54\x58\x5C\x63\x6F\x7B\x87\x91\x8E\x8A\x86"
+ "\x81\x75\x67\x5C\x55\x4E\x41\x34\x31"
+ "\x2C\x2B\x29\x25\x28\x26\x25\x24\x26\x25\x25\x25\x24\x25\x25\x26\x25\x25\x24\x25\x25\x24\x24"
+ "\x24\x25\x23\x21\x23\x25\x25\x27\x25"
+ "\xC7\xC8\xC3\x7B\x35\x2B\x26\x26\x24\x27\x28\x29\x29\x29\x2C\x2D\x2D\x2D\x2D\x30\x2F\x2F\x30"
+ "\x2F\x2C\x2D\x2B\x29\x27\x26\x26\x26"
+ "\x26\x24\x24\x24\x24\x24\x24\x25\x25\x25\x27\x26\x24\x24\x24\x25\x24\x24\x26\x25\x26\x23\x26"
+ "\x24\x24\x25\x24\x25\x22\x23\x25\x25"
+ "\xC8\xC8\xA4\x49\x29\x25\x22\x22\x24\x25\x26\x25\x24\x25\x25\x24\x25\x26\x27\x25\x26\x26\x26"
+ "\x25\x25\x25\x26\x26\x25\x23\x24\x25"
+ "\x25\x23\x25\x26\x27\x25\x24\x25\x26\x27\x27\x25\x22\x22\x23\x26\x24\x24\x24\x25\x26\x24\x26"
+ "\x25\x23\x25\x25\x25\x25\x25\x26\x24"
+ "\xC8\xC8\x80\x36\x26\x23\x22\x23\x23\x24\x27\x27\x24\x24\x25\x24\x24\x27\x26\x24\x24\x25\x25"
+ "\x24\x25\x24\x25\x25\x24\x25\x25\x24"
+ "\x25\x25\x25\x24\x23\x24\x25\x24\x24\x25\x23\x23\x24\x25\x24\x25\x24\x25\x27\x27\x24\x24\x26"
+ "\x26\x24\x26\x27\x25\x25\x25\x24\x26"
+ "\xC9\xC1\x6C\x30\x25\x22\x23\x23\x23\x24\x25\x26\x24\x24\x25\x26\x26\x27\x26\x26\x24\x24\x25"
+ "\x24\x24\x24\x26\x26\x25\x25\x25\x25"
+ "\x24\x26\x23\x23\x25\x24\x25\x25\x23\x24\x23\x24\x25\x23\x24\x23\x25\x25\x24\x24\x24\x23\x24"
+ "\x25\x26\x25\x25\x25\x24\x25\x24\x24"
+ "\xC9\xBE\x68\x2E\x24\x23\x25\x23\x23\x24\x24\x24\x24\x25\x25\x26\x27\x25\x25\x25\x24\x23\x23"
+ "\x24\x25\x25\x26\x25\x25\x25\x26\x26"
+ "\x25\x24\x22\x23\x26\x25\x24\x24\x25\x25\x25\x25\x24\x23\x23\x25\x25\x26\x25\x25\x24\x24\x23"
+ "\x23\x23\x24\x23\x25\x24\x25\x24\x24"
+ "\xCA\xBB\x66\x2D\x22\x23\x23\x24\x23\x24\x25\x25\x27\x26\x24\x23\x24\x25\x27\x24\x22\x24\x25"
+ "\x24\x25\x27\x25\x24\x26\x26\x26\x28"
+ "\x25\x25\x25\x25\x23\x25\x26\x25\x25\x24\x24\x21\x22\x23\x25\x25\x26\x24\x24\x27\x24\x26\x25"
+ "\x26\x24\x25\x24\x24\x25\x25\x25\x23"
+ "\xCB\xC0\x6A\x2D\x25\x25\x26\x24\x25\x25\x25\x24\x27\x27\x26\x26\x24\x25\x25\x25\x23\x25\x25"
+ "\x26\x26\x25\x25\x26\x24\x26\x25\x25"
+ "\x24\x23\x24\x23\x23\x23\x25\x24\x25\x23\x23\x22\x25\x25\x27\x24\x24\x26\x23\x23\x26\x24\x26"
+ "\x25\x24\x23\x23\x24\x25\x23\x25\x26"
+ "\xCB\xC6\x71\x2F\x25\x23\x25\x25\x23\x24\x25\x26\x24\x25\x24\x25\x26\x24\x24\x23\x25\x25\x25"
+ "\x24\x26\x25\x25\x26\x24\x26\x24\x25"
+ "\x25\x23\x24\x25\x23\x23\x25\x26\x25\x24\x24\x24\x26\x26\x25\x26\x22\x23\x23\x25\x23\x24\x25"
+ "\x25\x24\x24\x25\x25\x24\x25\x25\x24"
+ "\xCB\xCB\x87\x39\x25\x22\x24\x26\x25\x24\x24\x27\x24\x24\x24\x20\x23\x25\x24\x26\x24\x24\x24"
+ "\x24\x25\x23\x24\x25\x25\x28\x26\x26"
+ "\x27\x27\x26\x25\x25\x26\x26\x25\x23\x24\x24\x24\x24\x24\x25\x25\x23\x24\x23\x25\x22\x24\x27"
+ "\x24\x24\x26\x25\x24\x26\x25\x24\x23"
+ "\xCC\xCC\xA6\x47\x25\x26\x26\x25\x25\x22\x24\x25\x24\x25\x25\x24\x23\x22\x23\x26\x24\x23\x25"
+ "\x23\x24\x25\x25\x24\x23\x24\x24\x23"
+ "\x23\x26\x23\x22\x24\x25\x25\x26\x24\x23\x24\x24\x23\x23\x25\x23\x26\x24\x25\x24\x26\x25\x24"
+ "\x22\x26\x25\x24\x25\x23\x25\x25\x23"
+ "\xCC\xCC\xBE\x52\x2A\x26\x24\x25\x24\x26\x24\x23\x24\x24\x25\x25\x25\x25\x25\x29\x25\x24\x26"
+ "\x24\x24\x25\x26\x24\x25\x25\x24\x23"
+ "\x22\x22\x24\x24\x23\x25\x25\x24\x25\x24\x26\x27\x26\x24\x22\x23\x24\x25\x25\x25\x24\x25\x25"
+ "\x23\x24\x23\x23\x26\x23\x25\x25\x25"
+ "\xCC\xCC\xCA\x6C\x2F\x28\x25\x27\x25\x25\x24\x25\x25\x24\x22\x24\x25\x24\x25\x23\x24\x24\x25"
+ "\x24\x24\x25\x26\x25\x24\x26\x25\x24"
+ "\x25\x24\x26\x24\x24\x25\x25\x24\x27\x25\x25\x26\x26\x26\x25\x25\x26\x26\x24\x26\x24\x24\x24"
+ "\x25\x23\x24\x24\x24\x24\x23\x24\x25"
+ "\xCC\xCD\xCD\xA3\x35\x29\x25\x25\x26\x25\x26\x27\x25\x26\x23\x22\x25\x24\x24\x26\x26\x24\x25"
+ "\x25\x25\x25\x25\x23\x26\x27\x24\x24"
+ "\x22\x24\x24\x24\x24\x24\x24\x23\x23\x26\x26\x25\x25\x24\x25\x25\x24\x27\x25\x25\x24\x24\x24"
+ "\x26\x25\x24\x24\x24\x25\x24\x24\x23"
+ "\xCD\xCD\xCD\xC8\x53\x2E\x25\x23\x25\x26\x26\x26\x24\x26\x27\x25\x25\x25\x25\x24\x24\x25\x27"
+ "\x25\x24\x25\x25\x23\x26\x25\x24\x23"
+ "\x23\x25\x23\x23\x25\x24\x24\x25\x23\x26\x25\x25\x24\x22\x25\x26\x24\x25\x25\x25\x25\x24\x24"
+ "\x26\x25\x26\x25\x23\x24\x25\x24\x23"
+ "\xCD\xCD\xCD\xCD\x8E\x3C\x32\x30\x2F\x2F\x2D\x2E\x2B\x2A\x2B\x2A\x29\x29\x28\x27\x25\x28\x28"
+ "\x24\x23\x25\x25\x23\x22\x23\x24\x25"
+ "\x24\x25\x26\x25\x25\x24\x24\x23\x23\x24\x25\x23\x23\x23\x25\x24\x25\x25\x25\x24\x25\x23\x25"
+ "\x22\x24\x25\x25\x24\x24\x24\x23\x24"
+ "\xCD\xCD\xCD\xCD\xC5\xA2\x94\x8D\x87\x7F\x79\x71\x68\x65\x60\x5E\x5A\x51\x48\x41\x35\x31\x31"
+ "\x2E\x2D\x2A\x2A\x27\x26\x25\x24\x27"
+ "\x26\x24\x25\x24\x23\x23\x23\x23\x26\x24\x27\x26\x26\x24\x25\x24\x26\x26\x24\x24\x24\x24\x25"
+ "\x24\x25\x23\x26\x25\x26\x25\x26\x26"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xC9\xC6\xBF\xB4\xA8\x9C\x8D\x82\x76"
+ "\x6A\x5A\x4B\x3D\x35\x32\x2F\x2C\x2B"
+ "\x29\x26\x25\x24\x25\x23\x24\x26\x25\x25\x25\x25\x24\x24\x24\x24\x24\x25\x24\x24\x25\x24\x26"
+ "\x26\x26\x26\x25\x26\x26\x26\x25\x25"
+ "\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCA\xC5"
+ "\xC0\xB9\xB3\xAC\xA1\x8E\x76\x56\x3D"
+ "\x36\x31\x2A\x28\x29\x26\x27\x26\x25\x26\x24\x25\x24\x25\x26\x24\x25\x25\x26\x24\x24\x23\x23"
+ "\x26\x26\x26\x26\x24\x25\x24\x24\x24"
+ "\xCE\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xC6"
+ "\xA9\x8C\x6D\x5B\x49\x3D\x32\x2E\x2B\x28\x25\x23\x24\x24\x23\x26\x26\x25\x24\x23\x24\x23\x24"
+ "\x24\x26\x26\x26\x25\x26\x24\x21\x24"
+ "\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCB\xC0\xAB\x97\x82\x6E\x54\x3C\x30\x2C\x29\x27\x25\x27\x25\x25\x24\x23\x23\x24\x24"
+ "\x24\x26\x24\x25\x24\x23\x24\x25\x24"
+ "\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCC\xCD\xCD\xCA\xC2\xB6\xA7\x86\x5A\x38\x31\x2C\x28\x26\x25\x24\x24\x24\x25\x26"
+ "\x26\x29\x25\x25\x26\x27\x25\x25\x26"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xBB\x91\x68\x4F\x3A\x30\x2A\x28\x26\x25\x24"
+ "\x23\x24\x24\x25\x26\x23\x23\x26\x25"
+ "\xCB\xCA\xC9\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xC8\xB4\x93\x76\x57\x3B\x30\x2D\x28"
+ "\x25\x25\x25\x25\x25\x22\x23\x24\x25"
+ "\x64\x62\x61\x62\x68\x71\x7B\x81\x8C\x94\x9C\xA3\xAB\xB5\xC3\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xC5\xB8\xA5\x79\x4C\x34"
+ "\x2E\x26\x25\x25\x27\x21\x26\x26\x25"
+ "\x2C\x2B\x28\x2C\x2C\x2B\x2B\x2F\x2E\x31\x33\x34\x37\x38\x3B\x43\x5B\x70\x83\x95\xA7\xAF\xB6"
+ "\xBC\xC5\xCB\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCC\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCC\xC6\xA1"
+ "\x6E\x4A\x35\x29\x24\x27\x26\x24\x24"
+ "\x26\x25\x23\x23\x26\x25\x26\x25\x25\x24\x28\x2A\x26\x28\x29\x2C\x2B\x2E\x30\x33\x37\x44\x53"
+ "\x61\x74\x88\x9D\xB1\xC4\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCC\xCD\xCD\xCD\xCD\xCD"
+ "\xC9\xA7\x81\x58\x39\x30\x2A\x26\x25"
+ "\x25\x24\x26\x26\x24\x24\x23\x24\x23\x24\x26\x28\x25\x26\x25\x26\x27\x25\x26\x27\x28\x28\x2A"
+ "\x2C\x31\x35\x42\x50\x61\x74\x92\xAF"
+ "\xC9\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD"
+ "\xCD\xCC\xC8\xB8\x9B\x5E\x39\x2F\x2A"
+ "\x24\x23\x23\x24\x23\x24\x25\x26\x25\x25\x25\x25\x24\x24\x25\x24\x24\x24\x22\x25\x25\x24\x24"
+ "\x24\x27\x27\x28\x2C\x2B\x2C\x31\x36"
+ "\x3F\x5A\x7D\xA0\xB8\xC5\xCC\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCC\xCA\xB2\x7D\x55"
+ "\x22\x23\x25\x25\x25\x25\x25\x24\x24\x24\x23\x24\x25\x24\x24\x24\x25\x26\x26\x25\x24\x25\x25"
+ "\x25\x24\x24\x25\x26\x25\x26\x27\x29"
+ "\x2A\x2C\x31\x38\x57\x74\x91\xAD\xC7\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD"
+ "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCB\xB7";
+
+static const BYTE TEST_64X64_GREEN_PLANE_RLE[3696] =
+ "\x34\x2A\x25\x23\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25"
+ "\x25\x23\x23\x25\x24\x24\x25\x84\x26"
+ "\x27\x25\x24\x25\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23"
+ "\x21\xF0\x4E\x24\x1C\x12\x0C\x0A\x08"
+ "\x06\x08\x26\x4E\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02"
+ "\x02\xF0\x03\x03\x02\x02\x01\x00\x00"
+ "\x02\x00\x02\x07\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02"
+ "\x00\x40\x04\x04\x02\x06\xF0\xD2\xF8"
+ "\xCC\x9A\x5E\x40\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98"
+ "\x66\x4A\x2C\x14\x0C\x08\xF0\x0A\x08"
+ "\x01\x03\x01\x07\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04"
+ "\x04\x01\x01\x01\x00\x00\x40\x07\x00"
+ "\x04\x04\xC4\x02\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2"
+ "\xB2\x9C\x70\x3E\x1C\x0A\x04\x06\x00"
+ "\x02\x75\x00\x03\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06"
+ "\x00\x03\x10\x03\x04\x8C\x12\x40\x70"
+ "\x96\xAE\x6C\x02\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02"
+ "\x04\x01\x05\x03\x02\x01\x03\x02\x05"
+ "\x05\x03\x01\x00\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A"
+ "\x22\x1C\x00\x03\xF0\x0A\x20\x38\x72"
+ "\xCE\xF7\xBA\x70\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03"
+ "\x01\x03\x00\x04\x70\x00\x06\x0A\x03"
+ "\x01\x02\x04\x04\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6"
+ "\x74\x2E\x0E\x0A\x02\x00\x01\x01\xF0"
+ "\x00\x01\x00\x00\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23"
+ "\x01\x00\x47\x01\x00\x01\x00\x23\x01"
+ "\x00\xF0\x01\x01\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01"
+ "\x04\x02\x02\x01\x02\x00\x05\x01\x01"
+ "\x02\x90\x00\x00\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9"
+ "\xB1\x95\xBB\xF1\xE5\x63\x0F\x1C\x7C"
+ "\xC4\xF0\x48\x0A\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01"
+ "\x00\x00\x01\x00\x02\x04\x99\x02\x00"
+ "\x00\x00\x02\x02\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F"
+ "\xF0\x00\x46\x8C\x18\x08\x00\x05\x00"
+ "\x04\x02\x02\x00\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13"
+ "\x9D\x43\x26\xAA\xB8\xA6\x9C\xAA\xBC"
+ "\x70\x0D\x79\x71\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04"
+ "\x04\x06\x02\x00\x03\x01\x00\x41\xF0"
+ "\x01\x00\x09\xA5\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A"
+ "\x06\x03\x03\x01\x00\x01\x01\x00\xE0"
+ "\x01\x03\x00\x00\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03"
+ "\x37\x89\x9F\x7F\x25\x6C\x46\x1F\xF0"
+ "\x55\x09\x00\x26\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02"
+ "\x04\x02\x00\x00\x01\x41\xF0\x02\x00"
+ "\x2D\x17\x1E\x48\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06"
+ "\x04\x02\x03\x01\x04\x04\x93\x02\x03"
+ "\x04\x04\x01\x01\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12"
+ "\x46\x3A\x32\x4A\xF0\x40\x0A\x3A\x06"
+ "\x1B\x17\x00\x04\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01"
+ "\x03\x05\x01\x00\x10\x02\x11\xF0\x01"
+ "\x00\x02\x01\x00\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00"
+ "\x00\x1A\x42\x16\x06\x02\x00\xF0\x00"
+ "\x05\x01\x00\x02\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02"
+ "\x00\x24\x0E\x15\x29\x00\x02\x02\x08"
+ "\x24\x3E\xF0\x1C\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00"
+ "\x01\x03\x03\x02\x00\x00\x02\x02\x04"
+ "\x00\x00\x20\x00\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00"
+ "\x02\x02\x01\x03\x49\xF0\x65\x08\x38"
+ "\x1A\x00\x00\x00\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00"
+ "\x00\x04\x01\x04\xF0\x01\x01\x00\x01"
+ "\x01\x01\x00\x01\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00"
+ "\xE3\x07\x43\x91\x25\x3C\x5C\x02\x00"
+ "\x01\x01\x08\x52\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93"
+ "\x02\x02\x00\x02\x00\x00\x00\x02\x00"
+ "\xF0\x08\x18\x59\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91"
+ "\xB1\x49\x0C\x94\x30\x00\x00\x02\x02"
+ "\xF0\x00\x54\x10\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00"
+ "\x00\x04\x35\x02\x02\x00\xF0\x02\x3A"
+ "\x3A\x3B\xC3\xD9\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4"
+ "\x4C\x02\x00\xF0\x2C\x34\x04\x04\x02"
+ "\x03\x01\x03\x03\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C"
+ "\x52\x20\x0D\x51\xBB\xAF\x61\x19\x00"
+ "\x00\x12\x7E\xB0\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02"
+ "\x00\x02\x04\x04\x02\x01\x02\x06\x70"
+ "\x02\x03\x00\x01\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D"
+ "\x00\xC4\x0A\x4E\x94\xB4\xCC\xCE\xC0"
+ "\xAA\x78\x24\x02\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00"
+ "\x00\x01\x01\x04\x01\x01\x00\x68\x02"
+ "\x02\x02\x00\x02\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36"
+ "\x3A\x2C\x18\x06\x01\x00\x00\x00\x09"
+ "\x29\x95\xE9\x99\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06"
+ "\x01\x00\x00\x02\x78\x02\x00\x02\x00"
+ "\x00\x02\x00\xF0\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02"
+ "\x02\x00\x07\x2B\x63\x93\xBB\x97\x25"
+ "\x06\x24\x04\xF0\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02"
+ "\x01\x98\x02\x02\x00\x02\x00\x00\x00"
+ "\x02\x00\xF0\x04\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF"
+ "\xC7\xB1\x91\x4B\x0A\x56\xBC\xB4\x0A"
+ "\x00\x01\x64\x01\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02"
+ "\x00\x02\x00\x02\x00\xF0\x20\x54\xB6"
+ "\xFF\xD0\x82\x48\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08"
+ "\x00\x00\x00\x03\x01\xE0\x04\x01\x00"
+ "\x02\x00\x02\x02\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA"
+ "\xC2\xB8\xA0\x86\x68\x48\x32\x3E\xF0"
+ "\x54\x6E\x98\xB2\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06"
+ "\x00\x06\x04\x00\x01\x63\x02\x02\x00"
+ "\x00\x02\x00\x2E\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0"
+ "\x00\x00\x51\x43\x0D\x01\x02\x02\x05"
+ "\x03\x00\x02\x01\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00"
+ "\x05\xF0\x04\x10\x1C\x2A\x36\x30\x24"
+ "\x18\x06\x00\x01\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03"
+ "\x02\x60\x02\x00\x02\x01\x02\x04\x9F"
+ "\x02\x00\x02\x02\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08"
+ "\x01\x01\x02\xD0\x01\x03\x02\x00\x02"
+ "\x06\x00\x03\x00\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02"
+ "\x01\x00\x00\x00\x1B\x7F\xC1\xB3\x6D"
+ "\x1D\x03\x00\x01\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02"
+ "\x02\x00\xA6\x00\x02\x00\x00\x02\x00"
+ "\x01\x01\x02\x00\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F"
+ "\xF0\x2F\x0B\x09\x05\x01\x06\x06\x02"
+ "\x00\x06\x02\x01\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00"
+ "\x01\x17\x37\x45\x55\x57\x49\x39\x27"
+ "\x19\x0D\x37\x03\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0"
+ "\x03\x01\x05\x01\x01\x03\x01\x02\x05"
+ "\x03\x01\x04\x02\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD"
+ "\xBF\xBB\xBD\xB7\xBF\xC3\xC9\xCF\xD3"
+ "\xF0\xCF\xBB\xA3\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15"
+ "\x05\x01\x01\x01\x04\x02\x04\x02\x00"
+ "\xF0\x02\x02\x00\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02"
+ "\x00\x09\x99\xAD\x65\x4D\x45\x3B\x39"
+ "\x3D\x49\x55\x5D\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B"
+ "\x15\x0B\x0D\x09\x01\x07\x03\x01\x02"
+ "\x01\x00\x04\x02\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0"
+ "\x02\x00\x3D\x63\x17\x0B\x07\x07\x00"
+ "\x03\x03\x07\x09\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0"
+ "\x03\x01\x01\x01\x02\x04\x06\x02\x00"
+ "\x00\x02\x04\x00\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40"
+ "\x06\x04\x02\x01\xF0\x00\x00\x47\x25"
+ "\x05\x03\x00\x02\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01"
+ "\xF0\x04\x02\x01\x00\x04\x00\x03\x07"
+ "\x01\x02\x01\x03\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04"
+ "\x50\x00\x00\x00\x03\x04\xF0\x02\x0D"
+ "\x27\x0B\x01\x01\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00"
+ "\x01\x00\x02\x02\x02\x00\xF0\x00\x02"
+ "\x01\x02\x03\x01\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01"
+ "\x03\x01\x04\x01\x03\x00\x40\x01\x00"
+ "\x00\x03\xF0\x00\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01"
+ "\x01\x00\x01\x03\x00\x02\x02\x00\x01"
+ "\x00\x00\xF0\x02\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00"
+ "\x02\x02\x02\x00\x02\x01\x03\x05\x01"
+ "\x03\x00\xF0\x02\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04"
+ "\x01\x03\x02\x04\x00\x00\x04\x01\x01"
+ "\x02\x02\xF0\x00\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02"
+ "\x03\x01\x04\x00\x04\x04\x06\x02\x02"
+ "\x02\x01\x40\x02\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04"
+ "\xF0\x06\x00\x00\x03\x02\x02\x02\x00"
+ "\x04\x02\x03\x00\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06"
+ "\xF0\x04\x04\x01\x03\x04\x01\x07\x04"
+ "\x03\x02\x01\x00\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01"
+ "\x00\x04\x05\x03\x03\xA5\x01\x04\x01"
+ "\x01\x03\x04\x00\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02"
+ "\xF0\x02\x03\x04\x03\x05\x00\x04\x05"
+ "\x00\x01\x00\x00\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00"
+ "\x01\x02\x00\x01\x00\xF0\x09\x05\x02"
+ "\x00\x06\x01\x01\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01"
+ "\x03\x00\x00\x00\x03\xF0\x03\x00\x01"
+ "\x02\x02\x00\x00\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00"
+ "\x08\x04\x01\x00\x03\x00\x03\x00\x02"
+ "\x02\xF0\x08\x00\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05"
+ "\x05\x01\x01\x01\x02\x02\x01\x00\x00"
+ "\x01\xF0\x01\x00\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0"
+ "\x00\x00\x30\x16\x0A\x00\x03\x00\x01"
+ "\x08\x00\x03\x00\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01"
+ "\x07\x02\x04\x01\x00\x00\xF0\x03\x02"
+ "\x02\x04\x06\x06\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00"
+ "\x04\xF0\x00\x00\x18\x34\x0A\x04\x02"
+ "\x04\x02\x01\x00\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02"
+ "\x02\x06\x04\x04\x00\x02\x00\x00\x00"
+ "\x04\x02\xF0\x01\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02"
+ "\x03\x01\x00\xF0\x00\x02\x06\x6E\x0C"
+ "\x02\x00\x03\x02\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01"
+ "\x03\x04\x02\xF0\x01\x00\x05\x00\x03"
+ "\x00\x00\x01\x01\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04"
+ "\x00\x00\x00\x40\x02\x02\x00\x03\xF0"
+ "\x02\x00\x00\x4A\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02"
+ "\x04\x00\x01\x00\xF0\x03\x00\x01\x02"
+ "\x02\x01\x01\x02\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70"
+ "\x04\x02\x01\x01\x02\x00\x00\x03\xF0"
+ "\x0A\x76\x1C\x1A\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01"
+ "\x00\x00\x00\x07\x03\x00\x04\x02\xF0"
+ "\x00\x06\x04\x00\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02"
+ "\x07\x01\x01\x00\x02\x00\x01\x01\x10"
+ "\x02\x04\xF0\x6E\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12"
+ "\x14\x14\x0A\x0A\x08\x08\x04\x00\x04"
+ "\x04\x01\xF0\x01\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01"
+ "\x02\x00\x04\x02\x03\x02\x02\x04\x02"
+ "\x06\x04\x04\xF0\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2"
+ "\x8A\x78\x5A\x42\x26\x1C\x18\x14\x10"
+ "\x08\x06\x04\xF0\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00"
+ "\x02\x00\x02\x04\x02\x06\x01\x02\x00"
+ "\x02\x01\x01\x39\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE"
+ "\xF0\xD8\xB8\x8E\x54\x24\x1A\x16\x0A"
+ "\x08\x08\x06\x06\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00"
+ "\x70\x00\x02\x03\x01\x03\x01\x01\x6C"
+ "\x04\x00\x00\x00\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0"
+ "\xB6\x86\x66\x40\x2E\x16\x10\x0C\x04"
+ "\x02\x03\x00\x01\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10"
+ "\x00\x34\x01\x02\x00\x23\x01\x00\x2E"
+ "\x02\x00\xF0\x02\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04"
+ "\x02\x01\x00\x00\x00\x01\x02\x00\x00"
+ "\x00\x03\x60\x01\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00"
+ "\x28\x01\x00\xF0\x04\x18\x44\x6C\x90"
+ "\xA8\xC4\xD6\xAC\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08"
+ "\x02\x00\x04\x03\x25\x02\x00\x25\x01"
+ "\x00\x34\x01\x01\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0"
+ "\x78\xF0\x4E\x28\x16\x0C\x08\x04\x00"
+ "\x03\x05\x09\x01\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01"
+ "\x02\x00\x02\x24\x78\xC0\xCA\xB2\x8C"
+ "\x5A\x26\x14\x10\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3"
+ "\x97\x81\x71\x61\x53\x43\x2F\x13\x5D"
+ "\x01\x02\x02\x02\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E"
+ "\x18\x90\x12\x02\x00\x00\x04\x01\x06"
+ "\x04\x00\xF0\x6F\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93"
+ "\x6F\x4B\x3B\x2D\x21\x0F\x03\x00\x29"
+ "\x01\x00\x14\x01\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0"
+ "\x0B\x0B\x09\x11\x0B\x0B\x09\x13\x11"
+ "\x19\x15\x13\x21\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6"
+ "\x00\x00\x02\x00\x00\x00\x02\x02\x00"
+ "\x00\x02\x00\xF0\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01"
+ "\x01\x06\x06\x03\x01\x05\x01\x03\x00"
+ "\x03\x03\x01\x03\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75"
+ "\x3B\x07\x00\x01\x01\x00\x00\x02\x02"
+ "\x00\x00\x00\x02\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0"
+ "\x01\x01\x05\x03\x01\x00\x04\x04\x04"
+ "\x02\x01\x05\x01\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4"
+ "\xC1\xF1\xEC\xE5\x9D\x57\x29\x0F\x01"
+ "\x00\xF0\x01\x01\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56"
+ "\xF0\x03\x00\x04\x02\x04\x02\x00\x03"
+ "\x01\x01\x03\x01\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B"
+ "\xF0\x13\x19\x29\x5B\x97\xCF\xC1\xA1"
+ "\x75\x3F\x0B\x02\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4";
+
+static const BYTE TEST_64X64_BLUE_PLANE[4096] =
+ "\x27\x23\x23\x24\x25\x25\x25\x25\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27"
+ "\x27\x25\x24\x26\x28\x26\x27\x28\x28"
+ "\x26\x28\x28\x2A\x26\x27\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\x26\x27"
+ "\x27\x26\x27\x27\x27\x26\x25\x26\x23"
+ "\x32\x29\x29\x27\x26\x28\x27\x27\x29\x3C\x55\x56\x56\x54\x52\x50\x4D\x48\x40\x37\x2F\x2C\x2B"
+ "\x2A\x27\x27\x27\x27\x26\x26\x25\x26"
+ "\x27\x28\x28\x28\x27\x27\x27\x28\x25\x26\x25\x27\x26\x26\x29\x28\x26\x27\x25\x27\x25\x26\x25"
+ "\x27\x25\x27\x28\x27\x26\x27\x27\x27"
+ "\x4E\x4D\x45\x3D\x35\x30\x2D\x29\x2B\x3E\x56\x56\x57\x57\x57\x57\x56\x56\x56\x56\x56\x52\x49"
+ "\x3E\x36\x31\x2D\x29\x27\x27\x27\x28"
+ "\x27\x27\x24\x25\x28\x29\x27\x28\x26\x27\x29\x2A\x27\x25\x24\x26\x28\x27\x27\x25\x29\x27\x26"
+ "\x25\x26\x23\x26\x27\x25\x27\x29\x26"
+ "\x4F\x50\x50\x51\x52\x4A\x44\x3E\x36\x41\x55\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57"
+ "\x57\x53\x4D\x47\x40\x39\x31\x2D\x27"
+ "\x25\x27\x26\x25\x28\x26\x25\x26\x25\x28\x29\x29\x25\x25\x26\x26\x2A\x29\x28\x26\x29\x29\x27"
+ "\x27\x26\x25\x25\x24\x28\x27\x27\x25"
+ "\x4F\x50\x50\x51\x53\x54\x55\x53\x50\x51\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57"
+ "\x57\x58\x57\x57\x55\x52\x4E\x47\x38"
+ "\x2D\x2C\x26\x27\x28\x27\x26\x28\x27\x26\x24\x26\x24\x24\x24\x27\x27\x25\x26\x28\x27\x27\x25"
+ "\x26\x26\x27\x26\x27\x27\x28\x27\x24"
+ "\x4F\x50\x51\x52\x52\x54\x55\x54\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x56"
+ "\x52\x45\x38\x2F\x29\x27\x28\x25\x23\x25\x28\x26\x27\x27\x25\x27\x27\x26\x25\x28\x25\x26\x25"
+ "\x27\x27\x26\x2A\x2C\x27\x27\x28\x29"
+ "\x4F\x50\x51\x52\x53\x53\x54\x55\x55\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x52\x49\x3A\x2E\x29\x28\x27\x26\x26\x25\x25\x26\x27\x27\x27\x27\x27\x27\x25\x26\x27"
+ "\x25\x25\x28\x28\x28\x25\x25\x26\x27"
+ "\x4F\x50\x51\x52\x52\x53\x54\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x58\x56\x56\x56"
+ "\x56\x57\x56\x56\x52\x4C\x45\x42\x47"
+ "\x51\x56\x57\x57\x53\x45\x30\x28\x27\x27\x27\x28\x27\x29\x28\x26\x25\x26\x28\x26\x25\x25\x29"
+ "\x26\x27\x29\x27\x26\x26\x26\x25\x26"
+ "\x4F\x50\x51\x52\x52\x54\x55\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x56\x56\x50\x42\x30\x2A\x2B\x2D\x2D"
+ "\x2E\x35\x48\x54\x57\x57\x4C\x34\x27\x27\x29\x2A\x28\x26\x29\x26\x25\x26\x28\x25\x27\x25\x25"
+ "\x25\x27\x25\x27\x25\x26\x25\x25\x26"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x56\x4C\x37\x2D\x2E\x34\x3B\x3D\x3A"
+ "\x33\x2F\x2F\x3E\x54\x56\x56\x48\x2C\x29\x27\x25\x27\x26\x29\x25\x27\x26\x28\x28\x28\x26\x24"
+ "\x25\x25\x24\x29\x28\x25\x25\x25\x24"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56"
+ "\x53\x35\x2D\x32\x46\x51\x54\x55\x53"
+ "\x4E\x3F\x2D\x2D\x43\x56\x56\x56\x39\x2A\x28\x27\x2A\x28\x29\x26\x27\x28\x28\x29\x26\x26\x27"
+ "\x28\x27\x26\x25\x28\x26\x26\x27\x25"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x55"
+ "\x3B\x2E\x36\x4E\x57\x57\x56\x55\x54"
+ "\x56\x56\x43\x2E\x31\x4A\x56\x57\x4E\x2F\x28\x25\x27\x27\x28\x27\x26\x27\x27\x26\x26\x26\x26"
+ "\x26\x28\x25\x26\x26\x27\x27\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x4E"
+ "\x32\x2F\x45\x56\x57\x56\x4E\x42\x3D"
+ "\x44\x50\x53\x36\x2D\x3E\x54\x57\x54\x3D\x2A\x27\x27\x26\x27\x27\x27\x27\x27\x26\x27\x27\x27"
+ "\x23\x25\x27\x27\x29\x26\x26\x25\x26"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x49"
+ "\x2E\x33\x4F\x57\x57\x53\x3D\x2F\x2C"
+ "\x2F\x41\x55\x43\x2E\x39\x52\x56\x56\x48\x2E\x28\x28\x28\x26\x28\x28\x2A\x27\x25\x29\x29\x26"
+ "\x25\x27\x28\x27\x27\x27\x26\x26\x25"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x56\x56\x57\x41"
+ "\x2B\x33\x54\x56\x56\x55\x46\x34\x32"
+ "\x38\x49\x56\x4C\x2F\x36\x4E\x56\x57\x52\x39\x2A\x26\x26\x27\x28\x28\x28\x26\x27\x28\x28\x28"
+ "\x25\x25\x25\x24\x26\x25\x23\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x56\x57\x44"
+ "\x2D\x33\x52\x57\x56\x56\x55\x50\x4D"
+ "\x52\x56\x57\x4A\x2E\x36\x4F\x57\x57\x56\x42\x2B\x28\x27\x28\x28\x24\x25\x27\x28\x26\x27\x27"
+ "\x28\x27\x26\x25\x26\x26\x25\x27\x28"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x49"
+ "\x31\x2F\x4B\x57\x57\x58\x57\x56\x56"
+ "\x57\x57\x57\x40\x2E\x38\x52\x57\x57\x57\x4D\x2F\x28\x27\x28\x29\x26\x27\x27\x27\x26\x27\x28"
+ "\x28\x27\x26\x28\x29\x26\x24\x27\x24"
+ "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x56\x53\x57\x57\x57\x57\x57\x57\x56\x56\x51"
+ "\x37\x2D\x39\x52\x56\x57\x57\x57\x57"
+ "\x57\x57\x4C\x31\x2F\x41\x56\x57\x56\x56\x56\x39\x27\x26\x27\x24\x24\x26\x27\x27\x27\x26\x26"
+ "\x25\x25\x26\x28\x28\x27\x27\x26\x27"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x55\x49\x43\x50\x57\x57\x56\x56\x56\x56\x56"
+ "\x48\x2C\x30\x3F\x51\x57\x57\x57\x57"
+ "\x56\x4C\x37\x2C\x36\x50\x57\x57\x57\x57\x57\x44\x29\x27\x27\x25\x23\x26\x27\x27\x26\x26\x27"
+ "\x26\x27\x26\x26\x25\x28\x27\x27\x26"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x56\x4C\x35\x35\x46\x55\x56\x56\x57\x57\x57"
+ "\x55\x3B\x2C\x2E\x38\x46\x4E\x50\x4C"
+ "\x41\x32\x2C\x2B\x4B\x57\x57\x57\x57\x57\x57\x4F\x2D\x29\x27\x26\x26\x27\x29\x27\x28\x26\x25"
+ "\x25\x25\x25\x25\x24\x25\x26\x27\x26"
+ "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x57\x55\x40\x2C\x2B\x35\x48\x52\x56\x57\x57"
+ "\x57\x54\x44\x31\x29\x2C\x2C\x2D\x2C"
+ "\x2C\x2E\x38\x4B\x57\x57\x57\x57\x57\x57\x57\x57\x33\x28\x28\x27\x23\x26\x27\x24\x27\x27\x26"
+ "\x26\x25\x27\x27\x28\x26\x26\x26\x27"
+ "\x4F\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x57\x57\x4A\x30\x2A\x2A\x2E\x38\x48\x53\x57"
+ "\x57\x57\x55\x4A\x3F\x37\x31\x31\x35"
+ "\x3A\x44\x50\x56\x57\x56\x56\x57\x56\x57\x56\x4F\x2F\x28\x27\x27\x25\x28\x27\x27\x29\x28\x27"
+ "\x27\x28\x26\x25\x27\x26\x28\x27\x26"
+ "\x50\x51\x52\x53\x53\x54\x55\x55\x55\x56\x56\x56\x57\x57\x54\x41\x2D\x2B\x29\x27\x2F\x38\x48"
+ "\x55\x57\x57\x56\x55\x52\x4F\x4E\x53"
+ "\x53\x55\x57\x57\x57\x56\x56\x57\x56\x52\x40\x32\x29\x29\x29\x27\x25\x26\x26\x26\x26\x28\x27"
+ "\x29\x27\x28\x25\x26\x26\x27\x26\x27"
+ "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x56\x56\x52\x42\x36\x2F\x2B\x2B\x2C"
+ "\x31\x41\x4C\x52\x55\x57\x57\x57\x57"
+ "\x56\x56\x56\x57\x56\x56\x54\x51\x3F\x2F\x2A\x26\x27\x26\x26\x26\x27\x27\x28\x28\x26\x26\x28"
+ "\x26\x26\x27\x26\x28\x27\x27\x27\x28"
+ "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x56\x50\x46\x38\x2D\x29"
+ "\x29\x2A\x30\x38\x40\x49\x4E\x52\x56"
+ "\x57\x57\x56\x56\x4F\x48\x3F\x35\x2A\x2B\x2B\x2D\x28\x25\x26\x26\x27\x27\x27\x27\x26\x24\x28"
+ "\x25\x25\x24\x27\x29\x26\x28\x28\x26"
+ "\x51\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x57\x57\x56\x51\x49\x3D"
+ "\x31\x2D\x2B\x2A\x29\x2F\x31\x35\x3A"
+ "\x3D\x3B\x39\x37\x33\x2E\x29\x2A\x2B\x37\x46\x47\x28\x25\x27\x27\x28\x26\x25\x26\x28\x26\x28"
+ "\x25\x26\x24\x28\x29\x25\x28\x26\x25"
+ "\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57"
+ "\x54\x4A\x3E\x35\x2E\x28\x29\x28\x29"
+ "\x29\x2A\x2C\x2B\x2B\x2F\x35\x3E\x4E\x56\x56\x45\x2B\x25\x27\x29\x25\x26\x27\x25\x25\x26\x26"
+ "\x27\x26\x22\x25\x26\x26\x27\x25\x27"
+ "\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x56\x50\x48\x41\x3E\x38\x34"
+ "\x31\x33\x38\x3B\x41\x48\x4F\x55\x57\x57\x55\x38\x2A\x28\x28\x27\x25\x26\x26\x22\x27\x27\x27"
+ "\x26\x25\x25\x26\x26\x27\x27\x27\x28"
+ "\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x56\x56\x54\x52\x51"
+ "\x4F\x4F\x52\x53\x55\x56\x57\x57\x57\x57\x49\x2F\x26\x26\x29\x26\x26\x26\x27\x27\x27\x25\x26"
+ "\x28\x26\x27\x27\x27\x27\x27\x27\x27"
+ "\x52\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x56\x56"
+ "\x57\x56\x56\x56\x56\x56\x56\x57\x51\x43\x30\x2A\x24\x24\x25\x25\x27\x28\x25\x26\x26\x27\x28"
+ "\x25\x25\x27\x27\x26\x27\x27\x29\x26"
+ "\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x56\x56"
+ "\x57\x57\x56\x56\x56\x56\x52\x48\x38\x2C\x27\x27\x25\x25\x25\x25\x25\x27\x26\x26\x25\x26\x28"
+ "\x27\x27\x26\x25\x26\x28\x27\x25\x26"
+ "\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x56\x57\x57\x57\x57\x57\x57"
+ "\x58\x57\x57\x57\x52\x43\x35\x2E\x27\x28\x29\x27\x23\x25\x26\x27\x27\x27\x27\x28\x26\x25\x26"
+ "\x24\x25\x25\x25\x25\x26\x28\x27\x27"
+ "\x53\x54\x54\x55\x55\x54\x54\x55\x56\x56\x56\x57\x56\x56\x56\x57\x56\x56\x57\x57\x56\x57\x57"
+ "\x57\x56\x56\x56\x58\x56\x56\x56\x54"
+ "\x51\x4E\x46\x39\x2D\x2B\x29\x28\x27\x27\x28\x28\x26\x26\x26\x28\x2A\x28\x28\x28\x26\x26\x26"
+ "\x26\x26\x28\x28\x28\x25\x28\x29\x25"
+ "\x53\x54\x53\x54\x55\x52\x4C\x4B\x48\x48\x4B\x4F\x51\x52\x55\x56\x56\x56\x57\x57\x56\x56\x57"
+ "\x56\x57\x56\x54\x50\x4E\x49\x43\x3F"
+ "\x37\x30\x2A\x2A\x28\x27\x28\x28\x27\x25\x26\x27\x26\x27\x26\x27\x27\x25\x27\x28\x27\x26\x26"
+ "\x27\x26\x27\x25\x26\x26\x27\x27\x26"
+ "\x54\x54\x54\x54\x44\x37\x32\x31\x2D\x2D\x2E\x32\x35\x34\x34\x38\x3C\x40\x43\x45\x44\x43\x43"
+ "\x41\x3D\x38\x36\x33\x33\x2F\x2A\x2B"
+ "\x27\x29\x28\x26\x29\x27\x27\x27\x29\x27\x27\x28\x26\x25\x26\x28\x27\x25\x25\x27\x28\x26\x26"
+ "\x25\x26\x26\x23\x25\x28\x26\x28\x27"
+ "\x53\x53\x53\x3E\x2B\x25\x23\x25\x24\x27\x28\x28\x27\x27\x2A\x2A\x27\x27\x27\x29\x29\x28\x29"
+ "\x29\x28\x2A\x29\x28\x27\x26\x27\x29"
+ "\x26\x26\x26\x26\x27\x26\x26\x26\x28\x28\x29\x29\x27\x25\x25\x28\x27\x27\x29\x26\x29\x26\x27"
+ "\x27\x27\x27\x26\x26\x25\x25\x26\x26"
+ "\x53\x54\x49\x30\x26\x24\x23\x25\x25\x28\x28\x26\x25\x27\x27\x25\x26\x28\x28\x26\x27\x28\x27"
+ "\x26\x27\x26\x26\x27\x25\x24\x25\x27"
+ "\x25\x26\x27\x27\x29\x27\x26\x26\x27\x2A\x29\x27\x25\x24\x25\x28\x27\x26\x27\x28\x29\x26\x27"
+ "\x26\x26\x28\x28\x26\x26\x26\x26\x26"
+ "\x55\x54\x40\x29\x26\x24\x24\x27\x25\x26\x27\x29\x26\x24\x26\x26\x27\x29\x29\x25\x26\x28\x28"
+ "\x26\x28\x27\x28\x27\x25\x28\x27\x27"
+ "\x27\x27\x25\x25\x26\x27\x27\x26\x25\x28\x26\x24\x27\x25\x26\x27\x27\x27\x27\x28\x24\x27\x27"
+ "\x28\x26\x26\x29\x26\x25\x26\x26\x26"
+ "\x54\x52\x3A\x29\x25\x24\x26\x26\x26\x25\x26\x26\x27\x27\x27\x28\x29\x2A\x28\x29\x27\x26\x27"
+ "\x27\x27\x27\x28\x29\x28\x27\x27\x28"
+ "\x27\x28\x25\x23\x27\x26\x28\x26\x23\x25\x26\x26\x28\x26\x26\x25\x26\x27\x27\x27\x24\x24\x26"
+ "\x27\x27\x27\x28\x28\x27\x27\x26\x27"
+ "\x55\x52\x38\x29\x24\x26\x28\x26\x25\x25\x27\x27\x26\x26\x27\x26\x29\x27\x27\x27\x27\x25\x25"
+ "\x27\x27\x26\x27\x28\x28\x27\x28\x28"
+ "\x27\x27\x24\x25\x28\x27\x27\x27\x26\x27\x28\x27\x27\x25\x26\x26\x28\x27\x28\x27\x26\x25\x25"
+ "\x26\x25\x26\x26\x26\x27\x28\x26\x24"
+ "\x55\x51\x38\x28\x24\x26\x25\x25\x26\x25\x25\x27\x29\x28\x27\x24\x27\x28\x28\x26\x23\x25\x27"
+ "\x27\x25\x28\x27\x26\x29\x28\x27\x2A"
+ "\x28\x28\x26\x27\x26\x27\x27\x27\x28\x26\x26\x24\x23\x25\x25\x27\x27\x26\x27\x29\x27\x27\x27"
+ "\x29\x25\x28\x26\x24\x26\x28\x28\x24"
+ "\x56\x53\x39\x28\x28\x28\x28\x27\x28\x27\x27\x27\x2A\x2A\x29\x29\x27\x27\x27\x26\x25\x26\x27"
+ "\x28\x28\x26\x27\x28\x26\x27\x28\x27"
+ "\x26\x25\x26\x26\x24\x26\x26\x27\x25\x25\x23\x25\x27\x26\x28\x27\x27\x27\x26\x26\x29\x27\x27"
+ "\x27\x28\x26\x26\x25\x26\x25\x26\x26"
+ "\x56\x55\x3B\x27\x26\x24\x26\x27\x24\x26\x27\x28\x25\x27\x27\x27\x29\x27\x26\x26\x27\x26\x27"
+ "\x27\x28\x28\x28\x28\x25\x28\x27\x26"
+ "\x26\x25\x26\x27\x26\x25\x27\x28\x27\x25\x25\x26\x28\x26\x26\x28\x26\x26\x26\x27\x26\x26\x27"
+ "\x26\x27\x26\x28\x27\x27\x27\x26\x25"
+ "\x56\x56\x43\x2C\x25\x25\x27\x29\x27\x27\x28\x28\x26\x25\x27\x23\x23\x27\x26\x28\x27\x26\x27"
+ "\x27\x28\x26\x27\x28\x26\x2A\x29\x27"
+ "\x29\x29\x29\x27\x28\x28\x29\x27\x28\x27\x26\x26\x25\x27\x28\x28\x26\x27\x25\x25\x25\x27\x29"
+ "\x26\x25\x28\x28\x25\x28\x27\x27\x26"
+ "\x55\x56\x4B\x2E\x25\x28\x29\x28\x25\x25\x27\x28\x25\x25\x27\x27\x23\x24\x25\x26\x26\x26\x26"
+ "\x24\x25\x28\x28\x27\x25\x27\x27\x24"
+ "\x26\x29\x26\x23\x27\x28\x27\x28\x26\x26\x27\x27\x26\x26\x27\x25\x28\x27\x27\x26\x29\x28\x27"
+ "\x24\x29\x28\x27\x27\x26\x28\x28\x25"
+ "\x55\x56\x51\x32\x27\x28\x27\x27\x26\x29\x26\x26\x26\x26\x28\x27\x26\x28\x28\x29\x25\x27\x29"
+ "\x26\x26\x27\x29\x27\x26\x28\x27\x25"
+ "\x25\x25\x26\x27\x25\x27\x28\x26\x26\x26\x28\x2A\x29\x25\x24\x24\x26\x28\x28\x28\x26\x26\x26"
+ "\x25\x26\x24\x26\x27\x26\x27\x28\x27"
+ "\x56\x56\x55\x39\x29\x29\x28\x29\x28\x26\x26\x27\x26\x26\x25\x27\x25\x25\x26\x25\x26\x27\x28"
+ "\x26\x27\x26\x28\x27\x26\x27\x27\x26"
+ "\x26\x27\x27\x27\x27\x28\x28\x26\x29\x28\x26\x29\x29\x28\x27\x28\x27\x29\x27\x26\x25\x26\x25"
+ "\x26\x24\x25\x26\x27\x26\x25\x26\x28"
+ "\x57\x57\x57\x4A\x29\x27\x27\x27\x27\x28\x29\x29\x27\x29\x25\x25\x25\x25\x27\x29\x26\x26\x27"
+ "\x26\x28\x28\x27\x25\x29\x29\x27\x27"
+ "\x25\x26\x26\x24\x27\x27\x26\x26\x26\x28\x27\x27\x27\x26\x27\x27\x27\x28\x27\x26\x27\x26\x26"
+ "\x2A\x28\x26\x26\x25\x28\x26\x27\x26"
+ "\x57\x57\x57\x55\x32\x28\x25\x24\x28\x28\x27\x27\x27\x27\x27\x26\x26\x27\x28\x26\x25\x27\x29"
+ "\x27\x27\x28\x28\x24\x29\x27\x26\x26"
+ "\x25\x28\x26\x25\x29\x26\x26\x26\x26\x29\x28\x25\x25\x25\x27\x28\x26\x26\x26\x28\x28\x25\x27"
+ "\x28\x28\x28\x28\x24\x26\x26\x25\x25"
+ "\x57\x57\x56\x56\x43\x2B\x29\x28\x2A\x29\x29\x2A\x28\x27\x28\x27\x27\x27\x27\x29\x26\x2A\x28"
+ "\x24\x26\x28\x28\x25\x24\x26\x26\x26"
+ "\x26\x28\x28\x27\x28\x27\x25\x25\x26\x27\x27\x23\x24\x25\x25\x26\x28\x27\x27\x27\x26\x26\x28"
+ "\x25\x27\x27\x28\x26\x27\x27\x26\x25"
+ "\x56\x57\x56\x56\x54\x49\x47\x44\x42\x40\x3F\x3D\x37\x39\x38\x36\x37\x32\x30\x2F\x2A\x2C\x2B"
+ "\x29\x2B\x28\x28\x26\x26\x26\x27\x28"
+ "\x27\x25\x26\x26\x24\x25\x25\x26\x29\x27\x28\x27\x28\x26\x27\x26\x26\x28\x27\x27\x27\x26\x27"
+ "\x25\x28\x26\x29\x27\x27\x26\x27\x28"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x55\x54\x53\x4F\x4C\x48\x44\x41\x3D"
+ "\x3B\x36\x30\x2C\x2B\x2B\x2A\x2A\x2A"
+ "\x28\x27\x26\x27\x28\x26\x26\x27\x28\x28\x28\x26\x25\x25\x27\x27\x27\x25\x26\x26\x28\x27\x28"
+ "\x28\x28\x28\x27\x27\x27\x28\x29\x27"
+ "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x57\x56\x56\x54"
+ "\x53\x51\x50\x4D\x49\x43\x3D\x34\x2E"
+ "\x2D\x2A\x26\x25\x28\x27\x28\x26\x28\x29\x27\x25\x26\x27\x27\x27\x24\x26\x28\x26\x27\x26\x25"
+ "\x29\x28\x29\x27\x26\x27\x27\x26\x26"
+ "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x58\x56\x57\x56"
+ "\x4B\x42\x3A\x36\x2F\x2C\x29\x28\x27\x27\x24\x23\x26\x27\x26\x28\x27\x27\x26\x25\x25\x24\x25"
+ "\x27\x27\x27\x28\x28\x29\x25\x22\x26"
+ "\x57\x57\x57\x57\x56\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x57\x57\x57\x57"
+ "\x56\x57\x56\x52\x4C\x46\x41\x3C\x34\x2C\x28\x27\x27\x28\x26\x27\x28\x26\x27\x26\x25\x25\x26"
+ "\x27\x28\x26\x25\x26\x25\x27\x28\x26"
+ "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x57\x57\x57\x57\x57\x57\x57"
+ "\x56\x57\x57\x56\x56\x57\x55\x53\x50\x4B\x41\x35\x2B\x29\x28\x26\x26\x27\x26\x26\x27\x28\x29"
+ "\x29\x2B\x28\x27\x28\x29\x28\x28\x28"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57"
+ "\x57\x56\x57\x57\x56\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x52\x46\x38\x31\x2C\x2A\x27\x26\x25\x27\x27"
+ "\x26\x25\x24\x27\x28\x23\x26\x28\x27"
+ "\x56\x55\x55\x56\x58\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x55\x4F\x46\x3F\x34\x2B\x29\x2B\x28"
+ "\x26\x25\x28\x28\x27\x24\x26\x28\x25"
+ "\x39\x38\x37\x37\x39\x3B\x3F\x40\x44\x47\x49\x4A\x4C\x50\x53\x56\x57\x57\x57\x58\x57\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x58\x56\x57\x57\x57\x56\x54\x50\x4B\x3F\x31\x29"
+ "\x27\x26\x26\x26\x27\x23\x29\x27\x27"
+ "\x2A\x29\x26\x2A\x28\x27\x26\x28\x27\x2A\x29\x29\x2D\x2D\x2C\x2F\x35\x3C\x41\x47\x4B\x4E\x50"
+ "\x52\x54\x56\x57\x57\x57\x57\x57\x57"
+ "\x56\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x57\x57\x56\x56\x54\x4A"
+ "\x3D\x32\x2E\x27\x24\x28\x26\x26\x25"
+ "\x26\x25\x24\x24\x27\x26\x26\x25\x26\x24\x28\x2A\x26\x26\x26\x2A\x27\x29\x29\x2B\x2B\x31\x34"
+ "\x38\x3C\x43\x4A\x4E\x54\x56\x57\x57"
+ "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x56\x56\x56\x56\x56\x56\x56\x57\x56\x56\x56\x57\x57\x57"
+ "\x56\x4C\x41\x35\x2D\x2B\x28\x27\x27"
+ "\x27\x26\x28\x28\x25\x25\x25\x26\x26\x25\x29\x2A\x28\x28\x27\x26\x26\x27\x25\x26\x27\x29\x28"
+ "\x29\x2A\x2A\x2F\x34\x38\x3C\x46\x4D"
+ "\x53\x57\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57"
+ "\x57\x56\x55\x51\x48\x38\x2C\x28\x27"
+ "\x27\x25\x25\x27\x26\x26\x28\x26\x27\x26\x25\x25\x26\x27\x27\x25\x26\x25\x25\x26\x27\x27\x27"
+ "\x27\x29\x27\x28\x2B\x29\x28\x29\x2B"
+ "\x2D\x34\x3F\x4A\x51\x55\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x56\x57\x57\x56\x57\x57\x57"
+ "\x57\x56\x56\x56\x56\x56\x4E\x3F\x33"
+ "\x25\x26\x27\x28\x27\x27\x27\x25\x27\x27\x25\x27\x25\x27\x27\x27\x27\x29\x28\x28\x25\x28\x28"
+ "\x26\x27\x26\x28\x27\x26\x28\x27\x27"
+ "\x28\x28\x2A\x2C\x35\x3E\x46\x4E\x55\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57"
+ "\x57\x57\x57\x56\x57\x57\x57\x56\x51";
+
+static const BYTE TEST_64X64_BLUE_PLANE_RLE[3724] =
+ "\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27\xF0"
+ "\x27\x25\x24\x26\x28\x26\x27\x28\x28"
+ "\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\xB0"
+ "\x26\x27\x27\x26\x27\x27\x27\x26\x25"
+ "\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A\x3E\x3C\x30"
+ "\x20\x12\x0A\x08\x06\x04\x06\x02\x01"
+ "\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00\x00\x00\x03\x01\x00\x02\x04\xF0\x02\x04\x00\x01"
+ "\x01\x03\x00\x00\x00\x03\x00\x01\x00"
+ "\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00\x02\x06\x0A"
+ "\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C"
+ "\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00\x01\x07\x05\x02\x04\x00\x00\x02\x02\x08\x06\x02"
+ "\xF0\x01\x09\x03\x04\x00\x04\x03\x08"
+ "\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E\x2A\x16\x06"
+ "\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32"
+ "\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04\xF0\x00\x00\x05\x03\x03\x01\x02\x00\x01\x03\x00"
+ "\x04\x00\x04\x04\xE0\x02\x02\x00\x04"
+ "\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04\x96\x02\x14\x22\x2A\x34\x20\x02\x02\x00\xF0\x01"
+ "\x02\x00\x00\x00\x0A\x14\x20\x2A\x32"
+ "\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02\x04\x04\x03\x09\x05\x01\x01\x03\x02\x05\xF0\x07"
+ "\x03\x04\x03\x03\x03\x01\x00\x04\x02"
+ "\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01\x00\x00\x02\x0A\x08\x00\x01\x00\xF0\x03\x01\x00"
+ "\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10"
+ "\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06\x02\x00\x00\x02\x01\x00\x03\x01\xA0\x00\x02\x02"
+ "\x01\x08\x0A\x00\x01\x02\x0A\x04\x78"
+ "\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01\x01\x00\xF0\x02\x0A\x22\x34\x34\x22\x0E\x02\x06"
+ "\x08\x02\x03\x01\x03\x01\xF3\x04\x00"
+ "\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03\x07\x03\x04\x23\x01\x00\x28\x01\x00\xF0\x02\x00"
+ "\x00\x00\x01\x02\x00\x01\x09\x15\x23"
+ "\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00\x00\x02\x02\x06\x04\x06\x02\x01\x03\xF0\x01\x02"
+ "\x01\x00\x01\x04\x02\x04\x02\x01\x03"
+ "\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01\x02\x02\x00\x00\x01\x0B\x27\x43\x43\x33\x29\x33"
+ "\x45\x41\xE3\x1D\x05\x08\x24\x38\x18"
+ "\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04\x00\x07\x01\x00\x07\x00\x01\x00\x01\x00\x00\x04"
+ "\x7D\x02\x00\x00\x00\x02\x02\x00\xF0"
+ "\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03\x09\x01\x00"
+ "\x00\x01\x04\x00\x00\x06\x02\x02\xA0"
+ "\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30\x32\x36\x20"
+ "\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02"
+ "\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03\xB0\x00\x06\x06\x04\x04\x07\x00\x02\x02\x04\x02"
+ "\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38"
+ "\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23\x17\x00\x02\x2A\x0A\x00\x03\x05\x01\x01\x02\x01"
+ "\x01\xE0\x01\x05\x00\x00\x01\x03\x02"
+ "\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23\x0B\x20\x10"
+ "\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04"
+ "\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02\x02\x05\x05\x04\x02\x06\x01\x41\xF0\x02\x00\x09"
+ "\x07\x08\x14\x02\x00\x05\x21\x25\x21"
+ "\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16\x08\x02\x02\x04\x01\x02\x02\x06\xE0\x00\x01\x04"
+ "\x04\x01\x04\x04\x02\x00\x03\x02\x00"
+ "\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A\x01\x01\x04\x12\x0A\x0C\x12\x10\x02\x12\xF0\x02"
+ "\x05\x07\x00\x02\x14\x16\x04\x03\x03"
+ "\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00\x03\x05\x05\x01\x03\x05\x00\x04\x31\xF0\x02\x00"
+ "\x00\x06\x04\x00\x03\x02\x00\x02\x1E"
+ "\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02\x00\x08\x12\x02\x04\x02\x02\x00\x07\xF0\x05\x02"
+ "\x02\x03\x01\x01\x06\x04\x02\x02\x00"
+ "\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C\x12\xF0\x0A"
+ "\x02\x00\x13\x00\x04\x06\x00\x00\x02"
+ "\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01\x00\x00\x02\x00\x00\x00\x06\x06\x00\x01\x20\x00"
+ "\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C"
+ "\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15\x1D\x02\xF0\x12\x08\x00\x01\x01\x12\x14\x01\x01"
+ "\x01\x09\x03\x01\x00\x00\xC0\x02\x01"
+ "\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06\xF0\x01\x00\x01\x00\x00\x00\x01\x13\x27\x0D\x00"
+ "\x00\x01\x01\x00\x83\x00\x0A\x22\x01"
+ "\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E\x02\x00\x02\x02\x02\x16\x04\x02\x00\xF0\x02\x01"
+ "\x00\x00\x00\x01\x00\x02\x02\x04\x00"
+ "\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06\x1B\x35\x21\x03\x00\x00\x02\x02\x02\x1A\x1E\x07"
+ "\x21\xC4\x31\x21\x11\x0D\x15\x29\x33"
+ "\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02\x06\x02\x04\x00\x04\x00\x03\x01\x03\x01\x60\x01"
+ "\x01\x05\x01\x00\x00\x0C\xF0\x02\x12"
+ "\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29\x07\x18\x40"
+ "\x18\x00\xF0\x10\x0C\x01\x02\x02\x05"
+ "\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04\x08\x02\x00\x01\x02\x58\x00\x02\x02\x02\x00\xF0"
+ "\x04\x14\x08\x01\x15\x33\x33\x1B\x07"
+ "\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12\x1C\x2C\x30\x16\x00\x01\x01\x00\x01\x00\x01\xF0"
+ "\x0F\x07\x00\x01\x00\x04\x04\x00\x06"
+ "\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04\x02\x01\x24\x02\x00\x26\x02\x00\xF0\x14\x22\x06"
+ "\x02\x09\x21\x31\x35\x1D\x03\x00\x04"
+ "\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00\x00\x03\x01"
+ "\x01\x05\x00\x00\x04\x80\x01\x04\x00"
+ "\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47\x2B\x15\x07"
+ "\x00\x0A\xF0\x10\x12\x08\x06\x02\x01"
+ "\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03\x05\x05\x01\x04\x02\x04\x04\x00\x03\x02\x05\x01"
+ "\x01\x02\x50\x04\x02\x00\x02\x02\x0E"
+ "\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01\x02\x02\x00"
+ "\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E"
+ "\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01\x01\x05\x02\x02\x01\x02\x02\x03\x3E\x02\x02\x00"
+ "\xF0\x02\x0E\x20\x32\x38\x28\x10\x06"
+ "\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39\x3D\x37\x33\x2B\x15\x02\x18\x36\x34\x00\x00\x02"
+ "\xF0\x02\x02\x01\x03\x01\x04\x04\x00"
+ "\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44\x00\x00\x02\x00\x25\x02\x00\x23\x02\x00\xF0\x0A"
+ "\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F"
+ "\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28\x46\x3E\x20\x03\x06\x00\x00\x04\x05\x00\xE0\x04"
+ "\x01\x05\x00\x03\x04\x00\x03\x05\x05"
+ "\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0\x02\x02\x00\x00\x06\x1A\x30\x36\x34\x32\x2A\x20"
+ "\x16\x10\x12\xF0\x18\x20\x2C\x32\x34"
+ "\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0\x00\x01\x05\x04\x02\x02\x01\x01\x06\x02\x00\x02"
+ "\x00\x04\x02\x2F\x02\x00\x08\xF0\x02"
+ "\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28\x1C\x10\x04\xF0\x00\x00\x17\x11\x07\x03\x02\x01"
+ "\x02\x00\x02\x0A\x00\x03\x01\x90\x04"
+ "\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02\x00\x09\xF0\x02\x02\x06\x08\x0A\x10\x0E\x08\x06"
+ "\x02\x00\x01\x00\x0B\x27\xF0\x31\x09"
+ "\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04\x05\x01\x70\x00\x00\x01\x00\x00\x04\x01\x9F\x00"
+ "\x00\x02\x00\x02\x00\x00\x02\x00\x09"
+ "\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02\x02\x00\x00\x03\x01\x02\x00\x01\xB0\x01\x00\x04"
+ "\x04\x01\x03\x00\x02\x00\x07\x00\x29"
+ "\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x02\x07\x25\x39\x33\x21\x07\x04"
+ "\x00\x03\xF0\x00\x02\x04\x04\x00\x02"
+ "\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03\x02\x04\x02\x93\x00\x02\x00\x02\x00\x01\x01\x01"
+ "\x00\xA4\x01\x01\x01\x00\x01\x01\x00"
+ "\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0\x01\x01\x02"
+ "\x06\x02\x00\x02\x06\x02\x02\x00\x00"
+ "\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04\x03\xF0\x00\x00\x01\x01\x00\x03\x0F\x13\x1B\x1B"
+ "\x15\x0F\x09\x07\x01\x24\x01\x00\xF0"
+ "\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00\x00\x03\x03"
+ "\x01\x00\x02\x00\x01\x05\x05\x01\xD0"
+ "\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01\x03\x02\xF0\x02\x00\x02\x00\x21\x35\x33\x33\x35"
+ "\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33"
+ "\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07\x02\x00\x01"
+ "\x01\x04\x04\x02\x02\x00\xF0\x03\x00"
+ "\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01\x03\x01\x40\x04\x01\x02\x02\xF0\x01\x01\x01\x2B"
+ "\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B"
+ "\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33\x2F\x29\x1B\x19\x15\x17\x11\x83\x05\x03\x01\x05"
+ "\x03\x00\x03\x01\xF0\x02\x04\x02\x02"
+ "\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04\x80\x02\x02\x06\x02\x05\x01\x03\x01\xF0\x00\x02"
+ "\x13\x1B\x09\x01\x00\x00\x02\x02\x00"
+ "\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03\x00\x03\x05\x01\x07\x05\x01\x03\xF0\x01\x00\x02"
+ "\x02\x04\x02\x00\x00\x01\x04\x00\x03"
+ "\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00\x00\x01\x01\x02\x04\x00\x02\x02\x20\x00\x00\xF0"
+ "\x04\x00\x11\x0D\x00\x00\x02\x04\x00"
+ "\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01\x00\x02\x00\x02\x02\x04\x00\x00\x08\x04\x00\x04"
+ "\x02\xF0\x03\x03\x05\x00\x02\x00\x03"
+ "\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00\x00\x09\x02\x00\x04\x00\x03\x02\x00\x01\x00\x00"
+ "\x00\xF0\x01\x03\x0B\x00\x01\x00\x04"
+ "\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04\x02\x01\x08\x02\x03\x01\x02\x01\x00\x00\x04\x06"
+ "\x01\xF0\x00\x02\x00\x02\x00\x03\x02"
+ "\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00\x03\x01\x00\x00\x01\x00\x05\x01\x01\x02\x02\x01"
+ "\x04\x40\x04\x02\x00\x02\xF0\x02\x00"
+ "\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01\x01\x00\xF0\x03\x00\x05\x01\x03\x00\x01\x03\x00"
+ "\x00\x01\x01\x01\x00\x00\xF0\x02\x00"
+ "\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04\x02\x01\xF0\x01\x00\x02\x04\x00\x02\x00\x04\x02"
+ "\x01\x01\x03\x01\x03\x03\x40\x00\x02"
+ "\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01\x02\x00\x03\x00\x06\x04\x00\xF0\x03\x03\x02\x02"
+ "\x01\x07\x00\x04\x00\x03\x04\x00\x03"
+ "\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00\x00\x00\x04\x01\x03\x05\x07\xF0\x00\x01\x02\x01"
+ "\x01\x01\x04\x02\x04\x04\x06\x00\x04"
+ "\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02\x00\x08\x04\x06\x04\xF0\x00\x02\x04\x04\x0A\x00"
+ "\x01\x01\x00\x04\x02\x00\x02\x06\x03"
+ "\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01\x03\x01\x01\x00\x05\xF0\x01\x05\x02\x08\x02\x06"
+ "\x00\x00\x02\x01\x05\x04\x00\x00\x03"
+ "\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00\x04\x04\x01\x03\x07\x03\x00\x07\x01\x00\x02\x09"
+ "\x05\x03\xF0\x03\x04\x00\x01\x00\x04"
+ "\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01\x01\x00\x00\x00\x02\x04\x01\x02\x02\x04\x00\x04"
+ "\x02\x02\xF0\x00\x03\x02\x01\x01\x00"
+ "\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02\x04\x00\x01\xF0\x00\x02\x10\x0A\x01\x02\x02\x04"
+ "\x06\x02\x02\x00\x02\x03\x00\x64\x07"
+ "\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04\x04\x02\x06\x08\x06\x00\x04\x06\x04\x01\xF0\x02"
+ "\x04\x02\x00\x05\x02\x04\x00\x00\x02"
+ "\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03\x02\x00\x02\x02\xF0\x01\x00\x10\x04\x00\x06\x04"
+ "\x01\x03\x03\x01\x00\x01\x00\x00\xF0"
+ "\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04\x02\x01\x01\x05\xF0\x03\x05\x05\x00\x05\x07\x01"
+ "\x00\x03\x02\x03\x01\x02\x02\x02\xF0"
+ "\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03\x08\x00\x01\x04\x40\x03\x02\x02\x01\xF0\x00\x00"
+ "\x0C\x08\x04\x00\x03\x01\x02\x08\x01"
+ "\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01\x02\x06\x04\x02\x01\x02\x00\x02\x02\xF0\x00\x02"
+ "\x01\x07\x00\x08\x03\x01\x02\x03\x00"
+ "\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02\x04\x05\x03\x01\x02\x05\x07\x01\x00\x40\x00\x01"
+ "\x00\x04\xF0\x02\x00\x08\x0E\x04\x02"
+ "\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00\x01\x05\x03\x07\x02\x00\x01\x00\x02\x01\x01\x00"
+ "\x00\x01\xF0\x00\x02\x02\x04\x02\x00"
+ "\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06\x06\x08\x02\x02\x01\x03\x01\x00\x01\x02\x03\x02"
+ "\x00\x00\x40\x00\x03\x03\x02\xF0\x02"
+ "\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04\x02\x06\x00\xF0\x03\x00\x00\x02\x08\x00\x01\x01"
+ "\x00\x02\x04\x01\x03\x06\x04\xF0\x00"
+ "\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00\x02\x03\x03\xF0\x03\x00\x01\x00\x01\x00\x00\x04"
+ "\x00\x02\x08\x08\x02\x00\x03\x40\x04"
+ "\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02\x00\x03\x03\x00\x03\x04\x02\x02\x04\xF0\x02\x05"
+ "\x01\x02\x04\x02\x01\x00\x02\x01\x00"
+ "\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00\x00\x00\x02\x02\x03\x03\x01\x00\x02\xF0\x01\x03"
+ "\x01\x04\x02\x01\x02\x03\x00\x04\x04"
+ "\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02\x22\x06\x08\x08\x04\x02\x04\x06\x02\x00\x02\xF0"
+ "\x02\x02\x00\x01\x06\x02\x06\x01\x05"
+ "\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00\x04\x04\x01\x02\x01\x01\x00\x03\x01\x03\x01\xF0"
+ "\x00\x03\x03\x04\x02\x02\x01\x03\x02"
+ "\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38\x30\x2E\x2C"
+ "\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12"
+ "\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00\xF0\x02\x04\x02\x05\x03\x01\x07\x03\x00\x02\x06"
+ "\x00\x02\x08\x08\xF0\x02\x04\x00\x03"
+ "\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02\x40\x00\x01\x02\x06\xF0\x02\x00\x02\x02\x06\x1C"
+ "\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A"
+ "\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02\x04\x00\x02"
+ "\x08\x02\x02\x02\x01\x02\x00\x01\x05"
+ "\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02\x06\x00\x04\x03\x00\x40\x00\x04\x04\x01\x66\x01"
+ "\x01\x00\x00\x01\x00\xF0\x01\x02\x04"
+ "\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06\x00\x03\x00"
+ "\x02\x04\x01\x00\x02\xF0\x01\x01\x02"
+ "\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02\x00\x70\x02\x00\x01\x00\x01\x05\x01\x31\xF0\x01"
+ "\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A"
+ "\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02\x04\x01\x03\x05\x03\x00\x00\x01\x02\x06\xF0\x02"
+ "\x03\x01\x03\x03\x00\x03\x01\x03\x02"
+ "\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01\x01\x00\x2E\x02\x00\xF0\x01\x02\x00\x02\x16\x2A"
+ "\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08"
+ "\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00\x02\x02\x00\x02\x01\x60\x05\x03\x07\x04\x0C\x00"
+ "\x04\x2A\x02\x00\x53\x01\x00\x02\x02"
+ "\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04\x01\x03\xF0"
+ "\x02\x01\x00\x04\x06\x06\x04\x06\x04"
+ "\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00\x00\x01\x01\x00\x37\x01\x01\x00\x23\x01\x00\xF0"
+ "\x02\x00\x00\x02\x02\x00\x04\x08\x0E"
+ "\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02\x00\x03\x01\x03\x05\x0B\x07\x00\x00\x0B\x03\x20"
+ "\x00\x01\x64\x01\x03\x03\x01\x02\x00"
+ "\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34\x2A\x1A\x0A"
+ "\x08\x08\x02\x90\x00\x00\x08\x02\x01"
+ "\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07\x23\x01\x02"
+ "\x1F\x00\x13\x01\xF0\x00\x01\x01\x02"
+ "\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02\x90\x02\x02\x03\x03\x00\x01\x06\x01\x04\xF0\x1D"
+ "\x1D\x21\x19\x21\x27\x31\x2F\x39\x39"
+ "\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21\x17\x11\x0D\x09\x05\x01\x00\x29\x01\x00\xF0\x03"
+ "\x00\x01\x01\x01\x00\x06\x0E\x16\x2E"
+ "\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01\x03\xF0\x07\x07\x03\x0B\x01\x01\x00\x05\x01\x0B"
+ "\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25"
+ "\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05\x01\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00"
+ "\x02\x00\xF0\x02\x01\x01\x00\x02\x06"
+ "\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04\xF0\x02\x02\x08\x08\x03\x01\x01\x02\x00\x02\x02"
+ "\x00\x04\x04\x02\xF0\x07\x01\x03\x07"
+ "\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33\xE4\x21\x13\x07\x00\x01\x01\x00\x00\x02\x02\x00"
+ "\x00\x00\x02\xF0\x00\x02\x02\x02\x00"
+ "\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10\x00\xF0\x00\x01\x05\x01\x02\x02\x06\x00\x02\x02"
+ "\x07\x09\x03\x01\x00\xF0\x01\x00\x03"
+ "\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B\x03\x01\x00"
+ "\x00\x02\x02\x00\x01\x84\x01\x00\x00"
+ "\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44\x2E\x18\xF0\x03\x02\x04\x02\x02\x02\x01\x01\x00"
+ "\x02\x00\x04\x01\x00\x00\xF0\x04\x02"
+ "\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07\x05\x00\xF0\x03\x07\x09\x17\x29\x3B\x37\x2D\x1F"
+ "\x11\x03\x00\x00\x00\x02\x24\x02\x00"
+ "\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E\x3C";
+
+/**
+ * [MS-RDPEGDI] Test Bitmap 32x32 (16bpp)
+ */
+
+static const BYTE TEST_RLE_UNCOMPRESSED_BITMAP_16BPP[2048] =
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42"
+ "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42"
+ "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF"
+ "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6"
+ "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42"
+ "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84"
+ "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42"
+ "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF";
+
+static const UINT32 colorFormatList[] = {
+ PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_RGB24, PIXEL_FORMAT_BGR24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32,
+ PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRX32
+
+};
+static const UINT32 colorFormatCount = sizeof(colorFormatList) / sizeof(colorFormatList[0]);
+
+static BOOL CompareBitmap(const BYTE* srcA, UINT32 srcAFormat, const BYTE* srcB, UINT32 srcBFormat,
+ UINT32 width, UINT32 height)
+{
+ double maxDiff = NAN;
+ const UINT32 srcABits = FreeRDPGetBitsPerPixel(srcAFormat);
+ const UINT32 srcBBits = FreeRDPGetBitsPerPixel(srcBFormat);
+ UINT32 diff = fabs((double)srcABits - srcBBits);
+
+ /* No support for 8bpp */
+ if ((srcABits < 15) || (srcBBits < 15))
+ return FALSE;
+
+ /* Compare with folliwing granularity:
+ * 32 --> 24 bpp: Each color channel has 8bpp, no difference expected
+ * 24/32 --> 15/16 bpp: 8bit per channel against 5/6bit per channel, +/- 3bit
+ * 16 --> 15bpp: 5/6bit per channel against 5 bit per channel, +/- 1bit
+ */
+ switch (diff)
+ {
+ case 1:
+ maxDiff = 2 * 2.0;
+ break;
+
+ case 8:
+ case 9:
+ case 16:
+ case 17:
+ maxDiff = 2 * 8.0;
+ break;
+
+ default:
+ maxDiff = 0.0;
+ break;
+ }
+
+ if ((srcABits == 32) || (srcBBits == 32))
+ {
+ if (diff == 8)
+ maxDiff = 0.0;
+ }
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ const BYTE* lineA = &srcA[width * FreeRDPGetBytesPerPixel(srcAFormat) * y];
+ const BYTE* lineB = &srcB[width * FreeRDPGetBytesPerPixel(srcBFormat) * y];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ BYTE sR = 0;
+ BYTE sG = 0;
+ BYTE sB = 0;
+ BYTE sA = 0;
+ BYTE dR = 0;
+ BYTE dG = 0;
+ BYTE dB = 0;
+ BYTE dA = 0;
+ const BYTE* a = &lineA[x * FreeRDPGetBytesPerPixel(srcAFormat)];
+ const BYTE* b = &lineB[x * FreeRDPGetBytesPerPixel(srcBFormat)];
+ UINT32 colorA = FreeRDPReadColor(a, srcAFormat);
+ UINT32 colorB = FreeRDPReadColor(b, srcBFormat);
+ FreeRDPSplitColor(colorA, srcAFormat, &sR, &sG, &sB, &sA, NULL);
+ FreeRDPSplitColor(colorB, srcBFormat, &dR, &dG, &dB, &dA, NULL);
+
+ if (fabs((double)sR - dR) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sG - dG) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sB - dB) > maxDiff)
+ return FALSE;
+
+ if (fabs((double)sA - dA) > maxDiff)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL RunTestPlanar(BITMAP_PLANAR_CONTEXT* planar, const BYTE* srcBitmap,
+ const UINT32 srcFormat, const UINT32 dstFormat, const UINT32 width,
+ const UINT32 height)
+{
+ BOOL rc = FALSE;
+ UINT32 dstSize = 0;
+ BYTE* compressedBitmap = freerdp_bitmap_compress_planar(planar, srcBitmap, srcFormat, width,
+ height, 0, NULL, &dstSize);
+ BYTE* decompressedBitmap = (BYTE*)calloc(height, width * FreeRDPGetBytesPerPixel(dstFormat));
+ printf("%s [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat),
+ FreeRDPGetColorFormatName(dstFormat));
+ fflush(stdout);
+ printf("TODO: Skipping unfinished test!");
+ rc = TRUE;
+ goto fail;
+
+ if (!compressedBitmap || !decompressedBitmap)
+ goto fail;
+
+ if (!planar_decompress(planar, compressedBitmap, dstSize, width, height, decompressedBitmap,
+ dstFormat, 0, 0, 0, width, height, FALSE))
+ {
+ printf("failed to decompress experimental bitmap 01: width: %" PRIu32 " height: %" PRIu32
+ "\n",
+ width, height);
+ goto fail;
+ }
+
+ if (!CompareBitmap(decompressedBitmap, dstFormat, srcBitmap, srcFormat, width, height))
+ {
+ printf("FAIL");
+ goto fail;
+ }
+
+ printf("SUCCESS");
+ rc = TRUE;
+fail:
+ free(compressedBitmap);
+ free(decompressedBitmap);
+ printf("\n");
+ fflush(stdout);
+ return rc;
+}
+
+static BOOL RunTestPlanarSingleColor(BITMAP_PLANAR_CONTEXT* planar, const UINT32 srcFormat,
+ const UINT32 dstFormat)
+{
+ BOOL rc = FALSE;
+ printf("%s: [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat),
+ FreeRDPGetColorFormatName(dstFormat));
+ fflush(stdout);
+
+ for (UINT32 j = 0; j < 32; j += 8)
+ {
+ for (UINT32 i = 4; i < 32; i += 8)
+ {
+ UINT32 compressedSize = 0;
+ const UINT32 fill = j;
+ const UINT32 color =
+ FreeRDPGetColor(srcFormat, (fill >> 8) & 0xF, (fill >> 4) & 0xF, (fill)&0xF, 0xFF);
+ const UINT32 width = i;
+ const UINT32 height = i;
+ BOOL failed = TRUE;
+ const UINT32 srcSize = width * height * FreeRDPGetBytesPerPixel(srcFormat);
+ const UINT32 dstSize = width * height * FreeRDPGetBytesPerPixel(dstFormat);
+ BYTE* compressedBitmap = NULL;
+ BYTE* bmp = malloc(srcSize);
+ BYTE* decompressedBitmap = (BYTE*)malloc(dstSize);
+
+ if (!bmp || !decompressedBitmap)
+ goto fail_loop;
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ BYTE* line = &bmp[width * FreeRDPGetBytesPerPixel(srcFormat) * y];
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ FreeRDPWriteColor(line, srcFormat, color);
+ line += FreeRDPGetBytesPerPixel(srcFormat);
+ }
+ }
+
+ compressedBitmap = freerdp_bitmap_compress_planar(planar, bmp, srcFormat, width, height,
+ 0, NULL, &compressedSize);
+
+ if (!compressedBitmap)
+ goto fail_loop;
+
+ if (!planar_decompress(planar, compressedBitmap, compressedSize, width, height,
+ decompressedBitmap, dstFormat, 0, 0, 0, width, height, FALSE))
+ goto fail_loop;
+
+ if (!CompareBitmap(decompressedBitmap, dstFormat, bmp, srcFormat, width, height))
+ goto fail_loop;
+
+ failed = FALSE;
+ fail_loop:
+ free(bmp);
+ free(compressedBitmap);
+ free(decompressedBitmap);
+
+ if (failed)
+ {
+ printf("FAIL");
+ goto fail;
+ }
+ }
+ }
+
+ printf("SUCCESS");
+ rc = TRUE;
+fail:
+ printf("\n");
+ fflush(stdout);
+ return rc;
+}
+
+static BOOL TestPlanar(const UINT32 format)
+{
+ BOOL rc = FALSE;
+ const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE;
+ BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64);
+
+ if (!planar)
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_01, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_02, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_03, PIXEL_FORMAT_RGBX32, format, 64,
+ 64))
+ goto fail;
+
+ if (!RunTestPlanar(planar, TEST_RLE_UNCOMPRESSED_BITMAP_16BPP, PIXEL_FORMAT_RGB16, format, 32,
+ 32))
+ goto fail;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ if (!RunTestPlanarSingleColor(planar, format, colorFormatList[x]))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ freerdp_bitmap_planar_context_free(planar);
+ return rc;
+}
+
+static UINT32 prand(UINT32 max)
+{
+ UINT32 tmp = 0;
+ if (max <= 1)
+ return 1;
+ winpr_RAND(&tmp, sizeof(tmp));
+ return tmp % (max - 1) + 1;
+}
+
+static BOOL FuzzPlanar(void)
+{
+ BOOL rc = FALSE;
+ const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE;
+ BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64);
+
+ if (!planar)
+ goto fail;
+
+ for (UINT32 x = 0; x < 100; x++)
+ {
+ BYTE data[0x10000] = { 0 };
+ size_t dataSize = 0x10000;
+ BYTE dstData[0x10000] = { 0 };
+
+ UINT32 DstFormat = 0;
+ UINT32 nDstStep = 0;
+ UINT32 nXDst = 0;
+ UINT32 nYDst = 0;
+ UINT32 nDstWidth = 0;
+ UINT32 nDstHeight = 0;
+ BOOL invalid = TRUE;
+ do
+ {
+ switch (prand(17) - 1)
+ {
+ case 0:
+ DstFormat = PIXEL_FORMAT_RGB8;
+ break;
+ case 1:
+ DstFormat = PIXEL_FORMAT_BGR15;
+ break;
+ case 2:
+ DstFormat = PIXEL_FORMAT_RGB15;
+ break;
+ case 3:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case 4:
+ DstFormat = PIXEL_FORMAT_ABGR15;
+ break;
+ case 5:
+ DstFormat = PIXEL_FORMAT_BGR16;
+ break;
+ case 6:
+ DstFormat = PIXEL_FORMAT_RGB16;
+ break;
+ case 7:
+ DstFormat = PIXEL_FORMAT_BGR24;
+ break;
+ case 8:
+ DstFormat = PIXEL_FORMAT_RGB24;
+ break;
+ case 9:
+ DstFormat = PIXEL_FORMAT_BGRA32;
+ break;
+ case 10:
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ break;
+ case 11:
+ DstFormat = PIXEL_FORMAT_RGBA32;
+ break;
+ case 12:
+ DstFormat = PIXEL_FORMAT_RGBX32;
+ break;
+ case 13:
+ DstFormat = PIXEL_FORMAT_ABGR32;
+ break;
+ case 14:
+ DstFormat = PIXEL_FORMAT_XBGR32;
+ break;
+ case 15:
+ DstFormat = PIXEL_FORMAT_ARGB32;
+ break;
+ case 16:
+ DstFormat = PIXEL_FORMAT_XRGB32;
+ break;
+ default:
+ break;
+ }
+ nDstStep = prand(sizeof(dstData));
+ nXDst = prand(nDstStep);
+ nYDst = prand(sizeof(dstData) / nDstStep);
+ nDstWidth = prand(nDstStep / FreeRDPGetBytesPerPixel(DstFormat));
+ nDstHeight = prand(sizeof(dstData) / nDstStep);
+ invalid = nXDst * FreeRDPGetBytesPerPixel(DstFormat) + (nYDst + nDstHeight) * nDstStep >
+ sizeof(dstData);
+ } while (invalid);
+ printf("DstFormat=%s, nXDst=%" PRIu32 ", nYDst=%" PRIu32 ", nDstWidth=%" PRIu32
+ ", nDstHeight=%" PRIu32 ", nDstStep=%" PRIu32 ", total size=%" PRIuz "\n",
+ FreeRDPGetColorFormatName(DstFormat), nXDst, nYDst, nDstWidth, nDstHeight, nDstStep,
+ sizeof(dstData));
+ freerdp_planar_switch_bgr(planar, rand() % 2);
+ planar_decompress(planar, data, dataSize, prand(4096), prand(4096), dstData, DstFormat,
+ nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, prand(2));
+ }
+
+ rc = TRUE;
+fail:
+ freerdp_bitmap_planar_context_free(planar);
+ return rc;
+}
+
+int TestFreeRDPCodecPlanar(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!FuzzPlanar())
+ return -2;
+
+ for (UINT32 x = 0; x < colorFormatCount; x++)
+ {
+ if (!TestPlanar(colorFormatList[x]))
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c
new file mode 100644
index 0000000..fb36595
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c
@@ -0,0 +1,1146 @@
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/image.h>
+#include <winpr/print.h>
+#include <winpr/wlog.h>
+#include <winpr/sysinfo.h>
+#include <winpr/file.h>
+
+#include <freerdp/codec/region.h>
+
+#include <freerdp/codec/progressive.h>
+
+#include "../progressive.h"
+
+/**
+ * Microsoft Progressive Codec Sample Data
+ * (available under NDA only)
+ *
+ * <enc/dec>_<image#>_<quarter#>_<prog%>_<bitmap>.<type>
+ *
+ * readme.pdf
+ *
+ * bitmaps/
+ * 1920by1080-SampleImage1.bmp
+ * 1920by1080-SampleImage2.bmp
+ * 1920by1080-SampleImage3.bmp
+ *
+ * compress/
+ * enc_0_0_025_sampleimage1.bin
+ * enc_0_0_050_sampleimage1.bin
+ * enc_0_0_075_sampleimage1.bin
+ * enc_0_0_100_sampleimage1.bin
+ * enc_0_1_025_sampleimage1.bin
+ * enc_0_1_050_sampleimage1.bin
+ * enc_0_1_075_sampleimage1.bin
+ * enc_0_1_100_sampleimage1.bin
+ * enc_0_2_025_sampleimage1.bin
+ * enc_0_2_050_sampleimage1.bin
+ * enc_0_2_075_sampleimage1.bin
+ * enc_0_2_100_sampleimage1.bin
+ * enc_0_3_025_sampleimage1.bin
+ * enc_0_3_050_sampleimage1.bin
+ * enc_0_3_075_sampleimage1.bin
+ * enc_0_3_100_sampleimage1.bin
+ * enc_1_0_025_sampleimage2.bin
+ * enc_1_0_050_sampleimage2.bin
+ * enc_1_0_075_sampleimage2.bin
+ * enc_1_0_100_sampleimage2.bin
+ * enc_1_1_025_sampleimage2.bin
+ * enc_1_1_050_sampleimage2.bin
+ * enc_1_1_075_sampleimage2.bin
+ * enc_1_1_100_sampleimage2.bin
+ * enc_1_2_025_sampleimage2.bin
+ * enc_1_2_050_sampleimage2.bin
+ * enc_1_2_075_sampleimage2.bin
+ * enc_1_2_100_sampleimage2.bin
+ * enc_1_3_025_sampleimage2.bin
+ * enc_1_3_050_sampleimage2.bin
+ * enc_1_3_075_sampleimage2.bin
+ * enc_1_3_100_sampleimage2.bin
+ * enc_2_0_025_sampleimage3.bin
+ * enc_2_0_050_sampleimage3.bin
+ * enc_2_0_075_sampleimage3.bin
+ * enc_2_0_100_sampleimage3.bin
+ * enc_2_1_025_sampleimage3.bin
+ * enc_2_1_050_sampleimage3.bin
+ * enc_2_1_075_sampleimage3.bin
+ * enc_2_1_100_sampleimage3.bin
+ * enc_2_2_025_sampleimage3.bin
+ * enc_2_2_050_sampleimage3.bin
+ * enc_2_2_075_sampleimage3.bin
+ * enc_2_2_100_sampleimage3.bin
+ * enc_2_3_025_sampleimage3.bin
+ * enc_2_3_050_sampleimage3.bin
+ * enc_2_3_075_sampleimage3.bin
+ * enc_2_3_100_sampleimage3.bin
+ *
+ * decompress/
+ * dec_0_0_025_sampleimage1.bmp
+ * dec_0_0_050_sampleimage1.bmp
+ * dec_0_0_075_sampleimage1.bmp
+ * dec_0_0_100_sampleimage1.bmp
+ * dec_0_1_025_sampleimage1.bmp
+ * dec_0_1_050_sampleimage1.bmp
+ * dec_0_1_075_sampleimage1.bmp
+ * dec_0_1_100_sampleimage1.bmp
+ * dec_0_2_025_sampleimage1.bmp
+ * dec_0_2_050_sampleimage1.bmp
+ * dec_0_2_075_sampleimage1.bmp
+ * dec_0_2_100_sampleimage1.bmp
+ * dec_0_3_025_sampleimage1.bmp
+ * dec_0_3_050_sampleimage1.bmp
+ * dec_0_3_075_sampleimage1.bmp
+ * dec_0_3_100_sampleimage1.bmp
+ * dec_1_0_025_sampleimage2.bmp
+ * dec_1_0_050_sampleimage2.bmp
+ * dec_1_0_075_sampleimage2.bmp
+ * dec_1_0_100_sampleimage2.bmp
+ * dec_1_1_025_sampleimage2.bmp
+ * dec_1_1_050_sampleimage2.bmp
+ * dec_1_1_075_sampleimage2.bmp
+ * dec_1_1_100_sampleimage2.bmp
+ * dec_1_2_025_sampleimage2.bmp
+ * dec_1_2_050_sampleimage2.bmp
+ * dec_1_2_075_sampleimage2.bmp
+ * dec_1_2_100_sampleimage2.bmp
+ * dec_1_3_025_sampleimage2.bmp
+ * dec_1_3_050_sampleimage2.bmp
+ * dec_1_3_075_sampleimage2.bmp
+ * dec_1_3_100_sampleimage2.bmp
+ * dec_2_0_025_sampleimage3.bmp
+ * dec_2_0_050_sampleimage3.bmp
+ * dec_2_0_075_sampleimage3.bmp
+ * dec_2_0_100_sampleimage3.bmp
+ * dec_2_1_025_sampleimage3.bmp
+ * dec_2_1_050_sampleimage3.bmp
+ * dec_2_1_075_sampleimage3.bmp
+ * dec_2_1_100_sampleimage3.bmp
+ * dec_2_2_025_sampleimage3.bmp
+ * dec_2_2_050_sampleimage3.bmp
+ * dec_2_2_075_sampleimage3.bmp
+ * dec_2_2_100_sampleimage3.bmp
+ * dec_2_3_025_sampleimage3.bmp
+ * dec_2_3_050_sampleimage3.bmp
+ * dec_2_3_075_sampleimage3.bmp
+ * dec_2_3_100_sampleimage3.bmp
+ */
+
+typedef struct
+{
+ BYTE* buffer;
+ size_t size;
+} EGFX_SAMPLE_FILE;
+
+static int g_Width = 0;
+static int g_Height = 0;
+static int g_DstStep = 0;
+static BYTE* g_DstData = NULL;
+
+static void sample_file_free(EGFX_SAMPLE_FILE* file)
+{
+ if (!file)
+ return;
+
+ free(file->buffer);
+ file->buffer = NULL;
+ file->size = 0;
+}
+
+static void test_fill_image_alpha_channel(BYTE* data, int width, int height, BYTE value)
+{
+ UINT32* pixel = NULL;
+
+ for (int i = 0; i < height; i++)
+ {
+ for (int j = 0; j < width; j++)
+ {
+ pixel = (UINT32*)&data[((i * width) + j) * 4];
+ *pixel = ((*pixel & 0x00FFFFFF) | (value << 24));
+ }
+ }
+}
+
+static void* test_image_memset32(UINT32* ptr, UINT32 fill, size_t length)
+{
+ while (length--)
+ {
+ *ptr++ = fill;
+ }
+
+ return (void*)ptr;
+}
+
+static int test_image_fill(BYTE* pDstData, int nDstStep, int nXDst, int nYDst, int nWidth,
+ int nHeight, UINT32 color)
+{
+ UINT32* pDstPixel = NULL;
+
+ if (nDstStep < 0)
+ nDstStep = 4 * nWidth;
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ pDstPixel = (UINT32*)&pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+ test_image_memset32(pDstPixel, color, nWidth);
+ }
+
+ return 1;
+}
+
+static int test_image_fill_quarter(BYTE* pDstData, int nDstStep, int nWidth, int nHeight,
+ UINT32 color, int quarter)
+{
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+
+ switch (quarter)
+ {
+ case 0:
+ x = 0;
+ y = 0;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 1:
+ x = nWidth / 2;
+ y = nHeight / 2;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 2:
+ x = 0;
+ y = nHeight / 2;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+
+ case 3:
+ x = nWidth / 2;
+ y = 0;
+ width = nWidth / 2;
+ height = nHeight / 2;
+ break;
+ }
+
+ test_image_fill(pDstData, nDstStep, x, y, width, height, 0xFF000000);
+ return 1;
+}
+
+static int test_image_fill_unused_quarters(BYTE* pDstData, int nDstStep, int nWidth, int nHeight,
+ UINT32 color, int quarter)
+{
+ return 1;
+
+ if (quarter == 0)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 1)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 2)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3);
+ }
+ else if (quarter == 3)
+ {
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1);
+ test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2);
+ }
+
+ return 1;
+}
+
+static BYTE* test_progressive_load_file(const char* path, const char* file, size_t* size)
+{
+ char* filename = GetCombinedPath(path, file);
+
+ if (!filename)
+ return NULL;
+
+ FILE* fp = winpr_fopen(filename, "r");
+ free(filename);
+
+ if (!fp)
+ return NULL;
+
+ _fseeki64(fp, 0, SEEK_END);
+ const INT64 pos = _ftelli64(fp);
+ WINPR_ASSERT(pos >= 0);
+ WINPR_ASSERT(pos <= SIZE_MAX);
+ *size = (size_t)pos;
+ _fseeki64(fp, 0, SEEK_SET);
+ BYTE* buffer = (BYTE*)malloc(*size);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return NULL;
+ }
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ {
+ free(buffer);
+ fclose(fp);
+ return NULL;
+ }
+
+ fclose(fp);
+ return buffer;
+}
+
+static int test_progressive_load_files(char* ms_sample_path, EGFX_SAMPLE_FILE files[3][4][4])
+{
+ int imageNo = 0;
+ int quarterNo = 0;
+ int passNo = 0;
+ /* image 1 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_0_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_1_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_2_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_025_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_050_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_075_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_0_3_100_sampleimage1.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 2 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_0_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_1_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_2_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_025_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_050_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_075_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_1_3_100_sampleimage2.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 3 */
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_0_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_1_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_2_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_025_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_050_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_075_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+ passNo = (passNo + 1) % 4;
+ files[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_file(ms_sample_path, "compress/enc_2_3_100_sampleimage3.bin",
+ &(files[imageNo][quarterNo][passNo].size));
+
+ /* check if all test data has been loaded */
+
+ for (imageNo = 0; imageNo < 3; imageNo++)
+ {
+ for (quarterNo = 0; quarterNo < 4; quarterNo++)
+ {
+ for (passNo = 0; passNo < 4; passNo++)
+ {
+ if (!files[imageNo][quarterNo][passNo].buffer)
+ return -1;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static BYTE* test_progressive_load_bitmap(char* path, char* file, size_t* size, int quarter)
+{
+ int status = 0;
+ BYTE* buffer = NULL;
+ wImage* image = NULL;
+ char* filename = NULL;
+ filename = GetCombinedPath(path, file);
+
+ if (!filename)
+ return NULL;
+
+ image = winpr_image_new();
+
+ if (!image)
+ return NULL;
+
+ status = winpr_image_read(image, filename);
+
+ if (status < 0)
+ return NULL;
+
+ buffer = image->data;
+ *size = image->height * image->scanline;
+ test_fill_image_alpha_channel(image->data, image->width, image->height, 0xFF);
+ test_image_fill_unused_quarters(image->data, image->scanline, image->width, image->height,
+ quarter, 0xFF000000);
+ winpr_image_free(image, FALSE);
+ free(filename);
+ return buffer;
+}
+
+static int test_progressive_load_bitmaps(char* ms_sample_path, EGFX_SAMPLE_FILE bitmaps[3][4][4])
+{
+ int imageNo = 0;
+ int quarterNo = 0;
+ int passNo = 0;
+ /* image 1 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_025_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_050_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_075_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_100_sampleimage1.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 2 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_025_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_050_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_075_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_100_sampleimage2.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ imageNo++;
+ /* image 3 */
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ quarterNo = (quarterNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_025_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_050_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_075_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+ passNo = (passNo + 1) % 4;
+ bitmaps[imageNo][quarterNo][passNo].buffer =
+ test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_100_sampleimage3.bmp",
+ &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo);
+
+ /* check if all test data has been loaded */
+
+ for (imageNo = 0; imageNo < 3; imageNo++)
+ {
+ for (quarterNo = 0; quarterNo < 4; quarterNo++)
+ {
+ for (passNo = 0; passNo < 4; passNo++)
+ {
+ if (!bitmaps[imageNo][quarterNo][passNo].buffer)
+ return -1;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static size_t test_memcmp_count(const BYTE* mem1, const BYTE* mem2, size_t size, int margin)
+{
+ size_t count = 0;
+
+ for (size_t index = 0; index < size; index++)
+ {
+ if (*mem1 != *mem2)
+ {
+ const int error = (*mem1 > *mem2) ? *mem1 - *mem2 : *mem2 - *mem1;
+
+ if (error > margin)
+ count++;
+ }
+
+ mem1++;
+ mem2++;
+ }
+
+ return count;
+}
+
+static int test_progressive_decode(PROGRESSIVE_CONTEXT* progressive, EGFX_SAMPLE_FILE files[4],
+ EGFX_SAMPLE_FILE bitmaps[4], int quarter, int count)
+{
+ int nXSrc = 0;
+ int nYSrc = 0;
+
+ RECTANGLE_16 clippingRect = { 0 };
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height;
+
+ for (int pass = 0; pass < count; pass++)
+ {
+ const int status =
+ progressive_decompress(progressive, files[pass].buffer, files[pass].size, g_DstData,
+ PIXEL_FORMAT_XRGB32, g_DstStep, 0, 0, NULL, 0, 0);
+ printf("ProgressiveDecompress: status: %d pass: %d\n", status, pass + 1);
+ PROGRESSIVE_BLOCK_REGION* region = &(progressive->region);
+
+ switch (quarter)
+ {
+ case 0:
+ clippingRect.left = 0;
+ clippingRect.top = 0;
+ clippingRect.right = g_Width / 2;
+ clippingRect.bottom = g_Height / 2;
+ break;
+
+ case 1:
+ clippingRect.left = g_Width / 2;
+ clippingRect.top = g_Height / 2;
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height;
+ break;
+
+ case 2:
+ clippingRect.left = 0;
+ clippingRect.top = g_Height / 2;
+ clippingRect.right = g_Width / 2;
+ clippingRect.bottom = g_Height;
+ break;
+
+ case 3:
+ clippingRect.left = g_Width / 2;
+ clippingRect.top = 0;
+ clippingRect.right = g_Width;
+ clippingRect.bottom = g_Height / 2;
+ break;
+ }
+
+ for (UINT16 index = 0; index < region->numTiles; index++)
+ {
+ RFX_PROGRESSIVE_TILE* tile = region->tiles[index];
+
+ const RECTANGLE_16 tileRect = { tile->x, tile->y, tile->x + tile->width,
+ tile->y + tile->height };
+ RECTANGLE_16 updateRect = { 0 };
+ rectangles_intersection(&tileRect, &clippingRect, &updateRect);
+ const UINT16 nXDst = updateRect.left;
+ const UINT16 nYDst = updateRect.top;
+ const UINT16 nWidth = updateRect.right - updateRect.left;
+ const UINT16 nHeight = updateRect.bottom - updateRect.top;
+
+ if ((nWidth <= 0) || (nHeight <= 0))
+ continue;
+
+ nXSrc = nXDst - tile->x;
+ nYSrc = nYDst - tile->y;
+ freerdp_image_copy(g_DstData, PIXEL_FORMAT_XRGB32, g_DstStep, nXDst, nYDst, nWidth,
+ nHeight, tile->data, PIXEL_FORMAT_XRGB32, 64 * 4, nXSrc, nYSrc, NULL,
+ FREERDP_FLIP_NONE);
+ }
+
+ const size_t size = bitmaps[pass].size;
+ const size_t cnt = test_memcmp_count(g_DstData, bitmaps[pass].buffer, size, 1);
+
+ if (cnt)
+ {
+ const float rate = ((float)cnt) / ((float)size) * 100.0f;
+ printf("Progressive RemoteFX decompression failure\n");
+ printf("Actual, Expected (%" PRIuz "/%" PRIuz " = %.3f%%):\n", cnt, size, rate);
+ }
+
+ // WLog_Image(progressive->log, WLOG_TRACE, g_DstData, g_Width, g_Height, 32);
+ }
+
+ return 1;
+}
+
+static int test_progressive_ms_sample(char* ms_sample_path)
+{
+ int count = 0;
+ int status = 0;
+ EGFX_SAMPLE_FILE files[3][4][4] = { 0 };
+ EGFX_SAMPLE_FILE bitmaps[3][4][4] = { 0 };
+ PROGRESSIVE_CONTEXT* progressive = NULL;
+ g_Width = 1920;
+ g_Height = 1080;
+ g_DstStep = g_Width * 4;
+ status = test_progressive_load_files(ms_sample_path, files);
+
+ if (status < 0)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+
+ return -1;
+ }
+
+ status = test_progressive_load_bitmaps(ms_sample_path, bitmaps);
+
+ if (status < 0)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+
+ return -1;
+ }
+
+ count = 4;
+ progressive = progressive_context_new(FALSE);
+ g_DstData = winpr_aligned_malloc(g_DstStep * g_Height, 16);
+ progressive_create_surface_context(progressive, 0, g_Width, g_Height);
+
+ /* image 1 */
+
+ if (1)
+ {
+ printf("\nSample Image 1\n");
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[0][0], bitmaps[0][0], 0, count);
+ test_progressive_decode(progressive, files[0][1], bitmaps[0][1], 1, count);
+ test_progressive_decode(progressive, files[0][2], bitmaps[0][2], 2, count);
+ test_progressive_decode(progressive, files[0][3], bitmaps[0][3], 3, count);
+ }
+
+ /* image 2 */
+
+ if (0)
+ {
+ printf("\nSample Image 2\n"); /* sample data is in incorrect order */
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[1][0], bitmaps[1][0], 0, count);
+ test_progressive_decode(progressive, files[1][1], bitmaps[1][1], 1, count);
+ test_progressive_decode(progressive, files[1][2], bitmaps[1][2], 2, count);
+ test_progressive_decode(progressive, files[1][3], bitmaps[1][3], 3, count);
+ }
+
+ /* image 3 */
+
+ if (0)
+ {
+ printf("\nSample Image 3\n"); /* sample data is in incorrect order */
+ test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000);
+ test_progressive_decode(progressive, files[2][0], bitmaps[2][0], 0, count);
+ test_progressive_decode(progressive, files[2][1], bitmaps[2][1], 1, count);
+ test_progressive_decode(progressive, files[2][2], bitmaps[2][2], 2, count);
+ test_progressive_decode(progressive, files[2][3], bitmaps[2][3], 3, count);
+ }
+
+ progressive_context_free(progressive);
+
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 4; k++)
+ {
+ sample_file_free(&bitmaps[i][j][k]);
+ sample_file_free(&files[i][j][k]);
+ }
+ }
+ }
+
+ winpr_aligned_free(g_DstData);
+ return 0;
+}
+
+static BOOL diff(BYTE a, BYTE b)
+{
+ BYTE big = MAX(a, b);
+ BYTE little = MIN(a, b);
+ if (big - little <= 0x25)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL colordiff(UINT32 format, UINT32 a, UINT32 b)
+{
+ BYTE ar = 0;
+ BYTE ag = 0;
+ BYTE ab = 0;
+ BYTE aa = 0;
+ BYTE br = 0;
+ BYTE bg = 0;
+ BYTE bb = 0;
+ BYTE ba = 0;
+ FreeRDPSplitColor(a, format, &ar, &ag, &ab, &aa, NULL);
+ FreeRDPSplitColor(b, format, &br, &bg, &bb, &ba, NULL);
+ if (!diff(aa, ba) || !diff(ar, br) || !diff(ag, bg) || !diff(ab, bb))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL test_encode_decode(const char* path)
+{
+ BOOL res = FALSE;
+ int rc = 0;
+ BYTE* resultData = NULL;
+ BYTE* dstData = NULL;
+ UINT32 dstSize = 0;
+ UINT32 ColorFormat = PIXEL_FORMAT_BGRX32;
+ REGION16 invalidRegion = { 0 };
+ wImage* image = winpr_image_new();
+ wImage* dstImage = winpr_image_new();
+ char* name = GetCombinedPath(path, "progressive.bmp");
+ PROGRESSIVE_CONTEXT* progressiveEnc = progressive_context_new(TRUE);
+ PROGRESSIVE_CONTEXT* progressiveDec = progressive_context_new(FALSE);
+
+ region16_init(&invalidRegion);
+ if (!image || !dstImage || !name || !progressiveEnc || !progressiveDec)
+ goto fail;
+
+ rc = winpr_image_read(image, name);
+ if (rc <= 0)
+ goto fail;
+
+ resultData = calloc(image->scanline, image->height);
+ if (!resultData)
+ goto fail;
+
+ // Progressive encode
+ rc = progressive_compress(progressiveEnc, image->data, image->scanline * image->height,
+ ColorFormat, image->width, image->height, image->scanline, NULL,
+ &dstData, &dstSize);
+
+ // Progressive decode
+ rc = progressive_create_surface_context(progressiveDec, 0, image->width, image->height);
+ if (rc <= 0)
+ goto fail;
+
+ rc = progressive_decompress(progressiveDec, dstData, dstSize, resultData, ColorFormat,
+ image->scanline, 0, 0, &invalidRegion, 0, 0);
+ if (rc < 0)
+ goto fail;
+
+ // Compare result
+ if (0) // Dump result image for manual inspection
+ {
+ *dstImage = *image;
+ dstImage->data = resultData;
+ winpr_image_write(dstImage, "/tmp/test.bmp");
+ }
+ for (UINT32 y = 0; y < image->height; y++)
+ {
+ const BYTE* orig = &image->data[y * image->scanline];
+ const BYTE* dec = &resultData[y * image->scanline];
+ for (UINT32 x = 0; x < image->width; x++)
+ {
+ const BYTE* po = &orig[x * 4];
+ const BYTE* pd = &dec[x * 4];
+
+ const DWORD a = FreeRDPReadColor(po, ColorFormat);
+ const DWORD b = FreeRDPReadColor(pd, ColorFormat);
+ if (!colordiff(ColorFormat, a, b))
+ {
+ printf("xxxxxxx [%u:%u] [%s] %08X != %08X\n", x, y,
+ FreeRDPGetColorFormatName(ColorFormat), a, b);
+ goto fail;
+ }
+ }
+ }
+ res = TRUE;
+fail:
+ region16_uninit(&invalidRegion);
+ progressive_context_free(progressiveEnc);
+ progressive_context_free(progressiveDec);
+ winpr_image_free(image, TRUE);
+ winpr_image_free(dstImage, FALSE);
+ free(resultData);
+ free(name);
+ return res;
+}
+
+int TestFreeRDPCodecProgressive(int argc, char* argv[])
+{
+ int rc = -1;
+ char* ms_sample_path = NULL;
+ char name[8192];
+ SYSTEMTIME systemTime;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetSystemTime(&systemTime);
+ sprintf_s(name, sizeof(name),
+ "EGFX_PROGRESSIVE_MS_SAMPLE-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
+ "%02" PRIu16 "%02" PRIu16 "%04" PRIu16,
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
+ systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+ ms_sample_path = _strdup(CMAKE_CURRENT_SOURCE_DIR);
+
+ if (!ms_sample_path)
+ {
+ printf("Memory allocation failed\n");
+ goto fail;
+ }
+
+ if (winpr_PathFileExists(ms_sample_path))
+ {
+ /*
+ if (test_progressive_ms_sample(ms_sample_path) < 0)
+ goto fail;
+ */
+ if (!test_encode_decode(ms_sample_path))
+ goto fail;
+ rc = 0;
+ }
+
+fail:
+ free(ms_sample_path);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c
new file mode 100644
index 0000000..be67cea
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c
@@ -0,0 +1,894 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/rfx.h>
+
+static BYTE encodeHeaderSample[] = {
+ /* as in 4.2.2 */
+ 0xc0, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0xca, 0xac, 0xcc, 0xca, 0x00, 0x01, 0xc3, 0xcc, 0x0d, 0x00,
+ 0x00, 0x00, 0x01, 0xff, 0x00, 0x40, 0x00, 0x28, 0xa8, 0xc1, 0xcc, 0x0a, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0xc2, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00
+};
+
+static BYTE encodeDataSample[] = {
+ /* FRAME_BEGIN as in 4.2.3 */
+ 0xc4, 0xcc, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+
+ /* REGION as in 4.2.3 */
+ 0xc6, 0xcc, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0xc1, 0xca, 0x01, 0x00,
+
+ /* TILESET as in 4.2.4.1 */
+ 0xc7, 0xcc, 0x3e, 0x0b, 0x00, 0x00, 0x01, 0x00, 0xc2, 0xca, 0x00, 0x00, 0x51, 0x50, 0x01, 0x40,
+ 0x01, 0x00, 0x23, 0x0b, 0x00, 0x00, 0x66, 0x66, 0x77, 0x88, 0x98, 0xc3, 0xca, 0x23, 0x0b, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x03, 0xcf, 0x03, 0x93, 0x03, 0xc0, 0x01,
+ 0x01, 0x15, 0x48, 0x99, 0xc7, 0x41, 0xa1, 0x12, 0x68, 0x11, 0xdc, 0x22, 0x29, 0x74, 0xef, 0xfd,
+ 0x20, 0x92, 0xe0, 0x4e, 0xa8, 0x69, 0x3b, 0xfd, 0x41, 0x83, 0xbf, 0x28, 0x53, 0x0c, 0x1f, 0xe2,
+ 0x54, 0x0c, 0x77, 0x7c, 0xa3, 0x05, 0x7c, 0x30, 0xd0, 0x9c, 0xe8, 0x09, 0x39, 0x1a, 0x5d, 0xff,
+ 0xe2, 0x01, 0x22, 0x13, 0x80, 0x90, 0x87, 0xd2, 0x9f, 0xfd, 0xfd, 0x50, 0x09, 0x0d, 0x24, 0xa0,
+ 0x8f, 0xab, 0xfe, 0x3c, 0x04, 0x84, 0xc6, 0x9c, 0xde, 0xf8, 0x80, 0xc3, 0x22, 0x50, 0xaf, 0x4c,
+ 0x2a, 0x7f, 0xfe, 0xe0, 0x5c, 0xa9, 0x52, 0x8a, 0x06, 0x7d, 0x3d, 0x09, 0x03, 0x65, 0xa3, 0xaf,
+ 0xd2, 0x61, 0x1f, 0x72, 0x04, 0x50, 0x8d, 0x3e, 0x16, 0x4a, 0x3f, 0xff, 0xfd, 0x41, 0x42, 0x87,
+ 0x24, 0x37, 0x06, 0x17, 0x2e, 0x56, 0x05, 0x9c, 0x1c, 0xb3, 0x84, 0x6a, 0xff, 0xfb, 0x43, 0x8b,
+ 0xa3, 0x7a, 0x32, 0x43, 0x28, 0xe1, 0x1f, 0x50, 0x54, 0xfc, 0xca, 0xa5, 0xdf, 0xff, 0x08, 0x04,
+ 0x48, 0x15, 0x61, 0xd9, 0x76, 0x43, 0xf8, 0x2a, 0x07, 0xe9, 0x65, 0xf7, 0xc6, 0x89, 0x2d, 0x40,
+ 0xa1, 0xc3, 0x35, 0x8d, 0xf5, 0xed, 0xf5, 0x91, 0xae, 0x2f, 0xcc, 0x01, 0xce, 0x03, 0x48, 0xc0,
+ 0x8d, 0x63, 0xf4, 0xfd, 0x50, 0x20, 0x2d, 0x0c, 0x9b, 0xb0, 0x8d, 0x13, 0xc0, 0x8a, 0x09, 0x52,
+ 0x1b, 0x02, 0x6e, 0x42, 0x3b, 0xd0, 0x13, 0x4e, 0x84, 0x01, 0x26, 0x88, 0x6a, 0x04, 0x84, 0x34,
+ 0x2a, 0xa5, 0x00, 0xba, 0x54, 0x48, 0x58, 0xea, 0x54, 0x02, 0xb4, 0x1d, 0xa7, 0xfa, 0x47, 0x82,
+ 0xec, 0x7a, 0x77, 0xfd, 0x00, 0x92, 0x66, 0x62, 0x04, 0xa6, 0x9b, 0xff, 0xf6, 0x80, 0xc0, 0x69,
+ 0x01, 0xc2, 0x3e, 0x90, 0x14, 0x20, 0x2f, 0xfc, 0x40, 0x96, 0x59, 0x58, 0x0c, 0xb1, 0x13, 0x68,
+ 0x20, 0x2e, 0xb5, 0xf5, 0xdf, 0xff, 0xf8, 0xfc, 0x56, 0x88, 0x60, 0x24, 0x53, 0xb5, 0x41, 0x46,
+ 0x5f, 0xf8, 0xf1, 0x7e, 0xde, 0x4a, 0x08, 0x97, 0xe0, 0x55, 0x03, 0x8f, 0xe5, 0x75, 0x61, 0x03,
+ 0xf2, 0xe1, 0x90, 0x01, 0xa2, 0x8e, 0x88, 0x04, 0x98, 0x05, 0x93, 0x6b, 0xff, 0xea, 0xc0, 0x60,
+ 0xa1, 0x88, 0x04, 0x49, 0xbf, 0xf7, 0xff, 0x8c, 0xb4, 0x59, 0x90, 0x80, 0x30, 0x64, 0x53, 0xff,
+ 0xf5, 0xc4, 0x48, 0xda, 0xda, 0xcb, 0x80, 0x38, 0x61, 0x57, 0xb2, 0xaf, 0x00, 0xe8, 0x7b, 0x46,
+ 0xe6, 0xd8, 0x02, 0x03, 0x8a, 0x06, 0x18, 0x14, 0x32, 0x83, 0xd0, 0x8a, 0xee, 0xbc, 0x81, 0xb4,
+ 0x28, 0xc4, 0x7f, 0xf9, 0xa1, 0x69, 0x00, 0x91, 0xc5, 0x51, 0xff, 0xfe, 0x3f, 0xe9, 0xf1, 0x70,
+ 0x30, 0x24, 0x10, 0xa7, 0xcb, 0x1f, 0x8a, 0x24, 0x93, 0xed, 0x83, 0x00, 0x36, 0x20, 0xd1, 0x50,
+ 0xe7, 0xd8, 0xad, 0x58, 0x20, 0x09, 0x22, 0x80, 0xd0, 0xca, 0x5d, 0x1a, 0xd7, 0xf1, 0x60, 0x75,
+ 0x2a, 0xf2, 0xd7, 0xf8, 0xc0, 0x32, 0x45, 0x86, 0x00, 0x43, 0x01, 0xfe, 0x80, 0xf7, 0x42, 0x81,
+ 0x74, 0x84, 0x4c, 0xa1, 0x60, 0x4c, 0xcb, 0x14, 0x58, 0x01, 0x4d, 0x18, 0xa1, 0xaa, 0x47, 0x0e,
+ 0x11, 0x1a, 0x40, 0x7d, 0x41, 0x02, 0xe3, 0x30, 0xcd, 0x33, 0x81, 0x34, 0x06, 0x46, 0x83, 0xa2,
+ 0x47, 0x1c, 0x04, 0xaa, 0x20, 0x12, 0xa2, 0x8b, 0x81, 0xc4, 0x9c, 0xa0, 0x2e, 0x06, 0x32, 0xf8,
+ 0x86, 0x85, 0x01, 0xe8, 0x70, 0xf9, 0x46, 0x09, 0x6a, 0xbf, 0xe0, 0xf5, 0xa4, 0xc8, 0x78, 0xe7,
+ 0xd2, 0x97, 0x0b, 0xbc, 0x3c, 0x97, 0xff, 0xd5, 0x40, 0x94, 0xb2, 0xc1, 0x18, 0x18, 0x11, 0x1f,
+ 0x43, 0xc1, 0x18, 0xc3, 0x83, 0x7f, 0x9a, 0x31, 0xc4, 0x8e, 0x70, 0x56, 0xda, 0xf6, 0x17, 0xde,
+ 0xd1, 0x02, 0x0d, 0x42, 0x21, 0x13, 0xdc, 0x3a, 0x3c, 0x40, 0x9e, 0xf4, 0x01, 0x43, 0xea, 0x0c,
+ 0x46, 0x73, 0xa2, 0x7b, 0x0c, 0x80, 0xff, 0xe4, 0xad, 0x2e, 0x09, 0xb4, 0x63, 0xb0, 0x8c, 0x54,
+ 0x59, 0xfa, 0xac, 0x76, 0x36, 0x10, 0x05, 0xf0, 0x98, 0x88, 0x83, 0x42, 0x00, 0x20, 0x71, 0xcc,
+ 0xc1, 0xa9, 0x97, 0x3e, 0x5a, 0x0d, 0x04, 0x50, 0x92, 0x23, 0x20, 0x0d, 0x0a, 0x1c, 0x57, 0xd7,
+ 0xff, 0x10, 0xf2, 0x03, 0x0f, 0x58, 0x1b, 0xa5, 0x11, 0xf8, 0xf1, 0xb4, 0x12, 0xdb, 0x1a, 0x48,
+ 0x56, 0x1f, 0xe3, 0xc7, 0x50, 0xe9, 0x16, 0xb4, 0xbc, 0xb0, 0x40, 0x93, 0xea, 0xb5, 0x5b, 0x2f,
+ 0xfc, 0x50, 0x0a, 0x6f, 0xcc, 0x25, 0xe0, 0x06, 0xab, 0x5f, 0x24, 0xfe, 0x8b, 0xcb, 0x42, 0x43,
+ 0x7e, 0x69, 0x02, 0x25, 0xc7, 0x38, 0x00, 0x6e, 0xe5, 0x80, 0xa8, 0xa4, 0x30, 0x44, 0x15, 0x8f,
+ 0xe9, 0x0c, 0xd3, 0xa6, 0xc2, 0x14, 0x34, 0x4a, 0xfe, 0x03, 0x7f, 0x06, 0xa5, 0x91, 0x02, 0x54,
+ 0xf1, 0xa1, 0xa1, 0x53, 0xbf, 0x11, 0xf2, 0x8f, 0x83, 0x67, 0x80, 0x09, 0x08, 0x12, 0x3f, 0xfd,
+ 0x44, 0x91, 0xc2, 0x83, 0x30, 0x50, 0x07, 0x02, 0x82, 0x4d, 0x31, 0x34, 0x06, 0x41, 0x79, 0x6f,
+ 0xf0, 0xcc, 0x03, 0x79, 0x00, 0x2c, 0x05, 0x24, 0xec, 0x8d, 0x29, 0x15, 0xaf, 0x44, 0xc8, 0xeb,
+ 0x4f, 0xe1, 0xfd, 0xf1, 0x41, 0x48, 0x81, 0x08, 0xaf, 0xfe, 0x51, 0x48, 0xce, 0xe7, 0xf9, 0xb6,
+ 0x0a, 0x30, 0x83, 0x11, 0xf0, 0x0c, 0x3b, 0xd2, 0xa6, 0x24, 0x24, 0xef, 0x25, 0xfa, 0x5a, 0x3e,
+ 0x92, 0x3e, 0x79, 0x0e, 0x35, 0x61, 0xc8, 0xaa, 0x1c, 0x2e, 0x9a, 0x27, 0x7f, 0xff, 0xf0, 0x7d,
+ 0x30, 0x5b, 0xbc, 0x91, 0xff, 0xfe, 0x43, 0x24, 0x28, 0x66, 0xa7, 0x70, 0x99, 0x28, 0x6e, 0x2b,
+ 0x18, 0x2b, 0xd4, 0xa1, 0x77, 0x3b, 0x96, 0x9f, 0xf7, 0xeb, 0xbe, 0x1f, 0x04, 0x34, 0x75, 0x84,
+ 0x31, 0x42, 0x4c, 0x65, 0xaa, 0x09, 0x50, 0xa0, 0xc4, 0x51, 0x31, 0xd3, 0x26, 0x3a, 0x1b, 0xf4,
+ 0x6e, 0x4a, 0x4e, 0x17, 0x25, 0x84, 0x78, 0x7d, 0x2c, 0x3f, 0x46, 0x18, 0xca, 0x5f, 0xf9, 0xe5,
+ 0x38, 0x2f, 0xd8, 0x71, 0x94, 0x94, 0xe2, 0xcc, 0xa3, 0x15, 0xb0, 0xda, 0xa9, 0xcb, 0x58, 0xe4,
+ 0x18, 0x77, 0x93, 0x8a, 0x51, 0xc6, 0x23, 0xc4, 0x4e, 0x6d, 0xd9, 0x14, 0x1e, 0x9b, 0x8d, 0xbc,
+ 0xcb, 0x9d, 0xc4, 0x18, 0x05, 0xf5, 0xa9, 0x29, 0xf8, 0x6d, 0x29, 0x38, 0xc7, 0x44, 0xe5, 0x3a,
+ 0xcd, 0xba, 0x61, 0x98, 0x4a, 0x57, 0x02, 0x96, 0x42, 0x02, 0xd9, 0x37, 0x11, 0xde, 0x2d, 0xd4,
+ 0x3f, 0xfe, 0x61, 0xe7, 0x33, 0xd7, 0x89, 0x4a, 0xdd, 0xb0, 0x34, 0x47, 0xf4, 0xdc, 0xad, 0xaa,
+ 0xc9, 0x9d, 0x7e, 0x6d, 0x4b, 0xcc, 0xdc, 0x17, 0x89, 0x57, 0xfd, 0xbb, 0x37, 0x75, 0x47, 0x5a,
+ 0xec, 0x2c, 0x6e, 0x3c, 0x15, 0x92, 0x54, 0x64, 0x2c, 0xab, 0x9e, 0xab, 0x2b, 0xdd, 0x3c, 0x66,
+ 0xa0, 0x8f, 0x47, 0x5e, 0x93, 0x1a, 0x37, 0x16, 0xf4, 0x89, 0x23, 0x00, 0x00, 0xb0, 0x33, 0x56,
+ 0xfa, 0x14, 0x1e, 0xff, 0x48, 0x7a, 0x7e, 0x0f, 0x10, 0x1f, 0xf4, 0x91, 0xc8, 0x10, 0x56, 0x84,
+ 0xff, 0x08, 0xec, 0xb4, 0xac, 0x0e, 0x0f, 0xff, 0xad, 0xc5, 0xe0, 0x1a, 0x2f, 0x82, 0x04, 0x9f,
+ 0x91, 0xc2, 0x0e, 0xfe, 0x48, 0x36, 0x79, 0x01, 0x42, 0x14, 0xff, 0xfe, 0x30, 0xf0, 0x08, 0x18,
+ 0xf1, 0x81, 0x45, 0x9a, 0x60, 0xc1, 0x79, 0xf0, 0x14, 0x12, 0x10, 0xce, 0xea, 0x31, 0x5a, 0xff,
+ 0xfc, 0x20, 0x13, 0x82, 0x2f, 0xc9, 0x02, 0x1f, 0x81, 0xcb, 0x00, 0xe1, 0x10, 0xd2, 0xb4, 0xbe,
+ 0x87, 0xff, 0xb0, 0x1e, 0x27, 0x81, 0xb7, 0x04, 0x06, 0x3c, 0xc2, 0x04, 0xf6, 0x06, 0x0e, 0x28,
+ 0xbc, 0x40, 0xbf, 0x12, 0x1e, 0x86, 0xd4, 0x6a, 0x7f, 0x18, 0x1b, 0x96, 0x85, 0x4c, 0x16, 0x80,
+ 0xdf, 0x2c, 0xa5, 0x8d, 0x86, 0xa3, 0x4a, 0x8a, 0xb4, 0x1b, 0xa1, 0x38, 0xa9, 0xd5, 0xff, 0xff,
+ 0xea, 0x06, 0x20, 0xd2, 0x95, 0x1e, 0xf4, 0x2f, 0xb2, 0x12, 0x0e, 0x61, 0x78, 0x4a, 0x17, 0x52,
+ 0x5d, 0xe4, 0x25, 0x1f, 0xfe, 0xc0, 0xb3, 0x1f, 0xff, 0xff, 0xec, 0x02, 0x82, 0x80, 0x90, 0x41,
+ 0x88, 0xde, 0x48, 0x2c, 0x42, 0x52, 0x0b, 0x2f, 0x43, 0x7e, 0x50, 0x78, 0xf2, 0x67, 0x78, 0x41,
+ 0x34, 0x3d, 0xc8, 0x0f, 0x67, 0xa1, 0xeb, 0x21, 0xfe, 0xc0, 0x1f, 0x22, 0x60, 0x41, 0x6c, 0x00,
+ 0x92, 0x4b, 0x60, 0x10, 0xd0, 0x0d, 0x01, 0x35, 0x05, 0x0e, 0x87, 0xa2, 0xa0, 0x5d, 0x1f, 0xa3,
+ 0xaf, 0x7f, 0xf1, 0xbe, 0x8f, 0xcd, 0xa5, 0x00, 0x1c, 0x10, 0x40, 0x15, 0x76, 0x81, 0x05, 0xef,
+ 0xee, 0x00, 0x60, 0x84, 0x00, 0x99, 0x40, 0x4a, 0x82, 0x17, 0xe9, 0xfc, 0xc4, 0x7f, 0xff, 0xfd,
+ 0x04, 0x80, 0x06, 0x06, 0xdc, 0xaf, 0xa7, 0x7e, 0x94, 0x75, 0x74, 0x01, 0x00, 0xe0, 0x91, 0x00,
+ 0x85, 0x7f, 0x8e, 0xd6, 0x0b, 0x20, 0x21, 0x30, 0xca, 0x62, 0x8e, 0x07, 0x04, 0xe9, 0x45, 0x40,
+ 0x5f, 0x47, 0x4a, 0x30, 0x15, 0x41, 0xcb, 0xdf, 0xff, 0xfc, 0xbf, 0xc3, 0xb4, 0x46, 0x6a, 0x01,
+ 0x40, 0xd0, 0xa7, 0x34, 0x18, 0x24, 0x1c, 0x2a, 0x45, 0xfe, 0xa8, 0x05, 0x08, 0x61, 0xfd, 0xa8,
+ 0x80, 0x71, 0x01, 0x25, 0x9c, 0xc1, 0x47, 0x17, 0x37, 0x02, 0x7a, 0x15, 0xff, 0xf3, 0x01, 0x45,
+ 0x7f, 0xd6, 0x80, 0x60, 0x83, 0x67, 0xf8, 0x9d, 0x2f, 0xf4, 0xdd, 0x8c, 0x30, 0x01, 0x51, 0x42,
+ 0xbc, 0x43, 0x7a, 0x6b, 0x9f, 0x84, 0x1e, 0x00, 0x48, 0xc1, 0xe0, 0xb7, 0xe0, 0x7e, 0x99, 0xf2,
+ 0x4a, 0xe9, 0x40, 0x02, 0x81, 0xc3, 0x00, 0x24, 0x3a, 0xc5, 0x52, 0x0f, 0x91, 0xc8, 0x68, 0x25,
+ 0x40, 0x99, 0xa4, 0x25, 0x1a, 0x04, 0xd0, 0xa2, 0x91, 0xdd, 0xeb, 0x93, 0x00, 0x21, 0x49, 0x24,
+ 0x8b, 0x40, 0x75, 0x38, 0x14, 0xa1, 0xfd, 0x3f, 0x88, 0x25, 0xbf, 0x32, 0x00, 0xe3, 0x19, 0xfc,
+ 0xb9, 0xf8, 0x6f, 0x81, 0xc0, 0x01, 0xb3, 0x93, 0x20, 0x09, 0x08, 0x25, 0x84, 0xe1, 0x34, 0xd4,
+ 0x1b, 0x48, 0x88, 0x11, 0xa0, 0x15, 0x59, 0xd7, 0x07, 0x81, 0x81, 0x3b, 0xa1, 0x40, 0x2e, 0x2f,
+ 0x48, 0x70, 0x09, 0xc4, 0x76, 0x49, 0x0f, 0x2e, 0x50, 0x2e, 0x46, 0x19, 0xa4, 0x16, 0xa2, 0x1b,
+ 0x84, 0xa2, 0x89, 0x58, 0xfc, 0x4f, 0x3f, 0x40, 0x90, 0x4c, 0xa3, 0x01, 0x32, 0x09, 0x02, 0x80,
+ 0x9c, 0x91, 0x13, 0x2c, 0xba, 0xde, 0x5d, 0x99, 0xf2, 0xff, 0xff, 0x3d, 0x5a, 0x1f, 0xa9, 0x02,
+ 0x90, 0x8f, 0xf3, 0x08, 0xbd, 0x01, 0xf8, 0xd0, 0x2a, 0x95, 0x41, 0x0c, 0x40, 0x0a, 0x20, 0xc4,
+ 0xd4, 0xcc, 0x6b, 0x0f, 0xf0, 0x80, 0xb1, 0x5d, 0x28, 0x3d, 0x08, 0xc2, 0xf8, 0x31, 0x02, 0x49,
+ 0x88, 0x14, 0x28, 0xed, 0xe8, 0x86, 0x3b, 0x00, 0x9f, 0x95, 0x06, 0x37, 0x15, 0xa4, 0x59, 0xc8,
+ 0x80, 0xb6, 0x10, 0xf0, 0xe5, 0xb8, 0x18, 0x00, 0x56, 0x1c, 0xff, 0x95, 0x21, 0x0e, 0x7f, 0x2b,
+ 0xc5, 0x08, 0x59, 0x10, 0xe1, 0x46, 0x31, 0x8d, 0xec, 0xe0, 0xa1, 0x99, 0xbb, 0x21, 0xff, 0xfe,
+ 0x30, 0x10, 0xd0, 0x05, 0xe3, 0x08, 0x50, 0xfc, 0xf3, 0x0e, 0x00, 0x8d, 0x68, 0x8e, 0x07, 0xa6,
+ 0x80, 0x34, 0x42, 0xed, 0x1f, 0x88, 0x00, 0xf0, 0x8a, 0x21, 0xae, 0xf7, 0xfb, 0x80, 0x28, 0x86,
+ 0x0f, 0xff, 0xff, 0x82, 0xea, 0x47, 0x95, 0x91, 0xe0, 0x04, 0x01, 0x44, 0x0c, 0x29, 0xff, 0x0e,
+ 0x33, 0xe8, 0xc0, 0x54, 0x04, 0x23, 0xfc, 0x81, 0x5b, 0xf0, 0x3c, 0x07, 0x10, 0x70, 0x30, 0xd8,
+ 0x21, 0x6f, 0xef, 0xde, 0x46, 0x09, 0x43, 0xfa, 0x5f, 0xff, 0x0d, 0x72, 0x30, 0xdd, 0x00, 0xdb,
+ 0xe4, 0x48, 0x24, 0x97, 0x08, 0x46, 0xb1, 0x49, 0xc4, 0x4d, 0x80, 0x12, 0x60, 0xff, 0xa4, 0xa6,
+ 0xff, 0xf6, 0x8c, 0x00, 0x40, 0x05, 0x02, 0xb4, 0x0f, 0xf0, 0x3e, 0xfc, 0x84, 0x38, 0x81, 0x94,
+ 0x8b, 0xfe, 0x49, 0xef, 0xc0, 0x10, 0x49, 0x88, 0x28, 0xa2, 0x1c, 0x2a, 0x8b, 0x64, 0xd4, 0x86,
+ 0xd7, 0xff, 0xff, 0xff, 0xeb, 0x91, 0x6b, 0x11, 0x10, 0x00, 0x69, 0x4c, 0xbf, 0xb4, 0x1c, 0xd8,
+ 0x00, 0x07, 0x16, 0x80, 0x60, 0x0a, 0x1c, 0x82, 0x42, 0x27, 0x82, 0x43, 0xc9, 0x0a, 0x64, 0x20,
+ 0x5a, 0x5f, 0x4e, 0xbf, 0x8c, 0x38, 0x82, 0x36, 0x02, 0x07, 0x72, 0x79, 0x07, 0x23, 0xb4, 0xbb,
+ 0x57, 0x5f, 0xe8, 0x04, 0xdd, 0x39, 0xe9, 0x07, 0x95, 0xbe, 0x04, 0x2b, 0xdd, 0x8e, 0x22, 0xdc,
+ 0x14, 0x2c, 0x61, 0xa3, 0xa9, 0xcd, 0x4f, 0x82, 0x5d, 0xa0, 0x44, 0xdf, 0xf4, 0x96, 0xff, 0xf5,
+ 0x2b, 0xff, 0xfe, 0x01, 0x19, 0xd2, 0xa2, 0x9e, 0x43, 0xa5, 0x7f, 0xf0, 0x4c, 0x4c, 0x2b, 0x3c,
+ 0x33, 0xe2, 0x55, 0xff, 0x04, 0x06, 0x29, 0x2c, 0x0d, 0x22, 0x5d, 0x7c, 0x93, 0xba, 0x18, 0xaf,
+ 0xf9, 0x32, 0xa6, 0xc3, 0x99, 0x46, 0x79, 0xe3, 0x06, 0xa6, 0x38, 0x8b, 0x92, 0x22, 0x4b, 0xdb,
+ 0x1b, 0x36, 0x20, 0xb0, 0x6c, 0x20, 0xce, 0x37, 0x42, 0xe1, 0x66, 0xd4, 0x49, 0x34, 0x42, 0x8b,
+ 0xfa, 0x9c, 0x12, 0x99, 0xdc, 0x06, 0x87, 0xfa, 0x46, 0xf8, 0x2f, 0x04, 0xa9, 0xd8, 0x82, 0x07,
+ 0xa6, 0x30, 0x0f, 0xc0, 0xdf, 0x35, 0xe8, 0x90, 0xf0, 0xff, 0xff, 0xa8, 0xe0, 0xd7, 0x02, 0x60,
+ 0x1a, 0xc3, 0x20, 0x28, 0xa2, 0x31, 0x29, 0x3c, 0xeb, 0x04, 0xa5, 0xdd, 0x48, 0x0e, 0x82, 0xa4,
+ 0xb6, 0x56, 0x22, 0x06, 0x57, 0xe0, 0xda, 0x10, 0x27, 0x31, 0x0e, 0x11, 0x77, 0xfe, 0x02, 0x60,
+ 0x16, 0x48, 0x81, 0x8c, 0x0d, 0x05, 0x17, 0x7f, 0xcb, 0xbb, 0x7e, 0x25, 0x2a, 0x41, 0xfd, 0x8a,
+ 0x7f, 0xc9, 0x36, 0x7c, 0xe0, 0x98, 0x7e, 0x92, 0xef, 0x7e, 0x06, 0x03, 0x13, 0x3e, 0x20, 0x3a,
+ 0xbf, 0x4c, 0xc3, 0x0f, 0x2e, 0x80, 0x74, 0xbf, 0x39, 0x3c, 0xf0, 0xa6, 0xb2, 0xe9, 0x3f, 0x41,
+ 0x55, 0x1f, 0x2c, 0xf5, 0xd2, 0x7e, 0x8c, 0xae, 0x4e, 0xaa, 0x61, 0x3c, 0xbc, 0x3f, 0xc4, 0xc7,
+ 0x36, 0xdc, 0x23, 0xc8, 0xb8, 0x52, 0xe2, 0x8a, 0x80, 0x18, 0x00, 0x00, 0xb2, 0x46, 0xa2, 0x56,
+ 0x0d, 0x12, 0x94, 0xaa, 0xbd, 0x01, 0x07, 0xff, 0xfa, 0x34, 0x0c, 0x5f, 0xf8, 0x0c, 0x12, 0x50,
+ 0xaf, 0xd6, 0xd1, 0x89, 0x40, 0xa4, 0xff, 0xe0, 0xce, 0xc4, 0x49, 0x25, 0x9d, 0xc1, 0xff, 0x7e,
+ 0x60, 0x24, 0x5d, 0xcc, 0x10, 0xc0, 0xbe, 0x5a, 0x12, 0xd3, 0xc3, 0xfe, 0x2d, 0x40, 0x7c, 0x28,
+ 0x9e, 0x71, 0x01, 0xd2, 0x6e, 0x86, 0x0b, 0xc8, 0xf2, 0x9b, 0x45, 0x08, 0x4c, 0x04, 0x52, 0x7e,
+ 0xf2, 0x7e, 0xd9, 0xcc, 0x0b, 0x1c, 0x20, 0x80, 0xae, 0xaf, 0xfe, 0xb0, 0x6d, 0x23, 0xf2, 0x41,
+ 0xe3, 0x2e, 0x20, 0x11, 0x4b, 0x74, 0x89, 0xdd, 0xff, 0xa8, 0x38, 0xa3, 0x95, 0x82, 0x15, 0xf0,
+ 0xd0, 0xd5, 0xf1, 0x92, 0x8e, 0xee, 0xc0, 0x26, 0x81, 0xe9, 0x47, 0xff, 0xee, 0x0d, 0x20, 0x34,
+ 0x31, 0x3a, 0xef, 0x40, 0xb2, 0x29, 0x47, 0x19, 0x7f, 0x04, 0x27, 0xf1, 0x90, 0x85, 0x09, 0x86,
+ 0x7d, 0x42, 0xe2, 0x54, 0x5d, 0x5f, 0xe8, 0x0e, 0xd0, 0x2c, 0xaa, 0x16, 0xbf, 0x04, 0xa7, 0xf8,
+ 0xa2, 0x46, 0x0b, 0x08, 0x7a, 0x79, 0xe9, 0x28, 0x62, 0x7c, 0x33, 0xf4, 0x0b, 0x14, 0x82, 0xfa,
+ 0x61, 0xeb, 0xc1, 0xff, 0x4c, 0xa4, 0x11, 0x7f, 0x03, 0x68, 0x44, 0xc1, 0x1f, 0x81, 0x3a, 0x6c,
+ 0x77, 0x95, 0x02, 0x2b, 0x53, 0x80, 0xe5, 0x10, 0x1e, 0x90, 0xe8, 0xfd, 0x1f, 0xa6, 0x40, 0x0b,
+ 0x13, 0xff, 0x4e, 0x4d, 0x7f, 0x52, 0xe8, 0xaf, 0x9a, 0xc1, 0x80, 0x0f, 0x0a, 0x14, 0x02, 0x3c,
+ 0xc0, 0x09, 0x13, 0xe7, 0xdc, 0xc0, 0x1a, 0x28, 0xa0, 0xe4, 0x83, 0x8e, 0x03, 0x88, 0xd5, 0xaf,
+ 0x1a, 0xbd, 0x91, 0x00, 0xb7, 0x4e, 0xba, 0xdf, 0xf8, 0xdb, 0xcc, 0x02, 0x43, 0xc4, 0x14, 0x2a,
+ 0x3f, 0xc8, 0x0d, 0x09, 0x1c, 0x44, 0xf4, 0x01, 0x3c, 0xca, 0x28, 0x56, 0x80, 0xa6, 0x85, 0x00,
+ 0xea, 0x3e, 0x8f, 0xeb, 0x9f, 0xfc, 0x6e, 0x07, 0xc4, 0xe0, 0x30, 0x78, 0xa0, 0x1e, 0x6f, 0x54,
+ 0x78, 0x51, 0xff, 0x56, 0x4a, 0x01, 0x47, 0x02, 0x4c, 0x21, 0x3b, 0xfb, 0x90, 0x0a, 0xcc, 0x1d,
+ 0xd2, 0x47, 0xff, 0xfc, 0x70, 0x18, 0x22, 0xc0, 0xb9, 0x2f, 0xe9, 0x7f, 0x91, 0xd3, 0x66, 0x2f,
+ 0x80, 0x2c, 0x24, 0xa7, 0xfa, 0x84, 0x51, 0xab, 0x6b, 0x72, 0x00, 0xab, 0x33, 0x04, 0xcf, 0x43,
+ 0xff, 0x17, 0x51, 0x84, 0x0c, 0x01, 0x50, 0x10, 0x8f, 0x90, 0x34, 0x41, 0x44, 0x84, 0x8e, 0x08,
+ 0x19, 0x04, 0x48, 0x50, 0x84, 0x38, 0x3d, 0x02, 0x52, 0xf9, 0x7c, 0xd2, 0xd0, 0x1f, 0x13, 0x42,
+ 0xa0, 0x21, 0x41, 0xc4, 0x02, 0x02, 0x3d, 0x09, 0xc8, 0xfd, 0x60, 0x7d, 0x35, 0x4f, 0x7f, 0xff,
+ 0xf9, 0x97, 0x6a, 0xd8, 0x00, 0xc3, 0x83, 0x00, 0x09, 0x50, 0x4b, 0x90, 0x8a, 0xc7, 0x94, 0x4d,
+ 0x47, 0xc1, 0x62, 0x32, 0x28, 0x24, 0x09, 0x52, 0x2e, 0x2e, 0x1c, 0x96, 0x44, 0xa0, 0x09, 0xc8,
+ 0xce, 0x64, 0xa9, 0x1c, 0x19, 0x0e, 0x52, 0x3e, 0x3e, 0x19, 0x93, 0xa0, 0x36, 0x26, 0x22, 0x08,
+ 0x9a, 0x00, 0xdd, 0x66, 0x3a, 0x93, 0xd5, 0x89, 0xd1, 0x40, 0x06, 0xd4, 0xa8, 0x22, 0x73, 0x7b,
+ 0x3d, 0x3f, 0xe3, 0x04, 0x94, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x56, 0x77, 0xac, 0xe0, 0xc4, 0x06,
+ 0x1f, 0xb8, 0xa5, 0x80, 0xfd, 0x68, 0x1c, 0x32, 0x16, 0x03, 0xde, 0x71, 0x2a, 0x3d, 0x14, 0x19,
+ 0xbe, 0xc2, 0x88, 0xd9, 0x24, 0x92, 0x5f, 0xc5, 0x90, 0x0a, 0x85, 0xc2, 0x3f, 0x87, 0x03, 0xa8,
+ 0x26, 0x17, 0xc4, 0x06, 0x86, 0x12, 0x87, 0x76, 0x0a, 0x48, 0x16, 0xed, 0x96, 0x93, 0xec, 0x1b,
+ 0x30, 0x73, 0xe8, 0x1a, 0x3f, 0xff, 0x4d, 0xce, 0x40, 0xf3, 0x0c, 0x51, 0x4b, 0x84, 0x9e, 0x67,
+ 0x2b, 0x15, 0x40, 0x1a, 0xa0, 0xfc, 0x10, 0x0f, 0xd8, 0x81, 0x35, 0x87, 0xff, 0x98, 0x0f, 0x40,
+ 0x00, 0xba, 0xc0, 0x71, 0xe2, 0x00, 0x18, 0x28, 0xb3, 0x82, 0xcc, 0x80, 0x6a, 0xa0, 0x43, 0xff,
+ 0x2d, 0xd6, 0x04, 0x8a, 0x68, 0xff, 0xff, 0xff, 0xfc, 0x1a, 0xf3, 0x1a, 0x2a, 0x06, 0xc0, 0x01,
+ 0x40, 0x0c, 0x30, 0xc1, 0xd0, 0xd7, 0x4f, 0xcb, 0x74, 0x1f, 0x07, 0xd3, 0xb4, 0x0d, 0x88, 0x98,
+ 0xea, 0xda, 0x9f, 0xce, 0x2b, 0x3c, 0x55, 0xb3, 0x40, 0x14, 0xff, 0xff, 0xff, 0xea, 0xdb, 0x9b,
+ 0x92, 0xd8, 0x68, 0x08, 0x0b, 0x41, 0x09, 0x26, 0x40, 0x8c, 0xf1, 0xb0, 0x9a, 0x98, 0xc0, 0x80,
+ 0x8b, 0xf0, 0x3d, 0xe7, 0xec, 0x19, 0x68, 0x21, 0x03, 0x29, 0x7f, 0xe1, 0x6d, 0x4c, 0x0f, 0x01,
+ 0xd1, 0x51, 0x01, 0x1a, 0x50, 0x2a, 0x59, 0x27, 0x80, 0xc1, 0x6e, 0x33, 0xf1, 0x80, 0xe1, 0x49,
+ 0x08, 0xe9, 0x17, 0xff, 0xff, 0xff, 0x80, 0x5a, 0x10, 0x10, 0x36, 0x5e, 0xca, 0xf8, 0x3a, 0x00,
+ 0x1e, 0xb0, 0x06, 0x84, 0x01, 0xf3, 0x07, 0x1b, 0x4a, 0xc0, 0x1e, 0x21, 0x43, 0x8e, 0xa5, 0x55,
+ 0x77, 0xc7, 0x65, 0x7c, 0xc2, 0xdf, 0x5e, 0x0c, 0x42, 0x20, 0xd2, 0x48, 0x61, 0xc8, 0x1c, 0x65,
+ 0xf8, 0xfe, 0x4c, 0x88, 0x71, 0x1f, 0x82, 0x50, 0x81, 0xa3, 0x54, 0x09, 0x13, 0x28, 0x52, 0xf5,
+ 0xe0, 0x82, 0xc3, 0x06, 0x7f, 0xfa, 0x2c, 0xcf, 0xf8, 0xf4, 0x7f, 0xff, 0xfd, 0x01, 0x49, 0xa4,
+ 0xb8, 0xde, 0x62, 0x84, 0xfe, 0xed, 0x65, 0x1f, 0x3c, 0x3c, 0xb2, 0x50, 0x76, 0x30, 0x5b, 0x03,
+ 0xc0, 0x08, 0xa6, 0x64, 0x90, 0xc8, 0xcd, 0x14, 0x6e, 0x69, 0x46, 0x7a, 0xc6, 0x1c, 0x87, 0xd7,
+ 0x48, 0x7b, 0x49, 0x05, 0x2d, 0x5e, 0x7f, 0xcb, 0x67, 0xf0, 0xd9, 0x0d, 0x1e, 0x9e, 0x53, 0xb7,
+ 0x64, 0xa5, 0xa5, 0x10, 0x39, 0x06, 0x11, 0x3f, 0xb1, 0xa9, 0xa6, 0xe8, 0x4d, 0x47, 0x77, 0xda,
+ 0x43, 0x76, 0x89, 0x45, 0x09, 0x70, 0xc2, 0x38, 0x0f, 0x09, 0x6f, 0xe7, 0x2d, 0x82, 0x35, 0x07,
+ 0xfe, 0x64, 0x18, 0x2e, 0xb8, 0x04, 0x42, 0x54, 0x80, 0x43, 0x12, 0x6c, 0x9a, 0x55, 0xc9, 0x0a,
+ 0xa0, 0x79, 0x47, 0x52, 0x65, 0x2a, 0xff, 0x50, 0x11, 0xc9, 0x4e, 0xfe, 0x5b, 0x30, 0xa4, 0xe8,
+ 0x30, 0x63, 0xff, 0x21, 0x12, 0x1b, 0xdc, 0x1c, 0x01, 0x41, 0x51, 0x1f, 0xff, 0xfa, 0xc3, 0xe3,
+ 0x55, 0xf1, 0x66, 0xe2, 0xd5, 0x78, 0x5e, 0xfa, 0x4d, 0xf2, 0x61, 0x01, 0x26, 0x15, 0xa9, 0xf9,
+ 0xd9, 0x32, 0x41, 0x90, 0x36, 0x4e, 0xae, 0xe3, 0x0b, 0x16, 0x56, 0x8c, 0x6e, 0x42, 0x5d, 0xd8,
+ 0x1e, 0xfe, 0x1d, 0x40, 0x3a, 0x50, 0x9f, 0x09, 0x14, 0xeb, 0x6e, 0x48, 0x7a, 0x91, 0x88, 0x7b,
+ 0x7d, 0x8f, 0x72, 0x42, 0x39, 0xb0, 0x1c, 0x65, 0x18, 0x23, 0x8b, 0x60, 0x30, 0x00,
+
+ /* FRAME_END as in 4.2.3 */
+ 0xc5, 0xcc, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00
+};
+
+static UINT32 srefImage
+ [] = { /* 4.2.4.4 Inverse Color Conversion */
+ 0x00229cdf, 0x00249de0, 0x00259fe2, 0x002ca5e8, 0x00229cdf, 0x00229ce0, 0x00239de0,
+ 0x00229ce0, 0x00229cdf, 0x00229cdf, 0x00239ce0, 0x00249ce0, 0x00249ce0, 0x00219ce3,
+ 0x001e9ce6, 0x00209ae2, 0x002299dd, 0x002199de, 0x00209adf, 0x00209ae0, 0x001f9be0,
+ 0x001e9ae0, 0x001d99e0, 0x001c98e0, 0x001b97df, 0x001e96dc, 0x002194d9, 0x001f93dd,
+ 0x001d93e0, 0x001b94dc, 0x001895d8, 0x001c92db, 0x00208fde, 0x001b91de, 0x001693df,
+ 0x001793df, 0x001992df, 0x001891df, 0x00178fdf, 0x00178edf, 0x00168dde, 0x00158cdd,
+ 0x00148cdc, 0x00128cda, 0x00118cd9, 0x00118bd9, 0x00128ada, 0x001289da, 0x001288db,
+ 0x001187da, 0x001186da, 0x001085da, 0x000f85d9, 0x000f84d9, 0x000e83d9, 0x000d82d8,
+ 0x000d82d8, 0x000d81d8, 0x000d80d7, 0x000d7fd7, 0x000d7ed6, 0x000d7ed6, 0x000d7ed6,
+ 0x000d7ed6, 0x00259fe1, 0x0027a1e2, 0x0029a2e3, 0x002ba4e6, 0x00249fe1, 0x00249fe1,
+ 0x00249fe1, 0x00249ee1, 0x00239ee1, 0x00249ee1, 0x00249ee1, 0x00259de1, 0x00259de2,
+ 0x00249de2, 0x00229de2, 0x00229ce1, 0x00229bdf, 0x00219ce0, 0x00209ce1, 0x00209ce2,
+ 0x00209ce2, 0x00209ae0, 0x002199de, 0x001f99df, 0x001d98e0, 0x001e97e0, 0x001f97e0,
+ 0x001d96df, 0x001c95de, 0x001c94e0, 0x001c94e1, 0x001d93e1, 0x001d92e0, 0x001b93de,
+ 0x001a94dc, 0x001a93de, 0x001a93e0, 0x001992e0, 0x001891df, 0x00188fdf, 0x00178edf,
+ 0x00168ede, 0x00158edd, 0x00148ddc, 0x00138ddb, 0x00138cdb, 0x00138bdb, 0x00128adb,
+ 0x001289db, 0x001288db, 0x001187db, 0x001186db, 0x001085db, 0x000f84da, 0x000e83d9,
+ 0x000e83d9, 0x000e83d9, 0x000e82d9, 0x000e81d8, 0x000e80d8, 0x000d7fd7, 0x000d7fd7,
+ 0x000d7fd7, 0x000d7fd7, 0x0027a3e3, 0x002aa4e3, 0x002ea6e3, 0x002aa4e3, 0x0026a2e3,
+ 0x0026a1e3, 0x0025a1e3, 0x0025a0e3, 0x0025a0e3, 0x0025a0e3, 0x00259fe3, 0x00269fe3,
+ 0x00269ee4, 0x00279ee1, 0x00279edf, 0x00259ee0, 0x00239ee1, 0x00219ee2, 0x00209ee4,
+ 0x00209de4, 0x00219de3, 0x00229be0, 0x002499dc, 0x002299de, 0x001f98e0, 0x001d99e4,
+ 0x001b9ae7, 0x001c98e2, 0x001c96dc, 0x001e94e3, 0x002092ea, 0x001d94e6, 0x001a96e2,
+ 0x001c96de, 0x001d95da, 0x001c94de, 0x001b94e1, 0x001a93e0, 0x001a92e0, 0x001991e0,
+ 0x001890e0, 0x001790df, 0x00178fde, 0x00168fde, 0x00158edd, 0x00148ddd, 0x00138cdc,
+ 0x00138bdc, 0x00128adc, 0x001289dc, 0x001188dc, 0x001187dd, 0x001086dd, 0x000f85db,
+ 0x000e83d9, 0x000e84da, 0x000f84da, 0x000e83da, 0x000e82d9, 0x000e81d9, 0x000e80d8,
+ 0x000e80d8, 0x000e80d8, 0x000e80d8, 0x002aa7e5, 0x002da7e4, 0x0031a8e3, 0x002ca6e3,
+ 0x0027a4e4, 0x0027a3e4, 0x0027a3e4, 0x0027a3e4, 0x0026a2e4, 0x0026a2e4, 0x0027a1e5,
+ 0x0027a0e5, 0x0027a0e6, 0x0026a0e5, 0x0025a0e4, 0x00259fe4, 0x00259ee3, 0x00239ee5,
+ 0x00229fe6, 0x00229fe5, 0x00229fe4, 0x0013a5e6, 0x001b9fe8, 0x0016a0e8, 0x0011a0e7,
+ 0x00129fef, 0x00139ef7, 0x001b99ec, 0x00179ae2, 0x00149ce4, 0x001d98e5, 0x001c97e6,
+ 0x001b96e7, 0x001c98dc, 0x001d97df, 0x001c96e1, 0x001c94e2, 0x001b94e1, 0x001b93e1,
+ 0x001a93e0, 0x001a92e0, 0x001991e0, 0x001890e0, 0x001790df, 0x00168fdf, 0x00158ede,
+ 0x00158dde, 0x00148cdd, 0x00138bdc, 0x00128add, 0x001289dd, 0x001188de, 0x001187de,
+ 0x000f85dc, 0x000d83da, 0x000f85db, 0x001086db, 0x000f84db, 0x000f83da, 0x000e82da,
+ 0x000e81da, 0x000e81da, 0x000e81da, 0x000e81da, 0x002caae7, 0x0030aae5, 0x0034abe3,
+ 0x002ea8e4, 0x0029a6e5, 0x0028a6e5, 0x0028a5e5, 0x0028a5e5, 0x0028a5e6, 0x0028a4e6,
+ 0x0028a3e7, 0x0028a2e7, 0x0028a1e8, 0x0025a2e9, 0x0023a3ea, 0x0025a0e8, 0x00279ee6,
+ 0x00259fe7, 0x0023a0e9, 0x0018a4f5, 0x000ea7ff, 0x001ba6de, 0x00558ebb, 0x006f839c,
+ 0x0089797e, 0x008d797c, 0x00917979, 0x007f7b94, 0x005687af, 0x00229bd6, 0x0004a4fd,
+ 0x00109df4, 0x001c97eb, 0x001c9ada, 0x001c98e4, 0x001c97e3, 0x001d95e2, 0x001c95e2,
+ 0x001c94e2, 0x001c94e1, 0x001b94e1, 0x001a93e1, 0x001a92e1, 0x001991e1, 0x001890e1,
+ 0x00178fe0, 0x00158edf, 0x00148dde, 0x00138cdd, 0x00128bde, 0x00128adf, 0x001289df,
+ 0x001188e0, 0x000f85dd, 0x000d83da, 0x000f85db, 0x001187dd, 0x001086dc, 0x000f84dc,
+ 0x000e83db, 0x000e81db, 0x000e81db, 0x000e81db, 0x000e81db, 0x0030abe5, 0x0036afe8,
+ 0x0034abe4, 0x002faae5, 0x002ba8e6, 0x0036aee8, 0x0026a6e8, 0x0029a7e7, 0x002ca8e7,
+ 0x002da7e6, 0x002fa5e5, 0x002ca5e7, 0x0029a4e9, 0x002ba5e5, 0x002ca5e2, 0x0010aaef,
+ 0x0013adf6, 0x0023a3f8, 0x006091a5, 0x00a6755d, 0x00ec5915, 0x00ff490c, 0x00fa5504,
+ 0x00ff590f, 0x00ff5d1b, 0x00ff6116, 0x00fa6412, 0x00ff550f, 0x00ff4b0d, 0x00fb4918,
+ 0x00f54823, 0x008e737e, 0x00269eda, 0x0006a2ff, 0x001d97e2, 0x001799ea, 0x001c97e4,
+ 0x001a98e4, 0x001898e4, 0x001a96e3, 0x001b95e3, 0x001a94e2, 0x001a93e0, 0x001992e1,
+ 0x001891e2, 0x001790e1, 0x00168fe0, 0x00158fdf, 0x00138ede, 0x00138ddf, 0x00138ce0,
+ 0x00128be0, 0x001189e0, 0x001087de, 0x000f85db, 0x00138ae0, 0x000f87dc, 0x000f86dc,
+ 0x000f85dc, 0x000f84dc, 0x000e83db, 0x000e83db, 0x000e83db, 0x000e83db, 0x0034abe2,
+ 0x003cb4ec, 0x0034ace5, 0x0031abe6, 0x002daae8, 0x0044b6eb, 0x0024a7ea, 0x0029aaea,
+ 0x002face9, 0x0032a9e6, 0x0035a7e3, 0x0030a7e6, 0x002ba8ea, 0x0025aaf0, 0x0020adf6,
+ 0x004d8ba7, 0x00b8674c, 0x00ff5510, 0x00f7650c, 0x00f86313, 0x00fa611b, 0x00f0671f,
+ 0x00fc6222, 0x00fb6926, 0x00f96f29, 0x00f67122, 0x00f3721b, 0x00f26b20, 0x00f16424,
+ 0x00ff5622, 0x00ff531f, 0x00ff4b17, 0x00ff440e, 0x00b1615b, 0x001f95e0, 0x00129bf0,
+ 0x001c9ae5, 0x00189ae6, 0x00159be7, 0x001898e6, 0x001b95e5, 0x001b95e2, 0x001995e0,
+ 0x001994e1, 0x001892e2, 0x001792e1, 0x001691e0, 0x001590df, 0x00148fdf, 0x00148fe0,
+ 0x00148fe1, 0x00128de1, 0x00108be0, 0x001189de, 0x001186dd, 0x00178fe4, 0x000e87db,
+ 0x000e87dc, 0x000f87dd, 0x000f85dc, 0x000e84dc, 0x000e84dc, 0x000e84dc, 0x000e84dc,
+ 0x0036b1eb, 0x0036b4f0, 0x002eafed, 0x002caeec, 0x002aadec, 0x0041b4ef, 0x0029abe9,
+ 0x002cabe8, 0x002fabe7, 0x0031abe6, 0x0032aae6, 0x002faae7, 0x002ca9e8, 0x0025a7eb,
+ 0x00946a5f, 0x00ff3e06, 0x00f95618, 0x00e27312, 0x00f87329, 0x00f77427, 0x00f77626,
+ 0x00f27628, 0x00f8712b, 0x00f9772e, 0x00f97e30, 0x00f77f2e, 0x00f5812b, 0x00f57b2c,
+ 0x00f5752d, 0x00fd6a2b, 0x00fb652a, 0x00f65e2c, 0x00f1572e, 0x00ff4810, 0x00ff460f,
+ 0x00817680, 0x0002a7f1, 0x002496ea, 0x00199be4, 0x001b98e4, 0x001d96e5, 0x001b96e2,
+ 0x001a96e0, 0x001995e1, 0x001794e3, 0x001793e2, 0x001692e1, 0x001691e0, 0x001590df,
+ 0x001591e1, 0x001591e3, 0x00138fe1, 0x00108ce0, 0x00128be0, 0x00158ae0, 0x00168de2,
+ 0x000f89dd, 0x000f88dd, 0x000f88dd, 0x000f86dd, 0x000f85dc, 0x000f85dc, 0x000f85dc,
+ 0x000f85dc, 0x005fc1e7, 0x0057bee8, 0x004fbbe9, 0x004ebae6, 0x004ebae3, 0x0051b6ee,
+ 0x002eaee8, 0x002eade6, 0x002fabe5, 0x002face7, 0x002eade9, 0x002eace7, 0x002daae5,
+ 0x0015b2ff, 0x00ec4310, 0x00f15016, 0x00f75d1c, 0x00f87123, 0x00f9862a, 0x00f6882d,
+ 0x00f48b31, 0x00f48532, 0x00f47f33, 0x00f78535, 0x00fa8c37, 0x00f88e39, 0x00f7903a,
+ 0x00f88b38, 0x00f98635, 0x00f87e35, 0x00f77635, 0x00f76d34, 0x00f76532, 0x00f85e31,
+ 0x00f95730, 0x00ff5125, 0x00f65237, 0x0003a5fd, 0x001e9be1, 0x001e98e3, 0x001f96e5,
+ 0x001c97e2, 0x001a97df, 0x001896e1, 0x001795e4, 0x001794e3, 0x001793e2, 0x001692e1,
+ 0x001692e0, 0x001693e2, 0x001794e4, 0x001391e2, 0x000f8ee0, 0x00148ee1, 0x00198ee3,
+ 0x00148ce1, 0x000f8bde, 0x000f8ade, 0x000f89de, 0x000f88dd, 0x000f86dd, 0x000f86dd,
+ 0x000f86dd, 0x000f86dd, 0x003cb6ee, 0x0036b4ef, 0x0030b2f0, 0x0030b1ee, 0x002fb1ec,
+ 0x0038b0ef, 0x002eaee9, 0x002faee8, 0x0031ade6, 0x002fafe8, 0x002eb1ea, 0x0031adec,
+ 0x0029afee, 0x0030aac8, 0x00ff3d05, 0x00fa501a, 0x00f96021, 0x00f87428, 0x00f7882f,
+ 0x00fa9638, 0x00f59b38, 0x00f5973b, 0x00f6923e, 0x00f89440, 0x00fa9742, 0x00fa9a44,
+ 0x00fa9d46, 0x00f99845, 0x00f89444, 0x00f98d43, 0x00fa8641, 0x00f97d3f, 0x00f9743d,
+ 0x00f77039, 0x00f56d35, 0x00ff6122, 0x00bf6c63, 0x00129eef, 0x00229ae8, 0x001c99ed,
+ 0x00179ce4, 0x001498f0, 0x001b94e1, 0x001a96e2, 0x001998e3, 0x001897e4, 0x001896e5,
+ 0x001895e4, 0x001993e2, 0x001792e1, 0x001590df, 0x001692e2, 0x001793e5, 0x001490e4,
+ 0x00128ee2, 0x00118de3, 0x00108de3, 0x00118bde, 0x001289d9, 0x000f88e2, 0x000c89dd,
+ 0x001085e0, 0x000987e4, 0x000987e4, 0x0040b5e9, 0x003bb4e9, 0x0037b2ea, 0x0037b2e9,
+ 0x0038b1e8, 0x0033b0ea, 0x002eaeeb, 0x0030afe9, 0x0033afe8, 0x0030b2ea, 0x002eb5ec,
+ 0x0034aff2, 0x0025b4f7, 0x008d7f86, 0x00f64f00, 0x00ed5c1e, 0x00fa6326, 0x00f7762d,
+ 0x00f58a35, 0x00fea242, 0x00f7ab3f, 0x00f7a843, 0x00f7a548, 0x00f9a34a, 0x00faa24c,
+ 0x00fba64f, 0x00fcaa52, 0x00f9a652, 0x00f7a252, 0x00fa9c50, 0x00fd974e, 0x00fc8d4b,
+ 0x00fb8348, 0x00f68341, 0x00f1823a, 0x00f5732c, 0x00718cac, 0x00179af0, 0x002599ef,
+ 0x002697e9, 0x00269bc6, 0x001696f1, 0x001d91e3, 0x001c96e3, 0x001b9be3, 0x001a99e6,
+ 0x001998e9, 0x001b97e7, 0x001c95e5, 0x001891df, 0x00138dda, 0x001992e2, 0x001e98ea,
+ 0x001592e6, 0x000b8de2, 0x000e8ee5, 0x00108fe9, 0x00128cdf, 0x001489d4, 0x000e88e6,
+ 0x00088cdc, 0x001184e4, 0x000488ec, 0x000488ec, 0x003eb6ea, 0x003bb5eb, 0x0038b4eb,
+ 0x0038b4eb, 0x0038b3eb, 0x0035b2eb, 0x0033b1ec, 0x0034b1eb, 0x0035b1ea, 0x0032b3e9,
+ 0x0030b5e9, 0x0034b0f0, 0x0023b6f8, 0x00c56044, 0x00f9540c, 0x00f26322, 0x00f77029,
+ 0x00f77d2f, 0x00f78b35, 0x00fba142, 0x00f6b046, 0x00fbb44f, 0x00f7b051, 0x00f9af54,
+ 0x00fbad56, 0x00fcb25a, 0x00feb75d, 0x00fab35f, 0x00f6b061, 0x00faac5d, 0x00fda95a,
+ 0x00fb9f55, 0x00f99551, 0x00f7914b, 0x00f68d45, 0x00ff7e23, 0x001ba5f0, 0x00129ef4,
+ 0x002896f1, 0x00239fb1, 0x006c9600, 0x003c9c82, 0x00179ef8, 0x00169cf4, 0x00149de3,
+ 0x00169ae5, 0x001897e7, 0x001995e6, 0x001a93e5, 0x001993e3, 0x001793e0, 0x001c98e6,
+ 0x001a95e5, 0x001692e5, 0x00138fe5, 0x00138ceb, 0x00138be3, 0x000087e4, 0x00007cf5,
+ 0x001a86d3, 0x000d8cf1, 0x00008fe2, 0x000d85ea, 0x000886f1, 0x003cb7ec, 0x003bb7ed,
+ 0x003ab6ed, 0x0039b6ed, 0x0038b5ed, 0x0037b5ed, 0x0037b4ed, 0x0037b3ed, 0x0036b3ec,
+ 0x0034b4e9, 0x0031b5e5, 0x0035b1ef, 0x0021b8fa, 0x00fd4203, 0x00fc581e, 0x00f86a26,
+ 0x00f47c2d, 0x00f78431, 0x00f98c36, 0x00f8a041, 0x00f6b54d, 0x00fec05b, 0x00f6bc5a,
+ 0x00f8ba5d, 0x00fbb861, 0x00fdbe65, 0x00ffc469, 0x00fbc16c, 0x00f5bd70, 0x00fabc6b,
+ 0x00febb66, 0x00fab160, 0x00f6a75a, 0x00f89f55, 0x00fa984f, 0x00df956f, 0x0008a6fc,
+ 0x00259ddb, 0x00159ff3, 0x004aa172, 0x0069a90d, 0x0062a406, 0x005a981b, 0x0034969b,
+ 0x000e99ff, 0x001297f2, 0x001695e4, 0x001793e5, 0x001892e5, 0x001995e6, 0x001a98e7,
+ 0x00209deb, 0x001593df, 0x001892e4, 0x001a91e9, 0x002095eb, 0x00259dd1, 0x00d0f772,
+ 0x00c1f396, 0x000083f1, 0x001782a0, 0x003c7e2f, 0x001787cc, 0x000b8ada, 0x003db9ed,
+ 0x003cb8ed, 0x003bb8ed, 0x003ab7ed, 0x0039b7ed, 0x0039b7ed, 0x0039b6ed, 0x003ab6ed,
+ 0x003ab6ed, 0x0037b4ed, 0x0034b2ec, 0x0035abf3, 0x006e96b3, 0x00ff4601, 0x00f86520,
+ 0x00f67329, 0x00f58131, 0x00f78b37, 0x00f9953e, 0x00f8a649, 0x00f8b854, 0x00fcc260,
+ 0x00f8c465, 0x00f9c36a, 0x00fac26e, 0x00fac773, 0x00facb77, 0x00fbcb7b, 0x00fccb7e,
+ 0x00fac87b, 0x00f8c578, 0x00f9bc72, 0x00fbb46d, 0x00f6b069, 0x00feaa57, 0x0094a0a5,
+ 0x0013a1f3, 0x00219df0, 0x00199eff, 0x0071c124, 0x0079b826, 0x0072b21e, 0x006aaa24,
+ 0x0067a125, 0x00649a19, 0x00419d72, 0x001f9fcb, 0x001994ff, 0x001399f1, 0x00199cf4,
+ 0x001ea0f8, 0x001b9cff, 0x001193f6, 0x001293f1, 0x001393ec, 0x000083ff, 0x0072cca0,
+ 0x00cbf982, 0x00d0ffac, 0x0079a046, 0x00337700, 0x003a7c03, 0x000d8de2, 0x000d8edb,
+ 0x003fbbee, 0x003ebaed, 0x003db9ed, 0x003cb9ed, 0x003bb8ed, 0x003bb8ed, 0x003cb9ee,
+ 0x003cb9ee, 0x003db9ef, 0x003ab4f1, 0x0037aff3, 0x0032b3fe, 0x00b48f7d, 0x00ff5907,
+ 0x00f37122, 0x00f57c2b, 0x00f68735, 0x00f7923d, 0x00f89d45, 0x00f9ac50, 0x00f9bb5a,
+ 0x00f9c465, 0x00facd71, 0x00facd76, 0x00facd7b, 0x00f7cf80, 0x00f4d286, 0x00fcd689,
+ 0x00ffd98c, 0x00fbd48b, 0x00f3cf8a, 0x00f9c885, 0x00ffc17f, 0x00f5c27d, 0x00ffbc5e,
+ 0x0048abdc, 0x001e9deb, 0x001ea2e8, 0x001da8e5, 0x0099d31c, 0x008acb22, 0x0082c427,
+ 0x007abc2c, 0x0075b429, 0x0070ad25, 0x006dab17, 0x006ba908, 0x005ea912, 0x00519f54,
+ 0x00489b6d, 0x003e9887, 0x003b9592, 0x00389880, 0x00449663, 0x00509446, 0x0083b43c,
+ 0x004f851b, 0x00afe187, 0x009fcc83, 0x00368011, 0x0043821c, 0x0032853c, 0x000492f9,
+ 0x001092dd, 0x0040bcee, 0x003fbcee, 0x003ebbee, 0x003dbaed, 0x003cbaed, 0x003cb9ed,
+ 0x003cb9ec, 0x003cb9ec, 0x003cb8ec, 0x003fb4f0, 0x0043aff5, 0x000ebbe9, 0x00ffb897,
+ 0x00f7814d, 0x00f57623, 0x00f6812e, 0x00f88c39, 0x00f89943, 0x00f8a64d, 0x00f8b257,
+ 0x00f9bd60, 0x00fac96d, 0x00fbd47b, 0x00fad681, 0x00fad788, 0x00fbd98e, 0x00fbda93,
+ 0x00fae5a1, 0x00fed692, 0x00fadea0, 0x00f9db98, 0x00fad694, 0x00fbd090, 0x00ffd285,
+ 0x00ffc778, 0x00009afd, 0x0026a8f2, 0x0020a4f8, 0x0053bea5, 0x00a4da31, 0x009dd638,
+ 0x0097d03a, 0x0091ca3d, 0x008bc539, 0x0085c035, 0x007dbe31, 0x0074bc2d, 0x0076b81c,
+ 0x0077b027, 0x0072ab25, 0x006da724, 0x006ba328, 0x0068a31f, 0x0058951a, 0x0078b745,
+ 0x00bbf181, 0x0073ad4c, 0x00417c15, 0x00508b1e, 0x0043861c, 0x00498614, 0x0017868b,
+ 0x000b90f6, 0x00168ee8, 0x0042beef, 0x0041bdee, 0x0040bcee, 0x003fbced, 0x003ebbed,
+ 0x003dbaec, 0x003db9eb, 0x003cb8ea, 0x003bb7e9, 0x0039b9f0, 0x0037bbf7, 0x0050b5dc,
+ 0x00ff9744, 0x00fec49d, 0x00f87a24, 0x00f88530, 0x00f9913d, 0x00f8a049, 0x00f7af55,
+ 0x00f8b85d, 0x00f9c065, 0x00face75, 0x00fcdb85, 0x00fbde8d, 0x00fae195, 0x00fee29b,
+ 0x00ffe2a0, 0x00fbe9a4, 0x00ffbe6b, 0x00fdde9f, 0x00ffe8a6, 0x00fbe3a3, 0x00f8dea0,
+ 0x00fdd899, 0x00b6bdab, 0x00119ff1, 0x001ea4e9, 0x001a9fff, 0x0089d465, 0x00b0e245,
+ 0x00b0e04e, 0x00acdc4e, 0x00a7d94e, 0x00a1d649, 0x009ad345, 0x0097ce3d, 0x0094c935,
+ 0x008dc534, 0x0086c133, 0x007bbc32, 0x006fb731, 0x006db330, 0x006cae2e, 0x007eba3f,
+ 0x0070a531, 0x007bb54f, 0x00579a20, 0x005c9f2b, 0x00519425, 0x0080b965, 0x00609a1d,
+ 0x000390e3, 0x00118ef2, 0x001c89f2, 0x0044c0ef, 0x0043bfef, 0x0042beee, 0x0040bdee,
+ 0x003fbcee, 0x003fbbed, 0x0040baeb, 0x003eb9ed, 0x003cb9ee, 0x0037b9eb, 0x0027bcf7,
+ 0x00949c8f, 0x00fb9637, 0x00f9bc7c, 0x00f9b585, 0x00f7994a, 0x00f69b43, 0x00f6a64e,
+ 0x00f7b259, 0x00f8bc66, 0x00fac672, 0x00fad380, 0x00fae08d, 0x00f9e698, 0x00f9eba2,
+ 0x00feeaa6, 0x00ffeaab, 0x00fcefa9, 0x00faba62, 0x00fbdc99, 0x00fff4b9, 0x00fbecb2,
+ 0x00f7e6ab, 0x00ffe5a3, 0x0064b1d1, 0x00199ff0, 0x00269fe9, 0x000499f2, 0x00e3f051,
+ 0x00d5ef58, 0x00c0e364, 0x00bde165, 0x00bae065, 0x00b5de5d, 0x00b0dc56, 0x00aad74e,
+ 0x00a3d346, 0x009bd043, 0x0093cd3f, 0x008cc93e, 0x0084c63c, 0x0081c139, 0x007dbc36,
+ 0x008bc746, 0x0089c245, 0x0063a02c, 0x0065aa2c, 0x005ea42d, 0x00509626, 0x00a4cf98,
+ 0x00d9eadd, 0x00b9ddff, 0x00389ef4, 0x00008fd4, 0x0046c1ef, 0x0044c0ef, 0x0043bfef,
+ 0x0042beef, 0x0040bdef, 0x0042bced, 0x0043baec, 0x0040baf0, 0x003dbaf4, 0x0035b8e7,
+ 0x0017bdf7, 0x00d97f50, 0x00f79147, 0x00f7a554, 0x00ffdbba, 0x00f8a24d, 0x00f3a549,
+ 0x00f5ad53, 0x00f7b55e, 0x00f9c16f, 0x00fbcc7f, 0x00f9d88a, 0x00f8e595, 0x00f8eda2,
+ 0x00f8f5ae, 0x00fff3b2, 0x00fff2b6, 0x00fef5ae, 0x00f4b659, 0x00f9db93, 0x00feffcd,
+ 0x00fbf6c1, 0x00f7edb6, 0x00fff2ac, 0x0013a4f7, 0x0016a5f0, 0x0018a5e8, 0x0056b4cd,
+ 0x00f1f271, 0x00d5ef84, 0x00cfe67b, 0x00cde77c, 0x00cbe77c, 0x00c9e672, 0x00c7e567,
+ 0x00bce15f, 0x00b1dd57, 0x00a9dc51, 0x00a0da4b, 0x009dd749, 0x009ad447, 0x0094cf43,
+ 0x008fcb3f, 0x0088c43c, 0x0082be39, 0x0072b430, 0x0063a928, 0x0059a028, 0x004e9827,
+ 0x00a0c479, 0x00fffbf7, 0x007fd3f5, 0x00038fe2, 0x000e89e2, 0x0048c3ef, 0x0046c2ef,
+ 0x0045c1f0, 0x0043c0f0, 0x0042bff0, 0x0042beee, 0x0043bdec, 0x0041bcef, 0x003fbcf2,
+ 0x002fc0fe, 0x0036bdfc, 0x00f54c00, 0x00ff8a52, 0x00faa65e, 0x00fdc48e, 0x00fbc185,
+ 0x00f5ae50, 0x00f7b65e, 0x00f9be6c, 0x00fac978, 0x00fbd485, 0x00fede98, 0x00ffe8aa,
+ 0x00fdeeae, 0x00f9f5b2, 0x00fcf6ba, 0x00fff7c2, 0x00fcf0b2, 0x00f7cc6e, 0x00fbde91,
+ 0x00fdfcca, 0x00fffbd1, 0x00fffdc8, 0x00cae4c8, 0x0016a1f2, 0x001da4ef, 0x0012a1f1,
+ 0x009fd5b9, 0x00eaf28c, 0x00dcf095, 0x00d9eb90, 0x00d9ec93, 0x00d9ec95, 0x00d6eb8c,
+ 0x00d4ea83, 0x00c9e779, 0x00bfe36f, 0x00b8e368, 0x00b1e262, 0x00afe05e, 0x00addf5a,
+ 0x00a3d952, 0x0099d449, 0x008ecb41, 0x0084c33a, 0x0075b833, 0x0066ac2c, 0x005da329,
+ 0x00559927, 0x004b9421, 0x002499b9, 0x001593fe, 0x000993d8, 0x000f90d8, 0x004ac5ef,
+ 0x0048c4f0, 0x0046c2f0, 0x0045c1f1, 0x0043c0f1, 0x0043bfef, 0x0043bfed, 0x0042beee,
+ 0x0041bdf0, 0x0038bbf0, 0x0072a1b8, 0x00ff5d1e, 0x00f97931, 0x00f5a151, 0x00f9ad61,
+ 0x00fee0bd, 0x00f8b758, 0x00fabf69, 0x00fcc87a, 0x00fcd282, 0x00fcdc8b, 0x00fbde8f,
+ 0x00fbe193, 0x00fbeba4, 0x00fbf5b5, 0x00faf8c2, 0x00f9fcce, 0x00f9ecb7, 0x00fae183,
+ 0x00fee290, 0x00fbfac8, 0x00fdf8d8, 0x00fffccb, 0x008bcedc, 0x00189fee, 0x0025a3ee,
+ 0x000b9dfb, 0x00e8f6a5, 0x00e4f1a6, 0x00e4f0a6, 0x00e4efa6, 0x00e5f1aa, 0x00e6f2ad,
+ 0x00e3f1a6, 0x00e0ef9e, 0x00d7ec93, 0x00cde987, 0x00c8ea80, 0x00c2eb78, 0x00c1ea73,
+ 0x00c0e96e, 0x00b1e360, 0x00a3dd53, 0x0094d247, 0x0086c83b, 0x0078bc35, 0x0069b030,
+ 0x0062a52b, 0x005b9b27, 0x0057920a, 0x000995fc, 0x000d96e5, 0x001091eb, 0x001091eb,
+ 0x004ac5f0, 0x0049c4f0, 0x0047c3f1, 0x0045c2f1, 0x0044c1f2, 0x0041c1f2, 0x003fc1f2,
+ 0x003fbff1, 0x003fbcf0, 0x0032c3fe, 0x00be7f6e, 0x00fe6526, 0x00f67b35, 0x00f59a4d,
+ 0x00f8ab5c, 0x00fbd0a0, 0x00f7c783, 0x00fec16b, 0x00fdd17f, 0x00fbdb87, 0x00f9e590,
+ 0x00f8ed9a, 0x00f7f4a5, 0x00fbea9a, 0x00ffdf8e, 0x00fce3a0, 0x00f7e6b1, 0x00fceecc,
+ 0x00fffbcb, 0x00fff3c7, 0x00fcf1c3, 0x00fef5d2, 0x00fffcd3, 0x004bb5e7, 0x0021a5ed,
+ 0x001ca2ee, 0x003daae2, 0x00eef6ac, 0x00e6f2b1, 0x00e8f2b5, 0x00e9f3b8, 0x00eaf4ba,
+ 0x00ebf5bc, 0x00e8f3b6, 0x00e6f2af, 0x00e0f0a8, 0x00dbeea2, 0x00d6ef9a, 0x00d1f092,
+ 0x00c9ed82, 0x00c1eb73, 0x00b0e362, 0x00a1dc51, 0x0094d347, 0x0088ca3e, 0x007bbf38,
+ 0x006eb433, 0x0066a92e, 0x005da01b, 0x003d9448, 0x000a93f6, 0x000e94ec, 0x001193f0,
+ 0x001193f0, 0x004bc5f1, 0x004ac5f1, 0x0048c4f1, 0x0047c3f2, 0x0045c3f2, 0x0040c3f4,
+ 0x003bc4f6, 0x003cbff3, 0x003ebbf0, 0x002dcaff, 0x00ff5d25, 0x00fe6d2f, 0x00f37d39,
+ 0x00f59348, 0x00f8a958, 0x00f7c083, 0x00f7d7ae, 0x00ffc36d, 0x00ffda84, 0x00fbe48c,
+ 0x00f7ee94, 0x00f8ed9e, 0x00faeca7, 0x00f9f1b4, 0x00f8f6c1, 0x00fcf6c8, 0x00fff6d0,
+ 0x00fef2d3, 0x00fcf4ba, 0x00fffee8, 0x00f7fdea, 0x00fdfde3, 0x00fffcdc, 0x000b9df1,
+ 0x002aaaed, 0x001baaf6, 0x0080c8da, 0x00fdffbb, 0x00e8f2bd, 0x00ebf4c4, 0x00eff7cb,
+ 0x00eff7cb, 0x00eff7cb, 0x00edf6c5, 0x00ebf5c0, 0x00eaf4be, 0x00e8f3bd, 0x00e4f4b4,
+ 0x00e0f6ab, 0x00d0f191, 0x00c1ec77, 0x00b0e463, 0x009edb4e, 0x0095d448, 0x008bcc42,
+ 0x007fc23b, 0x0073b935, 0x006aac31, 0x0060a510, 0x00229687, 0x000b91f1, 0x000e93f3,
+ 0x001294f5, 0x001294f5, 0x004cc6f1, 0x004bc5f2, 0x0049c5f2, 0x0047c4f2, 0x0046c4f2,
+ 0x0043c4f1, 0x0040c4f0, 0x0042c0f3, 0x0039c1f6, 0x005eacca, 0x00fb591e, 0x00f36e31,
+ 0x00f88135, 0x00fb923f, 0x00fbaf5e, 0x00ffc373, 0x00fde2ba, 0x00ffcd75, 0x00ffd372,
+ 0x00ffe584, 0x00fff796, 0x00fef4a2, 0x00fdf1ae, 0x00fff8c2, 0x00fcf8cd, 0x00fef8d2,
+ 0x00fff9d6, 0x00fef6e1, 0x00fcf5dd, 0x00fffbee, 0x00fbfce8, 0x00fffce0, 0x00b2e0e8,
+ 0x0019a4f0, 0x0026abec, 0x0016a8f6, 0x00c2e4d8, 0x00f9fac5, 0x00eff6cb, 0x00f0f7ce,
+ 0x00f1f8d2, 0x00f1f8d1, 0x00f2f9d1, 0x00f1f9cd, 0x00f1f9ca, 0x00f2fbca, 0x00f4fdca,
+ 0x00e7f8b6, 0x00daf3a2, 0x00cbef8a, 0x00bcec71, 0x00b0e661, 0x00a5e151, 0x009ad949,
+ 0x008fd240, 0x0083c73b, 0x0077bc35, 0x006ab31d, 0x005ea905, 0x00138dea, 0x001193ef,
+ 0x001093f0, 0x000f93f0, 0x000f93f0, 0x004dc6f2, 0x004cc6f2, 0x004ac5f3, 0x0048c5f3,
+ 0x0047c5f3, 0x0046c4ef, 0x0046c4eb, 0x0048c0f3, 0x0034c7fb, 0x00989591, 0x00fc6428,
+ 0x00f1773b, 0x00fc8432, 0x00ff9135, 0x00ffb564, 0x00ffbe5a, 0x00f3ddb6, 0x00ccd097,
+ 0x00b4cea5, 0x00b0d3b1, 0x00abd7bd, 0x00c3e1bf, 0x00daebc1, 0x00f5fdc7, 0x00ffffbd,
+ 0x00fffecd, 0x00fffcdc, 0x00fffce0, 0x00fbfce5, 0x00fdfbe6, 0x00fffae7, 0x00fffbdd,
+ 0x0061c4f4, 0x0026aaee, 0x0022abec, 0x0010a7f6, 0x00ffffd7, 0x00f5f5d0, 0x00f6fad9,
+ 0x00f4f9d9, 0x00f2f9da, 0x00f3fad8, 0x00f4fbd7, 0x00f5fcd5, 0x00f7fdd4, 0x00f3face,
+ 0x00f0f7c8, 0x00e2f4b0, 0x00d4f199, 0x00c5ee82, 0x00b7eb6b, 0x00b1e95f, 0x00abe754,
+ 0x009fdf49, 0x0094d83f, 0x0087cc3a, 0x007bc034, 0x006bb425, 0x005ba332, 0x000495f9,
+ 0x001795ee, 0x001293ed, 0x000c91eb, 0x000c91eb, 0x004fc8f3, 0x004dc8f3, 0x004cc8f4,
+ 0x004bc8f4, 0x0049c8f4, 0x0047c5f2, 0x0045c2ef, 0x0042c2f8, 0x0034c8ff, 0x00df6746,
+ 0x00ff632a, 0x00ff701b, 0x00e18b53, 0x00a4a185, 0x0063c1cd, 0x0026c0ff, 0x002ab8ff,
+ 0x0025b5f1, 0x0027b7f9, 0x0026b5f6, 0x0023b3f2, 0x0024b5fa, 0x0025b7ff, 0x00189ddf,
+ 0x0043bbf4, 0x009edae8, 0x00f9f9dc, 0x00f3fbe6, 0x00ffffea, 0x00fdffe6, 0x00fafce2,
+ 0x00ffffff, 0x001ea8ef, 0x001ca8f1, 0x001ba8f2, 0x005bc4f1, 0x00ffffe7, 0x00fbf9e1,
+ 0x00fbfce3, 0x00f8fbe0, 0x00f5fadd, 0x00f5fbdb, 0x00f5fbda, 0x00f6fcd7, 0x00f6fdd3,
+ 0x00f0f8c9, 0x00ebf4be, 0x00dff2a9, 0x00d4f094, 0x00c7f47b, 0x00baf862, 0x00b0ef58,
+ 0x00a6e64e, 0x00a3e248, 0x0098d73a, 0x008acd38, 0x007bc435, 0x0070b821, 0x003b9c84,
+ 0x000d93f4, 0x001394ed, 0x001193e9, 0x000f92e6, 0x000f92e6, 0x0050c9f4, 0x004fcaf4,
+ 0x004ecaf5, 0x004dcaf5, 0x004ccaf6, 0x0048c5f4, 0x0045c0f3, 0x0047c2ef, 0x004ac4eb,
+ 0x00ff521f, 0x00a79a92, 0x0051b7e6, 0x0028c7ff, 0x002cc4f9, 0x0031c1f1, 0x003fbbf0,
+ 0x0037c0ef, 0x0039b9f0, 0x003bb3f1, 0x0038b5f4, 0x0036b7f7, 0x0032b9f0, 0x002fbbe8,
+ 0x002fb8eb, 0x002fb5ed, 0x0020acf3, 0x0010a3fa, 0x0070c9f3, 0x00f5f9df, 0x00f6fbde,
+ 0x00f6fdde, 0x00d8ebe4, 0x0011a5ee, 0x002db2f5, 0x0014a5f8, 0x00a5e2ec, 0x00fffff8,
+ 0x00fffef3, 0x00fffded, 0x00fcfde6, 0x00f8fce0, 0x00f7fcde, 0x00f6fcdd, 0x00f6fcd8,
+ 0x00f5fdd3, 0x00edf7c4, 0x00e5f1b4, 0x00e5f5b8, 0x00e4f9bb, 0x00ecfed2, 0x00f3ffe9,
+ 0x00edfedb, 0x00e8f9cd, 0x00caef89, 0x009cd636, 0x0084c72e, 0x006bb826, 0x006cb315,
+ 0x001a95d6, 0x001591ef, 0x001093eb, 0x001193e6, 0x001294e1, 0x001294e1, 0x0052cbf4,
+ 0x0050caf4, 0x004ecaf4, 0x004ccaf3, 0x004ac9f3, 0x0048c8f5, 0x0046c7f6, 0x0040bfed,
+ 0x0041bfeb, 0x0041d4f9, 0x0033c9fc, 0x002fc9ff, 0x0042c3ec, 0x0040c3f4, 0x003ec3fc,
+ 0x0035bbf4, 0x0033bbf3, 0x0049bdf7, 0x0039b7f9, 0x0037b7f6, 0x0035b7f2, 0x002eb5f4,
+ 0x0028b3f5, 0x002fbbf8, 0x002fbaf2, 0x0030b5f2, 0x0031b0f1, 0x001facf6, 0x000dabed,
+ 0x007fd2ed, 0x00ffffe6, 0x0080d9d2, 0x002faaf8, 0x001dafec, 0x0003aae6, 0x00fff8ff,
+ 0x00fffffe, 0x00fffff9, 0x00fffdf4, 0x00fdfeeb, 0x00fbfee3, 0x00f9fde1, 0x00f7fce0,
+ 0x00f5fdd8, 0x00f4fdcf, 0x00f5fce2, 0x00f6fde8, 0x00f3fde8, 0x00f1fde9, 0x00ebfdd3,
+ 0x00e6fdbe, 0x00e0f8ba, 0x00daf2b7, 0x00eafcd2, 0x00f2fde6, 0x00b7de8d, 0x0084c73d,
+ 0x009ab848, 0x0014a1f9, 0x000494f3, 0x001094ef, 0x001095ec, 0x001095e9, 0x001095e9,
+ 0x0054ccf5, 0x0051cbf4, 0x004ecaf3, 0x004cc9f2, 0x0049c8f1, 0x0048cbf5, 0x0048cef9,
+ 0x0040c4f3, 0x0049cafc, 0x0040c2f1, 0x0047caf5, 0x0046c7f4, 0x0046c4f3, 0x0039b5ee,
+ 0x002ca5e8, 0x002eb1e1, 0x0056c1ea, 0x006dc9e9, 0x0037c2e5, 0x0051caeb, 0x006bd2f1,
+ 0x0074d1f5, 0x007dcff9, 0x0056c7f8, 0x001fafe8, 0x0025b1ee, 0x002cb3f4, 0x003eb5f9,
+ 0x002bb3ee, 0x001baff5, 0x0032b5f0, 0x003fb2f9, 0x0026a9f2, 0x001faeeb, 0x003fb8f4,
+ 0x00fcfff3, 0x00ffffff, 0x00ffffff, 0x00fffefb, 0x00fefff1, 0x00feffe6, 0x00fbffe5,
+ 0x00f8fde3, 0x00f5fdd7, 0x00f3fecb, 0x00f5fbeb, 0x00f7feee, 0x00f2fdde, 0x00edfccf,
+ 0x00e3f9b0, 0x00d9f692, 0x00d2f48b, 0x00ccf184, 0x00ceee97, 0x00d0eaa9, 0x00daebc1,
+ 0x00f4fbe9, 0x007fc679, 0x005ac1ff, 0x001aa1eb, 0x001195f2, 0x000f96f2, 0x000e97f2,
+ 0x000e97f2, 0x0054cdf5, 0x0052ccf4, 0x004fcbf3, 0x004dc9f3, 0x004ac8f2, 0x0049c6f2,
+ 0x0047c4f2, 0x0049d2f3, 0x0046c8f3, 0x004dc5fc, 0x002c9add, 0x001883cd, 0x00046cbe,
+ 0x000080c5, 0x000f96d4, 0x002eaddb, 0x0060c6eb, 0x0076cdef, 0x0051caea, 0x0069d2f0,
+ 0x0081daf5, 0x009ae4f7, 0x00b3eff9, 0x00cffaff, 0x00e3feff, 0x009ae1ff, 0x0048bcf7,
+ 0x0011b5dd, 0x0032aef0, 0x0028acfc, 0x0031b2f3, 0x0034b1f6, 0x0025adf0, 0x0026acf6,
+ 0x0098d1fc, 0x00fffdf8, 0x00ffffff, 0x00fffffb, 0x00fefff4, 0x00fdffee, 0x00fcfde7,
+ 0x00fbfee4, 0x00faffe0, 0x00f8fde7, 0x00f7fcef, 0x00f3fbeb, 0x00effdd9, 0x00e9fbc2,
+ 0x00e3f9ac, 0x00d9f49b, 0x00ceef8b, 0x00c1ea76, 0x00b4e562, 0x00abdd5a, 0x00a2d261,
+ 0x00c1e98e, 0x00dbe8b9, 0x0096d4ff, 0x008ed0fa, 0x0042aeee, 0x001095f1, 0x001096f1,
+ 0x000f96f1, 0x000f96f1, 0x0055cef5, 0x0053ccf4, 0x0050cbf4, 0x004ecaf4, 0x004cc8f4,
+ 0x0051caf7, 0x0057cbfa, 0x0045c0ea, 0x001a75c7, 0x000058ad, 0x00015bb4, 0x00066fc0,
+ 0x000b84cd, 0x000093ce, 0x0011a7e0, 0x003eb9e6, 0x006bcbeb, 0x007ed1f6, 0x006cd3f0,
+ 0x0082dbf4, 0x0098e3f9, 0x00a5ecf7, 0x00b2f4f5, 0x00c7f7f9, 0x00ddfafd, 0x00f2ffff,
+ 0x00f8fff6, 0x00bcebfe, 0x0022b4f2, 0x0029afff, 0x002fb0f7, 0x0029b1f2, 0x0023b1ee,
+ 0x001aa7fa, 0x00cae6f4, 0x00f7f8f4, 0x00feffff, 0x00fefff7, 0x00feffed, 0x00fcffeb,
+ 0x00fbfae9, 0x00fbfee3, 0x00fbffdc, 0x00fbffe9, 0x00fbfff7, 0x00f1fedd, 0x00e7fbc3,
+ 0x00e0f6b4, 0x00d8f0a5, 0x00ceec94, 0x00c4e884, 0x00b8e678, 0x00ace36c, 0x00a0df53,
+ 0x0094d455, 0x0080bd41, 0x00d2e599, 0x002ca1f4, 0x0030a2f6, 0x00209cf3, 0x001096f1,
+ 0x001096f1, 0x001096f1, 0x001096f1, 0x0055cef4, 0x0053cdf4, 0x0051cbf5, 0x0050cbf5,
+ 0x004ecaf6, 0x004dc9f4, 0x0054d0fa, 0x002b86ce, 0x000752b1, 0x00045fb9, 0x000a74c9,
+ 0x000882ce, 0x000691d4, 0x0002a0d5, 0x0024b5e7, 0x004cc4ea, 0x0074d3ee, 0x0083d9f5,
+ 0x007fddf4, 0x0093e4f6, 0x00a8ecf9, 0x00b6f2f9, 0x00c3f9f9, 0x00d3fafb, 0x00e3fcfc,
+ 0x00edfefb, 0x00f0f9f3, 0x00ffffff, 0x00fffdff, 0x007edcef, 0x0026adfd, 0x002aaff7,
+ 0x002db2f2, 0x0034b1e0, 0x0009a7f7, 0x008dd3f5, 0x00fdfbf9, 0x00fffff6, 0x00fdffeb,
+ 0x00fcffe6, 0x00fcfce0, 0x00f9fcde, 0x00f7fcdd, 0x00fcffef, 0x00f9fdec, 0x00e8f5d0,
+ 0x00dff5bd, 0x00d9f1ad, 0x00d2ed9d, 0x00c5e97e, 0x00b8e26d, 0x00abdd5e, 0x009fd74f,
+ 0x0098c95f, 0x0092c735, 0x008bc942, 0x0080b34d, 0x00009bf2, 0x001894f8, 0x001595f5,
+ 0x001397f2, 0x001296f1, 0x001195f0, 0x001195f0, 0x0056cff4, 0x0054cdf5, 0x0052ccf5,
+ 0x0051cbf7, 0x0051cbf9, 0x0049c8f1, 0x0051d5fa, 0x001662c1, 0x00005cbb, 0x000874cd,
+ 0x00037cce, 0x00028dd4, 0x00019edb, 0x0009aedc, 0x0037c2ee, 0x005acfef, 0x007edcf0,
+ 0x0088e1f4, 0x0092e6f8, 0x00a5eef8, 0x00b9f5f9, 0x00c7f9fb, 0x00d5fdfe, 0x00dffdfc,
+ 0x00e9fdfa, 0x00f0fefe, 0x00f8ffff, 0x00fafffe, 0x00fdfffc, 0x00fdfbff, 0x001db0e8,
+ 0x002ab1ee, 0x0037b2f5, 0x0025b9f7, 0x0029b4f8, 0x0022aff5, 0x001baaf2, 0x009fd7f6,
+ 0x00fdffea, 0x00fcfee0, 0x00fcfdd7, 0x00f8fada, 0x00f4f7dd, 0x00fdfef5, 0x00f6fae1,
+ 0x00dfecc3, 0x00d8efb6, 0x00d2eca6, 0x00ccea95, 0x00bce567, 0x00abdb56, 0x009fd344,
+ 0x0092cb33, 0x0085c824, 0x0079b46a, 0x003a9eaf, 0x000c97ff, 0x001994f9, 0x000f9bee,
+ 0x00139af0, 0x001699f3, 0x001497f1, 0x001295ef, 0x001295ef, 0x0058d0f5, 0x0056cef5,
+ 0x0053cdf4, 0x0053ccf6, 0x0052cbf8, 0x0053d6fb, 0x004fc8fc, 0x00004cad, 0x00096fca,
+ 0x000b80d4, 0x000588d5, 0x000598db, 0x0005a8e1, 0x0018b6e6, 0x003fc8f2, 0x0063d3f3,
+ 0x0086dff5, 0x0091e4f7, 0x009ce9fa, 0x00aef0f9, 0x00c0f7f9, 0x00cbfafb, 0x00d7fdfd,
+ 0x00defdfc, 0x00e6fefb, 0x00f0fffe, 0x00faffff, 0x00f2fefb, 0x00fefffd, 0x00c6e9fb,
+ 0x001eb0ec, 0x0030b4f6, 0x0030b7f8, 0x0019a8f7, 0x0026b0f0, 0x0022aef3, 0x001eabf5,
+ 0x0027aafa, 0x001ca6f6, 0x007dcdea, 0x00dff4dd, 0x00eaffb0, 0x00fdfeed, 0x00ffffef,
+ 0x00fcf9d3, 0x00edeeb4, 0x00e6e9ac, 0x00d9e68a, 0x00cbe367, 0x00b9e153, 0x00a6dd4d,
+ 0x0075c57f, 0x0043adb0, 0x00229bf3, 0x000a9cff, 0x000998f6, 0x00109cef, 0x00189aee,
+ 0x00149ded, 0x00159bf0, 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005ad1f6,
+ 0x0057cff5, 0x0054cef4, 0x0054cdf6, 0x0053cbf8, 0x004dd3f4, 0x002c9add, 0x00045ec1,
+ 0x000572c9, 0x000683d2, 0x000794dc, 0x0008a2e2, 0x0008b1e8, 0x0028bfef, 0x0048cef6,
+ 0x006bd8f8, 0x008fe3fa, 0x009be8fa, 0x00a6edfb, 0x00b7f3fb, 0x00c7f9fa, 0x00d0fbfc,
+ 0x00d9fdfd, 0x00defefd, 0x00e2fffc, 0x00effffe, 0x00fcffff, 0x00ebfef7, 0x00fffffe,
+ 0x008fd7f8, 0x001eb0f1, 0x002eb0f6, 0x0018abec, 0x00e0f7fd, 0x0024ade9, 0x0023acf1,
+ 0x0021acf8, 0x0026aef7, 0x002cb0f6, 0x001aa9f5, 0x0008a3f4, 0x0022a7f9, 0x004cc2f2,
+ 0x006dcdef, 0x007ec9db, 0x007fcac2, 0x0081c6c6, 0x0061bccb, 0x0041b3d0, 0x0024a7e9,
+ 0x00089bff, 0x00119dff, 0x001a9fff, 0x000f99e9, 0x00149cf9, 0x00159cf7, 0x00159cf5,
+ 0x00179df1, 0x00199eed, 0x00179cef, 0x001599f1, 0x001397ef, 0x001195ed, 0x001195ed,
+ 0x005cd2f6, 0x0059d0f5, 0x0055cff3, 0x0054cdf5, 0x0053ccf8, 0x0051d5f6, 0x00167bcf,
+ 0x000467c6, 0x00067bcf, 0x00068bd7, 0x00059cdf, 0x0008a9e5, 0x000ab6eb, 0x002bc4f1,
+ 0x004cd2f7, 0x006ddbf9, 0x008ee5fa, 0x009deafb, 0x00aceffb, 0x00bdf5fb, 0x00cefbfa,
+ 0x00d5fbfc, 0x00dcfcfd, 0x00dcfefd, 0x00ddfffd, 0x00e4fffd, 0x00eafffd, 0x00fffffe,
+ 0x00ffffff, 0x0027c0de, 0x0026b5f6, 0x001fb0f9, 0x004dc6ff, 0x00fff9ef, 0x00fefffa,
+ 0x008bd8f7, 0x0018a7f3, 0x001daaf4, 0x0023acf6, 0x0022acf3, 0x0022abf0, 0x001aa3f2,
+ 0x001aa6ee, 0x0018a8f5, 0x000ea2f3, 0x0011a4f2, 0x0014a4ff, 0x0015a3fc, 0x0016a3fa,
+ 0x0017a2f3, 0x0019a2ec, 0x000e99fe, 0x00169bed, 0x0000a1ff, 0x002b9de8, 0x0061b5b0,
+ 0x00109af7, 0x00149cf2, 0x00189eed, 0x00169cef, 0x00149af0, 0x001298ee, 0x001096ec,
+ 0x001096ec, 0x005fd3f7, 0x005bd2f5, 0x0056d0f3, 0x0055cef5, 0x0053cdf7, 0x0056d8f8,
+ 0x00005cc0, 0x000370cb, 0x000785d6, 0x000594dc, 0x0004a3e2, 0x0008afe8, 0x000cbcee,
+ 0x002ec8f3, 0x0050d5f9, 0x006fdefa, 0x008de7fb, 0x009fecfb, 0x00b1f2fb, 0x00c3f7fb,
+ 0x00d4fcfa, 0x00d9fcfc, 0x00defcfd, 0x00dbfdfd, 0x00d9fffd, 0x00d9fdfb, 0x00d9fcfa,
+ 0x00e5fafa, 0x00a4eaf7, 0x002badfb, 0x002fb9fa, 0x001aaeed, 0x0099dbf8, 0x00ffffff,
+ 0x00fefdfc, 0x00fffefd, 0x00fffffd, 0x008cd4fa, 0x0019a9f6, 0x0018a9f7, 0x0016aaf9,
+ 0x001aa7f3, 0x001ea5ee, 0x001fa7f2, 0x0021a9f6, 0x001ea7f7, 0x001ba5f7, 0x0017a4f9,
+ 0x0012a2fb, 0x000b9dfd, 0x000399fe, 0x0026a2fa, 0x006fc0b0, 0x00cfca5e, 0x00ffe528,
+ 0x0074b4b3, 0x000b98fa, 0x00119af4, 0x00179dee, 0x00159cee, 0x00139aef, 0x001198ed,
+ 0x000f96eb, 0x000f96eb, 0x005dd1f6, 0x005bd2f5, 0x0058d2f4, 0x0053cef4, 0x0056d2fb,
+ 0x0040b2e6, 0x000164c6, 0x000376cf, 0x000487d7, 0x000296dd, 0x0001a4e4, 0x0004b1ea,
+ 0x0007bdf1, 0x001bc8f2, 0x0043d5fc, 0x0064ddfb, 0x0085e6fb, 0x0098ebfc, 0x00acf1fd,
+ 0x00bef9ff, 0x00cfffff, 0x00cffdff, 0x00cff9fb, 0x00d2fefe, 0x00d5ffff, 0x00c6f9ff,
+ 0x00b8efff, 0x005ad7d9, 0x0040b9e9, 0x002fb9ff, 0x002bb2f0, 0x0028afeb, 0x00def0f2,
+ 0x00ffffff, 0x00feffff, 0x00fffefe, 0x00fffefa, 0x00fffffa, 0x00fffff9, 0x00c2e8f0,
+ 0x0084cde7, 0x0053bbe9, 0x0022a9eb, 0x0014a1ff, 0x00069ff8, 0x000fa0f8, 0x0019a3eb,
+ 0x0043b1e1, 0x006ec2c9, 0x00b0d79a, 0x00f2eb6b, 0x00ebee32, 0x00f8e647, 0x00ffe23a,
+ 0x00fde142, 0x000098f4, 0x0019a1fc, 0x00169ef7, 0x00129bf1, 0x00139af1, 0x00149af0,
+ 0x001298ee, 0x001096ec, 0x001096ec, 0x005ccff6, 0x005bd2f6, 0x005ad4f6, 0x0052cdf2,
+ 0x005ad6fe, 0x00298cd5, 0x00026ccc, 0x00027bd2, 0x000189d8, 0x000097df, 0x0000a6e6,
+ 0x0000b2ed, 0x0002bef4, 0x0009c7f1, 0x0035d5ff, 0x0059ddfd, 0x007ce5fb, 0x0091eafd,
+ 0x00a6f0ff, 0x00b1f2ff, 0x00bbf5ff, 0x00bef5fc, 0x00c1f6f9, 0x00c1f7f7, 0x00c1f9f4,
+ 0x00c7fdfc, 0x00cdffff, 0x00c2f9f8, 0x005acdf4, 0x0039b1f3, 0x0038baf5, 0x002ab4f7,
+ 0x00fcfbf8, 0x00fdfeff, 0x00feffff, 0x00fffeff, 0x00fffcf6, 0x00fdfef2, 0x00f7ffee,
+ 0x00fcffea, 0x00ffffe5, 0x00ffffd8, 0x00ffffcb, 0x00fffbf1, 0x00ffffdf, 0x00fdfdc2,
+ 0x00f7ff88, 0x00fbfe92, 0x00ffff7f, 0x00fdfc6c, 0x00faf759, 0x00f8f059, 0x00f7e958,
+ 0x00f7e359, 0x00d0d368, 0x000998ff, 0x00189aef, 0x00129af2, 0x000c99f5, 0x001199f3,
+ 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005fd2f9, 0x005cd3f8, 0x0059d4f6,
+ 0x0058d3f8, 0x005edaff, 0x001971cd, 0x00026ecd, 0x00037bd3, 0x000488d9, 0x000497e0,
+ 0x0005a6e6, 0x0001ade7, 0x0000b5e8, 0x0007beea, 0x0023cbf5, 0x004cd7f8, 0x0074e4fc,
+ 0x0089e8fd, 0x009fecfe, 0x00a5edfe, 0x00abeffe, 0x00aeeffc, 0x00b0eff9, 0x00b3f3f9,
+ 0x00b6f6f8, 0x00b6f9fc, 0x00b5fcff, 0x00daf3ff, 0x001ab9f1, 0x0028b3f4, 0x002bb3f6,
+ 0x0073cef4, 0x00fdfdf5, 0x00fdfefa, 0x00fdfffe, 0x00fffef9, 0x00fffdf3, 0x00fdfeee,
+ 0x00faffe9, 0x00fdffe4, 0x00ffffde, 0x00ffffd0, 0x00ffffc2, 0x00fdfad7, 0x00fffcf3,
+ 0x00ffffc0, 0x00fcfbc5, 0x00fcff84, 0x00fcfb8b, 0x00fbf67a, 0x00f9f269, 0x00f7ed5e,
+ 0x00f4e954, 0x00f7e948, 0x0087bda9, 0x00109afc, 0x00179cf2, 0x00149bf1, 0x00119af1,
+ 0x001399f2, 0x001698f3, 0x001496f1, 0x001294ef, 0x001294ef, 0x0062d4fc, 0x005dd4f9,
+ 0x0059d4f6, 0x0056d1f6, 0x0053cef5, 0x00014ebe, 0x00026fcd, 0x00057bd4, 0x000787da,
+ 0x000996e0, 0x000ca5e7, 0x000bb0e9, 0x0009bbeb, 0x0015c5f3, 0x0021d0fc, 0x0046dafc,
+ 0x006ce3fc, 0x0082e6fd, 0x0097e9fe, 0x0099e9fe, 0x009ce8fe, 0x009ee9fb, 0x00a0e9f9,
+ 0x00a6eefa, 0x00acf3fc, 0x00b0effc, 0x00b5ecfb, 0x0089ddf9, 0x0028b4f3, 0x003ebef7,
+ 0x001eadf7, 0x00bde8f0, 0x00fefff2, 0x00fefff3, 0x00fdfff4, 0x00fefef2, 0x00fefef0,
+ 0x00fefeea, 0x00fefee4, 0x00fefede, 0x00fefed8, 0x00fcffc9, 0x00fbffba, 0x00f6fea0,
+ 0x00ffffce, 0x00fff9f6, 0x00ffffc9, 0x00fdf7be, 0x00f8f87a, 0x00f9f66b, 0x00f9f35c,
+ 0x00f5ee56, 0x00f1e84f, 0x00f8ee37, 0x003fa7ea, 0x00189df5, 0x00179df4, 0x00169cf1,
+ 0x00159bee, 0x00169af2, 0x001798f5, 0x001596f3, 0x001394f1, 0x001394f1, 0x0066d7fc,
+ 0x005fd1f5, 0x0060d4f6, 0x0059d8f9, 0x00399ddb, 0x000858be, 0x00096ccd, 0x000c7ad2,
+ 0x001087d7, 0x001296df, 0x0013a6e8, 0x0013b0eb, 0x001bc3f5, 0x000fc8f3, 0x0017d0f9,
+ 0x0027d3f4, 0x004bd7f7, 0x0061dbf8, 0x0077def9, 0x007fe0fa, 0x0088e1fa, 0x008de4fb,
+ 0x0091e7fb, 0x0096eafc, 0x009aedfd, 0x009feafb, 0x00a3e7fa, 0x005eccfb, 0x002db7f5,
+ 0x0024b8f9, 0x0014b1f5, 0x00fffbff, 0x00feffec, 0x00ffffed, 0x00ffffee, 0x00ffffec,
+ 0x00fefdeb, 0x00fefde4, 0x00fefddd, 0x00fefed6, 0x00fefece, 0x00fcfdc1, 0x00fcfcb5,
+ 0x00f6fb8d, 0x00f8fc8a, 0x00f8facc, 0x00f8fef2, 0x00f9ffbe, 0x00fbf9c2, 0x00fbf8ac,
+ 0x00fcf796, 0x00faf491, 0x00f7f18d, 0x00ffe5a9, 0x000096f7, 0x00089af7, 0x00159ef7,
+ 0x00169df4, 0x00169cf0, 0x00169bf2, 0x001699f4, 0x001497f3, 0x001396f1, 0x001396f1,
+ 0x006bd9fb, 0x0061cef1, 0x0067d3f7, 0x005cdefd, 0x001f6cc0, 0x000f63bf, 0x000f6acd,
+ 0x001478d1, 0x001887d4, 0x001997df, 0x001aa6e9, 0x0014a9e4, 0x001dbbef, 0x000dbeeb,
+ 0x0023c5f6, 0x0013c6ed, 0x002acbf3, 0x0040cff4, 0x0056d4f4, 0x0065d7f6, 0x0074daf7,
+ 0x007bdffb, 0x0083e5fe, 0x0086e6fe, 0x0089e8fd, 0x008ee5fb, 0x0092e2fa, 0x0033bcfc,
+ 0x0032b9f7, 0x0031bafd, 0x0057c5f7, 0x00f4ffde, 0x00fdffe7, 0x00ffffe7, 0x00ffffe7,
+ 0x00ffffe6, 0x00fdfce6, 0x00fdfddd, 0x00fdfdd5, 0x00fdfdcd, 0x00fefdc5, 0x00fdfaba,
+ 0x00fcf8af, 0x00fef99f, 0x00fffb8e, 0x00fafe77, 0x00f4fb7d, 0x00f9f8d2, 0x00fdffee,
+ 0x00fefedf, 0x00fffcd0, 0x00fefacd, 0x00fdf9ca, 0x00a6d3ce, 0x000399eb, 0x001ea1ec,
+ 0x00149ffa, 0x00159ef6, 0x00179ef2, 0x00169cf3, 0x00159af3, 0x001499f2, 0x001398f1,
+ 0x001398f1, 0x0055d4f4, 0x005bd1f1, 0x0069d6f6, 0x006ee2ff, 0x000c50a8, 0x001161be,
+ 0x000f6acd, 0x001f83d6, 0x001f89dc, 0x000f8cdd, 0x001a9be0, 0x0022b1f4, 0x001dabe1,
+ 0x0014aedf, 0x0026bdee, 0x0015bae7, 0x001fc1ef, 0x0025c7ef, 0x002bcdef, 0x003dcdf1,
+ 0x004ecef3, 0x005bd6f9, 0x0068defe, 0x006eddfc, 0x0073ddfb, 0x0076ddf5, 0x0070d3f7,
+ 0x0031bafb, 0x0033b9f6, 0x0024b6ff, 0x00a4dee5, 0x00f9ffdc, 0x00fdfedc, 0x00ffffdc,
+ 0x00ffffdc, 0x00fefedb, 0x00fcfdda, 0x00fdfdd2, 0x00fdfdcb, 0x00fdfdc3, 0x00fefdbc,
+ 0x00fdfbaf, 0x00fcfaa2, 0x00fdfb93, 0x00fefb83, 0x00fcfd6b, 0x00f9fc60, 0x00fbf85d,
+ 0x00fdf74c, 0x00fef576, 0x00fff2a1, 0x00f6ec87, 0x00f8e360, 0x0051bbb4, 0x000d9afe,
+ 0x001a9ef7, 0x00159ef6, 0x00159df4, 0x00159df2, 0x00149bf2, 0x001299f2, 0x001299f2,
+ 0x001299f2, 0x001299f2, 0x0067d4fd, 0x0069d6f9, 0x006cd9f5, 0x004fb7dc, 0x001953af,
+ 0x001c67c6, 0x00005abd, 0x001a7eca, 0x00157bd4, 0x000581dc, 0x002aa1e7, 0x000189d3,
+ 0x002dabe3, 0x0023a7dc, 0x0029b4e6, 0x0017ade1, 0x0014b7ec, 0x0015b9ea, 0x0016bbe9,
+ 0x001fbfec, 0x0028c2ef, 0x003bcdf7, 0x004ed8ff, 0x0056d5fb, 0x005dd2f8, 0x005ed6f0,
+ 0x004ec5f4, 0x002fb9fa, 0x0035b8f4, 0x0017b1ff, 0x00f0f7d2, 0x00feffda, 0x00fdfcd2,
+ 0x00fdfdd1, 0x00fdfed1, 0x00fdfecf, 0x00fcfecd, 0x00fcfdc7, 0x00fdfdc0, 0x00fdfdb9,
+ 0x00fdfdb2, 0x00fdfca4, 0x00fdfc95, 0x00fdfc87, 0x00fdfc79, 0x00fdfa6c, 0x00fef85f,
+ 0x00f9f645, 0x00f6ef47, 0x00f2e938, 0x00efe428, 0x00eee425, 0x00ffdd05, 0x000399ff,
+ 0x0017a1f5, 0x00179ef4, 0x00169cf3, 0x00159cf3, 0x00149cf3, 0x00129bf1, 0x001099f0,
+ 0x00119af1, 0x00129bf2, 0x00129bf2, 0x0066d5fb, 0x0070d5fc, 0x0078e2ff, 0x003b86c7,
+ 0x00235fba, 0x001e6aba, 0x00227ad1, 0x002787d8, 0x00248cd7, 0x001d8dd4, 0x002189d1,
+ 0x002ca1ea, 0x002296d5, 0x0031aaef, 0x0020a1db, 0x0017a1dd, 0x000ea1e0, 0x001aace3,
+ 0x0013b1eb, 0x0010b8ed, 0x000dc0ef, 0x001cc1ef, 0x002cc3f0, 0x0036c4f2, 0x0040c5f4,
+ 0x0047c9f2, 0x0045c3f6, 0x0031bafa, 0x0031b7f7, 0x004cc2f4, 0x00f5fac0, 0x00fdffc6,
+ 0x00fdfcc5, 0x00fdfdc4, 0x00fdfdc4, 0x00fcfdc2, 0x00fbfdc1, 0x00f8f9b6, 0x00fdfdb3,
+ 0x00fdfdab, 0x00fdfca3, 0x00fcfc95, 0x00fcfb88, 0x00fcfb7b, 0x00fbfb6d, 0x00fcf962,
+ 0x00fcf757, 0x00f8f245, 0x00f4eb41, 0x00f0e532, 0x00ebe023, 0x00fbe01c, 0x00c5d244,
+ 0x000aa2fe, 0x00169ff9, 0x00179ff6, 0x00189ff3, 0x00179ef2, 0x00159df2, 0x00179ff5,
+ 0x0018a1f8, 0x00159ef5, 0x00129bf2, 0x00129bf2, 0x0065d7fa, 0x0064d1f7, 0x005de7ff,
+ 0x0004439b, 0x000e4ca5, 0x00317bcd, 0x000455c1, 0x000053c9, 0x000368c6, 0x002687ca,
+ 0x002881ca, 0x002789d1, 0x002791d7, 0x000774c9, 0x00178dcf, 0x001f9ce1, 0x00179be4,
+ 0x001e9eda, 0x000097de, 0x0003a5e6, 0x0008b1ee, 0x0009b0e8, 0x000aafe2, 0x0017b4e9,
+ 0x0024b9ef, 0x0030bdf4, 0x003cc1f9, 0x0034bcf9, 0x002cb6f9, 0x0080d2e8, 0x00fafdaf,
+ 0x00fcfdb3, 0x00fdfcb7, 0x00fdfcb7, 0x00fdfdb7, 0x00fcfcb6, 0x00fbfcb5, 0x00f4f4a5,
+ 0x00fdfda5, 0x00fcfc9d, 0x00fcfc94, 0x00fbfb87, 0x00fbfb7b, 0x00fafa6e, 0x00fafa61,
+ 0x00faf758, 0x00faf54e, 0x00f7ee44, 0x00f3e73a, 0x00ede12c, 0x00e7db1e, 0x00ffd21a,
+ 0x0078b090, 0x0009a0fd, 0x00159dfd, 0x0018a0f8, 0x001aa2f2, 0x0018a0f2, 0x00169ef2,
+ 0x00139bf2, 0x001099f1, 0x00119af2, 0x00129bf3, 0x00129bf3, 0x0060d4f7, 0x0067dcfd,
+ 0x004fc2f0, 0x00002c8a, 0x002e6bc0, 0x000547ad, 0x000044ba, 0x003685c4, 0x00064ebc,
+ 0x001462c3, 0x002d70cb, 0x000f5ab4, 0x002274cd, 0x001169c2, 0x001979c2, 0x001d80d0,
+ 0x001980d7, 0x001a86d3, 0x001090de, 0x00038dda, 0x000599e6, 0x00059ce1, 0x00049edd,
+ 0x0005a6e1, 0x0000a7de, 0x001fb6ee, 0x0039bdf7, 0x0038bcf6, 0x0024b5fc, 0x00bfe8b9,
+ 0x00fafea2, 0x00fbfca5, 0x00fcfaa8, 0x00fcfca7, 0x00fdfda6, 0x00fbfca3, 0x00f9fb9f,
+ 0x00f6f795, 0x00fafb92, 0x00fbfb8b, 0x00fbfb85, 0x00fafa79, 0x00fafa6d, 0x00f9f961,
+ 0x00f8f956, 0x00f9f64c, 0x00f9f442, 0x00f5ec39, 0x00f2e531, 0x00efde28, 0x00ecd620,
+ 0x00eed900, 0x0032a6e5, 0x0019a4ff, 0x0029a4f4, 0x0020a2f4, 0x0018a0f5, 0x00179ef4,
+ 0x00159df4, 0x00139bf3, 0x001199f2, 0x00129af2, 0x00129af3, 0x00129af3, 0x005bd1f5,
+ 0x0063dffa, 0x00318dcc, 0x00062d91, 0x000e499a, 0x0000369f, 0x00003897, 0x00155fb6,
+ 0x0053aad9, 0x0031a6e2, 0x0045bcef, 0x006dddff, 0x0076defa, 0x006dd9f9, 0x0064d5f9,
+ 0x0054c5f3, 0x0045b5ed, 0x00238ed6, 0x001277ce, 0x00006cc6, 0x000282de, 0x000187db,
+ 0x00008dd7, 0x00079be1, 0x000099dc, 0x0022b1f0, 0x0036baf4, 0x003cbcf4, 0x001cb5ff,
+ 0x00fffe89, 0x00fbff96, 0x00fbfc98, 0x00fbf99a, 0x00fcfb98, 0x00fdfd96, 0x00fafb90,
+ 0x00f6f98a, 0x00f7f984, 0x00f8fa7f, 0x00fafa7a, 0x00fbfb75, 0x00fafa6a, 0x00f9f960,
+ 0x00f8f855, 0x00f7f84a, 0x00f7f540, 0x00f8f336, 0x00f4eb2f, 0x00f0e328, 0x00f0da24,
+ 0x00f0d121, 0x00e9ca24, 0x00049bff, 0x0020a3f6, 0x0016a1f7, 0x0016a0f7, 0x00169ef7,
+ 0x00159df6, 0x00149cf5, 0x00139bf4, 0x00129af3, 0x00129af3, 0x00129af3, 0x00129af3,
+ 0x005ae3ff, 0x0064d8ff, 0x000d4798, 0x00002682, 0x001d6bb7, 0x003aa2de, 0x005fe5ff,
+ 0x0052d8fd, 0x004dd6f6, 0x0048ccf5, 0x005fd0f6, 0x0068d9ff, 0x0061d3f8, 0x005bd2f8,
+ 0x0042cbff, 0x0053cefe, 0x0051cff5, 0x0049caf6, 0x004acdff, 0x0040baff, 0x000e7edb,
+ 0x000069c2, 0x000584da, 0x000184d5, 0x00068cd8, 0x0038bef8, 0x003abef7, 0x0035beff,
+ 0x0062c7e2, 0x00fbf379, 0x00f8fa83, 0x00f9f983, 0x00faf884, 0x00f9f77f, 0x00f7f77b,
+ 0x00f8f979, 0x00f9fa77, 0x00f8f972, 0x00f7f86c, 0x00fcfc6c, 0x00f9f864, 0x00f8f85b,
+ 0x00f8f752, 0x00f7f649, 0x00f6f53f, 0x00f5f237, 0x00f4ef2f, 0x00f1e628, 0x00eede20,
+ 0x00ead61f, 0x00f2cc11, 0x009db96c, 0x000c9ffe, 0x001ba3f9, 0x0017a2f9, 0x0017a0f9,
+ 0x00169ef8, 0x00169df7, 0x00159cf6, 0x00149bf5, 0x00139af5, 0x00139af5, 0x00139af5,
+ 0x00139af5, 0x0060d8f9, 0x005bd9f8, 0x004cadd7, 0x0069ddff, 0x0056ddf8, 0x0055d6fc,
+ 0x0055d0ff, 0x005cd5ff, 0x0053cbf2, 0x004bcaf6, 0x0043cafa, 0x0047c9f8, 0x004cc8f6,
+ 0x005ccff1, 0x0046ccf8, 0x0055caff, 0x003ec4fa, 0x0043c3fb, 0x0048c2fd, 0x003ebff4,
+ 0x0044ccfb, 0x0037b3fc, 0x000b7bdd, 0x00006dc9, 0x000d80d4, 0x004eccff, 0x003ec3fa,
+ 0x002ec2ff, 0x00a7dea8, 0x00f8ec5b, 0x00f5f570, 0x00f7f66f, 0x00faf76e, 0x00f5f467,
+ 0x00f1f060, 0x00f6f663, 0x00fbfc65, 0x00f8f95f, 0x00f6f659, 0x00fefe5d, 0x00f7f652,
+ 0x00f7f54c, 0x00f7f545, 0x00f6f33d, 0x00f6f235, 0x00f3ef2f, 0x00f1eb29, 0x00efe221,
+ 0x00ecd818, 0x00e5d21a, 0x00f3c700, 0x0052a9b4, 0x0014a4fb, 0x0015a3fb, 0x0017a3fc,
+ 0x0017a1fa, 0x00179ff8, 0x00169df8, 0x00159cf7, 0x00159bf7, 0x001499f6, 0x001499f6,
+ 0x001499f6, 0x001499f6, 0x0058cff2, 0x0059ddfd, 0x0055d5f9, 0x005ddeff, 0x004dcef3,
+ 0x004dcbf3, 0x004cc8f3, 0x0056d2fc, 0x0059d3fd, 0x0050cefb, 0x0047cafa, 0x0048c9f9,
+ 0x0049c7f9, 0x0051cbf6, 0x0045c9f9, 0x004bc8fd, 0x003fc5f9, 0x0041c4fa, 0x0043c2fb,
+ 0x003bbdf3, 0x003ac0f4, 0x003ec7fc, 0x003ac6fc, 0x0025a1e3, 0x001f8dd9, 0x0037b9f7,
+ 0x0026bbfa, 0x002abbf4, 0x00ced857, 0x00f9fa5b, 0x00d9db49, 0x00edec58, 0x00faf560,
+ 0x00f2ef4d, 0x00e9ea3b, 0x00eeef46, 0x00f2f451, 0x00f9f34f, 0x00edf145, 0x00fef84b,
+ 0x00f4f542, 0x00f5f43d, 0x00f6f337, 0x00f5f131, 0x00f5ef2b, 0x00f2eb27, 0x00f0e622,
+ 0x00eedb1d, 0x00ecd117, 0x00f1cc09, 0x00f5c509, 0x000fadff, 0x0017a1f9, 0x0018a1f9,
+ 0x0018a1f8, 0x0018a0f9, 0x00179ff9, 0x00169df9, 0x00169cf8, 0x00159bf8, 0x001599f8,
+ 0x001599f8, 0x001599f8, 0x001599f8, 0x0060d5fb, 0x005bd3fb, 0x0056d2fb, 0x0055d1fc,
+ 0x0055d0fe, 0x0054d0fa, 0x0053d1f6, 0x0051cef7, 0x004ecbf8, 0x004dcbf9, 0x004ccafb,
+ 0x0049c8fb, 0x0047c6fc, 0x0045c6fb, 0x0043c6fa, 0x0041c6fa, 0x0040c7f9, 0x003fc5f9,
+ 0x003ec3f9, 0x003fc3fb, 0x0041c4fd, 0x0038baf2, 0x0040c1f8, 0x003dc3fb, 0x003bc5fe,
+ 0x0037c1f6, 0x0034beef, 0x002ebcf0, 0x00ded722, 0x00bfdc38, 0x00dee142, 0x00ecea4a,
+ 0x00eae442, 0x00eee942, 0x00f2ee42, 0x00eeed3f, 0x00eaec3d, 0x00fbee3f, 0x00e5ec31,
+ 0x00fff239, 0x00f2f531, 0x00f4f32e, 0x00f5f12a, 0x00f5ee25, 0x00f4ec21, 0x00f2e71e,
+ 0x00f0e11c, 0x00eed519, 0x00ecc917, 0x00dec40c, 0x00bbbe39, 0x000798f8, 0x001a9ff8,
+ 0x001a9ff7, 0x001a9ff5, 0x00189ff7, 0x00179ff9, 0x00179ef9, 0x00169cf9, 0x00169bf9,
+ 0x001699f9, 0x001699f9, 0x001699f9, 0x001699f9, 0x005cd4f9, 0x0058d4f9, 0x0055d3f9,
+ 0x0056d2fa, 0x0058d0fb, 0x0056d0f8, 0x0054d0f6, 0x0051cef7, 0x004dccf9, 0x004ccbfa,
+ 0x004bcafb, 0x0049c8fb, 0x0047c7fb, 0x0045c7fb, 0x0043c6fa, 0x0041c6fa, 0x0040c6f9,
+ 0x003fc4f9, 0x003ec3f9, 0x003ec2fa, 0x003ec2fb, 0x003abef5, 0x003ec2f8, 0x003bc1f9,
+ 0x0037c0f9, 0x0036beff, 0x0035bbff, 0x0067bb84, 0x00b0d219, 0x00b4d31a, 0x00d3da39,
+ 0x00e2dd3d, 0x00d6d532, 0x00e1df38, 0x00ece93e, 0x00e1e636, 0x00e9e536, 0x00f1e634,
+ 0x00e5e42b, 0x00f6e62e, 0x00e9eb29, 0x00f0ee2a, 0x00f0e824, 0x00ece420, 0x00e9e01d,
+ 0x00ebdb1c, 0x00edd71c, 0x00e9ce19, 0x00e5c516, 0x00e7c004, 0x006cb292, 0x00109dfc,
+ 0x0018a1f7, 0x001aa0f5, 0x001ca0f3, 0x0019a0f6, 0x00179ff9, 0x00169ef9, 0x00169cf9,
+ 0x00159bf8, 0x00159af8, 0x001499f8, 0x001499f7, 0x001499f7, 0x0058d4f6, 0x0056d4f6,
+ 0x0054d5f7, 0x0057d3f7, 0x005bd1f8, 0x0058d0f6, 0x0054cff5, 0x0050cef8, 0x004dcdfa,
+ 0x004bcbfb, 0x004acafb, 0x0048c9fb, 0x0046c7fb, 0x0045c7fa, 0x0043c7fa, 0x0042c6fa,
+ 0x0040c6f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003cc1f8, 0x003cc2f7,
+ 0x0038bff6, 0x0034bbf5, 0x0035bdfd, 0x0037beff, 0x0046bcfc, 0x0082c92c, 0x00a0be02,
+ 0x00b8c420, 0x00d8cf31, 0x00d2d632, 0x00d4d52e, 0x00d7d42a, 0x00cdd725, 0x00e9df2f,
+ 0x00e6dd2a, 0x00e4dc25, 0x00edd922, 0x00e0e220, 0x00ede927, 0x00eae01e, 0x00e4da1c,
+ 0x00ded319, 0x00e5d01a, 0x00ebcd1b, 0x00e5c818, 0x00dec214, 0x00f0bc00, 0x001da5eb,
+ 0x0019a1ff, 0x0016a2f7, 0x0019a2f4, 0x001ea2f1, 0x001aa0f5, 0x00169ff9, 0x00169ef8,
+ 0x00159df8, 0x00159cf8, 0x00149bf8, 0x00139af7, 0x001299f6, 0x001299f6, 0x005ed5f9,
+ 0x0063d6fc, 0x0068d6ff, 0x005fd3fc, 0x0056d0f8, 0x0053cff8, 0x0051cef8, 0x004ecdf9,
+ 0x004bccfb, 0x004acbfb, 0x0048cafb, 0x0047c9fa, 0x0046c8fb, 0x0044c7fa, 0x0043c7fa,
+ 0x0042c6fa, 0x0040c5f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003bc1f9,
+ 0x003bc1f8, 0x0038bff7, 0x0036bdf7, 0x0035bdfa, 0x0034bdfe, 0x0022c3f6, 0x0027bbfc,
+ 0x0053b0b2, 0x009bc606, 0x00c1d322, 0x00d3dd36, 0x00b4ba12, 0x00c4c71f, 0x00c5cf22,
+ 0x00d9d82d, 0x00dfdb30, 0x00dcd52b, 0x00e8d520, 0x00d5d51c, 0x00e8e428, 0x00ece324,
+ 0x00d1ce1f, 0x00d3c51d, 0x00dcc302, 0x00cfc312, 0x00e3c209, 0x00e3be00, 0x0084bf6e,
+ 0x000ca0f6, 0x00129ffd, 0x0018a2f6, 0x0019a1f5, 0x001ba1f4, 0x0018a0f6, 0x00169ff8,
+ 0x00159ef8, 0x00159df8, 0x00149cf7, 0x00139bf7, 0x00129af6, 0x001098f4, 0x001098f4,
+ 0x0065d7fb, 0x005dd4fa, 0x0056d2f8, 0x0053d0f9, 0x0050cff9, 0x004fcef9, 0x004dcdfa,
+ 0x004bcdfa, 0x004accfb, 0x0048cbfb, 0x0047cafb, 0x0046c9fa, 0x0045c8fa, 0x0044c7fa,
+ 0x0043c7fa, 0x0042c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003bc0f9,
+ 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0037bff9, 0x0034bef8, 0x0031bcf7, 0x0033bbf8,
+ 0x0035bbfa, 0x002cbcff, 0x0061c2df, 0x0093cb85, 0x00c5d52b, 0x00cbd82f, 0x00b0bb13,
+ 0x00b5be17, 0x00b9c21b, 0x00c7c826, 0x00c5bf21, 0x00dbc817, 0x00cac819, 0x00dbd722,
+ 0x00ddd61a, 0x00b7bd0d, 0x00c8bd04, 0x00d0c000, 0x00adc951, 0x006cb8b1, 0x0004a3ff,
+ 0x0013a4fb, 0x0021a4f5, 0x001ea3f5, 0x001aa1f6, 0x0019a1f6, 0x0018a0f7, 0x0017a0f7,
+ 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00139cf6, 0x00119af4, 0x000f98f2,
+ 0x000f98f2, 0x005cd5f9, 0x0058d3f8, 0x0053d1f8, 0x0052d0f9, 0x0050cff9, 0x004ecefa,
+ 0x004ccdfa, 0x004accfa, 0x0048ccfa, 0x0047cbfa, 0x0046cafa, 0x0045c9fa, 0x0044c8fa,
+ 0x0043c7fa, 0x0042c7fa, 0x0041c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec2f9, 0x003cc1f9,
+ 0x003bc0f9, 0x003ac0f9, 0x0038bff9, 0x0037bff9, 0x0036bff9, 0x0035bdf6, 0x0034bbf3,
+ 0x0035b9f7, 0x0035b8fb, 0x0022b5ff, 0x002fb5ff, 0x004dbae6, 0x006bbfce, 0x0027b1c5,
+ 0x006cbc7c, 0x008abd49, 0x00a7be15, 0x00b9bf09, 0x00ccc000, 0x00dac43d, 0x00bbca20,
+ 0x00aec73e, 0x0099bc54, 0x005aad8b, 0x0036abc4, 0x0004b3ff, 0x0015a7ff, 0x0021a4ff,
+ 0x0019a0fb, 0x001ba2fa, 0x001da4f9, 0x001ba3f8, 0x001aa1f7, 0x0019a1f7, 0x0018a0f7,
+ 0x0017a0f7, 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00129cf6, 0x00119af5,
+ 0x000f99f3, 0x000f99f3, 0x0053d2f6, 0x0052d1f7, 0x0051d1f8, 0x0050d0f9, 0x004fcffa,
+ 0x004dcefa, 0x004bcdfa, 0x0049ccfa, 0x0047cbfa, 0x0046caf9, 0x0045caf9, 0x0044c9f9,
+ 0x0044c8fa, 0x0043c7fa, 0x0042c6f9, 0x0041c6f9, 0x0040c5fa, 0x003fc4f9, 0x003dc2f9,
+ 0x003cc1f9, 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0036bff9, 0x0035bef8, 0x0036bcf4,
+ 0x0038baf0, 0x0036b8f6, 0x0034b5fc, 0x002cb6f9, 0x0023b7f6, 0x0025b5fa, 0x0028b4ff,
+ 0x0028b6ff, 0x0029b7ff, 0x001fb5ff, 0x0015b2ff, 0x0020aef7, 0x003cb9ff, 0x005acbf0,
+ 0x0042befa, 0x002ab6fc, 0x0012adff, 0x0018acfc, 0x001eacfa, 0x001ea9fd, 0x001ea7ff,
+ 0x001ba8fa, 0x0018a8f4, 0x0018a6f8, 0x0018a4fd, 0x0019a3fa, 0x001aa1f7, 0x0019a1f7,
+ 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf6,
+ 0x00119af5, 0x001099f4, 0x001099f4, 0x0054d1f8, 0x0052d1f8, 0x0051d0f9, 0x004fcff9,
+ 0x004ecffa, 0x004ccefa, 0x004acdf9, 0x0048ccf9, 0x0045cbf9, 0x0045caf9, 0x0044c9f9,
+ 0x0043c8f9, 0x0043c8f9, 0x0042c7f9, 0x0042c6f9, 0x0041c5f9, 0x0040c5fa, 0x003fc4f9,
+ 0x003dc2f9, 0x003bc1f9, 0x003ac0fa, 0x0038bff9, 0x0037bff9, 0x0036bef9, 0x0034bef8,
+ 0x0035bcf6, 0x0035baf5, 0x0034b8f8, 0x0033b6fc, 0x002eb6f9, 0x0029b6f7, 0x0029b5f8,
+ 0x002ab4fa, 0x002ab5fb, 0x002ab5fc, 0x002ab2f6, 0x002aafef, 0x001ba9f6, 0x009bcfd9,
+ 0x006dcfe9, 0x0074c7e4, 0x0080c9dd, 0x0019adfb, 0x001cacf9, 0x001fabf8, 0x001fa9f9,
+ 0x001ea7fb, 0x001ca7f9, 0x001aa7f6, 0x001aa5f8, 0x001aa4fb, 0x001aa3fa, 0x001aa2f8,
+ 0x0019a1f8, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6,
+ 0x00129bf6, 0x00119bf5, 0x00119af5, 0x00119af5, 0x0055d0f9, 0x0053d0fa, 0x0051d0fa,
+ 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8, 0x0043caf8,
+ 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9, 0x0040c5fa,
+ 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9, 0x0035bef9,
+ 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa, 0x002eb6f8,
+ 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x0029b2f9, 0x0028b2fc, 0x0030b2f7,
+ 0x0012a8fe, 0x007fd4e1, 0x0058bbe6, 0x0015aafb, 0x001fadf8, 0x0020acf7, 0x0020aaf5,
+ 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9, 0x001ba3f9,
+ 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7,
+ 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9, 0x0053d0fa,
+ 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8,
+ 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9,
+ 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9,
+ 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa,
+ 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002ab2f8, 0x0029b2fa,
+ 0x002db6f5, 0x001db5f6, 0x00239bff, 0x0020b6f3, 0x000cacfb, 0x001eacf7, 0x001fabf6,
+ 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9,
+ 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7,
+ 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9,
+ 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9,
+ 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9,
+ 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9,
+ 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc,
+ 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002bb2f8,
+ 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5, 0x001dabf6,
+ 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8,
+ 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8,
+ 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5,
+ 0x0055d0f9, 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9,
+ 0x0046ccf9, 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9,
+ 0x0041c6f9, 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa,
+ 0x0038bff9, 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb,
+ 0x0032b8fc, 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7,
+ 0x002bb2f8, 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5,
+ 0x001dabf6, 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8,
+ 0x001ca4f8, 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8,
+ 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5,
+ 0x00129bf5
+ };
+
+#define IMG_WIDTH 64
+#define IMG_HEIGHT 64
+#define FORMAT_SIZE 4
+#define FORMAT PIXEL_FORMAT_XRGB32
+
+static INLINE size_t fuzzyCompare(BYTE b1, BYTE b2)
+{
+ if (b1 > b2)
+ return b1 - b2;
+ return b2 - b1;
+}
+
+static BOOL fuzzyCompareImage(const UINT32* crefImage, const BYTE* img, size_t npixels)
+{
+ size_t totalDelta = 0;
+
+ for (size_t i = 0; i < npixels; i++, crefImage++)
+ {
+ BYTE A = *img++;
+ BYTE R = *img++;
+ BYTE G = *img++;
+ BYTE B = *img++;
+ size_t delta = 0;
+
+ if (A != 0x00)
+ return FALSE;
+
+ delta = fuzzyCompare(R, (*crefImage & 0x00ff0000) >> 16);
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+
+ delta = fuzzyCompare(G, (*crefImage & 0x0000ff00) >> 8);
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+
+ delta = fuzzyCompare(B, (*crefImage & 0x0000ff));
+ if (delta > 1)
+ return FALSE;
+ totalDelta += delta;
+ }
+
+ WLog_DBG("test", "totalDelta=%d (npixels=%d)", totalDelta, npixels);
+ return TRUE;
+}
+
+int TestFreeRDPCodecRemoteFX(int argc, char* argv[])
+{
+ int rc = -1;
+ REGION16 region = { 0 };
+ RFX_CONTEXT* context = NULL;
+ BYTE* dest = NULL;
+ size_t stride = FORMAT_SIZE * IMG_WIDTH;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* use default threading options here, pass zero as
+ * ThreadingFlags */
+ context = rfx_context_new(FALSE);
+ if (!context)
+ goto fail;
+
+ dest = calloc(IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE);
+ if (!dest)
+ goto fail;
+
+ region16_init(&region);
+ if (!rfx_process_message(context, encodeHeaderSample, sizeof(encodeHeaderSample), 0, 0, dest,
+ FORMAT, stride, IMG_HEIGHT, &region))
+ goto fail;
+
+ region16_clear(&region);
+ if (!rfx_process_message(context, encodeDataSample, sizeof(encodeDataSample), 0, 0, dest,
+ FORMAT, stride, IMG_HEIGHT, &region))
+ goto fail;
+ region16_print(&region);
+
+#if 0
+ FILE *f = fopen("/tmp/windows.data", "w");
+ if (f) {
+ fwrite(dest, IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE, f);
+ fclose(f);
+ }
+#endif
+
+ if (!fuzzyCompareImage(srefImage, dest, IMG_WIDTH * IMG_HEIGHT))
+ goto fail;
+
+ rc = 0;
+fail:
+ region16_uninit(&region);
+ rfx_context_free(context);
+ free(dest);
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c
new file mode 100644
index 0000000..5b13532
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c
@@ -0,0 +1,130 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include "../xcrush.h"
+
+static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!";
+
+static const BYTE TEST_BELLS_DATA_XCRUSH[] =
+ "\x12\x00\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62"
+ "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\x2e\x74\x68\x65\x2e\x62"
+ "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2e\x66\x6f\x72\x2e\x74\x68"
+ "\x65";
+
+static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man "
+ "is a piece of the continent, a part of the main; "
+ "if a clod be washed away by the sea, Europe "
+ "is the less, as well as if a promontory were, as"
+ "well as any manner of thy friends or of thine "
+ "own were; any man's death diminishes me, "
+ "because I am involved in mankind. "
+ "And therefore never send to know for whom "
+ "the bell tolls; it tolls for thee.";
+
+static const BYTE TEST_ISLAND_DATA_XCRUSH[] =
+ "\x12\x61\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2"
+ "\xdc\xc8\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6"
+ "\xca\xd8\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65"
+ "\x63\xfc\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2"
+ "\x40\xe0\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b"
+ "\x37\xd7\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2"
+ "\x10\x30\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20"
+ "\x45\x75\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69"
+ "\xcc\x81\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2"
+ "\xb3\x7f\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96"
+ "\xe6\x57\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f"
+ "\xd2\xb6\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49"
+ "\x3b\x99\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96"
+ "\xce\x64\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30"
+ "\xb6\x90\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69"
+ "\x6e\x64\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd"
+ "\xbf\x04\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81"
+ "\x03\xbb\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec"
+ "\xf3\xa7\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80";
+
+static void test_dump(const char* fkt, const void* generated, size_t generated_size,
+ const void* expected, size_t expected_size)
+{
+ printf("[%s] output size mismatch: Actual: %" PRIuz ", Expected: %" PRIuz "\n", fkt,
+ generated_size, expected_size);
+ printf("[%s] Actual\n", fkt);
+ BitDump(fkt, WLOG_INFO, generated, generated_size * 8ull, 0);
+ printf("[%s] Expected\n", fkt);
+ BitDump(fkt, WLOG_INFO, expected, expected_size * 8ull, 0);
+}
+
+static BOOL test_compare(const char* fkt, const void* generated, size_t generated_size,
+ const void* expected, size_t expected_size)
+{
+ if (generated_size != expected_size)
+ {
+ test_dump(fkt, generated, generated_size, expected, expected_size);
+ return FALSE;
+ }
+
+ if (memcmp(generated, expected, generated_size) != 0)
+ {
+ test_dump(fkt, generated, generated_size, expected, expected_size);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_run(const char* fkt, const void* src, UINT32 src_size, const void* expected,
+ size_t expected_size)
+{
+ BOOL rc = FALSE;
+ int status = -1;
+ UINT32 Flags = 0;
+ const BYTE* pDstData = NULL;
+ BYTE OutputBuffer[65536] = { 0 };
+ UINT32 DstSize = sizeof(OutputBuffer);
+ XCRUSH_CONTEXT* xcrush = xcrush_context_new(TRUE);
+ if (!xcrush)
+ return -1;
+ status = xcrush_compress(xcrush, src, src_size, OutputBuffer, &pDstData, &DstSize, &Flags);
+ printf("[%s] status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", fkt, status, Flags,
+ DstSize);
+
+ rc = test_compare(fkt, pDstData, DstSize, expected, expected_size);
+
+ xcrush_context_free(xcrush);
+ return rc;
+}
+
+struct test_argument
+{
+ const char* name;
+ const void* src;
+ UINT32 src_size;
+ const void* expected;
+ size_t expected_size;
+};
+
+static const struct test_argument tests[] = {
+ { "XCrushCompressIsland", TEST_ISLAND_DATA, sizeof(TEST_ISLAND_DATA) - 1,
+ TEST_ISLAND_DATA_XCRUSH, sizeof(TEST_ISLAND_DATA_XCRUSH) - 1 }
+#if 0
+ ,{ "XCrushCompressBells", TEST_BELLS_DATA, sizeof(TEST_BELLS_DATA) - 1, TEST_BELLS_DATA_XCRUSH,
+ sizeof(TEST_BELLS_DATA_XCRUSH) - 1 }
+#endif
+};
+
+int TestFreeRDPCodecXCrush(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (size_t x = 0; x < ARRAYSIZE(tests); x++)
+ {
+ const struct test_argument* arg = &tests[x];
+
+ if (!test_run(arg->name, arg->src, arg->src_size, arg->expected, arg->expected_size))
+ rc = -1;
+ }
+
+ return rc;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c
new file mode 100644
index 0000000..68db553
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c
@@ -0,0 +1,274 @@
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/zgfx.h>
+#include <freerdp/log.h>
+
+/* Sample from [MS-RDPEGFX] */
+static const BYTE TEST_FOX_DATA[] = "The quick brown "
+ "fox jumps over t"
+ "he lazy dog";
+
+static const BYTE TEST_FOX_DATA_SINGLE[] =
+ "\xE0\x04\x54\x68\x65\x20\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77"
+ "\x6E\x20\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x72"
+ "\x20\x74\x68\x65\x20\x6C\x61\x7A\x79\x20\x64\x6F\x67";
+
+static const BYTE TEST_FOX_DATA_MULTIPART[] =
+ "\xE1\x03\x00\x2B\x00\x00\x00\x11\x00\x00\x00\x04\x54\x68\x65\x20"
+ "\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77\x6E\x20\x0E\x00\x00\x00"
+ "\x04\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x10\x00"
+ "\x00\x00\x24\x39\x08\x0E\x91\xF8\xD8\x61\x3D\x1E\x44\x06\x43\x79"
+ "\x9C\x02";
+
+static int test_ZGfxCompressFox(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA_SINGLE) - 1;
+ status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxCompressFox: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
+ "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA_SINGLE, DstSize) != 0)
+ {
+ printf("test_ZGfxCompressFox: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA_SINGLE, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxDecompressFoxSingle(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA_SINGLE) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA_SINGLE;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA) - 1;
+ status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxDecompressFoxMultipart(void)
+{
+ int rc = -1;
+ int status = 0;
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ SrcSize = sizeof(TEST_FOX_DATA_MULTIPART) - 1;
+ pSrcData = (const BYTE*)TEST_FOX_DATA_MULTIPART;
+ Flags = 0;
+ expectedSize = sizeof(TEST_FOX_DATA) - 1;
+ status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+static int test_ZGfxCompressConsistent(void)
+{
+ int rc = -1;
+ int status = 0;
+
+ UINT32 Flags = 0;
+ const BYTE* pSrcData = NULL;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize2 = 0;
+ BYTE* pDstData2 = NULL;
+ ZGFX_CONTEXT* zgfx = NULL;
+ UINT32 expectedSize = 0;
+ BYTE BigBuffer[65536];
+ memset(BigBuffer, 0xaa, sizeof(BigBuffer));
+ memcpy(BigBuffer, TEST_FOX_DATA, sizeof(TEST_FOX_DATA) - 1);
+ zgfx = zgfx_context_new(TRUE);
+
+ if (!zgfx)
+ return -1;
+
+ /* Compress */
+ expectedSize = SrcSize = sizeof(BigBuffer);
+ pSrcData = (const BYTE*)BigBuffer;
+ Flags = 0;
+ status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData2, &DstSize2, &Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Compress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize2);
+ /* Decompress */
+ status = zgfx_decompress(zgfx, pDstData2, DstSize2, &pDstData, &DstSize, Flags);
+
+ if (status < 0)
+ goto fail;
+
+ printf("Decompress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize);
+
+ if (DstSize != expectedSize)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32
+ ", Expected: %" PRIu32 "\n",
+ DstSize, expectedSize);
+ goto fail;
+ }
+
+ if (memcmp(pDstData, BigBuffer, DstSize) != 0)
+ {
+ printf("test_ZGfxDecompressFoxSingle: output mismatch\n");
+ printf("Actual\n");
+ BitDump(__func__, WLOG_INFO, pDstData, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, pDstData + DstSize - 64, 64 * 8, 0);
+ printf("Expected\n");
+ BitDump(__func__, WLOG_INFO, BigBuffer, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, BigBuffer + DstSize - 64, 64 * 8, 0);
+ printf("Middle Result\n");
+ BitDump(__func__, WLOG_INFO, pDstData2, 64 * 8, 0);
+ printf("...\n");
+ BitDump(__func__, WLOG_INFO, pDstData2 + DstSize2 - 64, 64 * 8, 0);
+ goto fail;
+ }
+
+ rc = 0;
+fail:
+ free(pDstData);
+ free(pDstData2);
+ zgfx_context_free(zgfx);
+ return rc;
+}
+
+int TestFreeRDPCodecZGfx(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (test_ZGfxCompressFox() < 0)
+ return -1;
+
+ if (test_ZGfxDecompressFoxSingle() < 0)
+ return -1;
+
+ if (test_ZGfxDecompressFoxMultipart() < 0)
+ return -1;
+
+ if (test_ZGfxCompressConsistent() < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/libfreerdp/codec/test/TestFreeRDPRegion.c b/libfreerdp/codec/test/TestFreeRDPRegion.c
new file mode 100644
index 0000000..6f75320
--- /dev/null
+++ b/libfreerdp/codec/test/TestFreeRDPRegion.c
@@ -0,0 +1,863 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Hardening <contact@hardening-consulting.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.
+ */
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/codec/region.h>
+
+static BOOL compareRectangles(const RECTANGLE_16* src1, const RECTANGLE_16* src2, int nb)
+{
+ for (int i = 0; i < nb; i++, src1++, src2++)
+ {
+ if (memcmp(src1, src2, sizeof(RECTANGLE_16)))
+ {
+ fprintf(stderr,
+ "expecting rect %d (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16
+ ") and have (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")\n",
+ i, src2->left, src2->top, src2->right, src2->bottom, src1->left, src1->top,
+ src1->right, src1->bottom);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int test_basic(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ /* R1 + R2 ==> disjointed rects */
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r1_r2[] = { { 0, 101, 200, 201 }, { 150, 301, 250, 401 } };
+ /* r1 */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 1 || memcmp(rects, &r1, sizeof(RECTANGLE_16)))
+ goto out;
+
+ /* r1 + r2 */
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2, nbRects))
+ goto out;
+
+ /* clear region */
+ region16_clear(&region);
+ region16_rects(&region, &nbRects);
+
+ if (nbRects)
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r3(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r1_r3[] = { { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 } };
+ region16_init(&region);
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || +-+------+ +-----+--------+
+ * || | r3 | | |
+ * |+---+ | ====> +-----+--------+
+ * | | | | |
+ * | +--------+ +--------+
+ */
+
+ /* R1 + R3 */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects))
+ goto out;
+
+ /* R3 + R1 */
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r9_r10(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ /*
+ * +===============================================================
+ * |
+ * | +---+ +---+
+ * |+--|r10|-+ +--+---+-+
+ * ||r9| | | | |
+ * || | | | | |
+ * || | | | =====> | |
+ * || | | | | |
+ * || | | | | |
+ * |+--| |-+ +--+---+-+
+ * | +---+ +---+
+ */
+ RECTANGLE_16 r9 = { 0, 100, 400, 200 };
+ RECTANGLE_16 r10 = { 200, 0, 300, 300 };
+ RECTANGLE_16 r9_r10[] = {
+ { 200, 0, 300, 100 },
+ { 0, 100, 400, 200 },
+ { 200, 200, 300, 300 },
+ };
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r9))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r10))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r9_r10, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r5(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r5 = { 150, 121, 300, 131 };
+ RECTANGLE_16 r1_r5[] = { { 0, 101, 200, 121 }, { 0, 121, 300, 131 }, { 0, 131, 200, 201 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+
+ * || r1 | | |
+ * || +--+----+ +--------+----+
+ * || | r5 | =====> | |
+ * || +-------+ +--------+----+
+ * || | | |
+ * |+--------+ +--------+
+ * |
+ *
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r5))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r5, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r6(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r6 = { 150, 121, 170, 131 };
+ region16_init(&region);
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+
+ * || r1 | | |
+ * || +--+ | | |
+ * || |r6| | =====> | |
+ * || +--+ | | |
+ * || | | |
+ * |+--------+ +--------+
+ * |
+ */
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r6))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, &r1, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r2_r4(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r4 = { 150, 251, 250, 301 };
+ RECTANGLE_16 r1_r2_r4[] = { { 0, 101, 200, 201 }, { 150, 251, 250, 401 } };
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || | | |
+ * || | | |
+ * |+-----+ ====> +-----+
+ * |
+ * | +--------+ +--------+
+ * | | r4 | | |
+ * | +--------+ | |
+ * | | r2 | | |
+ * | | | | |
+ * | +--------+ +--------+
+ *
+ */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r4))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2_r4, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r7_r8(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r7 = { 300, 101, 500, 201 };
+ RECTANGLE_16 r8 = { 150, 121, 400, 131 };
+ RECTANGLE_16 r1_r7_r8[] = {
+ { 0, 101, 200, 121 }, { 300, 101, 500, 121 }, { 0, 121, 500, 131 },
+ { 0, 131, 200, 201 }, { 300, 131, 500, 201 },
+ };
+ /*
+ * +===============================================================
+ * |
+ * |+--------+ +--------+ +--------+ +--------+
+ * || r1 | | r7 | | | | |
+ * || +------------+ | +--------+---+--------+
+ * || | r8 | | =====> | |
+ * || +------------+ | +--------+---+--------+
+ * || | | | | | | |
+ * |+--------+ +--------+ +--------+ +--------+
+ * |
+ */
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ region16_clear(&region);
+
+ if (!region16_union_rect(&region, &region, &r8))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r7))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_r2_r3_r4(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r2 = { 150, 301, 250, 401 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r4 = { 150, 251, 250, 301 };
+ RECTANGLE_16 r1_r2_r3[] = {
+ { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 }, { 150, 301, 250, 401 }
+ };
+ RECTANGLE_16 r1_r2_r3_r4[] = { { 0, 101, 200, 151 },
+ { 0, 151, 250, 201 },
+ { 150, 201, 250, 401 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || r1 | | |
+ * || +-+------+ +-----+--------+
+ * || | r3 | | |
+ * |+---+ | ====> +-----+--------+
+ * | | | | |
+ * | +--------+ +--------+
+ * | +--------+ +--------+
+ * | | r2 | | |
+ * | | | | |
+ * | +--------+ +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 4 || !compareRectangles(rects, r1_r2_r3, 4))
+ goto out;
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+ +-----+
+ * || | | |
+ * |+-----+--------+ +-----+--------+
+ * || | ==> | |
+ * |+-----+--------+ +-----+--------+
+ * | | | | |
+ * | +--------+ | |
+ * | | + r4 | | |
+ * | +--------+ | |
+ * | | | | |
+ * | | | | |
+ * | +--------+ +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r4))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3_r4, 3))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_from_weston(void)
+{
+ /*
+ * 0: 0,0 -> 640,32 (w=640 h=32)
+ * 1: 236,169 -> 268,201 (w=32 h=32)
+ * 2: 246,258 -> 278,290 (w=32 h=32)
+ */
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 0, 640, 32 };
+ RECTANGLE_16 r2 = { 236, 169, 268, 201 };
+ RECTANGLE_16 r3 = { 246, 258, 278, 290 };
+ RECTANGLE_16 r1_r2_r3[] = { { 0, 0, 640, 32 }, { 236, 169, 268, 201 }, { 246, 258, 278, 290 } };
+ region16_init(&region);
+
+ /*
+ * +===============================================================
+ * |+-------------------------------------------------------------+
+ * || r1 |
+ * |+-------------------------------------------------------------+
+ * |
+ * | +---------------+
+ * | | r2 |
+ * | +---------------+
+ * |
+ * | +---------------+
+ * | | r3 |
+ * | +---------------+
+ * |
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r2))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&region, &nbRects);
+
+ if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3, 3))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_r1_inter_r3(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r1_inter_r3[] = {
+ { 150, 151, 200, 201 },
+ };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+
+ * || r1 |
+ * || +-+------+ +-+
+ * || | r3 | r1&r3 | |
+ * |+---+ | ====> +-+
+ * | | |
+ * | +--------+
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_intersects_rect(&region, &r3))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &r3))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, r1_inter_r3, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ region16_uninit(&intersection);
+ return retCode;
+}
+
+static int test_r1_r3_inter_r11(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 r1 = { 0, 101, 200, 201 };
+ RECTANGLE_16 r3 = { 150, 151, 250, 251 };
+ RECTANGLE_16 r11 = { 170, 151, 600, 301 };
+ RECTANGLE_16 r1_r3_inter_r11[] = {
+ { 170, 151, 250, 251 },
+ };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * +===============================================================
+ * |
+ * |+-----+
+ * || |
+ * || +------+
+ * || r1+r3 | (r1+r3) & r11
+ * || +----------------+ +--------+
+ * |+---+ | | | ====> | |
+ * | | | | | | |
+ * | | | | | | |
+ * | +-|------+ | +--------+
+ * | | r11 |
+ * | +----------------+
+ *
+ *
+ * R1+R3 is made of 3 bands, R11 overlap the second and the third band. The
+ * intersection is made of two band that must be reassembled to give only
+ * one
+ */
+ if (!region16_union_rect(&region, &region, &r1))
+ goto out;
+
+ if (!region16_union_rect(&region, &region, &r3))
+ goto out;
+
+ if (!region16_intersects_rect(&region, &r11))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &r11))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 1 || !compareRectangles(rects, r1_r3_inter_r11, nbRects))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_norbert_case(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 inRectangles[5] = { { 1680, 0, 1920, 242 },
+ { 294, 242, 971, 776 },
+ { 1680, 242, 1920, 776 },
+ { 1680, 776, 1920, 1036 },
+ { 2, 1040, 53, 1078 } };
+ RECTANGLE_16 screenRect = { 0, 0, 1920, 1080 };
+ RECTANGLE_16 expected_inter_extents = { 2, 0, 1920, 1078 };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /*
+ * Consider following as a screen with resolution 1920*1080
+ * | | | | | | |
+ * | |2 |53 |294 |971 |1680 |
+ * | | | | | | |
+ * 0 +=+======================================+======+
+ * | | | |
+ * | | R[0]|
+ * 242 | +-----------+ +------+
+ * | | | | | |
+ * | | | | |
+ * | | R[1]| | R[2]|
+ * 776 | | +-----------+ +------+
+ * | | |
+ * | | R[3]|
+ * 1036 | | +------+
+ * 1040 | +----+
+ * | |R[4]| Union of R[0-4]|
+ * 1078 | +----+ - - - - - - - -+
+ * 1080 |
+ *
+ *
+ * The result is union of R[0] - R[4].
+ * After intersected with the full screen rect, the
+ * result should keep the same.
+ */
+ for (int i = 0; i < 5; i++)
+ {
+ if (!region16_union_rect(&region, &region, &inRectangles[i]))
+ goto out;
+ }
+
+ if (!compareRectangles(region16_extents(&region), &expected_inter_extents, 1))
+ goto out;
+
+ if (!region16_intersect_rect(&intersection, &region, &screenRect))
+ goto out;
+
+ rects = region16_rects(&intersection, &nbRects);
+
+ if (!rects || nbRects != 5 || !compareRectangles(rects, inRectangles, nbRects))
+ goto out;
+
+ if (!compareRectangles(region16_extents(&intersection), &expected_inter_extents, 1))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_norbert2_case(void)
+{
+ REGION16 region;
+ int retCode = -1;
+ const RECTANGLE_16* rects = NULL;
+ UINT32 nbRects = 0;
+ RECTANGLE_16 rect1 = { 464, 696, 476, 709 };
+ RECTANGLE_16 rect2 = { 0, 0, 1024, 32 };
+ region16_init(&region);
+
+ if (!region16_union_rect(&region, &region, &rect1))
+ {
+ fprintf(stderr, "%s: Error 1 - region16_union_rect failed\n", __func__);
+ goto out;
+ }
+
+ if (!(rects = region16_rects(&region, &nbRects)))
+ {
+ fprintf(stderr, "%s: Error 2 - region16_rects failed\n", __func__);
+ goto out;
+ }
+
+ if (nbRects != 1)
+ {
+ fprintf(stderr, "%s: Error 3 - expected nbRects == 1 but got %" PRIu32 "\n", __func__,
+ nbRects);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[0], &rect1, 1))
+ {
+ fprintf(stderr, "%s: Error 4 - compare failed\n", __func__);
+ goto out;
+ }
+
+ if (!region16_union_rect(&region, &region, &rect2))
+ {
+ fprintf(stderr, "%s: Error 5 - region16_union_rect failed\n", __func__);
+ goto out;
+ }
+
+ if (!(rects = region16_rects(&region, &nbRects)))
+ {
+ fprintf(stderr, "%s: Error 6 - region16_rects failed\n", __func__);
+ goto out;
+ }
+
+ if (nbRects != 2)
+ {
+ fprintf(stderr, "%s: Error 7 - expected nbRects == 2 but got %" PRIu32 "\n", __func__,
+ nbRects);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[0], &rect2, 1))
+ {
+ fprintf(stderr, "%s: Error 8 - compare failed\n", __func__);
+ goto out;
+ }
+
+ if (!compareRectangles(&rects[1], &rect1, 1))
+ {
+ fprintf(stderr, "%s: Error 9 - compare failed\n", __func__);
+ goto out;
+ }
+
+ retCode = 0;
+out:
+ region16_uninit(&region);
+ return retCode;
+}
+
+static int test_empty_rectangle(void)
+{
+ REGION16 region;
+ REGION16 intersection;
+ int retCode = -1;
+ RECTANGLE_16 emptyRectangles[3] = { { 0, 0, 0, 0 }, { 10, 10, 10, 11 }, { 10, 10, 11, 10 } };
+ RECTANGLE_16 firstRect = { 0, 0, 100, 100 };
+ RECTANGLE_16 anotherRect = { 100, 100, 200, 200 };
+ RECTANGLE_16 expected_inter_extents = { 0, 0, 0, 0 };
+ region16_init(&region);
+ region16_init(&intersection);
+
+ /* Check for empty rectangles */
+ for (int i = 0; i < 3; i++)
+ {
+ if (!rectangle_is_empty(&emptyRectangles[i]))
+ goto out;
+ }
+
+ /* Check for non-empty rectangles */
+ if (rectangle_is_empty(&firstRect))
+ goto out;
+
+ /* Intersect 2 non-intersect rectangle, result should be empty */
+ if (!region16_union_rect(&region, &region, &firstRect))
+ goto out;
+
+ if (!region16_intersect_rect(&region, &region, &anotherRect))
+ goto out;
+
+ if (!compareRectangles(region16_extents(&region), &expected_inter_extents, 1))
+ goto out;
+
+ if (!region16_is_empty(&region))
+ goto out;
+
+ if (!rectangle_is_empty(region16_extents(&intersection)))
+ goto out;
+
+ retCode = 0;
+out:
+ region16_uninit(&intersection);
+ region16_uninit(&region);
+ return retCode;
+}
+
+typedef int (*TestFunction)(void);
+struct UnitaryTest
+{
+ const char* name;
+ TestFunction func;
+};
+
+static struct UnitaryTest tests[] = { { "Basic trivial tests", test_basic },
+ { "R1+R3 and R3+R1", test_r1_r3 },
+ { "R1+R5", test_r1_r5 },
+ { "R1+R6", test_r1_r6 },
+ { "R9+R10", test_r9_r10 },
+ { "R1+R2+R4", test_r1_r2_r4 },
+ { "R1+R7+R8 in many orders", test_r1_r7_r8 },
+ { "R1+R2+R3+R4", test_r1_r2_r3_r4 },
+ { "data from weston", test_from_weston },
+ { "R1 & R3", test_r1_inter_r3 },
+ { "(R1+R3)&R11 (band merge)", test_r1_r3_inter_r11 },
+ { "norbert's case", test_norbert_case },
+ { "norbert's case 2", test_norbert2_case },
+ { "empty rectangle case", test_empty_rectangle },
+
+ { NULL, NULL } };
+
+int TestFreeRDPRegion(int argc, char* argv[])
+{
+ int testNb = 0;
+ int retCode = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ for (int i = 0; tests[i].func; i++)
+ {
+ testNb++;
+ fprintf(stderr, "%d: %s\n", testNb, tests[i].name);
+ retCode = tests[i].func();
+
+ if (retCode < 0)
+ break;
+ }
+
+ if (retCode < 0)
+ fprintf(stderr, "failed for test %d\n", testNb);
+
+ return retCode;
+}
diff --git a/libfreerdp/codec/test/progressive.bmp b/libfreerdp/codec/test/progressive.bmp
new file mode 100644
index 0000000..1b19c3b
--- /dev/null
+++ b/libfreerdp/codec/test/progressive.bmp
Binary files differ
diff --git a/libfreerdp/codec/test/rfx.bmp b/libfreerdp/codec/test/rfx.bmp
new file mode 100644
index 0000000..2b66651
--- /dev/null
+++ b/libfreerdp/codec/test/rfx.bmp
Binary files differ
diff --git a/libfreerdp/codec/test/test01.bmp b/libfreerdp/codec/test/test01.bmp
new file mode 100644
index 0000000..fea498f
--- /dev/null
+++ b/libfreerdp/codec/test/test01.bmp
Binary files differ
diff --git a/libfreerdp/codec/xcrush.c b/libfreerdp/codec/xcrush.c
new file mode 100644
index 0000000..eed2a86
--- /dev/null
+++ b/libfreerdp/codec/xcrush.c
@@ -0,0 +1,1175 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XCrush (RDP6.1) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <winpr/assert.h>
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include "xcrush.h"
+
+#define TAG FREERDP_TAG("codec")
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ UINT32 MatchOffset;
+ UINT32 ChunkOffset;
+ UINT32 MatchLength;
+} XCRUSH_MATCH_INFO;
+
+typedef struct
+{
+ UINT32 offset;
+ UINT32 next;
+} XCRUSH_CHUNK;
+
+typedef struct
+{
+ UINT16 seed;
+ UINT16 size;
+} XCRUSH_SIGNATURE;
+
+typedef struct
+{
+ UINT16 MatchLength;
+ UINT16 MatchOutputOffset;
+ UINT32 MatchHistoryOffset;
+} RDP61_MATCH_DETAILS;
+
+typedef struct
+{
+ BYTE Level1ComprFlags;
+ BYTE Level2ComprFlags;
+ UINT16 MatchCount;
+ RDP61_MATCH_DETAILS* MatchDetails;
+ BYTE* Literals;
+} RDP61_COMPRESSED_DATA;
+
+#pragma pack(pop)
+
+struct s_XCRUSH_CONTEXT
+{
+ ALIGN64 BOOL Compressor;
+ ALIGN64 MPPC_CONTEXT* mppc;
+ ALIGN64 BYTE* HistoryPtr;
+ ALIGN64 UINT32 HistoryOffset;
+ ALIGN64 UINT32 HistoryBufferSize;
+ ALIGN64 BYTE HistoryBuffer[2000000];
+ ALIGN64 BYTE BlockBuffer[16384];
+ ALIGN64 UINT32 CompressionFlags;
+ ALIGN64 UINT32 SignatureIndex;
+ ALIGN64 UINT32 SignatureCount;
+ ALIGN64 XCRUSH_SIGNATURE Signatures[1000];
+ ALIGN64 UINT32 ChunkHead;
+ ALIGN64 UINT32 ChunkTail;
+ ALIGN64 XCRUSH_CHUNK Chunks[65534];
+ ALIGN64 UINT16 NextChunks[65536];
+ ALIGN64 UINT32 OriginalMatchCount;
+ ALIGN64 UINT32 OptimizedMatchCount;
+ ALIGN64 XCRUSH_MATCH_INFO OriginalMatches[1000];
+ ALIGN64 XCRUSH_MATCH_INFO OptimizedMatches[1000];
+};
+
+//#define DEBUG_XCRUSH 1
+#if defined(DEBUG_XCRUSH)
+static const char* xcrush_get_level_2_compression_flags_string(UINT32 flags)
+{
+ flags &= 0xE0;
+
+ if (flags == 0)
+ return "PACKET_UNCOMPRESSED";
+ else if (flags == PACKET_COMPRESSED)
+ return "PACKET_COMPRESSED";
+ else if (flags == PACKET_AT_FRONT)
+ return "PACKET_AT_FRONT";
+ else if (flags == PACKET_FLUSHED)
+ return "PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT";
+ else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_FLUSHED";
+ else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_AT_FRONT | PACKET_FLUSHED";
+ else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED))
+ return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED";
+
+ return "PACKET_UNKNOWN";
+}
+
+static const char* xcrush_get_level_1_compression_flags_string(UINT32 flags)
+{
+ flags &= 0x17;
+
+ if (flags == 0)
+ return "L1_UNKNOWN";
+ else if (flags == L1_PACKET_AT_FRONT)
+ return "L1_PACKET_AT_FRONT";
+ else if (flags == L1_NO_COMPRESSION)
+ return "L1_NO_COMPRESSION";
+ else if (flags == L1_COMPRESSED)
+ return "L1_COMPRESSED";
+ else if (flags == L1_INNER_COMPRESSION)
+ return "L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED))
+ return "L1_PACKET_AT_FRONT | L1_COMPRESSED";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION";
+ else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED))
+ return "L1_NO_COMPRESSION | L1_COMPRESSED";
+ else if (flags == (L1_NO_COMPRESSION | L1_INNER_COMPRESSION))
+ return "L1_NO_COMPRESSION | L1_INNER_COMPRESSION";
+ else if (flags == (L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION";
+ else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED";
+ else if (flags ==
+ (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION))
+ return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION";
+
+ return "L1_UNKNOWN";
+}
+#endif
+
+static UINT32 xcrush_update_hash(const BYTE* data, UINT32 size)
+{
+ const BYTE* end = NULL;
+ UINT32 seed = 5381; /* same value as in djb2 */
+
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(size >= 4);
+
+ if (size > 32)
+ {
+ size = 32;
+ seed = 5413;
+ }
+
+ end = &data[size - 4];
+
+ while (data < end)
+ {
+ seed += (data[3] ^ data[0]) + (data[1] << 8);
+ data += 4;
+ }
+
+ return (UINT16)seed;
+}
+
+static int xcrush_append_chunk(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32* beg, UINT32 end)
+{
+ UINT32 size = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(beg);
+
+ if (xcrush->SignatureIndex >= xcrush->SignatureCount)
+ return 0;
+
+ size = end - *beg;
+
+ if (size > 65535)
+ return 0;
+
+ if (size >= 15)
+ {
+ UINT32 seed = xcrush_update_hash(&data[*beg], (UINT16)size);
+ xcrush->Signatures[xcrush->SignatureIndex].size = size;
+ xcrush->Signatures[xcrush->SignatureIndex].seed = seed;
+ xcrush->SignatureIndex++;
+ *beg = end;
+ }
+
+ return 1;
+}
+
+static int xcrush_compute_chunks(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size,
+ UINT32* pIndex)
+{
+ UINT32 offset = 0;
+ UINT32 rotation = 0;
+ UINT32 accumulator = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(pIndex);
+
+ *pIndex = 0;
+ xcrush->SignatureIndex = 0;
+
+ if (size < 128)
+ return 0;
+
+ for (UINT32 i = 0; i < 32; i++)
+ {
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i] ^ rotation;
+ }
+
+ for (UINT32 i = 0; i < size - 64; i++)
+ {
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+
+ i++;
+ rotation = _rotl(accumulator, 1);
+ accumulator = data[i + 32] ^ data[i] ^ rotation;
+
+ if (!(accumulator & 0x7F))
+ {
+ if (!xcrush_append_chunk(xcrush, data, &offset, i + 32))
+ return 0;
+ }
+ }
+
+ if ((size == offset) || xcrush_append_chunk(xcrush, data, &offset, size))
+ {
+ *pIndex = xcrush->SignatureIndex;
+ return 1;
+ }
+
+ return 0;
+}
+
+static UINT32 xcrush_compute_signatures(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size)
+{
+ UINT32 index = 0;
+
+ if (xcrush_compute_chunks(xcrush, data, size, &index))
+ return index;
+
+ return 0;
+}
+
+static void xcrush_clear_hash_table_range(XCRUSH_CONTEXT* xcrush, UINT32 beg, UINT32 end)
+{
+ WINPR_ASSERT(xcrush);
+
+ for (UINT32 index = 0; index < 65536; index++)
+ {
+ if (xcrush->NextChunks[index] >= beg)
+ {
+ if (xcrush->NextChunks[index] <= end)
+ {
+ xcrush->NextChunks[index] = 0;
+ }
+ }
+ }
+
+ for (UINT32 index = 0; index < 65534; index++)
+ {
+ if (xcrush->Chunks[index].next >= beg)
+ {
+ if (xcrush->Chunks[index].next <= end)
+ {
+ xcrush->Chunks[index].next = 0;
+ }
+ }
+ }
+}
+
+static int xcrush_find_next_matching_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_CHUNK* chunk,
+ XCRUSH_CHUNK** pNextChunk)
+{
+ UINT32 index = 0;
+ XCRUSH_CHUNK* next = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ if (!chunk)
+ return -4001; /* error */
+
+ if (chunk->next)
+ {
+ index = (chunk - xcrush->Chunks) / sizeof(XCRUSH_CHUNK);
+
+ if (index >= 65534)
+ return -4002; /* error */
+
+ if ((index < xcrush->ChunkHead) || (chunk->next >= xcrush->ChunkHead))
+ {
+ if (chunk->next >= 65534)
+ return -4003; /* error */
+
+ next = &xcrush->Chunks[chunk->next];
+ }
+ }
+
+ WINPR_ASSERT(pNextChunk);
+ *pNextChunk = next;
+ return 1;
+}
+
+static int xcrush_insert_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_SIGNATURE* signature, UINT32 offset,
+ XCRUSH_CHUNK** pPrevChunk)
+{
+ UINT32 seed = 0;
+ UINT32 index = 0;
+
+ WINPR_ASSERT(xcrush);
+
+ if (xcrush->ChunkHead >= 65530)
+ {
+ xcrush->ChunkHead = 1;
+ xcrush->ChunkTail = 1;
+ }
+
+ if (xcrush->ChunkHead >= xcrush->ChunkTail)
+ {
+ xcrush_clear_hash_table_range(xcrush, xcrush->ChunkTail, xcrush->ChunkTail + 10000);
+ xcrush->ChunkTail += 10000;
+ }
+
+ index = xcrush->ChunkHead++;
+
+ if (xcrush->ChunkHead >= 65534)
+ return -3001; /* error */
+
+ xcrush->Chunks[index].offset = offset;
+ seed = signature->seed;
+
+ if (seed >= 65536)
+ return -3002; /* error */
+
+ if (xcrush->NextChunks[seed])
+ {
+ if (xcrush->NextChunks[seed] >= 65534)
+ return -3003; /* error */
+
+ WINPR_ASSERT(pPrevChunk);
+ *pPrevChunk = &xcrush->Chunks[xcrush->NextChunks[seed]];
+ }
+
+ xcrush->Chunks[index].next = xcrush->NextChunks[seed] & 0xFFFF;
+ xcrush->NextChunks[seed] = index;
+ return 1;
+}
+
+static int xcrush_find_match_length(XCRUSH_CONTEXT* xcrush, UINT32 MatchOffset, UINT32 ChunkOffset,
+ UINT32 HistoryOffset, UINT32 SrcSize, UINT32 MaxMatchLength,
+ XCRUSH_MATCH_INFO* MatchInfo)
+{
+ UINT32 MatchSymbol = 0;
+ UINT32 ChunkSymbol = 0;
+ BYTE* ChunkBuffer = NULL;
+ BYTE* MatchBuffer = NULL;
+ BYTE* MatchStartPtr = NULL;
+ BYTE* ForwardChunkPtr = NULL;
+ BYTE* ReverseChunkPtr = NULL;
+ BYTE* ForwardMatchPtr = NULL;
+ BYTE* ReverseMatchPtr = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 ReverseMatchLength = 0;
+ UINT32 ForwardMatchLength = 0;
+ UINT32 TotalMatchLength = 0;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 HistoryBufferSize = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(MatchInfo);
+
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryBufferSize = xcrush->HistoryBufferSize;
+ HistoryBufferEnd = &HistoryBuffer[HistoryOffset + SrcSize];
+
+ if (MatchOffset > HistoryBufferSize)
+ return -2001; /* error */
+
+ MatchBuffer = &HistoryBuffer[MatchOffset];
+
+ if (ChunkOffset > HistoryBufferSize)
+ return -2002; /* error */
+
+ ChunkBuffer = &HistoryBuffer[ChunkOffset];
+
+ if (MatchOffset == ChunkOffset)
+ return -2003; /* error */
+
+ if (MatchBuffer < HistoryBuffer)
+ return -2004; /* error */
+
+ if (ChunkBuffer < HistoryBuffer)
+ return -2005; /* error */
+
+ ForwardMatchPtr = &HistoryBuffer[MatchOffset];
+ ForwardChunkPtr = &HistoryBuffer[ChunkOffset];
+
+ if ((&MatchBuffer[MaxMatchLength + 1] < HistoryBufferEnd) &&
+ (MatchBuffer[MaxMatchLength + 1] != ChunkBuffer[MaxMatchLength + 1]))
+ {
+ return 0;
+ }
+
+ while (1)
+ {
+ MatchSymbol = *ForwardMatchPtr++;
+ ChunkSymbol = *ForwardChunkPtr++;
+
+ if (MatchSymbol != ChunkSymbol)
+ break;
+
+ if (ForwardMatchPtr > HistoryBufferEnd)
+ break;
+
+ ForwardMatchLength++;
+ }
+
+ ReverseMatchPtr = MatchBuffer - 1;
+ ReverseChunkPtr = ChunkBuffer - 1;
+
+ while ((ReverseMatchPtr > &HistoryBuffer[HistoryOffset]) && (ReverseChunkPtr > HistoryBuffer) &&
+ (*ReverseMatchPtr == *ReverseChunkPtr))
+ {
+ ReverseMatchLength++;
+ ReverseMatchPtr--;
+ ReverseChunkPtr--;
+ }
+
+ MatchStartPtr = MatchBuffer - ReverseMatchLength;
+ TotalMatchLength = ReverseMatchLength + ForwardMatchLength;
+
+ if (TotalMatchLength < 11)
+ return 0;
+
+ if (MatchStartPtr < HistoryBuffer)
+ return -2006; /* error */
+
+ MatchInfo->MatchOffset = MatchStartPtr - HistoryBuffer;
+ MatchInfo->ChunkOffset = ChunkBuffer - ReverseMatchLength - HistoryBuffer;
+ MatchInfo->MatchLength = TotalMatchLength;
+ return (int)TotalMatchLength;
+}
+
+static int xcrush_find_all_matches(XCRUSH_CONTEXT* xcrush, UINT32 SignatureIndex,
+ UINT32 HistoryOffset, UINT32 SrcOffset, UINT32 SrcSize)
+{
+ UINT32 j = 0;
+ int status = 0;
+ UINT32 ChunkIndex = 0;
+ UINT32 ChunkCount = 0;
+ XCRUSH_CHUNK* chunk = NULL;
+ UINT32 MatchLength = 0;
+ UINT32 MaxMatchLength = 0;
+ UINT32 PrevMatchEnd = 0;
+ XCRUSH_SIGNATURE* Signatures = NULL;
+ XCRUSH_MATCH_INFO MaxMatchInfo = { 0 };
+
+ WINPR_ASSERT(xcrush);
+
+ Signatures = xcrush->Signatures;
+
+ for (UINT32 i = 0; i < SignatureIndex; i++)
+ {
+ XCRUSH_MATCH_INFO MatchInfo = { 0 };
+ UINT32 offset = SrcOffset + HistoryOffset;
+
+ if (!Signatures[i].size)
+ return -1001; /* error */
+
+ status = xcrush_insert_chunk(xcrush, &Signatures[i], offset, &chunk);
+
+ if (status < 0)
+ return status;
+
+ if (chunk && (SrcOffset + HistoryOffset + Signatures[i].size >= PrevMatchEnd))
+ {
+ ChunkCount = 0;
+ MaxMatchLength = 0;
+
+ while (chunk)
+ {
+ if ((chunk->offset < HistoryOffset) || (chunk->offset < offset) ||
+ (chunk->offset > SrcSize + HistoryOffset))
+ {
+ status = xcrush_find_match_length(xcrush, offset, chunk->offset, HistoryOffset,
+ SrcSize, MaxMatchLength, &MatchInfo);
+
+ if (status < 0)
+ return status; /* error */
+
+ MatchLength = (UINT32)status;
+
+ if (MatchLength > MaxMatchLength)
+ {
+ MaxMatchLength = MatchLength;
+ MaxMatchInfo.MatchOffset = MatchInfo.MatchOffset;
+ MaxMatchInfo.ChunkOffset = MatchInfo.ChunkOffset;
+ MaxMatchInfo.MatchLength = MatchInfo.MatchLength;
+
+ if (MatchLength > 256)
+ break;
+ }
+ }
+
+ ChunkIndex = ChunkCount++;
+
+ if (ChunkIndex > 4)
+ break;
+
+ status = xcrush_find_next_matching_chunk(xcrush, chunk, &chunk);
+
+ if (status < 0)
+ return status; /* error */
+ }
+
+ if (MaxMatchLength)
+ {
+ xcrush->OriginalMatches[j].MatchOffset = MaxMatchInfo.MatchOffset;
+ xcrush->OriginalMatches[j].ChunkOffset = MaxMatchInfo.ChunkOffset;
+ xcrush->OriginalMatches[j].MatchLength = MaxMatchInfo.MatchLength;
+
+ if (xcrush->OriginalMatches[j].MatchOffset < HistoryOffset)
+ return -1002; /* error */
+
+ PrevMatchEnd =
+ xcrush->OriginalMatches[j].MatchLength + xcrush->OriginalMatches[j].MatchOffset;
+ j++;
+
+ if (j >= 1000)
+ return -1003; /* error */
+ }
+ }
+
+ SrcOffset += Signatures[i].size;
+
+ if (SrcOffset > SrcSize)
+ return -1004; /* error */
+ }
+
+ if (SrcOffset > SrcSize)
+ return -1005; /* error */
+
+ return (int)j;
+}
+
+static int xcrush_optimize_matches(XCRUSH_CONTEXT* xcrush)
+{
+ UINT32 j = 0;
+ UINT32 MatchDiff = 0;
+ UINT32 PrevMatchEnd = 0;
+ UINT32 TotalMatchLength = 0;
+ UINT32 OriginalMatchCount = 0;
+ UINT32 OptimizedMatchCount = 0;
+ XCRUSH_MATCH_INFO* OriginalMatch = NULL;
+ XCRUSH_MATCH_INFO* OptimizedMatch = NULL;
+ XCRUSH_MATCH_INFO* OriginalMatches = NULL;
+ XCRUSH_MATCH_INFO* OptimizedMatches = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ OriginalMatches = xcrush->OriginalMatches;
+ OriginalMatchCount = xcrush->OriginalMatchCount;
+ OptimizedMatches = xcrush->OptimizedMatches;
+
+ for (UINT32 i = 0; i < OriginalMatchCount; i++)
+ {
+ if (OriginalMatches[i].MatchOffset <= PrevMatchEnd)
+ {
+ if ((OriginalMatches[i].MatchOffset < PrevMatchEnd) &&
+ (OriginalMatches[i].MatchLength + OriginalMatches[i].MatchOffset >
+ PrevMatchEnd + 6))
+ {
+ MatchDiff = PrevMatchEnd - OriginalMatches[i].MatchOffset;
+ OriginalMatch = &OriginalMatches[i];
+ OptimizedMatch = &OptimizedMatches[j];
+ OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset;
+ OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset;
+ OptimizedMatch->MatchLength = OriginalMatch->MatchLength;
+
+ if (OptimizedMatches[j].MatchLength <= MatchDiff)
+ return -5001; /* error */
+
+ if (MatchDiff >= 20000)
+ return -5002; /* error */
+
+ OptimizedMatches[j].MatchLength -= MatchDiff;
+ OptimizedMatches[j].MatchOffset += MatchDiff;
+ OptimizedMatches[j].ChunkOffset += MatchDiff;
+ PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset;
+ TotalMatchLength += OptimizedMatches[j].MatchLength;
+ j++;
+ }
+ }
+ else
+ {
+ OriginalMatch = &OriginalMatches[i];
+ OptimizedMatch = &OptimizedMatches[j];
+ OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset;
+ OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset;
+ OptimizedMatch->MatchLength = OriginalMatch->MatchLength;
+ PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset;
+ TotalMatchLength += OptimizedMatches[j].MatchLength;
+ j++;
+ }
+ }
+
+ OptimizedMatchCount = j;
+ xcrush->OptimizedMatchCount = OptimizedMatchCount;
+ return (int)TotalMatchLength;
+}
+
+static int xcrush_generate_output(XCRUSH_CONTEXT* xcrush, BYTE* OutputBuffer, UINT32 OutputSize,
+ UINT32 HistoryOffset, UINT32* pDstSize)
+{
+ BYTE* Literals = NULL;
+ BYTE* OutputEnd = NULL;
+ UINT32 MatchIndex = 0;
+ UINT32 MatchOffset = 0;
+ UINT16 MatchLength = 0;
+ UINT32 MatchCount = 0;
+ UINT32 CurrentOffset = 0;
+ UINT32 MatchOffsetDiff = 0;
+ UINT32 HistoryOffsetDiff = 0;
+ RDP61_MATCH_DETAILS* MatchDetails = NULL;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(OutputBuffer);
+ WINPR_ASSERT(OutputSize >= 2);
+ WINPR_ASSERT(pDstSize);
+
+ MatchCount = xcrush->OptimizedMatchCount;
+ OutputEnd = &OutputBuffer[OutputSize];
+
+ if (&OutputBuffer[2] >= &OutputBuffer[OutputSize])
+ return -6001; /* error */
+
+ Data_Write_UINT16(OutputBuffer, MatchCount);
+ MatchDetails = (RDP61_MATCH_DETAILS*)&OutputBuffer[2];
+ Literals = (BYTE*)&MatchDetails[MatchCount];
+
+ if (Literals > OutputEnd)
+ return -6002; /* error */
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ Data_Write_UINT16(&MatchDetails[MatchIndex].MatchLength,
+ xcrush->OptimizedMatches[MatchIndex].MatchLength);
+ Data_Write_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset,
+ xcrush->OptimizedMatches[MatchIndex].MatchOffset - HistoryOffset);
+ Data_Write_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset,
+ xcrush->OptimizedMatches[MatchIndex].ChunkOffset);
+ }
+
+ CurrentOffset = HistoryOffset;
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ MatchLength = (UINT16)(xcrush->OptimizedMatches[MatchIndex].MatchLength);
+ MatchOffset = xcrush->OptimizedMatches[MatchIndex].MatchOffset;
+
+ if (MatchOffset <= CurrentOffset)
+ {
+ if (MatchOffset != CurrentOffset)
+ return -6003; /* error */
+
+ CurrentOffset = MatchOffset + MatchLength;
+ }
+ else
+ {
+ MatchOffsetDiff = MatchOffset - CurrentOffset;
+
+ if (Literals + MatchOffset - CurrentOffset >= OutputEnd)
+ return -6004; /* error */
+
+ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], MatchOffsetDiff);
+
+ if (Literals >= OutputEnd)
+ return -6005; /* error */
+
+ Literals += MatchOffsetDiff;
+ CurrentOffset = MatchOffset + MatchLength;
+ }
+ }
+
+ HistoryOffsetDiff = xcrush->HistoryOffset - CurrentOffset;
+
+ if (Literals + HistoryOffsetDiff >= OutputEnd)
+ return -6006; /* error */
+
+ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], HistoryOffsetDiff);
+ *pDstSize = Literals + HistoryOffsetDiff - OutputBuffer;
+ return 1;
+}
+
+static INLINE size_t xcrush_copy_bytes(BYTE* dst, const BYTE* src, size_t num)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+
+ if (src + num < dst || src > dst + num)
+ memcpy(dst, src, num);
+ else if (src != dst)
+ {
+ // src and dst overlaps
+ // we should copy the area that doesn't overlap repeatly
+ const size_t diff = (dst > src) ? dst - src : src - dst;
+ const size_t rest = num % diff;
+ const size_t end = num - rest;
+
+ for (size_t a = 0; a < end; a += diff)
+ memcpy(&dst[a], &src[a], diff);
+
+ if (rest != 0)
+ memcpy(&dst[end], &src[end], rest);
+ }
+
+ return num;
+}
+
+static int xcrush_decompress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ const BYTE* pSrcEnd = NULL;
+ const BYTE* Literals = NULL;
+ UINT16 MatchCount = 0;
+ UINT16 MatchIndex = 0;
+ BYTE* OutputPtr = NULL;
+ size_t OutputLength = 0;
+ UINT32 OutputOffset = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ BYTE* HistoryBufferEnd = NULL;
+ UINT32 HistoryBufferSize = 0;
+ UINT16 MatchLength = 0;
+ UINT16 MatchOutputOffset = 0;
+ UINT32 MatchHistoryOffset = 0;
+ const RDP61_MATCH_DETAILS* MatchDetails = NULL;
+
+ WINPR_ASSERT(xcrush);
+
+ if (SrcSize < 1)
+ return -1001;
+
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ if (flags & L1_PACKET_AT_FRONT)
+ xcrush->HistoryOffset = 0;
+
+ pSrcEnd = &pSrcData[SrcSize];
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryBufferSize = xcrush->HistoryBufferSize;
+ HistoryBufferEnd = &(HistoryBuffer[HistoryBufferSize]);
+ xcrush->HistoryPtr = HistoryPtr = &(HistoryBuffer[xcrush->HistoryOffset]);
+
+ if (flags & L1_NO_COMPRESSION)
+ {
+ Literals = pSrcData;
+ }
+ else
+ {
+ if (!(flags & L1_COMPRESSED))
+ return -1002;
+
+ if ((pSrcData + 2) > pSrcEnd)
+ return -1003;
+
+ Data_Read_UINT16(pSrcData, MatchCount);
+ MatchDetails = (const RDP61_MATCH_DETAILS*)&pSrcData[2];
+ Literals = (const BYTE*)&MatchDetails[MatchCount];
+ OutputOffset = 0;
+
+ if (Literals > pSrcEnd)
+ return -1004;
+
+ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++)
+ {
+ Data_Read_UINT16(&MatchDetails[MatchIndex].MatchLength, MatchLength);
+ Data_Read_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, MatchOutputOffset);
+ Data_Read_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, MatchHistoryOffset);
+
+ if (MatchOutputOffset < OutputOffset)
+ return -1005;
+
+ if (MatchLength > HistoryBufferSize)
+ return -1006;
+
+ if (MatchHistoryOffset > HistoryBufferSize)
+ return -1007;
+
+ OutputLength = MatchOutputOffset - OutputOffset;
+
+ if ((MatchOutputOffset - OutputOffset) > HistoryBufferSize)
+ return -1008;
+
+ if (OutputLength > 0)
+ {
+ if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (Literals >= pSrcEnd) ||
+ (&Literals[OutputLength] > pSrcEnd))
+ return -1009;
+
+ xcrush_copy_bytes(HistoryPtr, Literals, OutputLength);
+ HistoryPtr += OutputLength;
+ Literals += OutputLength;
+ OutputOffset += OutputLength;
+
+ if (Literals > pSrcEnd)
+ return -1010;
+ }
+
+ OutputPtr = &xcrush->HistoryBuffer[MatchHistoryOffset];
+
+ if ((&HistoryPtr[MatchLength] >= HistoryBufferEnd) ||
+ (&OutputPtr[MatchLength] >= HistoryBufferEnd))
+ return -1011;
+
+ xcrush_copy_bytes(HistoryPtr, OutputPtr, MatchLength);
+ OutputOffset += MatchLength;
+ HistoryPtr += MatchLength;
+ }
+ }
+
+ if (Literals < pSrcEnd)
+ {
+ OutputLength = pSrcEnd - Literals;
+
+ if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (&Literals[OutputLength] > pSrcEnd))
+ return -1012;
+
+ xcrush_copy_bytes(HistoryPtr, Literals, OutputLength);
+ HistoryPtr += OutputLength;
+ }
+
+ xcrush->HistoryOffset = HistoryPtr - HistoryBuffer;
+ *pDstSize = HistoryPtr - xcrush->HistoryPtr;
+ *ppDstData = xcrush->HistoryPtr;
+ return 1;
+}
+
+int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags)
+{
+ int status = 0;
+ UINT32 DstSize = 0;
+ const BYTE* pDstData = NULL;
+ BYTE Level1ComprFlags = 0;
+ BYTE Level2ComprFlags = 0;
+
+ WINPR_ASSERT(xcrush);
+
+ if (SrcSize < 2)
+ return -1;
+
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+
+ Level1ComprFlags = pSrcData[0];
+ Level2ComprFlags = pSrcData[1];
+ pSrcData += 2;
+ SrcSize -= 2;
+
+ if (flags & PACKET_FLUSHED)
+ {
+ ZeroMemory(xcrush->HistoryBuffer, xcrush->HistoryBufferSize);
+ xcrush->HistoryOffset = 0;
+ }
+
+ if (!(Level2ComprFlags & PACKET_COMPRESSED))
+ {
+ status =
+ xcrush_decompress_l1(xcrush, pSrcData, SrcSize, ppDstData, pDstSize, Level1ComprFlags);
+ return status;
+ }
+
+ status =
+ mppc_decompress(xcrush->mppc, pSrcData, SrcSize, &pDstData, &DstSize, Level2ComprFlags);
+
+ if (status < 0)
+ return status;
+
+ status = xcrush_decompress_l1(xcrush, pDstData, DstSize, ppDstData, pDstSize, Level1ComprFlags);
+ return status;
+}
+
+static int xcrush_compress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ UINT32 Flags = 0;
+ UINT32 HistoryOffset = 0;
+ BYTE* HistoryPtr = NULL;
+ BYTE* HistoryBuffer = NULL;
+ UINT32 SignatureIndex = 0;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(SrcSize > 0);
+ WINPR_ASSERT(pDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ if (xcrush->HistoryOffset + SrcSize + 8 > xcrush->HistoryBufferSize)
+ {
+ xcrush->HistoryOffset = 0;
+ Flags |= L1_PACKET_AT_FRONT;
+ }
+
+ HistoryOffset = xcrush->HistoryOffset;
+ HistoryBuffer = xcrush->HistoryBuffer;
+ HistoryPtr = &HistoryBuffer[HistoryOffset];
+ MoveMemory(HistoryPtr, pSrcData, SrcSize);
+ xcrush->HistoryOffset += SrcSize;
+
+ if (SrcSize > 50)
+ {
+ SignatureIndex = xcrush_compute_signatures(xcrush, pSrcData, SrcSize);
+
+ if (SignatureIndex)
+ {
+ status = xcrush_find_all_matches(xcrush, SignatureIndex, HistoryOffset, 0, SrcSize);
+
+ if (status < 0)
+ return status;
+
+ xcrush->OriginalMatchCount = (UINT32)status;
+ xcrush->OptimizedMatchCount = 0;
+
+ if (xcrush->OriginalMatchCount)
+ {
+ status = xcrush_optimize_matches(xcrush);
+
+ if (status < 0)
+ return status;
+ }
+
+ if (xcrush->OptimizedMatchCount)
+ {
+ status = xcrush_generate_output(xcrush, pDstData, SrcSize, HistoryOffset, pDstSize);
+
+ if (status < 0)
+ return status;
+
+ Flags |= L1_COMPRESSED;
+ }
+ }
+ }
+
+ if (!(Flags & L1_COMPRESSED))
+ {
+ Flags |= L1_NO_COMPRESSION;
+ *pDstSize = SrcSize;
+ }
+
+ *pFlags = Flags;
+ return 1;
+}
+
+int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer,
+ const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ UINT32 DstSize = 0;
+ BYTE* pDstData = NULL;
+ const BYTE* CompressedData = NULL;
+ UINT32 CompressedDataSize = 0;
+ BYTE* OriginalData = NULL;
+ UINT32 OriginalDataSize = 0;
+ UINT32 Level1ComprFlags = 0;
+ UINT32 Level2ComprFlags = 0;
+ UINT32 CompressionLevel = 3;
+
+ WINPR_ASSERT(xcrush);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(SrcSize > 0);
+ WINPR_ASSERT(ppDstData);
+ WINPR_ASSERT(pDstSize);
+ WINPR_ASSERT(pFlags);
+
+ if (SrcSize > 16384)
+ return -1001;
+
+ if ((SrcSize + 2) > *pDstSize)
+ return -1002;
+
+ OriginalData = pDstBuffer;
+ *ppDstData = pDstBuffer;
+ OriginalDataSize = SrcSize;
+ pDstData = xcrush->BlockBuffer;
+ CompressedDataSize = SrcSize;
+ status = xcrush_compress_l1(xcrush, pSrcData, SrcSize, pDstData, &CompressedDataSize,
+ &Level1ComprFlags);
+
+ if (status < 0)
+ return status;
+
+ if (Level1ComprFlags & L1_COMPRESSED)
+ {
+ CompressedData = pDstData;
+
+ if (CompressedDataSize > SrcSize)
+ return -1003;
+ }
+ else
+ {
+ CompressedData = pSrcData;
+
+ if (CompressedDataSize != SrcSize)
+ return -1004;
+ }
+
+ status = 0;
+ pDstData = &OriginalData[2];
+ DstSize = OriginalDataSize - 2;
+
+ if (CompressedDataSize > 50)
+ {
+ const BYTE* pUnusedDstData = NULL;
+ status = mppc_compress(xcrush->mppc, CompressedData, CompressedDataSize, pDstData,
+ &pUnusedDstData, &DstSize, &Level2ComprFlags);
+ }
+
+ if (status < 0)
+ return status;
+
+ if (!status || (Level2ComprFlags & PACKET_FLUSHED))
+ {
+ if (CompressedDataSize > DstSize)
+ {
+ xcrush_context_reset(xcrush, TRUE);
+ *ppDstData = pSrcData;
+ *pDstSize = SrcSize;
+ *pFlags = 0;
+ return 1;
+ }
+
+ DstSize = CompressedDataSize;
+ CopyMemory(&OriginalData[2], CompressedData, CompressedDataSize);
+ }
+
+ if (Level2ComprFlags & PACKET_COMPRESSED)
+ {
+ Level2ComprFlags |= xcrush->CompressionFlags;
+ xcrush->CompressionFlags = 0;
+ }
+ else if (Level2ComprFlags & PACKET_FLUSHED)
+ {
+ xcrush->CompressionFlags = PACKET_FLUSHED;
+ }
+
+ Level1ComprFlags |= L1_INNER_COMPRESSION;
+ OriginalData[0] = (BYTE)Level1ComprFlags;
+ OriginalData[1] = (BYTE)Level2ComprFlags;
+#if defined(DEBUG_XCRUSH)
+ WLog_DBG(TAG, "XCrushCompress: Level1ComprFlags: %s Level2ComprFlags: %s",
+ xcrush_get_level_1_compression_flags_string(Level1ComprFlags),
+ xcrush_get_level_2_compression_flags_string(Level2ComprFlags));
+#endif
+
+ if (*pDstSize < (DstSize + 2))
+ return -1006;
+
+ *pDstSize = DstSize + 2;
+ *pFlags = PACKET_COMPRESSED | CompressionLevel;
+ return 1;
+}
+
+void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush)
+{
+ WINPR_ASSERT(xcrush);
+
+ xcrush->SignatureIndex = 0;
+ xcrush->SignatureCount = 1000;
+ ZeroMemory(&(xcrush->Signatures), sizeof(XCRUSH_SIGNATURE) * xcrush->SignatureCount);
+ xcrush->CompressionFlags = 0;
+ xcrush->ChunkHead = xcrush->ChunkTail = 1;
+ ZeroMemory(&(xcrush->Chunks), sizeof(xcrush->Chunks));
+ ZeroMemory(&(xcrush->NextChunks), sizeof(xcrush->NextChunks));
+ ZeroMemory(&(xcrush->OriginalMatches), sizeof(xcrush->OriginalMatches));
+ ZeroMemory(&(xcrush->OptimizedMatches), sizeof(xcrush->OptimizedMatches));
+
+ if (flush)
+ xcrush->HistoryOffset = xcrush->HistoryBufferSize + 1;
+ else
+ xcrush->HistoryOffset = 0;
+
+ mppc_context_reset(xcrush->mppc, flush);
+}
+
+XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor)
+{
+ XCRUSH_CONTEXT* xcrush = (XCRUSH_CONTEXT*)calloc(1, sizeof(XCRUSH_CONTEXT));
+
+ if (!xcrush)
+ goto fail;
+
+ xcrush->Compressor = Compressor;
+ xcrush->mppc = mppc_context_new(1, Compressor);
+ if (!xcrush->mppc)
+ goto fail;
+ xcrush->HistoryBufferSize = 2000000;
+ xcrush_context_reset(xcrush, FALSE);
+
+ return xcrush;
+fail:
+ xcrush_context_free(xcrush);
+
+ return NULL;
+}
+
+void xcrush_context_free(XCRUSH_CONTEXT* xcrush)
+{
+ if (xcrush)
+ {
+ mppc_context_free(xcrush->mppc);
+ free(xcrush);
+ }
+}
diff --git a/libfreerdp/codec/xcrush.h b/libfreerdp/codec/xcrush.h
new file mode 100644
index 0000000..5997c21
--- /dev/null
+++ b/libfreerdp/codec/xcrush.h
@@ -0,0 +1,51 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * XCrush (RDP6.1) 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.
+ */
+
+#ifndef FREERDP_CODEC_XCRUSH_H
+#define FREERDP_CODEC_XCRUSH_H
+
+#include <freerdp/api.h>
+#include <freerdp/types.h>
+
+#include "mppc.h"
+
+typedef struct s_XCRUSH_CONTEXT XCRUSH_CONTEXT;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ FREERDP_LOCAL int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize,
+ BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32* pFlags);
+ FREERDP_LOCAL int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData,
+ UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize,
+ UINT32 flags);
+
+ FREERDP_LOCAL void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush);
+
+ FREERDP_LOCAL XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor);
+ FREERDP_LOCAL void xcrush_context_free(XCRUSH_CONTEXT* xcrush);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CODEC_XCRUSH_H */
diff --git a/libfreerdp/codec/yuv.c b/libfreerdp/codec/yuv.c
new file mode 100644
index 0000000..c546566
--- /dev/null
+++ b/libfreerdp/codec/yuv.c
@@ -0,0 +1,887 @@
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+#include <winpr/pool.h>
+
+#include <freerdp/settings.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/primitives.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/yuv.h>
+
+#define TAG FREERDP_TAG("codec")
+
+#define TILE_SIZE 64
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pYUVData[3];
+ UINT32 iStride[3];
+ DWORD DstFormat;
+ BYTE* dest;
+ UINT32 nDstStep;
+ RECTANGLE_16 rect;
+} YUV_PROCESS_WORK_PARAM;
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pYUVData[3];
+ UINT32 iStride[3];
+ BYTE* pYUVDstData[3];
+ UINT32 iDstStride[3];
+ RECTANGLE_16 rect;
+ BYTE type;
+} YUV_COMBINE_WORK_PARAM;
+
+typedef struct
+{
+ YUV_CONTEXT* context;
+ const BYTE* pSrcData;
+
+ DWORD SrcFormat;
+ UINT32 nSrcStep;
+ RECTANGLE_16 rect;
+ BYTE version;
+
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ UINT32 iStride[3];
+} YUV_ENCODE_WORK_PARAM;
+
+struct S_YUV_CONTEXT
+{
+ UINT32 width, height;
+ BOOL useThreads;
+ BOOL encoder;
+ UINT32 nthreads;
+ UINT32 heightStep;
+
+ PTP_POOL threadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+
+ UINT32 work_object_count;
+ PTP_WORK* work_objects;
+ YUV_ENCODE_WORK_PARAM* work_enc_params;
+ YUV_PROCESS_WORK_PARAM* work_dec_params;
+ YUV_COMBINE_WORK_PARAM* work_combined_params;
+};
+
+static INLINE BOOL avc420_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3],
+ const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData,
+ DWORD DstFormat)
+{
+ primitives_t* prims = primitives_get();
+ prim_size_t roi;
+ const BYTE* pYUVPoint[3];
+
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(pDstData);
+
+ const INT32 width = rect->right - rect->left;
+ const INT32 height = rect->bottom - rect->top;
+ BYTE* pDstPoint =
+ pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left;
+ pYUVPoint[1] = pYUVData[1] + rect->top / 2 * iStride[1] + rect->left / 2;
+ pYUVPoint[2] = pYUVData[2] + rect->top / 2 * iStride[2] + rect->left / 2;
+
+ roi.width = width;
+ roi.height = height;
+
+ if (prims->YUV420ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static INLINE BOOL avc444_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3],
+ const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData,
+ DWORD DstFormat)
+{
+ primitives_t* prims = primitives_get();
+ prim_size_t roi;
+ const BYTE* pYUVPoint[3];
+
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(pDstData);
+
+ const INT32 width = rect->right - rect->left;
+ const INT32 height = rect->bottom - rect->top;
+ BYTE* pDstPoint =
+ pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat);
+
+ pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left;
+ pYUVPoint[1] = pYUVData[1] + rect->top * iStride[1] + rect->left;
+ pYUVPoint[2] = pYUVData[2] + rect->top * iStride[2] + rect->left;
+
+ roi.width = width;
+ roi.height = height;
+
+ if (prims->YUV444ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) !=
+ PRIMITIVES_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void CALLBACK yuv420_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context;
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ if (!avc420_yuv_to_rgb(param->pYUVData, param->iStride, &param->rect, param->nDstStep,
+ param->dest, param->DstFormat))
+ WLog_WARN(TAG, "avc420_yuv_to_rgb failed");
+}
+
+static void CALLBACK yuv444_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context;
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ if (!avc444_yuv_to_rgb(param->pYUVData, param->iStride, &param->rect, param->nDstStep,
+ param->dest, param->DstFormat))
+ WLog_WARN(TAG, "avc444_yuv_to_rgb failed");
+}
+
+BOOL yuv_context_reset(YUV_CONTEXT* context, UINT32 width, UINT32 height)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(context);
+
+ context->width = width;
+ context->height = height;
+ context->heightStep = (height / context->nthreads);
+
+ if (context->useThreads)
+ {
+ const UINT32 pw = (width + TILE_SIZE - width % TILE_SIZE) / TILE_SIZE;
+ const UINT32 ph = (height + TILE_SIZE - height % TILE_SIZE) / TILE_SIZE;
+
+ /* We´ve calculated the amount of workers for 64x64 tiles, but the decoder
+ * might get 16x16 tiles mixed in. */
+ const UINT32 count = pw * ph * 16;
+
+ context->work_object_count = 0;
+ if (context->encoder)
+ {
+ void* tmp = winpr_aligned_recalloc(context->work_enc_params, count,
+ sizeof(YUV_ENCODE_WORK_PARAM), 32);
+ if (!tmp)
+ goto fail;
+ memset(tmp, 0, count * sizeof(YUV_ENCODE_WORK_PARAM));
+
+ context->work_enc_params = tmp;
+ }
+ else
+ {
+ void* tmp = winpr_aligned_recalloc(context->work_dec_params, count,
+ sizeof(YUV_PROCESS_WORK_PARAM), 32);
+ if (!tmp)
+ goto fail;
+ memset(tmp, 0, count * sizeof(YUV_PROCESS_WORK_PARAM));
+
+ context->work_dec_params = tmp;
+
+ void* ctmp = winpr_aligned_recalloc(context->work_combined_params, count,
+ sizeof(YUV_COMBINE_WORK_PARAM), 32);
+ if (!ctmp)
+ goto fail;
+ memset(ctmp, 0, count * sizeof(YUV_COMBINE_WORK_PARAM));
+
+ context->work_combined_params = ctmp;
+ }
+
+ void* wtmp = winpr_aligned_recalloc(context->work_objects, count, sizeof(PTP_WORK), 32);
+ if (!wtmp)
+ goto fail;
+ memset(wtmp, 0, count * sizeof(PTP_WORK));
+
+ context->work_objects = wtmp;
+ context->work_object_count = count;
+ }
+ rc = TRUE;
+fail:
+ return rc;
+}
+
+YUV_CONTEXT* yuv_context_new(BOOL encoder, UINT32 ThreadingFlags)
+{
+ SYSTEM_INFO sysInfos;
+ YUV_CONTEXT* ret = winpr_aligned_calloc(1, sizeof(*ret), 32);
+ if (!ret)
+ return NULL;
+
+ /** do it here to avoid a race condition between threads */
+ primitives_get();
+
+ ret->encoder = encoder;
+ ret->nthreads = 1;
+ if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS))
+ {
+ GetNativeSystemInfo(&sysInfos);
+ ret->useThreads = (sysInfos.dwNumberOfProcessors > 1);
+ if (ret->useThreads)
+ {
+ ret->nthreads = sysInfos.dwNumberOfProcessors;
+ ret->threadPool = CreateThreadpool(NULL);
+ if (!ret->threadPool)
+ {
+ goto error_threadpool;
+ }
+
+ InitializeThreadpoolEnvironment(&ret->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&ret->ThreadPoolEnv, ret->threadPool);
+ }
+ }
+
+ return ret;
+
+error_threadpool:
+ yuv_context_free(ret);
+ return NULL;
+}
+
+void yuv_context_free(YUV_CONTEXT* context)
+{
+ if (!context)
+ return;
+ if (context->useThreads)
+ {
+ if (context->threadPool)
+ CloseThreadpool(context->threadPool);
+ DestroyThreadpoolEnvironment(&context->ThreadPoolEnv);
+ winpr_aligned_free(context->work_objects);
+ winpr_aligned_free(context->work_combined_params);
+ winpr_aligned_free(context->work_enc_params);
+ winpr_aligned_free(context->work_dec_params);
+ }
+ winpr_aligned_free(context);
+}
+
+static INLINE YUV_PROCESS_WORK_PARAM pool_decode_param(const RECTANGLE_16* rect,
+ YUV_CONTEXT* context,
+ const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 DstFormat,
+ BYTE* dest, UINT32 nDstStep)
+{
+ YUV_PROCESS_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(dest);
+
+ current.context = context;
+ current.DstFormat = DstFormat;
+ current.pYUVData[0] = pYUVData[0];
+ current.pYUVData[1] = pYUVData[1];
+ current.pYUVData[2] = pYUVData[2];
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+ current.nDstStep = nDstStep;
+ current.dest = dest;
+ current.rect = *rect;
+ return current;
+}
+
+static BOOL submit_object(PTP_WORK* work_object, PTP_WORK_CALLBACK cb, const void* param,
+ YUV_CONTEXT* context)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+
+ cnv.cpv = param;
+
+ if (!work_object)
+ return FALSE;
+
+ *work_object = NULL;
+
+ if (!param || !context)
+ return FALSE;
+
+ *work_object = CreateThreadpoolWork(cb, cnv.pv, &context->ThreadPoolEnv);
+ if (!*work_object)
+ return FALSE;
+
+ SubmitThreadpoolWork(*work_object);
+ return TRUE;
+}
+
+static void free_objects(PTP_WORK* work_objects, UINT32 waitCount)
+{
+ WINPR_ASSERT(work_objects || (waitCount == 0));
+
+ for (UINT32 i = 0; i < waitCount; i++)
+ {
+ PTP_WORK cur = work_objects[i];
+ work_objects[i] = NULL;
+
+ if (!cur)
+ continue;
+
+ WaitForThreadpoolWorkCallbacks(cur, FALSE);
+ CloseThreadpoolWork(cur);
+ }
+}
+
+static BOOL intersects(UINT32 pos, const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ for (UINT32 x = pos + 1; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* what = &regionRects[pos];
+ const RECTANGLE_16* rect = &regionRects[x];
+
+ if (rectangles_intersects(what, rect))
+ {
+ WLog_WARN(TAG, "YUV decoder: intersecting rectangles, aborting");
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static RECTANGLE_16 clamp(YUV_CONTEXT* context, const RECTANGLE_16* rect, UINT32 srcHeight)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(rect);
+
+ RECTANGLE_16 c = *rect;
+ const UINT32 height = MIN(context->height, srcHeight);
+ if (c.top > height)
+ c.top = height;
+ if (c.bottom > height)
+ c.bottom = height;
+ return c;
+}
+
+static BOOL pool_decode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 yuvHeight, UINT32 DstFormat, BYTE* dest,
+ UINT32 nDstStep, const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ UINT32 waitCount = 0;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cb);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (context->encoder)
+ {
+ WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting");
+ return FALSE;
+ }
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 y = 0; y < numRegionRects; y++)
+ {
+ const RECTANGLE_16 rect = clamp(context, &regionRects[y], yuvHeight);
+ YUV_PROCESS_WORK_PARAM current =
+ pool_decode_param(&rect, context, pYUVData, iStride, DstFormat, dest, nDstStep);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ RECTANGLE_16 r = clamp(context, &regionRects[x], yuvHeight);
+
+ if (intersects(x, regionRects, numRegionRects))
+ continue;
+
+ while (r.left < r.right)
+ {
+ RECTANGLE_16 y = r;
+ y.right = MIN(r.right, r.left + TILE_SIZE);
+
+ while (y.top < y.bottom)
+ {
+ RECTANGLE_16 z = y;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV decoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+
+ YUV_PROCESS_WORK_PARAM* cur = &context->work_dec_params[waitCount];
+ z.bottom = MIN(z.bottom, z.top + TILE_SIZE);
+ if (rectangle_is_empty(&z))
+ continue;
+ *cur = pool_decode_param(&z, context, pYUVData, iStride, DstFormat, dest, nDstStep);
+ if (!submit_object(&context->work_objects[waitCount], cb, cur, context))
+ goto fail;
+ waitCount++;
+ y.top += TILE_SIZE;
+ }
+
+ r.left += TILE_SIZE;
+ }
+ }
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+static INLINE BOOL check_rect(const YUV_CONTEXT* yuv, const RECTANGLE_16* rect, UINT32 nDstWidth,
+ UINT32 nDstHeight)
+{
+ WINPR_ASSERT(yuv);
+ WINPR_ASSERT(rect);
+
+ /* Check, if the output rectangle is valid in decoded h264 frame. */
+ if ((rect->right > yuv->width) || (rect->left > yuv->width))
+ return FALSE;
+
+ if ((rect->top > yuv->height) || (rect->bottom > yuv->height))
+ return FALSE;
+
+ /* Check, if the output rectangle is valid in destination buffer. */
+ if ((rect->right > nDstWidth) || (rect->left > nDstWidth))
+ return FALSE;
+
+ if ((rect->bottom > nDstHeight) || (rect->top > nDstHeight))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void CALLBACK yuv444_combine_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ YUV_COMBINE_WORK_PARAM* param = (YUV_COMBINE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(param);
+ YUV_CONTEXT* yuv = param->context;
+ WINPR_ASSERT(yuv);
+
+ const RECTANGLE_16* rect = &param->rect;
+ WINPR_ASSERT(rect);
+
+ const UINT32 alignedWidth = yuv->width + ((yuv->width % 16 != 0) ? 16 - yuv->width % 16 : 0);
+ const UINT32 alignedHeight =
+ yuv->height + ((yuv->height % 16 != 0) ? 16 - yuv->height % 16 : 0);
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+
+ if (!check_rect(param->context, rect, yuv->width, yuv->height))
+ return;
+
+ if (prims->YUV420CombineToYUV444(param->type, param->pYUVData, param->iStride, alignedWidth,
+ alignedHeight, param->pYUVDstData, param->iDstStride,
+ rect) != PRIMITIVES_SUCCESS)
+ WLog_WARN(TAG, "YUV420CombineToYUV444 failed");
+}
+
+static INLINE YUV_COMBINE_WORK_PARAM pool_decode_rect_param(
+ const RECTANGLE_16* rect, YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], BYTE* pYUVDstData[3], const UINT32 iDstStride[3])
+{
+ YUV_COMBINE_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+
+ current.context = context;
+ current.pYUVData[0] = pYUVData[0];
+ current.pYUVData[1] = pYUVData[1];
+ current.pYUVData[2] = pYUVData[2];
+ current.pYUVDstData[0] = pYUVDstData[0];
+ current.pYUVDstData[1] = pYUVDstData[1];
+ current.pYUVDstData[2] = pYUVDstData[2];
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+ current.iDstStride[0] = iDstStride[0];
+ current.iDstStride[1] = iDstStride[1];
+ current.iDstStride[2] = iDstStride[2];
+ current.type = type;
+ current.rect = *rect;
+ return current;
+}
+
+static BOOL pool_decode_rect(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], BYTE* pYUVDstData[3],
+ const UINT32 iDstStride[3], const RECTANGLE_16* regionRects,
+ UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ UINT32 waitCount = 0;
+ PTP_WORK_CALLBACK cb = yuv444_combine_work_callback;
+ primitives_t* prims = primitives_get();
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 y = 0; y < numRegionRects; y++)
+ {
+ YUV_COMBINE_WORK_PARAM current = pool_decode_rect_param(
+ &regionRects[y], context, type, pYUVData, iStride, pYUVDstData, iDstStride);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (waitCount = 0; waitCount < numRegionRects; waitCount++)
+ {
+ YUV_COMBINE_WORK_PARAM* current = NULL;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV rect decoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+ current = &context->work_combined_params[waitCount];
+ *current = pool_decode_rect_param(&regionRects[waitCount], context, type, pYUVData, iStride,
+ pYUVDstData, iDstStride);
+
+ if (!submit_object(&context->work_objects[waitCount], cb, current, context))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+BOOL yuv444_context_decode(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3],
+ const UINT32 iStride[3], UINT32 srcYuvHeight, BYTE* pYUVDstData[3],
+ const UINT32 iDstStride[3], DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ const BYTE* pYUVCDstData[3];
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pYUVData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVDstData);
+ WINPR_ASSERT(iDstStride);
+ WINPR_ASSERT(dest);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (context->encoder)
+ {
+ WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting");
+ return FALSE;
+ }
+ if (!pool_decode_rect(context, type, pYUVData, iStride, pYUVDstData, iDstStride, regionRects,
+ numRegionRects))
+ return FALSE;
+
+ pYUVCDstData[0] = pYUVDstData[0];
+ pYUVCDstData[1] = pYUVDstData[1];
+ pYUVCDstData[2] = pYUVDstData[2];
+ return pool_decode(context, yuv444_process_work_callback, pYUVCDstData, iDstStride,
+ srcYuvHeight, DstFormat, dest, nDstStep, regionRects, numRegionRects);
+}
+
+BOOL yuv420_context_decode(YUV_CONTEXT* context, const BYTE* pYUVData[3], const UINT32 iStride[3],
+ UINT32 yuvHeight, DWORD DstFormat, BYTE* dest, UINT32 nDstStep,
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ return pool_decode(context, yuv420_process_work_callback, pYUVData, iStride, yuvHeight,
+ DstFormat, dest, nDstStep, regionRects, numRegionRects);
+}
+
+static void CALLBACK yuv420_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVData[0] = param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+
+ if (prims->RGBToYUV420_8u_P3AC4R(src, param->SrcFormat, param->nSrcStep, pYUVData,
+ param->iStride, &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static void CALLBACK yuv444v1_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVLumaData[0] =
+ param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVLumaData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVLumaData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ pYUVChromaData[0] =
+ param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVChromaData[1] =
+ param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVChromaData[2] =
+ param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ if (prims->RGBToAVC444YUV(src, param->SrcFormat, param->nSrcStep, pYUVLumaData, param->iStride,
+ pYUVChromaData, param->iStride, &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static void CALLBACK yuv444v2_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context,
+ PTP_WORK work)
+{
+ prim_size_t roi;
+ YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context;
+ primitives_t* prims = primitives_get();
+ BYTE* pYUVLumaData[3];
+ BYTE* pYUVChromaData[3];
+ const BYTE* src = NULL;
+
+ WINPR_UNUSED(instance);
+ WINPR_UNUSED(work);
+ WINPR_ASSERT(param);
+
+ roi.width = param->rect.right - param->rect.left;
+ roi.height = param->rect.bottom - param->rect.top;
+ src = param->pSrcData + param->nSrcStep * param->rect.top +
+ param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat);
+ pYUVLumaData[0] =
+ param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVLumaData[1] =
+ param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVLumaData[2] =
+ param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ pYUVChromaData[0] =
+ param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left;
+ pYUVChromaData[1] =
+ param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2;
+ pYUVChromaData[2] =
+ param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2;
+ if (prims->RGBToAVC444YUVv2(src, param->SrcFormat, param->nSrcStep, pYUVLumaData,
+ param->iStride, pYUVChromaData, param->iStride,
+ &roi) != PRIMITIVES_SUCCESS)
+ {
+ WLog_ERR(TAG, "error when decoding lines");
+ }
+}
+
+static INLINE YUV_ENCODE_WORK_PARAM pool_encode_fill(const RECTANGLE_16* rect, YUV_CONTEXT* context,
+ const BYTE* pSrcData, UINT32 nSrcStep,
+ UINT32 SrcFormat, const UINT32 iStride[],
+ BYTE* pYUVLumaData[], BYTE* pYUVChromaData[])
+{
+ YUV_ENCODE_WORK_PARAM current = { 0 };
+
+ WINPR_ASSERT(rect);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(pYUVLumaData);
+
+ current.context = context;
+ current.pSrcData = pSrcData;
+ current.SrcFormat = SrcFormat;
+ current.nSrcStep = nSrcStep;
+ current.pYUVLumaData[0] = pYUVLumaData[0];
+ current.pYUVLumaData[1] = pYUVLumaData[1];
+ current.pYUVLumaData[2] = pYUVLumaData[2];
+ if (pYUVChromaData)
+ {
+ current.pYUVChromaData[0] = pYUVChromaData[0];
+ current.pYUVChromaData[1] = pYUVChromaData[1];
+ current.pYUVChromaData[2] = pYUVChromaData[2];
+ }
+ current.iStride[0] = iStride[0];
+ current.iStride[1] = iStride[1];
+ current.iStride[2] = iStride[2];
+
+ current.rect = *rect;
+
+ return current;
+}
+
+static BOOL pool_encode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[],
+ BYTE* pYUVLumaData[], BYTE* pYUVChromaData[],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ BOOL rc = FALSE;
+ primitives_t* prims = primitives_get();
+ UINT32 waitCount = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cb);
+ WINPR_ASSERT(pSrcData);
+ WINPR_ASSERT(iStride);
+ WINPR_ASSERT(regionRects || (numRegionRects == 0));
+
+ if (!context->encoder)
+ {
+
+ WLog_ERR(TAG, "YUV context set up for decoding, can not encode with it, aborting");
+ return FALSE;
+ }
+
+ if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU))
+ {
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ YUV_ENCODE_WORK_PARAM current =
+ pool_encode_fill(&regionRects[x], context, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVLumaData, pYUVChromaData);
+ cb(NULL, &current, NULL);
+ }
+ return TRUE;
+ }
+
+ /* case where we use threads */
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* rect = &regionRects[x];
+ const UINT32 height = rect->bottom - rect->top;
+ const UINT32 steps = (height + context->heightStep / 2) / context->heightStep;
+
+ waitCount += steps;
+ }
+
+ for (UINT32 x = 0; x < numRegionRects; x++)
+ {
+ const RECTANGLE_16* rect = &regionRects[x];
+ const UINT32 height = rect->bottom - rect->top;
+ const UINT32 steps = (height + context->heightStep / 2) / context->heightStep;
+
+ for (UINT32 y = 0; y < steps; y++)
+ {
+ RECTANGLE_16 r = *rect;
+ YUV_ENCODE_WORK_PARAM* current = NULL;
+
+ if (context->work_object_count <= waitCount)
+ {
+ WLog_ERR(TAG,
+ "YUV encoder: invalid number of tiles, only support less than %" PRIu32
+ ", got %" PRIu32,
+ context->work_object_count, waitCount);
+ goto fail;
+ }
+
+ current = &context->work_enc_params[waitCount];
+ r.top += y * context->heightStep;
+ *current = pool_encode_fill(&r, context, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVLumaData, pYUVChromaData);
+ if (!submit_object(&context->work_objects[waitCount], cb, current, context))
+ goto fail;
+ waitCount++;
+ }
+ }
+
+ rc = TRUE;
+fail:
+ free_objects(context->work_objects, context->work_object_count);
+ return rc;
+}
+
+BOOL yuv420_context_encode(YUV_CONTEXT* context, const BYTE* pSrcData, UINT32 nSrcStep,
+ UINT32 SrcFormat, const UINT32 iStride[3], BYTE* pYUVData[3],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ if (!context || !pSrcData || !iStride || !pYUVData || !regionRects)
+ return FALSE;
+
+ return pool_encode(context, yuv420_encode_work_callback, pSrcData, nSrcStep, SrcFormat, iStride,
+ pYUVData, NULL, regionRects, numRegionRects);
+}
+
+BOOL yuv444_context_encode(YUV_CONTEXT* context, BYTE version, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[3],
+ BYTE* pYUVLumaData[3], BYTE* pYUVChromaData[3],
+ const RECTANGLE_16* regionRects, UINT32 numRegionRects)
+{
+ PTP_WORK_CALLBACK cb = NULL;
+ switch (version)
+ {
+ case 1:
+ cb = yuv444v1_encode_work_callback;
+ break;
+ case 2:
+ cb = yuv444v2_encode_work_callback;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return pool_encode(context, cb, pSrcData, nSrcStep, SrcFormat, iStride, pYUVLumaData,
+ pYUVChromaData, regionRects, numRegionRects);
+}
diff --git a/libfreerdp/codec/zgfx.c b/libfreerdp/codec/zgfx.c
new file mode 100644
index 0000000..881823a
--- /dev/null
+++ b/libfreerdp/codec/zgfx.c
@@ -0,0 +1,603 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ZGFX (RDP8) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/zgfx.h>
+
+#define TAG FREERDP_TAG("codec")
+
+/**
+ * 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
+ */
+
+typedef struct
+{
+ UINT32 prefixLength;
+ UINT32 prefixCode;
+ UINT32 valueBits;
+ UINT32 tokenType;
+ UINT32 valueBase;
+} ZGFX_TOKEN;
+
+struct S_ZGFX_CONTEXT
+{
+ BOOL Compressor;
+
+ const BYTE* pbInputCurrent;
+ const BYTE* pbInputEnd;
+
+ UINT32 bits;
+ UINT32 cBitsRemaining;
+ UINT32 BitsCurrent;
+ UINT32 cBitsCurrent;
+
+ BYTE OutputBuffer[65536];
+ UINT32 OutputCount;
+
+ BYTE HistoryBuffer[2500000];
+ UINT32 HistoryIndex;
+ UINT32 HistoryBufferSize;
+};
+
+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 }
+};
+
+static INLINE BOOL zgfx_GetBits(ZGFX_CONTEXT* zgfx, UINT32 nbits)
+{
+ if (!zgfx)
+ return FALSE;
+
+ 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);
+ return TRUE;
+}
+
+static void zgfx_history_buffer_ring_write(ZGFX_CONTEXT* zgfx, const BYTE* src, size_t count)
+{
+ UINT32 front = 0;
+
+ if (count <= 0)
+ return;
+
+ if (count > zgfx->HistoryBufferSize)
+ {
+ const size_t 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;
+ }
+}
+
+static void zgfx_history_buffer_ring_read(ZGFX_CONTEXT* zgfx, int offset, BYTE* dst, UINT32 count)
+{
+ UINT32 front = 0;
+ UINT32 index = 0;
+ INT32 bytes = 0;
+ UINT32 valid = 0;
+ INT32 bytesLeft = 0;
+ BYTE* dptr = dst;
+ BYTE* origDst = dst;
+
+ if ((count <= 0) || (count > INT32_MAX))
+ return;
+
+ bytesLeft = (INT32)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);
+}
+
+static BOOL zgfx_decompress_segment(ZGFX_CONTEXT* zgfx, wStream* stream, size_t segmentSize)
+{
+ BYTE c = 0;
+ BYTE flags = 0;
+ UINT32 extra = 0;
+ int opIndex = 0;
+ UINT32 haveBits = 0;
+ UINT32 inPrefix = 0;
+ UINT32 count = 0;
+ UINT32 distance = 0;
+ BYTE* pbSegment = NULL;
+ size_t cbSegment = 0;
+
+ if (!zgfx || !stream || (segmentSize < 2))
+ return FALSE;
+
+ cbSegment = segmentSize - 1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, segmentSize) || (segmentSize > UINT32_MAX))
+ return FALSE;
+
+ Stream_Read_UINT8(stream, flags); /* header (1 byte) */
+ zgfx->OutputCount = 0;
+ pbSegment = Stream_Pointer(stream);
+ if (!Stream_SafeSeek(stream, cbSegment))
+ return FALSE;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ zgfx_history_buffer_ring_write(zgfx, pbSegment, cbSegment);
+
+ if (cbSegment > sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ CopyMemory(zgfx->OutputBuffer, pbSegment, cbSegment);
+ zgfx->OutputCount = cbSegment;
+ return TRUE;
+ }
+
+ zgfx->pbInputCurrent = pbSegment;
+ zgfx->pbInputEnd = &pbSegment[cbSegment - 1];
+ /* NumberOfBitsToDecode = ((NumberOfBytesToDecode - 1) * 8) - ValueOfLastByte */
+ const UINT32 bits = 8u * (cbSegment - 1u);
+ if (bits < *zgfx->pbInputEnd)
+ return FALSE;
+
+ zgfx->cBitsRemaining = bits - *zgfx->pbInputEnd;
+ 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)
+ {
+ zgfx_GetBits(zgfx, 1);
+ inPrefix = (inPrefix << 1) + zgfx->bits;
+ haveBits++;
+ }
+
+ if (inPrefix == ZGFX_TOKEN_TABLE[opIndex].prefixCode)
+ {
+ if (ZGFX_TOKEN_TABLE[opIndex].tokenType == 0)
+ {
+ /* Literal */
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ c = (BYTE)(ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits);
+ zgfx->HistoryBuffer[zgfx->HistoryIndex] = c;
+
+ if (++zgfx->HistoryIndex == zgfx->HistoryBufferSize)
+ zgfx->HistoryIndex = 0;
+
+ if (zgfx->OutputCount >= sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ zgfx->OutputBuffer[zgfx->OutputCount++] = c;
+ }
+ else
+ {
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ distance = ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits;
+
+ if (distance != 0)
+ {
+ /* Match */
+ zgfx_GetBits(zgfx, 1);
+
+ if (zgfx->bits == 0)
+ {
+ count = 3;
+ }
+ else
+ {
+ count = 4;
+ extra = 2;
+ zgfx_GetBits(zgfx, 1);
+
+ while (zgfx->bits == 1)
+ {
+ count *= 2;
+ extra++;
+ zgfx_GetBits(zgfx, 1);
+ }
+
+ zgfx_GetBits(zgfx, extra);
+ count += zgfx->bits;
+ }
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ 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;
+ }
+ else
+ {
+ /* Unencoded */
+ zgfx_GetBits(zgfx, 15);
+ count = zgfx->bits;
+ zgfx->cBitsRemaining -= zgfx->cBitsCurrent;
+ zgfx->cBitsCurrent = 0;
+ zgfx->BitsCurrent = 0;
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ if (count > zgfx->cBitsRemaining / 8)
+ return FALSE;
+
+ CopyMemory(&(zgfx->OutputBuffer[zgfx->OutputCount]), zgfx->pbInputCurrent,
+ count);
+ zgfx_history_buffer_ring_write(zgfx, zgfx->pbInputCurrent, count);
+ zgfx->pbInputCurrent += count;
+ zgfx->cBitsRemaining -= (8 * count);
+ zgfx->OutputCount += count;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* Allocate the buffers a bit larger.
+ *
+ * Due to optimizations some h264 decoders will read data beyond
+ * the actual available data, so ensure that it will never be a
+ * out of bounds read.
+ */
+static BYTE* aligned_zgfx_malloc(size_t size)
+{
+ return malloc(size + 64);
+}
+
+int zgfx_decompress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32 flags)
+{
+ int status = -1;
+ BYTE descriptor = 0;
+ wStream sbuffer = { 0 };
+ wStream* stream = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize);
+
+ if (!stream)
+ return -1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 1))
+ goto fail;
+
+ Stream_Read_UINT8(stream, descriptor); /* descriptor (1 byte) */
+
+ if (descriptor == ZGFX_SEGMENTED_SINGLE)
+ {
+ if (!zgfx_decompress_segment(zgfx, stream, Stream_GetRemainingLength(stream)))
+ goto fail;
+
+ *ppDstData = NULL;
+
+ if (zgfx->OutputCount > 0)
+ *ppDstData = aligned_zgfx_malloc(zgfx->OutputCount);
+
+ if (!*ppDstData)
+ goto fail;
+
+ *pDstSize = zgfx->OutputCount;
+ CopyMemory(*ppDstData, zgfx->OutputBuffer, zgfx->OutputCount);
+ }
+ else if (descriptor == ZGFX_SEGMENTED_MULTIPART)
+ {
+ UINT32 segmentSize = 0;
+ UINT16 segmentNumber = 0;
+ UINT16 segmentCount = 0;
+ UINT32 uncompressedSize = 0;
+ BYTE* pConcatenated = NULL;
+ size_t used = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 6))
+ goto fail;
+
+ Stream_Read_UINT16(stream, segmentCount); /* segmentCount (2 bytes) */
+ Stream_Read_UINT32(stream, uncompressedSize); /* uncompressedSize (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, stream, segmentCount, sizeof(UINT32)))
+ goto fail;
+
+ pConcatenated = aligned_zgfx_malloc(uncompressedSize);
+
+ if (!pConcatenated)
+ goto fail;
+
+ *ppDstData = pConcatenated;
+ *pDstSize = uncompressedSize;
+
+ for (segmentNumber = 0; segmentNumber < segmentCount; segmentNumber++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, sizeof(UINT32)))
+ goto fail;
+
+ Stream_Read_UINT32(stream, segmentSize); /* segmentSize (4 bytes) */
+
+ if (!zgfx_decompress_segment(zgfx, stream, segmentSize))
+ goto fail;
+
+ if (zgfx->OutputCount > UINT32_MAX - used)
+ goto fail;
+
+ if (used + zgfx->OutputCount > uncompressedSize)
+ goto fail;
+
+ CopyMemory(pConcatenated, zgfx->OutputBuffer, zgfx->OutputCount);
+ pConcatenated += zgfx->OutputCount;
+ used += zgfx->OutputCount;
+ }
+ }
+ else
+ {
+ goto fail;
+ }
+
+ status = 1;
+fail:
+ return status;
+}
+
+static BOOL zgfx_compress_segment(ZGFX_CONTEXT* zgfx, wStream* s, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32* pFlags)
+{
+ /* FIXME: Currently compression not implemented. Just copy the raw source */
+ if (!Stream_EnsureRemainingCapacity(s, SrcSize + 1))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return FALSE;
+ }
+
+ (*pFlags) |= ZGFX_PACKET_COMPR_TYPE_RDP8; /* RDP 8.0 compression format */
+ Stream_Write_UINT8(s, (*pFlags)); /* header (1 byte) */
+ Stream_Write(s, pSrcData, SrcSize);
+ return TRUE;
+}
+
+int zgfx_compress_to_stream(ZGFX_CONTEXT* zgfx, wStream* sDst, const BYTE* pUncompressed,
+ UINT32 uncompressedSize, UINT32* pFlags)
+{
+ int fragment = 0;
+ UINT16 maxLength = 0;
+ UINT32 totalLength = 0;
+ size_t posSegmentCount = 0;
+ const BYTE* pSrcData = NULL;
+ int status = 0;
+ maxLength = ZGFX_SEGMENTED_MAXSIZE;
+ totalLength = uncompressedSize;
+ pSrcData = pUncompressed;
+
+ for (; (totalLength > 0) || (fragment == 0); fragment++)
+ {
+ UINT32 SrcSize = 0;
+ size_t posDstSize = 0;
+ size_t posDataStart = 0;
+ UINT32 DstSize = 0;
+ SrcSize = (totalLength > maxLength) ? maxLength : totalLength;
+ posDstSize = 0;
+ totalLength -= SrcSize;
+
+ /* Ensure we have enough space for headers */
+ if (!Stream_EnsureRemainingCapacity(sDst, 12))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return -1;
+ }
+
+ if (fragment == 0)
+ {
+ /* First fragment */
+ /* descriptor (1 byte) */
+ Stream_Write_UINT8(sDst, (totalLength == 0) ? ZGFX_SEGMENTED_SINGLE
+ : ZGFX_SEGMENTED_MULTIPART);
+
+ if (totalLength > 0)
+ {
+ posSegmentCount = Stream_GetPosition(sDst); /* segmentCount (2 bytes) */
+ Stream_Seek(sDst, 2);
+ Stream_Write_UINT32(sDst, uncompressedSize); /* uncompressedSize (4 bytes) */
+ }
+ }
+
+ if (fragment > 0 || totalLength > 0)
+ {
+ /* Multipart */
+ posDstSize = Stream_GetPosition(sDst); /* size (4 bytes) */
+ Stream_Seek(sDst, 4);
+ }
+
+ posDataStart = Stream_GetPosition(sDst);
+
+ if (!zgfx_compress_segment(zgfx, sDst, pSrcData, SrcSize, pFlags))
+ return -1;
+
+ if (posDstSize)
+ {
+ /* Fill segment data size */
+ DstSize = Stream_GetPosition(sDst) - posDataStart;
+ Stream_SetPosition(sDst, posDstSize);
+ Stream_Write_UINT32(sDst, DstSize);
+ Stream_SetPosition(sDst, posDataStart + DstSize);
+ }
+
+ pSrcData += SrcSize;
+ }
+
+ Stream_SealLength(sDst);
+
+ /* fill back segmentCount */
+ if (posSegmentCount)
+ {
+ Stream_SetPosition(sDst, posSegmentCount);
+ Stream_Write_UINT16(sDst, fragment);
+ Stream_SetPosition(sDst, Stream_Length(sDst));
+ }
+
+ return status;
+}
+
+int zgfx_compress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ wStream* s = Stream_New(NULL, SrcSize);
+ status = zgfx_compress_to_stream(zgfx, s, pSrcData, SrcSize, pFlags);
+ (*ppDstData) = Stream_Buffer(s);
+ (*pDstSize) = Stream_GetPosition(s);
+ Stream_Free(s, FALSE);
+ return status;
+}
+
+void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush)
+{
+ zgfx->HistoryIndex = 0;
+}
+
+ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor)
+{
+ ZGFX_CONTEXT* zgfx = NULL;
+ zgfx = (ZGFX_CONTEXT*)calloc(1, sizeof(ZGFX_CONTEXT));
+
+ if (zgfx)
+ {
+ zgfx->Compressor = Compressor;
+ zgfx->HistoryBufferSize = sizeof(zgfx->HistoryBuffer);
+ zgfx_context_reset(zgfx, FALSE);
+ }
+
+ return zgfx;
+}
+
+void zgfx_context_free(ZGFX_CONTEXT* zgfx)
+{
+ free(zgfx);
+}