diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /server/Sample | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/Sample')
-rw-r--r-- | server/Sample/CMakeLists.txt | 86 | ||||
-rw-r--r-- | server/Sample/ModuleOptions.cmake | 4 | ||||
-rw-r--r-- | server/Sample/sf_ainput.c | 92 | ||||
-rw-r--r-- | server/Sample/sf_ainput.h | 37 | ||||
-rw-r--r-- | server/Sample/sf_audin.c | 112 | ||||
-rw-r--r-- | server/Sample/sf_audin.h | 35 | ||||
-rw-r--r-- | server/Sample/sf_encomsp.c | 43 | ||||
-rw-r--r-- | server/Sample/sf_encomsp.h | 31 | ||||
-rw-r--r-- | server/Sample/sf_rdpsnd.c | 62 | ||||
-rw-r--r-- | server/Sample/sf_rdpsnd.h | 31 | ||||
-rw-r--r-- | server/Sample/sfreerdp.c | 1440 | ||||
-rw-r--r-- | server/Sample/sfreerdp.h | 76 | ||||
-rw-r--r-- | server/Sample/test_icon.bmp | bin | 0 -> 5770 bytes | |||
-rw-r--r-- | server/Sample/test_icon.jpg | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | server/Sample/test_icon.png | bin | 0 -> 6140 bytes | |||
-rw-r--r-- | server/Sample/test_icon.ppm | 5572 | ||||
-rw-r--r-- | server/Sample/test_icon.webp | bin | 0 -> 2122 bytes |
17 files changed, 7621 insertions, 0 deletions
diff --git a/server/Sample/CMakeLists.txt b/server/Sample/CMakeLists.txt new file mode 100644 index 0000000..e23f65c --- /dev/null +++ b/server/Sample/CMakeLists.txt @@ -0,0 +1,86 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Sample Server cmake build script +# +# Copyright 2012 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. + +set(MODULE_NAME "sfreerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER_SAMPLE") + +set(SRCS + sfreerdp.c + sfreerdp.h + sf_audin.c + sf_audin.h + sf_rdpsnd.c + sf_rdpsnd.h + sf_encomsp.c + sf_encomsp.h) + +if (CHANNEL_AINPUT_SERVER) + list(APPEND SRCS sf_ainput.c sf_ainput.h) +endif() + + # On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +if (WITH_BINARY_VERSIONING) + set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP${FREERDP_VERSION_MAJOR}/images) +else() + set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP/images) +endif() + +set(SAMPLE_ICONS + test_icon.bmp + test_icon.png + test_icon.jpg + test_icon.webp +) +install(FILES ${SAMPLE_ICONS} DESTINATION ${SAMPLE_RESOURCE_ROOT}) + +# We need this in runtime path for TestConnect +file(COPY test_icon.bmp DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(${MODULE_NAME} ${SRCS}) +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") +endif() + +target_compile_definitions(${MODULE_NAME} + PRIVATE + -DSAMPLE_RESOURCE_ROOT="${SAMPLE_RESOURCE_ROOT}" +) +list(APPEND LIBS freerdp-server) +list(APPEND LIBS winpr freerdp) + +target_link_libraries(${MODULE_NAME} ${LIBS}) +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Sample") diff --git a/server/Sample/ModuleOptions.cmake b/server/Sample/ModuleOptions.cmake new file mode 100644 index 0000000..5621f6d --- /dev/null +++ b/server/Sample/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_SERVER_NAME "sfreerdp-server") +set(FREERDP_SERVER_PLATFORM "Sample") +set(FREERDP_SERVER_VENDOR "FreeRDP") diff --git a/server/Sample/sf_ainput.c b/server/Sample/sf_ainput.c new file mode 100644 index 0000000..1c19e07 --- /dev/null +++ b/server/Sample/sf_ainput.c @@ -0,0 +1,92 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Advanced Input) + * + * Copyright 2022 Armin Novak <armin.novak@thincast.com> + * Copyright 2022 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 "sfreerdp.h" + +#include "sf_ainput.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/server/ainput.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample.ainput") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sf_peer_ainput_mouse_event(ainput_server_context* context, UINT64 timestamp, + UINT64 flags, INT32 x, INT32 y) +{ + /* TODO: Implement */ + WINPR_ASSERT(context); + + WLog_WARN(TAG, "not implemented: 0x%08" PRIx64 ", 0x%08" PRIx64 ", %" PRId32 "x%" PRId32, + timestamp, flags, x, y); + return CHANNEL_RC_OK; +} + +void sf_peer_ainput_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->ainput = ainput_server_context_new(context->vcm); + WINPR_ASSERT(context->ainput); + + context->ainput->rdpcontext = &context->_p; + context->ainput->data = context; + + context->ainput->MouseEvent = sf_peer_ainput_mouse_event; +} + +BOOL sf_peer_ainput_start(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->Open) + return FALSE; + + return context->ainput->Open(context->ainput) == CHANNEL_RC_OK; +} + +BOOL sf_peer_ainput_stop(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->Close) + return FALSE; + + return context->ainput->Close(context->ainput) == CHANNEL_RC_OK; +} + +BOOL sf_peer_ainput_running(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->IsOpen) + return FALSE; + + return context->ainput->IsOpen(context->ainput); +} + +void sf_peer_ainput_uninit(testPeerContext* context) +{ + ainput_server_context_free(context->ainput); +} diff --git a/server/Sample/sf_ainput.h b/server/Sample/sf_ainput.h new file mode 100644 index 0000000..3cf78d5 --- /dev/null +++ b/server/Sample/sf_ainput.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Advanced Input) + * + * Copyright 2022 Armin Novak <armin.novak@thincast.com> + * Copyright 2022 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_SERVER_SAMPLE_SF_AINPUT_H +#define FREERDP_SERVER_SAMPLE_SF_AINPUT_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/ainput.h> + +#include "sfreerdp.h" + +void sf_peer_ainput_init(testPeerContext* context); +void sf_peer_ainput_uninit(testPeerContext* context); + +BOOL sf_peer_ainput_running(testPeerContext* context); +BOOL sf_peer_ainput_start(testPeerContext* context); +BOOL sf_peer_ainput_stop(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_AINPUT_H */ diff --git a/server/Sample/sf_audin.c b/server/Sample/sf_audin.c new file mode 100644 index 0000000..5783fe6 --- /dev/null +++ b/server/Sample/sf_audin.c @@ -0,0 +1,112 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Input) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de> + * + * 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 "sfreerdp.h" + +#include "sf_audin.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +#if defined(CHANNEL_AUDIN_SERVER) + +static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data) +{ + /* TODO: Implement */ + WINPR_ASSERT(audin); + WINPR_ASSERT(data); + + WLog_WARN(TAG, "not implemented"); + WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data)); + return CHANNEL_RC_OK; +} + +#endif + +BOOL sf_peer_audin_init(testPeerContext* context) +{ + WINPR_ASSERT(context); +#if defined(CHANNEL_AUDIN_SERVER) + context->audin = audin_server_context_new(context->vcm); + WINPR_ASSERT(context->audin); + + context->audin->rdpcontext = &context->_p; + context->audin->userdata = context; + + context->audin->Data = sf_peer_audin_data; + + return audin_server_set_formats(context->audin, -1, NULL); +#else + return TRUE; +#endif +} + +BOOL sf_peer_audin_start(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->Open) + return FALSE; + + return context->audin->Open(context->audin); +#else + return FALSE; +#endif +} + +BOOL sf_peer_audin_stop(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->Close) + return FALSE; + + return context->audin->Close(context->audin); +#else + return FALSE; +#endif +} + +BOOL sf_peer_audin_running(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->IsOpen) + return FALSE; + + return context->audin->IsOpen(context->audin); +#else + return FALSE; +#endif +} + +void sf_peer_audin_uninit(testPeerContext* context) +{ + WINPR_ASSERT(context); + +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context_free(context->audin); + context->audin = NULL; +#endif +} diff --git a/server/Sample/sf_audin.h b/server/Sample/sf_audin.h new file mode 100644 index 0000000..1769603 --- /dev/null +++ b/server/Sample/sf_audin.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Input) + * + * Copyright 2012 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_SERVER_SAMPLE_SF_AUDIN_H +#define FREERDP_SERVER_SAMPLE_SF_AUDIN_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include "sfreerdp.h" + +BOOL sf_peer_audin_init(testPeerContext* context); +void sf_peer_audin_uninit(testPeerContext* context); + +BOOL sf_peer_audin_running(testPeerContext* context); +BOOL sf_peer_audin_start(testPeerContext* context); +BOOL sf_peer_audin_stop(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_AUDIN_H */ diff --git a/server/Sample/sf_encomsp.c b/server/Sample/sf_encomsp.c new file mode 100644 index 0000000..6d03f79 --- /dev/null +++ b/server/Sample/sf_encomsp.c @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Lync Multiparty) + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@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 "sf_encomsp.h" + +BOOL sf_peer_encomsp_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->encomsp = encomsp_server_context_new(context->vcm); + if (!context->encomsp) + return FALSE; + + context->encomsp->rdpcontext = &context->_p; + + WINPR_ASSERT(context->encomsp->Start); + if (context->encomsp->Start(context->encomsp) != CHANNEL_RC_OK) + return FALSE; + + return TRUE; +} diff --git a/server/Sample/sf_encomsp.h b/server/Sample/sf_encomsp.h new file mode 100644 index 0000000..7976abc --- /dev/null +++ b/server/Sample/sf_encomsp.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Lync Multiparty) + * + * 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_SERVER_SAMPLE_SF_ENCOMSP_H +#define FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/encomsp.h> + +#include "sfreerdp.h" + +BOOL sf_peer_encomsp_init(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H */ diff --git a/server/Sample/sf_rdpsnd.c b/server/Sample/sf_rdpsnd.c new file mode 100644 index 0000000..6d4c1ec --- /dev/null +++ b/server/Sample/sf_rdpsnd.c @@ -0,0 +1,62 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@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 "sf_rdpsnd.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +static void sf_peer_rdpsnd_activated(RdpsndServerContext* context) +{ + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WLog_DBG(TAG, "RDPSND Activated"); +} + +BOOL sf_peer_rdpsnd_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->rdpsnd = rdpsnd_server_context_new(context->vcm); + WINPR_ASSERT(context->rdpsnd); + context->rdpsnd->rdpcontext = &context->_p; + context->rdpsnd->data = context; + context->rdpsnd->num_server_formats = + server_rdpsnd_get_formats(&context->rdpsnd->server_formats); + + if (context->rdpsnd->num_server_formats > 0) + context->rdpsnd->src_format = &context->rdpsnd->server_formats[0]; + + context->rdpsnd->Activated = sf_peer_rdpsnd_activated; + + WINPR_ASSERT(context->rdpsnd->Initialize); + if (context->rdpsnd->Initialize(context->rdpsnd, TRUE) != CHANNEL_RC_OK) + { + return FALSE; + } + + return TRUE; +} diff --git a/server/Sample/sf_rdpsnd.h b/server/Sample/sf_rdpsnd.h new file mode 100644 index 0000000..f9b0ef4 --- /dev/null +++ b/server/Sample/sf_rdpsnd.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Output) + * + * Copyright 2012 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_SERVER_SAMPLE_SF_RDPSND_H +#define FREERDP_SERVER_SAMPLE_SF_RDPSND_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/rdpsnd.h> + +#include "sfreerdp.h" + +BOOL sf_peer_rdpsnd_init(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_RDPSND_H */ diff --git a/server/Sample/sfreerdp.c b/server/Sample/sfreerdp.c new file mode 100644 index 0000000..f39a747 --- /dev/null +++ b/server/Sample/sfreerdp.c @@ -0,0 +1,1440 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Test Server + * + * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2011 Vic Lee + * Copyright 2014 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 <errno.h> +#include <signal.h> + +#include <winpr/winpr.h> +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/ssl.h> +#include <winpr/synch.h> +#include <winpr/file.h> +#include <winpr/string.h> +#include <winpr/path.h> +#include <winpr/image.h> +#include <winpr/winsock.h> + +#include <freerdp/streamdump.h> +#include <freerdp/transport_io.h> + +#include <freerdp/channels/wtsvc.h> +#include <freerdp/channels/channels.h> +#include <freerdp/channels/drdynvc.h> + +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/server/rdpsnd.h> +#include <freerdp/settings.h> + +#include "sf_ainput.h" +#include "sf_audin.h" +#include "sf_rdpsnd.h" +#include "sf_encomsp.h" + +#include "sfreerdp.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +#define SAMPLE_SERVER_USE_CLIENT_RESOLUTION 1 +#define SAMPLE_SERVER_DEFAULT_WIDTH 1024 +#define SAMPLE_SERVER_DEFAULT_HEIGHT 768 + +struct server_info +{ + BOOL test_dump_rfx_realtime; + const char* test_pcap_file; + const char* replay_dump; + const char* cert; + const char* key; +}; + +static void test_peer_context_free(freerdp_peer* client, rdpContext* ctx) +{ + testPeerContext* context = (testPeerContext*)ctx; + + WINPR_UNUSED(client); + + if (context) + { + winpr_image_free(context->image, TRUE); + if (context->debug_channel_thread) + { + WINPR_ASSERT(context->stopEvent); + SetEvent(context->stopEvent); + WaitForSingleObject(context->debug_channel_thread, INFINITE); + CloseHandle(context->debug_channel_thread); + } + + Stream_Free(context->s, TRUE); + free(context->bg_data); + rfx_context_free(context->rfx_context); + nsc_context_free(context->nsc_context); + + if (context->debug_channel) + WTSVirtualChannelClose(context->debug_channel); + + sf_peer_audin_uninit(context); + +#if defined(CHANNEL_AINPUT_SERVER) + sf_peer_ainput_uninit(context); +#endif + + rdpsnd_server_context_free(context->rdpsnd); + encomsp_server_context_free(context->encomsp); + + WTSCloseServer((HANDLE)context->vcm); + } +} + +static BOOL test_peer_context_new(freerdp_peer* client, rdpContext* ctx) +{ + testPeerContext* context = (testPeerContext*)ctx; + + WINPR_ASSERT(client); + WINPR_ASSERT(context); + WINPR_ASSERT(ctx->settings); + + context->image = winpr_image_new(); + if (!context->image) + goto fail; + if (!(context->rfx_context = rfx_context_new_ex( + TRUE, freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags)))) + goto fail; + + if (!rfx_context_reset(context->rfx_context, SAMPLE_SERVER_DEFAULT_WIDTH, + SAMPLE_SERVER_DEFAULT_HEIGHT)) + goto fail; + + rfx_context_set_mode(context->rfx_context, RLGR3); + + if (!(context->nsc_context = nsc_context_new())) + goto fail; + + if (!(context->s = Stream_New(NULL, 65536))) + goto fail; + + context->icon_x = UINT32_MAX; + context->icon_y = UINT32_MAX; + context->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE) + goto fail; + + return TRUE; +fail: + test_peer_context_free(client, ctx); + return FALSE; +} + +static BOOL test_peer_init(freerdp_peer* client) +{ + WINPR_ASSERT(client); + + client->ContextSize = sizeof(testPeerContext); + client->ContextNew = test_peer_context_new; + client->ContextFree = test_peer_context_free; + return freerdp_peer_context_new(client); +} + +static wStream* test_peer_stream_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->s); + + Stream_Clear(context->s); + Stream_SetPosition(context->s, 0); + return context->s; +} + +static void test_peer_begin_frame(freerdp_peer* client) +{ + rdpUpdate* update = NULL; + SURFACE_FRAME_MARKER fm = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + update = client->context->update; + WINPR_ASSERT(update); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + fm.frameAction = SURFACECMD_FRAMEACTION_BEGIN; + fm.frameId = context->frame_id; + WINPR_ASSERT(update->SurfaceFrameMarker); + update->SurfaceFrameMarker(update->context, &fm); +} + +static void test_peer_end_frame(freerdp_peer* client) +{ + rdpUpdate* update = NULL; + SURFACE_FRAME_MARKER fm = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + update = client->context->update; + WINPR_ASSERT(update); + + fm.frameAction = SURFACECMD_FRAMEACTION_END; + fm.frameId = context->frame_id; + WINPR_ASSERT(update->SurfaceFrameMarker); + update->SurfaceFrameMarker(update->context, &fm); + context->frame_id++; +} + +static BOOL test_peer_draw_background(freerdp_peer* client) +{ + size_t size = 0; + wStream* s = NULL; + RFX_RECT rect; + BYTE* rgb_data = NULL; + const rdpSettings* settings = NULL; + rdpUpdate* update = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + testPeerContext* context = NULL; + BOOL ret = FALSE; + const UINT32 colorFormat = PIXEL_FORMAT_RGB24; + const size_t bpp = FreeRDPGetBytesPerPixel(colorFormat); + + WINPR_ASSERT(client); + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + update = client->context->update; + WINPR_ASSERT(update); + + const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec); + if (!RemoteFxCodec && !freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + return FALSE; + + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + + s = test_peer_stream_init(context); + rect.x = 0; + rect.y = 0; + rect.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + rect.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + size = bpp * rect.width * rect.height; + + if (!(rgb_data = malloc(size))) + { + WLog_ERR(TAG, "Problem allocating memory"); + return FALSE; + } + + memset(rgb_data, 0xA0, size); + + if (RemoteFxCodec) + { + WLog_DBG(TAG, "Using RemoteFX codec"); + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + if (!rfx_compose_message(context->rfx_context, s, &rect, 1, rgb_data, rect.width, + rect.height, rect.width * bpp)) + { + goto out; + } + + const UINT32 RemoteFxCodecId = + freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)RemoteFxCodecId; + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + } + else + { + WLog_DBG(TAG, "Using NSCodec"); + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, rgb_data, rect.width, rect.height, + rect.width * bpp); + const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + WINPR_ASSERT(NSCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)NSCodecId; + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + } + + cmd.destLeft = 0; + cmd.destTop = 0; + cmd.destRight = rect.width; + cmd.destBottom = rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + test_peer_begin_frame(client); + update->SurfaceBits(update->context, &cmd); + test_peer_end_frame(client); + ret = TRUE; +out: + free(rgb_data); + return ret; +} + +static int open_icon(wImage* img) +{ + char* paths[] = { SAMPLE_RESOURCE_ROOT, "." }; + const char* names[] = { "test_icon.webp", "test_icon.png", "test_icon.jpg", "test_icon.bmp" }; + + for (size_t x = 0; x < ARRAYSIZE(paths); x++) + { + const char* path = paths[x]; + if (!winpr_PathFileExists(path)) + continue; + + for (size_t y = 0; y < ARRAYSIZE(names); y++) + { + const char* name = names[y]; + char* file = GetCombinedPath(path, name); + int rc = winpr_image_read(img, file); + free(file); + if (rc > 0) + return rc; + } + } + WLog_ERR(TAG, "Unable to open test icon"); + return -1; +} + +static BOOL test_peer_load_icon(freerdp_peer* client) +{ + testPeerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && + !freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + { + WLog_ERR(TAG, "Client doesn't support RemoteFX or NSCodec"); + return FALSE; + } + + int rc = open_icon(context->image); + if (rc <= 0) + goto out_fail; + + /* background with same size, which will be used to erase the icon from old position */ + if (!(context->bg_data = calloc(context->image->height, context->image->width * 3))) + goto out_fail; + + memset(context->bg_data, 0xA0, context->image->height * context->image->width * 3ull); + return TRUE; +out_fail: + context->bg_data = NULL; + return FALSE; +} + +static void test_peer_draw_icon(freerdp_peer* client, UINT32 x, UINT32 y) +{ + wStream* s = NULL; + RFX_RECT rect; + rdpUpdate* update = NULL; + rdpSettings* settings = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + update = client->context->update; + WINPR_ASSERT(update); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (freerdp_settings_get_bool(settings, FreeRDP_DumpRemoteFx)) + return; + + if (context->image->width < 1 || !context->activated) + return; + + rect.x = 0; + rect.y = 0; + rect.width = context->image->width; + rect.height = context->image->height; + + const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + if (context->icon_x + context->image->width > w) + return; + if (context->icon_y + context->image->height > h) + return; + if (x + context->image->width > w) + return; + if (y + context->image->height > h) + return; + + test_peer_begin_frame(client); + const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec); + if (RemoteFxCodec) + { + const UINT32 RemoteFxCodecId = + freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)RemoteFxCodecId; + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + } + else + { + const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + WINPR_ASSERT(NSCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)NSCodecId; + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + } + + if (context->icon_x != UINT32_MAX) + { + const UINT32 colorFormat = PIXEL_FORMAT_RGB24; + const UINT32 bpp = FreeRDPGetBytesPerPixel(colorFormat); + s = test_peer_stream_init(context); + + if (RemoteFxCodec) + { + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + rfx_compose_message(context->rfx_context, s, &rect, 1, context->bg_data, rect.width, + rect.height, rect.width * bpp); + } + else + { + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, context->bg_data, rect.width, rect.height, + rect.width * bpp); + } + + cmd.destLeft = context->icon_x; + cmd.destTop = context->icon_y; + cmd.destRight = context->icon_x + rect.width; + cmd.destBottom = context->icon_y + rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + WINPR_ASSERT(update->SurfaceBits); + update->SurfaceBits(update->context, &cmd); + } + + s = test_peer_stream_init(context); + + { + const UINT32 colorFormat = + context->image->bitsPerPixel > 24 ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGR24; + + if (RemoteFxCodec) + { + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + rfx_compose_message(context->rfx_context, s, &rect, 1, context->image->data, rect.width, + rect.height, context->image->scanline); + } + else + { + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, context->image->data, rect.width, + rect.height, context->image->scanline); + } + } + + cmd.destLeft = x; + cmd.destTop = y; + cmd.destRight = x + rect.width; + cmd.destBottom = y + rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + WINPR_ASSERT(update->SurfaceBits); + update->SurfaceBits(update->context, &cmd); + context->icon_x = x; + context->icon_y = y; + test_peer_end_frame(client); +} + +static BOOL test_sleep_tsdiff(UINT32* old_sec, UINT32* old_usec, UINT32 new_sec, UINT32 new_usec) +{ + INT64 sec = 0; + INT64 usec = 0; + + WINPR_ASSERT(old_sec); + WINPR_ASSERT(old_usec); + + if ((*old_sec == 0) && (*old_usec == 0)) + { + *old_sec = new_sec; + *old_usec = new_usec; + return TRUE; + } + + sec = new_sec - *old_sec; + usec = new_usec - *old_usec; + + if ((sec < 0) || ((sec == 0) && (usec < 0))) + { + WLog_ERR(TAG, "Invalid time stamp detected."); + return FALSE; + } + + *old_sec = new_sec; + *old_usec = new_usec; + + while (usec < 0) + { + usec += 1000000; + sec--; + } + + if (sec > 0) + Sleep((DWORD)sec * 1000); + + if (usec > 0) + USleep((DWORD)usec); + + return TRUE; +} + +static BOOL tf_peer_dump_rfx(freerdp_peer* client) +{ + wStream* s = NULL; + UINT32 prev_seconds = 0; + UINT32 prev_useconds = 0; + rdpUpdate* update = NULL; + rdpPcap* pcap_rfx = NULL; + pcap_record record = { 0 }; + struct server_info* info = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + s = Stream_New(NULL, 512); + + if (!s) + return FALSE; + + update = client->context->update; + WINPR_ASSERT(update); + + if (!(pcap_rfx = pcap_open(info->test_pcap_file, FALSE))) + return FALSE; + + prev_seconds = prev_useconds = 0; + + while (pcap_has_next_record(pcap_rfx)) + { + if (!pcap_get_next_record_header(pcap_rfx, &record)) + break; + + if (!Stream_EnsureCapacity(s, record.length)) + break; + + record.data = Stream_Buffer(s); + pcap_get_next_record_content(pcap_rfx, &record); + Stream_SetPosition(s, Stream_Capacity(s)); + + if (info->test_dump_rfx_realtime && + test_sleep_tsdiff(&prev_seconds, &prev_useconds, record.header.ts_sec, + record.header.ts_usec) == FALSE) + break; + + WINPR_ASSERT(update->SurfaceCommand); + update->SurfaceCommand(update->context, s); + + WINPR_ASSERT(client->CheckFileDescriptor); + if (client->CheckFileDescriptor(client) != TRUE) + break; + } + + Stream_Free(s, TRUE); + pcap_close(pcap_rfx); + return TRUE; +} + +static DWORD WINAPI tf_debug_channel_thread_func(LPVOID arg) +{ + void* fd = NULL; + wStream* s = NULL; + void* buffer = NULL; + DWORD BytesReturned = 0; + ULONG written = 0; + testPeerContext* context = (testPeerContext*)arg; + + WINPR_ASSERT(context); + if (WTSVirtualChannelQuery(context->debug_channel, WTSVirtualFileHandle, &buffer, + &BytesReturned) == TRUE) + { + fd = *((void**)buffer); + WTSFreeMemory(buffer); + + if (!(context->event = CreateWaitObjectEvent(NULL, TRUE, FALSE, fd))) + return 0; + } + + s = Stream_New(NULL, 4096); + WTSVirtualChannelWrite(context->debug_channel, (PCHAR) "test1", 5, &written); + + while (1) + { + DWORD status = 0; + DWORD nCount = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + + handles[nCount++] = context->event; + handles[nCount++] = freerdp_abort_event(&context->_p); + handles[nCount++] = context->stopEvent; + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + default: + goto fail; + } + + Stream_SetPosition(s, 0); + + if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + if (BytesReturned == 0) + break; + + Stream_EnsureRemainingCapacity(s, BytesReturned); + + if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + /* should not happen */ + break; + } + } + + Stream_SetPosition(s, BytesReturned); + WLog_DBG(TAG, "got %" PRIu32 " bytes", BytesReturned); + } +fail: + Stream_Free(s, TRUE); + return 0; +} + +static BOOL tf_peer_post_connect(freerdp_peer* client) +{ + testPeerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + /** + * This callback is called when the entire connection sequence is done, i.e. we've received the + * Font List PDU from the client and sent out the Font Map PDU. + * The server may start sending graphics output and receiving keyboard/mouse input after this + * callback returns. + */ + WLog_DBG(TAG, "Client %s is activated (osMajorType %" PRIu32 " osMinorType %" PRIu32 ")", + client->local ? "(local)" : client->hostname, + freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType), + freerdp_settings_get_uint32(settings, FreeRDP_OsMinorType)); + + if (freerdp_settings_get_bool(settings, FreeRDP_AutoLogonEnabled)) + { + const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain); + WLog_DBG(TAG, " and wants to login automatically as %s\\%s", Domain ? Domain : "", + Username); + /* A real server may perform OS login here if NLA is not executed previously. */ + } + + WLog_DBG(TAG, ""); + WLog_DBG(TAG, "Client requested desktop: %" PRIu32 "x%" PRIu32 "x%" PRIu32 "", + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); +#if (SAMPLE_SERVER_USE_CLIENT_RESOLUTION == 1) + + if (!rfx_context_reset(context->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + WLog_DBG(TAG, "Using resolution requested by client."); +#else + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) = + context->rfx_context->width; + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) = + context->rfx_context->height; + WLog_DBG(TAG, "Resizing client to %" PRIu32 "x%" PRIu32 "", + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + client->update->DesktopResize(client->update->context); +#endif + + /* A real server should tag the peer as activated here and start sending updates in main loop. + */ + if (!test_peer_load_icon(client)) + { + WLog_DBG(TAG, "Unable to load icon"); + return FALSE; + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpdbg")) + { + context->debug_channel = WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, "rdpdbg"); + + if (context->debug_channel != NULL) + { + WLog_DBG(TAG, "Open channel rdpdbg."); + + if (!(context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "Failed to create stop event"); + return FALSE; + } + + if (!(context->debug_channel_thread = + CreateThread(NULL, 0, tf_debug_channel_thread_func, (void*)context, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create debug channel thread"); + CloseHandle(context->stopEvent); + context->stopEvent = NULL; + return FALSE; + } + } + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, RDPSND_CHANNEL_NAME)) + { + sf_peer_rdpsnd_init(context); /* Audio Output */ + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, ENCOMSP_SVC_CHANNEL_NAME)) + { + sf_peer_encomsp_init(context); /* Lync Multiparty */ + } + + /* Dynamic Virtual Channels */ + sf_peer_audin_init(context); /* Audio Input */ + +#if defined(CHANNEL_AINPUT_SERVER) + sf_peer_ainput_init(context); +#endif + + /* Return FALSE here would stop the execution of the peer main loop. */ + return TRUE; +} + +static BOOL tf_peer_activate(freerdp_peer* client) +{ + testPeerContext* context = NULL; + struct server_info* info = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + context->activated = TRUE; + // PACKET_COMPR_TYPE_8K; + // PACKET_COMPR_TYPE_64K; + // PACKET_COMPR_TYPE_RDP6; + if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8)) + return FALSE; + + if (info->test_pcap_file != NULL) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_DumpRemoteFx, TRUE)) + return FALSE; + + if (!tf_peer_dump_rfx(client)) + return FALSE; + } + else + test_peer_draw_background(client); + + return TRUE; +} + +static BOOL tf_peer_synchronize_event(rdpInput* input, UINT32 flags) +{ + WINPR_UNUSED(input); + WINPR_ASSERT(input); + WLog_DBG(TAG, "Client sent a synchronize event (flags:0x%" PRIX32 ")", flags); + return TRUE; +} + +static BOOL tf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + freerdp_peer* client = NULL; + rdpUpdate* update = NULL; + rdpContext* context = NULL; + testPeerContext* tcontext = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(input); + + context = input->context; + WINPR_ASSERT(context); + + client = context->peer; + WINPR_ASSERT(client); + + settings = context->settings; + WINPR_ASSERT(settings); + + update = context->update; + WINPR_ASSERT(update); + + tcontext = (testPeerContext*)context; + WINPR_ASSERT(tcontext); + + WLog_DBG(TAG, "Client sent a keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX8 ")", flags, + code); + + if (((flags & KBD_FLAGS_RELEASE) == 0) && (code == RDP_SCANCODE_KEY_G)) /* 'g' key */ + { + if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != 800) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600)) + return FALSE; + } + else + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, + SAMPLE_SERVER_DEFAULT_WIDTH)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, + SAMPLE_SERVER_DEFAULT_HEIGHT)) + return FALSE; + } + + if (!rfx_context_reset(tcontext->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + WINPR_ASSERT(update->DesktopResize); + update->DesktopResize(update->context); + tcontext->activated = FALSE; + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_C) /* 'c' key */ + { + if (tcontext->debug_channel) + { + ULONG written = 0; + WTSVirtualChannelWrite(tcontext->debug_channel, (PCHAR) "test2", 5, &written); + } + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_X) /* 'x' key */ + { + WINPR_ASSERT(client->Close); + client->Close(client); + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_R) /* 'r' key */ + { + tcontext->audin_open = !tcontext->audin_open; + } +#if defined(CHANNEL_AINPUT_SERVER) + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_I) /* 'i' key */ + { + tcontext->ainput_open = !tcontext->ainput_open; + } +#endif + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_S) /* 's' key */ + { + } + + return TRUE; +} + +static BOOL tf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + WINPR_UNUSED(input); + WINPR_ASSERT(input); + + WLog_DBG(TAG, + "Client sent a unicode keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX16 ")", + flags, code); + return TRUE; +} + +static BOOL tf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(flags); + WINPR_ASSERT(input); + WINPR_ASSERT(input->context); + + // WLog_DBG(TAG, "Client sent a mouse event (flags:0x%04"PRIX16" pos:%"PRIu16",%"PRIu16")", + // flags, x, y); + test_peer_draw_icon(input->context->peer, x + 10, y); + return TRUE; +} + +static BOOL tf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(flags); + WINPR_ASSERT(input); + WINPR_ASSERT(input->context); + + // WLog_DBG(TAG, "Client sent an extended mouse event (flags:0x%04"PRIX16" + // pos:%"PRIu16",%"PRIu16")", flags, x, y); + test_peer_draw_icon(input->context->peer, x + 10, y); + return TRUE; +} + +static BOOL tf_peer_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas) +{ + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(areas || (count == 0)); + + WLog_DBG(TAG, "Client requested to refresh:"); + + for (BYTE i = 0; i < count; i++) + { + WLog_DBG(TAG, " (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ")", areas[i].left, + areas[i].top, areas[i].right, areas[i].bottom); + } + + return TRUE; +} + +static BOOL tf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + WINPR_UNUSED(context); + + if (allow > 0) + { + WINPR_ASSERT(area); + WLog_DBG(TAG, + "Client restore output (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ").", + area->left, area->top, area->right, area->bottom); + } + else + { + WLog_DBG(TAG, "Client minimized and suppress output."); + } + + return TRUE; +} + +static int hook_peer_write_pdu(rdpTransport* transport, wStream* s) +{ + UINT64 ts = 0; + wStream* ls = NULL; + UINT64 last_ts = 0; + const struct server_info* info = NULL; + freerdp_peer* client = NULL; + testPeerContext* peerCtx = NULL; + size_t offset = 0; + UINT32 flags = 0; + rdpContext* context = transport_get_context(transport); + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + client = context->peer; + WINPR_ASSERT(client); + + peerCtx = (testPeerContext*)client->context; + WINPR_ASSERT(peerCtx); + WINPR_ASSERT(peerCtx->io.WritePdu); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + /* Let the client authenticate. + * After that is done, we stop the normal operation and send + * a previously recorded session PDU by PDU to the client. + * + * This is fragile and the connecting client needs to use the same + * configuration as the one that recorded the session! + */ + WINPR_ASSERT(info); + CONNECTION_STATE state = freerdp_get_state(context); + if (state < CONNECTION_STATE_NEGO) + return peerCtx->io.WritePdu(transport, s); + + ls = Stream_New(NULL, 4096); + if (!ls) + goto fail; + + while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0) + { + int rc = 0; + /* Skip messages from client. */ + if (flags & STREAM_MSG_SRV_TX) + { + if ((last_ts > 0) && (ts > last_ts)) + { + UINT64 diff = ts - last_ts; + Sleep(diff); + } + last_ts = ts; + rc = peerCtx->io.WritePdu(transport, ls); + if (rc < 0) + goto fail; + } + Stream_SetPosition(ls, 0); + } + +fail: + Stream_Free(ls, TRUE); + return -1; +} + +static DWORD WINAPI test_peer_mainloop(LPVOID arg) +{ + BOOL rc = 0; + DWORD error = CHANNEL_RC_OK; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = 0; + DWORD status = 0; + testPeerContext* context = NULL; + struct server_info* info = NULL; + rdpSettings* settings = NULL; + rdpInput* input = NULL; + rdpUpdate* update = NULL; + freerdp_peer* client = (freerdp_peer*)arg; + + WINPR_ASSERT(client); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + if (!test_peer_init(client)) + { + freerdp_peer_free(client); + return 0; + } + + /* Initialize the real server settings here */ + WINPR_ASSERT(client->context); + settings = client->context->settings; + WINPR_ASSERT(settings); + if (info->replay_dump) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE) || + !freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, info->replay_dump)) + goto fail; + } + + rdpPrivateKey* key = freerdp_key_new_from_file(info->key); + if (!key) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + goto fail; + rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert); + if (!cert) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + goto fail; + if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel, + ENCRYPTION_LEVEL_CLIENT_COMPATIBLE)) + goto fail; + /* ENCRYPTION_LEVEL_HIGH; */ + /* ENCRYPTION_LEVEL_LOW; */ + /* ENCRYPTION_LEVEL_FIPS; */ + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) || + !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE)) + goto fail; + + client->PostConnect = tf_peer_post_connect; + client->Activate = tf_peer_activate; + + WINPR_ASSERT(client->context); + input = client->context->input; + WINPR_ASSERT(input); + + input->SynchronizeEvent = tf_peer_synchronize_event; + input->KeyboardEvent = tf_peer_keyboard_event; + input->UnicodeKeyboardEvent = tf_peer_unicode_keyboard_event; + input->MouseEvent = tf_peer_mouse_event; + input->ExtendedMouseEvent = tf_peer_extended_mouse_event; + + update = client->context->update; + WINPR_ASSERT(update); + + update->RefreshRect = tf_peer_refresh_rect; + update->SuppressOutput = tf_peer_suppress_output; + if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize, + 0xFFFFFF /* FIXME */)) + goto fail; + + WINPR_ASSERT(client->Initialize); + rc = client->Initialize(client); + if (!rc) + goto fail; + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + if (info->replay_dump) + { + const rdpTransportIo* cb = freerdp_get_io_callbacks(client->context); + rdpTransportIo replay; + + WINPR_ASSERT(cb); + replay = *cb; + context->io = *cb; + replay.WritePdu = hook_peer_write_pdu; + freerdp_set_io_callbacks(client->context, &replay); + } + + WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname); + + while (error == CHANNEL_RC_OK) + { + count = 0; + { + WINPR_ASSERT(client->GetEventHandles); + DWORD tmp = client->GetEventHandles(client, &handles[count], 32 - count); + + if (tmp == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP transport event handles"); + break; + } + + count += tmp; + } + + HANDLE channelHandle = WTSVirtualChannelManagerGetEventHandle(context->vcm); + handles[count++] = channelHandle; + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed (errno: %d)", errno); + break; + } + + WINPR_ASSERT(client->CheckFileDescriptor); + if (client->CheckFileDescriptor(client) != TRUE) + break; + + if (WaitForSingleObject(channelHandle, 0) != WAIT_OBJECT_0) + continue; + + if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE) + break; + + /* Handle dynamic virtual channel intializations */ + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, DRDYNVC_SVC_CHANNEL_NAME)) + { + switch (WTSVirtualChannelManagerGetDrdynvcState(context->vcm)) + { + case DRDYNVC_STATE_NONE: + break; + + case DRDYNVC_STATE_INITIALIZED: + break; + + case DRDYNVC_STATE_READY: + + /* Here is the correct state to start dynamic virtual channels */ + if (sf_peer_audin_running(context) != context->audin_open) + { + if (!sf_peer_audin_running(context)) + sf_peer_audin_start(context); + else + sf_peer_audin_stop(context); + } + +#if defined(CHANNEL_AINPUT_SERVER) + if (sf_peer_ainput_running(context) != context->ainput_open) + { + if (!sf_peer_ainput_running(context)) + sf_peer_ainput_start(context); + else + sf_peer_ainput_stop(context); + } +#endif + + break; + + case DRDYNVC_STATE_FAILED: + default: + break; + } + } + } + + WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname); + + WINPR_ASSERT(client->Disconnect); + client->Disconnect(client); +fail: + freerdp_peer_context_free(client); + freerdp_peer_free(client); + return error; +} + +static BOOL test_peer_accepted(freerdp_listener* instance, freerdp_peer* client) +{ + HANDLE hThread = NULL; + struct server_info* info = NULL; + + WINPR_UNUSED(instance); + + WINPR_ASSERT(instance); + WINPR_ASSERT(client); + + info = instance->info; + client->ContextExtra = info; + + if (!(hThread = CreateThread(NULL, 0, test_peer_mainloop, (void*)client, 0, NULL))) + return FALSE; + + CloseHandle(hThread); + return TRUE; +} + +static void test_server_mainloop(freerdp_listener* instance) +{ + HANDLE handles[32] = { 0 }; + DWORD count = 0; + DWORD status = 0; + + WINPR_ASSERT(instance); + while (1) + { + WINPR_ASSERT(instance->GetEventHandles); + count = instance->GetEventHandles(instance, handles, 32); + + if (0 == count) + { + WLog_ERR(TAG, "Failed to get FreeRDP event handles"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + WLog_ERR(TAG, "select failed"); + break; + } + + WINPR_ASSERT(instance->CheckFileDescriptor); + if (instance->CheckFileDescriptor(instance) != TRUE) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + WINPR_ASSERT(instance->Close); + instance->Close(instance); +} + +static const struct +{ + const char spcap[7]; + const char sfast[7]; + const char sport[7]; + const char slocal_only[13]; + const char scert[7]; + const char skey[6]; +} options = { "--pcap=", "--fast", "--port=", "--local-only", "--cert=", "--key=" }; + +WINPR_ATTR_FORMAT_ARG(2, 0) +static void print_entry(FILE* fp, WINPR_FORMAT_ARG const char* fmt, const char* what, size_t size) +{ + char buffer[32] = { 0 }; + strncpy(buffer, what, MIN(size, sizeof(buffer) - 1)); + fprintf(fp, fmt, buffer); +} + +static WINPR_NORETURN(void usage(const char* app, const char* invalid)) +{ + FILE* fp = stdout; + + fprintf(fp, "Invalid argument '%s'\n", invalid); + fprintf(fp, "Usage: %s <arg>[ <arg> ...]\n", app); + fprintf(fp, "Arguments:\n"); + print_entry(fp, "\t%s<pcap file>\n", options.spcap, sizeof(options.spcap)); + print_entry(fp, "\t%s<cert file>\n", options.scert, sizeof(options.scert)); + print_entry(fp, "\t%s<key file>\n", options.skey, sizeof(options.skey)); + print_entry(fp, "\t%s\n", options.sfast, sizeof(options.sfast)); + print_entry(fp, "\t%s<port>\n", options.sport, sizeof(options.sport)); + print_entry(fp, "\t%s\n", options.slocal_only, sizeof(options.slocal_only)); + exit(-1); +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + BOOL started = FALSE; + WSADATA wsaData = { 0 }; + freerdp_listener* instance = NULL; + char* file = NULL; + char name[MAX_PATH] = { 0 }; + long port = 3389; + BOOL localOnly = FALSE; + struct server_info info = { 0 }; + const char* app = argv[0]; + + info.test_dump_rfx_realtime = TRUE; + + errno = 0; + + for (int i = 1; i < argc; i++) + { + char* arg = argv[i]; + + if (strncmp(arg, options.sfast, sizeof(options.sfast)) == 0) + info.test_dump_rfx_realtime = FALSE; + else if (strncmp(arg, options.sport, sizeof(options.sport)) == 0) + { + const char* sport = &arg[sizeof(options.sport)]; + port = strtol(sport, NULL, 10); + + if ((port < 1) || (port > UINT16_MAX) || (errno != 0)) + usage(app, arg); + } + else if (strncmp(arg, options.slocal_only, sizeof(options.slocal_only)) == 0) + localOnly = TRUE; + else if (strncmp(arg, options.spcap, sizeof(options.spcap)) == 0) + { + info.test_pcap_file = &arg[sizeof(options.spcap)]; + if (!winpr_PathFileExists(info.test_pcap_file)) + usage(app, arg); + } + else if (strncmp(arg, options.scert, sizeof(options.scert)) == 0) + { + info.cert = &arg[sizeof(options.scert)]; + if (!winpr_PathFileExists(info.cert)) + usage(app, arg); + } + else if (strncmp(arg, options.skey, sizeof(options.skey)) == 0) + { + info.key = &arg[sizeof(options.skey)]; + if (!winpr_PathFileExists(info.key)) + usage(app, arg); + } + else + usage(app, arg); + } + + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); + instance = freerdp_listener_new(); + + if (!instance) + return -1; + + if (!info.cert) + info.cert = "server.crt"; + if (!info.key) + info.key = "server.key"; + + instance->info = (void*)&info; + instance->PeerAccepted = test_peer_accepted; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + goto fail; + + /* Open the server socket and start listening. */ + sprintf_s(name, sizeof(name), "tfreerdp-server.%ld", port); + file = GetKnownSubPath(KNOWN_PATH_TEMP, name); + + if (!file) + goto fail; + + if (localOnly) + { + WINPR_ASSERT(instance->OpenLocal); + started = instance->OpenLocal(instance, file); + } + else + { + WINPR_ASSERT(instance->Open); + started = instance->Open(instance, NULL, (UINT16)port); + } + + if (started) + { + /* Entering the server main loop. In a real server the listener can be run in its own + * thread. */ + test_server_mainloop(instance); + } + + rc = 0; +fail: + free(file); + freerdp_listener_free(instance); + WSACleanup(); + return rc; +} diff --git a/server/Sample/sfreerdp.h b/server/Sample/sfreerdp.h new file mode 100644 index 0000000..cba1052 --- /dev/null +++ b/server/Sample/sfreerdp.h @@ -0,0 +1,76 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server + * + * Copyright 2012 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_SERVER_SAMPLE_SFREERDP_SERVER_H +#define FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/codec/nsc.h> +#include <freerdp/channels/wtsvc.h> +#if defined(CHANNEL_AINPUT_SERVER) +#include <freerdp/server/ainput.h> +#endif +#if defined(CHANNEL_AUDIN_SERVER) +#include <freerdp/server/audin.h> +#endif +#include <freerdp/server/rdpsnd.h> +#include <freerdp/server/encomsp.h> +#include <freerdp/transport_io.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/image.h> + +struct test_peer_context +{ + rdpContext _p; + + RFX_CONTEXT* rfx_context; + NSC_CONTEXT* nsc_context; + wStream* s; + BYTE* bg_data; + UINT32 icon_x; + UINT32 icon_y; + BOOL activated; + HANDLE event; + HANDLE stopEvent; + HANDLE vcm; + void* debug_channel; + HANDLE debug_channel_thread; +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context* audin; +#endif + BOOL audin_open; +#if defined(CHANNEL_AINPUT_SERVER) + ainput_server_context* ainput; + BOOL ainput_open; +#endif + UINT32 frame_id; + RdpsndServerContext* rdpsnd; + EncomspServerContext* encomsp; + + rdpTransportIo io; + wImage* image; +}; +typedef struct test_peer_context testPeerContext; + +#endif /* FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H */ diff --git a/server/Sample/test_icon.bmp b/server/Sample/test_icon.bmp Binary files differnew file mode 100644 index 0000000..08935d4 --- /dev/null +++ b/server/Sample/test_icon.bmp diff --git a/server/Sample/test_icon.jpg b/server/Sample/test_icon.jpg Binary files differnew file mode 100644 index 0000000..758b59e --- /dev/null +++ b/server/Sample/test_icon.jpg diff --git a/server/Sample/test_icon.png b/server/Sample/test_icon.png Binary files differnew file mode 100644 index 0000000..91a4a5a --- /dev/null +++ b/server/Sample/test_icon.png diff --git a/server/Sample/test_icon.ppm b/server/Sample/test_icon.ppm new file mode 100644 index 0000000..bcc4a0e --- /dev/null +++ b/server/Sample/test_icon.ppm @@ -0,0 +1,5572 @@ +P3 +# CREATOR: GIMP PNM Filter Version 1.1 +29 64 +255 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +159 +159 +160 +135 +154 +160 +85 +141 +160 +82 +141 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +112 +148 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +155 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +114 +148 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +91 +143 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +136 +154 +160 +76 +140 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +137 +154 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +152 +158 +160 +79 +141 +160 +75 +140 +161 +74 +139 +160 +74 +139 +160 +83 +141 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +90 +143 +160 +75 +140 +161 +74 +139 +160 +75 +140 +161 +75 +140 +161 +105 +147 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +125 +151 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +74 +139 +160 +111 +154 +167 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +158 +160 +88 +143 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +138 +160 +99 +170 +189 +134 +171 +180 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +126 +151 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +137 +158 +63 +124 +147 +108 +181 +198 +152 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +80 +141 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +138 +159 +21 +72 +99 +78 +143 +164 +126 +180 +192 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +129 +152 +160 +74 +139 +160 +75 +140 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +138 +159 +25 +77 +104 +12 +60 +88 +105 +176 +194 +145 +167 +172 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +98 +145 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +27 +80 +106 +6 +53 +82 +57 +117 +140 +121 +182 +195 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +83 +141 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +76 +141 +162 +30 +83 +109 +6 +53 +82 +28 +81 +107 +108 +180 +197 +154 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +155 +158 +160 +80 +140 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +74 +139 +160 +74 +138 +159 +73 +138 +159 +75 +140 +161 +37 +92 +118 +2 +48 +78 +19 +69 +96 +100 +171 +189 +147 +165 +170 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +149 +157 +160 +79 +140 +160 +74 +139 +160 +74 +139 +160 +76 +142 +163 +82 +149 +169 +96 +165 +184 +106 +178 +196 +111 +185 +202 +114 +188 +205 +114 +188 +205 +97 +168 +186 +107 +179 +196 +150 +164 +168 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +160 +80 +141 +160 +74 +139 +160 +86 +154 +173 +102 +173 +191 +111 +184 +200 +111 +184 +201 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +111 +184 +201 +110 +183 +200 +113 +179 +195 +138 +170 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +160 +77 +139 +159 +93 +162 +182 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +109 +182 +199 +110 +183 +200 +138 +169 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +159 +99 +165 +182 +112 +185 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +150 +165 +168 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +138 +168 +175 +110 +182 +199 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +107 +180 +197 +107 +168 +183 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +156 +161 +162 +117 +179 +194 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +185 +201 +83 +149 +168 +7 +52 +81 +101 +119 +130 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +144 +167 +172 +110 +183 +200 +110 +182 +199 +110 +183 +200 +109 +182 +199 +61 +125 +148 +47 +91 +116 +58 +95 +118 +43 +88 +112 +64 +128 +151 +108 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +185 +202 +98 +168 +186 +125 +165 +175 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +126 +175 +187 +109 +182 +199 +110 +182 +199 +111 +184 +201 +55 +119 +143 +108 +136 +153 +230 +234 +237 +186 +198 +205 +234 +233 +221 +51 +89 +113 +68 +133 +155 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +123 +176 +188 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +114 +179 +195 +110 +183 +200 +110 +183 +200 +103 +175 +193 +66 +114 +136 +247 +248 +249 +240 +243 +244 +57 +95 +118 +22 +66 +93 +40 +80 +106 +64 +113 +135 +108 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +119 +177 +191 +156 +161 +162 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +182 +199 +110 +183 +200 +110 +183 +200 +100 +172 +190 +106 +143 +161 +249 +250 +250 +251 +252 +252 +132 +155 +170 +51 +89 +113 +145 +165 +178 +79 +121 +142 +108 +181 +198 +110 +183 +200 +109 +182 +199 +110 +184 +200 +113 +187 +203 +103 +175 +193 +104 +177 +194 +117 +180 +195 +154 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +112 +180 +196 +110 +183 +200 +110 +183 +200 +107 +180 +197 +74 +124 +145 +250 +251 +251 +254 +254 +254 +250 +251 +251 +238 +241 +243 +220 +226 +230 +67 +122 +144 +109 +182 +199 +110 +183 +200 +110 +183 +200 +101 +171 +189 +38 +91 +116 +154 +176 +179 +94 +128 +141 +99 +165 +182 +153 +164 +166 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +120 +177 +190 +109 +182 +199 +110 +183 +200 +110 +184 +200 +81 +152 +172 +113 +144 +161 +242 +244 +245 +242 +244 +245 +235 +239 +241 +92 +129 +148 +87 +157 +177 +110 +183 +200 +110 +183 +200 +115 +189 +206 +37 +91 +116 +8 +55 +84 +0 +46 +77 +55 +117 +140 +118 +186 +201 +152 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +134 +171 +180 +109 +182 +199 +109 +181 +198 +109 +182 +199 +113 +187 +203 +88 +159 +179 +83 +140 +160 +105 +152 +169 +76 +134 +155 +88 +159 +179 +112 +185 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +109 +182 +199 +81 +147 +167 +61 +122 +144 +113 +187 +204 +113 +179 +195 +153 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +152 +163 +166 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +182 +199 +108 +180 +198 +109 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +106 +178 +195 +74 +138 +159 +110 +183 +200 +117 +178 +192 +155 +162 +163 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +161 +121 +177 +190 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +95 +164 +183 +77 +142 +163 +110 +183 +200 +123 +175 +188 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +139 +169 +176 +110 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +70 +133 +154 +103 +173 +191 +109 +181 +198 +136 +171 +179 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +159 +160 +160 +115 +179 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +108 +181 +198 +95 +164 +183 +110 +183 +200 +110 +182 +199 +156 +161 +162 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +149 +164 +168 +109 +182 +199 +109 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +111 +183 +200 +109 +182 +199 +136 +170 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +137 +169 +177 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +119 +178 +192 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +162 +120 +176 +189 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +181 +198 +148 +165 +169 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +143 +167 +172 +110 +181 +198 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +181 +198 +109 +182 +199 +126 +175 +186 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +116 +180 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +109 +182 +199 +153 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +132 +172 +181 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +124 +175 +187 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +138 +169 +176 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +115 +165 +178 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +134 +171 +180 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +184 +201 +96 +165 +184 +133 +152 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +119 +177 +190 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +107 +179 +197 +90 +141 +158 +156 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +156 +161 +163 +111 +181 +197 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +82 +149 +169 +127 +151 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +143 +167 +172 +111 +182 +199 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +96 +166 +184 +77 +138 +160 +158 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +131 +172 +182 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +103 +174 +191 +77 +142 +163 +99 +145 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +162 +121 +176 +189 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +82 +149 +168 +74 +140 +160 +126 +152 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +162 +164 +111 +180 +196 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +184 +201 +91 +160 +178 +74 +139 +160 +82 +141 +160 +148 +157 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +145 +166 +171 +128 +173 +184 +122 +176 +189 +129 +174 +184 +160 +160 +160 +160 +160 +160 +160 +160 +160 +144 +166 +171 +109 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +103 +174 +192 +74 +139 +160 +74 +140 +160 +94 +144 +160 +156 +159 +160 +160 +160 +160 +160 +160 +160 +152 +163 +165 +120 +177 +190 +109 +182 +199 +109 +182 +199 +109 +181 +198 +153 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +128 +173 +184 +110 +183 +200 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +184 +201 +74 +139 +160 +75 +139 +160 +75 +140 +161 +95 +144 +160 +160 +160 +160 +158 +160 +161 +114 +180 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +151 +164 +167 +160 +160 +160 +160 +160 +160 +160 +160 +160 +115 +179 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +185 +202 +77 +143 +163 +74 +139 +160 +75 +140 +160 +74 +139 +160 +104 +145 +159 +134 +169 +177 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +182 +199 +109 +182 +199 +122 +176 +188 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +112 +185 +202 +79 +144 +165 +74 +139 +160 +75 +140 +161 +74 +139 +160 +74 +138 +160 +92 +155 +174 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +181 +198 +110 +183 +200 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +186 +202 +75 +141 +162 +74 +139 +160 +75 +140 +161 +75 +140 +160 +74 +139 +160 +74 +139 +160 +87 +154 +173 +111 +184 +201 +110 +183 +200 +110 +183 +200 +109 +181 +198 +110 +183 +200 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +93 +162 +181 +110 +183 +200 +109 +182 +199 +109 +181 +198 +109 +182 +199 +160 +160 +160 +160 +160 +160 +160 +160 +160 +113 +181 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +104 +176 +194 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +76 +141 +162 +103 +174 +192 +110 +182 +199 +110 +183 +200 +109 +182 +199 +160 +160 +160 +160 +160 +160 +160 +160 +160 +121 +176 +189 +109 +182 +199 +110 +183 +200 +109 +182 +199 +119 +187 +203 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +97 +167 +186 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +80 +145 +165 +111 +185 +202 +109 +182 +199 +143 +167 +173 +160 +160 +160 +160 +160 +160 +160 +160 +160 +133 +172 +181 +109 +182 +199 +110 +182 +199 +109 +182 +199 +140 +198 +211 +111 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +184 +201 +87 +156 +175 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +138 +160 +117 +157 +169 +159 +161 +161 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +147 +165 +169 +110 +183 +200 +109 +182 +199 +110 +183 +200 +161 +208 +219 +113 +184 +201 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +105 +177 +194 +77 +142 +163 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +94 +144 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +162 +164 +114 +181 +196 +109 +182 +199 +109 +182 +200 +181 +218 +227 +114 +185 +201 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +95 +163 +183 +50 +106 +130 +75 +141 +162 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +81 +140 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +160 +161 +125 +175 +187 +109 +182 +199 +108 +182 +199 +201 +228 +235 +116 +186 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +77 +141 +162 +36 +88 +115 +48 +104 +128 +69 +133 +154 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +83 +141 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +139 +169 +176 +110 +182 +199 +107 +180 +197 +204 +230 +236 +137 +196 +210 +108 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +184 +200 +46 +101 +126 +38 +90 +116 +48 +103 +128 +61 +120 +143 +69 +133 +154 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +97 +145 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +160 +161 +113 +181 +197 +107 +181 +198 +191 +223 +230 +176 +215 +225 +105 +180 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +112 +185 +202 +81 +146 +166 +35 +87 +114 +57 +116 +139 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +127 +152 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +140 +169 +175 +107 +181 +199 +171 +212 +222 +218 +237 +241 +101 +178 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +105 +177 +195 +42 +96 +122 +44 +98 +124 +71 +134 +156 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +75 +140 +161 +88 +143 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +120 +176 +189 +144 +200 +213 +251 +252 +252 +108 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +53 +110 +135 +37 +90 +116 +46 +101 +127 +72 +135 +157 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +160 +74 +139 +160 +82 +141 +160 +146 +156 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +159 +159 +146 +160 +164 +108 +179 +196 +248 +252 +253 +151 +203 +215 +107 +182 +199 +108 +181 +198 +69 +132 +153 +5 +52 +81 +5 +52 +81 +11 +59 +87 +45 +102 +126 +73 +138 +159 +75 +140 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +81 +140 +160 +142 +155 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +141 +147 +150 +70 +98 +115 +41 +77 +99 +41 +95 +119 +120 +157 +173 +157 +187 +198 +67 +131 +154 +46 +103 +127 +4 +50 +79 +6 +53 +82 +6 +54 +83 +6 +53 +82 +3 +50 +79 +28 +81 +108 +52 +111 +135 +61 +123 +145 +77 +132 +151 +94 +139 +154 +116 +146 +157 +154 +158 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +155 +156 +157 +61 +91 +110 +19 +62 +89 +6 +53 +82 +6 +53 +82 +10 +55 +83 +19 +62 +89 +44 +81 +104 +47 +82 +103 +11 +56 +84 +6 +53 +82 +10 +56 +84 +28 +68 +93 +50 +83 +104 +71 +98 +115 +97 +116 +128 +126 +137 +144 +145 +152 +155 +150 +155 +157 +155 +157 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 diff --git a/server/Sample/test_icon.webp b/server/Sample/test_icon.webp Binary files differnew file mode 100644 index 0000000..5a3fa95 --- /dev/null +++ b/server/Sample/test_icon.webp |