From 782962b8cf60a0c726393d93e5d421ceaa06f10b Mon Sep 17 00:00:00 2001 From: Ged Murphy Date: Sat, 3 Nov 2007 22:02:59 +0000 Subject: [PATCH] Add an mstsc (remote desktop client) which is based on the GPL WinRDesktop. It's a VS project at the moment, not yet converted to be buildable via rbuild, and still pretty incomplete. svn path=/trunk/; revision=30094 --- reactos/base/applications/mstsc/bitmap.c | 1021 ++++++++++ reactos/base/applications/mstsc/bsops.c | 838 ++++++++ reactos/base/applications/mstsc/bsops.h | 49 + reactos/base/applications/mstsc/cache.c | 432 +++++ reactos/base/applications/mstsc/channels.c | 181 ++ .../base/applications/mstsc/connectdialog.c | 439 +++++ reactos/base/applications/mstsc/constants.h | 435 +++++ reactos/base/applications/mstsc/iso.c | 231 +++ reactos/base/applications/mstsc/lang/en-US.rc | 53 + reactos/base/applications/mstsc/licence.c | 345 ++++ reactos/base/applications/mstsc/manifest.xml | 23 + reactos/base/applications/mstsc/mcs.c | 469 +++++ reactos/base/applications/mstsc/mppc.c | 380 ++++ reactos/base/applications/mstsc/mstsc.rbuild | 33 + .../applications/mstsc/mstsc_vc8_auto.sln | 32 + .../applications/mstsc/mstsc_vc8_auto.vcproj | 645 +++++++ reactos/base/applications/mstsc/orders.c | 1307 +++++++++++++ reactos/base/applications/mstsc/orders.h | 368 ++++ reactos/base/applications/mstsc/parse.h | 95 + reactos/base/applications/mstsc/proto.h | 310 +++ reactos/base/applications/mstsc/pstcache.c | 200 ++ reactos/base/applications/mstsc/rdc.rc | 9 + reactos/base/applications/mstsc/rdesktop.h | 126 ++ reactos/base/applications/mstsc/rdp.c | 1441 ++++++++++++++ reactos/base/applications/mstsc/rdp5.c | 121 ++ .../base/applications/mstsc/res/colors.ico | Bin 0 -> 17542 bytes .../applications/mstsc/res/connection.ico | Bin 0 -> 17542 bytes .../base/applications/mstsc/res/header.bmp | Bin 0 -> 86294 bytes .../base/applications/mstsc/res/header.pdn | Bin 0 -> 49953 bytes reactos/base/applications/mstsc/res/logon.ico | Bin 0 -> 17542 bytes reactos/base/applications/mstsc/res/mstsc.ico | Bin 0 -> 40070 bytes .../base/applications/mstsc/res/remote.ico | Bin 0 -> 17542 bytes .../base/applications/mstsc/res/spectrum.bmp | Bin 0 -> 6552 bytes .../applications/mstsc/res/video-display.png | Bin 0 -> 33353 bytes reactos/base/applications/mstsc/resource.h | 28 + reactos/base/applications/mstsc/rsrc.rc | 16 + reactos/base/applications/mstsc/secure.c | 1003 ++++++++++ reactos/base/applications/mstsc/ssl_calls.c | 1644 ++++++++++++++++ reactos/base/applications/mstsc/tcp.c | 309 +++ reactos/base/applications/mstsc/types.h | 270 +++ reactos/base/applications/mstsc/uimain.c | 979 ++++++++++ reactos/base/applications/mstsc/uimain.h | 80 + reactos/base/applications/mstsc/win32.c | 1712 +++++++++++++++++ 43 files changed, 15624 insertions(+) create mode 100644 reactos/base/applications/mstsc/bitmap.c create mode 100644 reactos/base/applications/mstsc/bsops.c create mode 100644 reactos/base/applications/mstsc/bsops.h create mode 100644 reactos/base/applications/mstsc/cache.c create mode 100644 reactos/base/applications/mstsc/channels.c create mode 100644 reactos/base/applications/mstsc/connectdialog.c create mode 100644 reactos/base/applications/mstsc/constants.h create mode 100644 reactos/base/applications/mstsc/iso.c create mode 100644 reactos/base/applications/mstsc/lang/en-US.rc create mode 100644 reactos/base/applications/mstsc/licence.c create mode 100644 reactos/base/applications/mstsc/manifest.xml create mode 100644 reactos/base/applications/mstsc/mcs.c create mode 100644 reactos/base/applications/mstsc/mppc.c create mode 100644 reactos/base/applications/mstsc/mstsc.rbuild create mode 100644 reactos/base/applications/mstsc/mstsc_vc8_auto.sln create mode 100644 reactos/base/applications/mstsc/mstsc_vc8_auto.vcproj create mode 100644 reactos/base/applications/mstsc/orders.c create mode 100644 reactos/base/applications/mstsc/orders.h create mode 100644 reactos/base/applications/mstsc/parse.h create mode 100644 reactos/base/applications/mstsc/proto.h create mode 100644 reactos/base/applications/mstsc/pstcache.c create mode 100644 reactos/base/applications/mstsc/rdc.rc create mode 100644 reactos/base/applications/mstsc/rdesktop.h create mode 100644 reactos/base/applications/mstsc/rdp.c create mode 100644 reactos/base/applications/mstsc/rdp5.c create mode 100644 reactos/base/applications/mstsc/res/colors.ico create mode 100644 reactos/base/applications/mstsc/res/connection.ico create mode 100644 reactos/base/applications/mstsc/res/header.bmp create mode 100644 reactos/base/applications/mstsc/res/header.pdn create mode 100644 reactos/base/applications/mstsc/res/logon.ico create mode 100644 reactos/base/applications/mstsc/res/mstsc.ico create mode 100644 reactos/base/applications/mstsc/res/remote.ico create mode 100644 reactos/base/applications/mstsc/res/spectrum.bmp create mode 100644 reactos/base/applications/mstsc/res/video-display.png create mode 100644 reactos/base/applications/mstsc/resource.h create mode 100644 reactos/base/applications/mstsc/rsrc.rc create mode 100644 reactos/base/applications/mstsc/secure.c create mode 100644 reactos/base/applications/mstsc/ssl_calls.c create mode 100644 reactos/base/applications/mstsc/tcp.c create mode 100644 reactos/base/applications/mstsc/types.h create mode 100644 reactos/base/applications/mstsc/uimain.c create mode 100644 reactos/base/applications/mstsc/uimain.h create mode 100644 reactos/base/applications/mstsc/win32.c diff --git a/reactos/base/applications/mstsc/bitmap.c b/reactos/base/applications/mstsc/bitmap.c new file mode 100644 index 00000000000..18e0cee757f --- /dev/null +++ b/reactos/base/applications/mstsc/bitmap.c @@ -0,0 +1,1021 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Bitmap decompression routines + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* three seperate function for speed when decompressing the bitmaps */ +/* when modifing one function make the change in the others */ +/* comment out #define BITMAP_SPEED_OVER_SIZE below for one slower function */ +/* j@american-data.com */ + +#define BITMAP_SPEED_OVER_SIZE + +/* indent is confused by this file */ +/* *INDENT-OFF* */ + +#include "rdesktop.h" + +#define CVAL(p) (*(p++)) +#ifdef NEED_ALIGN +#ifdef L_ENDIAN +#define CVAL2(p, v) { v = (*(p++)); v |= (*(p++)) << 8; } +#else +#define CVAL2(p, v) { v = (*(p++)) << 8; v |= (*(p++)); } +#endif /* L_ENDIAN */ +#else +#define CVAL2(p, v) { v = (*((uint16*)p)); p += 2; } +#endif /* NEED_ALIGN */ + +#define UNROLL8(exp) { exp exp exp exp exp exp exp exp } + +#define REPEAT(statement) \ +{ \ + while((count & ~0x7) && ((x+8) < width)) \ + UNROLL8( statement; count--; x++; ); \ + \ + while((count > 0) && (x < width)) \ + { \ + statement; \ + count--; \ + x++; \ + } \ +} + +#define MASK_UPDATE() \ +{ \ + mixmask <<= 1; \ + if (mixmask == 0) \ + { \ + mask = fom_mask ? fom_mask : CVAL(input); \ + mixmask = 1; \ + } \ +} + +#ifdef BITMAP_SPEED_OVER_SIZE + +/* 1 byte bitmap decompress */ +static BOOL +bitmap_decompress1(uint8 * output, int width, int height, uint8 * input, int size) +{ + uint8 *end = input + size; + uint8 *prevline = NULL, *line = NULL; + int opcode, count, offset, isfillormix, x = width; + int lastopcode = -1, insertmix = False, bicolour = False; + uint8 code; + uint8 colour1 = 0, colour2 = 0; + uint8 mixmask, mask = 0; + uint8 mix = 0xff; + int fom_mask = 0; + + while (input < end) + { + fom_mask = 0; + code = CVAL(input); + opcode = code >> 4; + /* Handle different opcode forms */ + switch (opcode) + { + case 0xc: + case 0xd: + case 0xe: + opcode -= 6; + count = code & 0xf; + offset = 16; + break; + case 0xf: + opcode = code & 0xf; + if (opcode < 9) + { + count = CVAL(input); + count |= CVAL(input) << 8; + } + else + { + count = (opcode < 0xb) ? 8 : 1; + } + offset = 0; + break; + default: + opcode >>= 1; + count = code & 0x1f; + offset = 32; + break; + } + /* Handle strange cases for counts */ + if (offset != 0) + { + isfillormix = ((opcode == 2) || (opcode == 7)); + if (count == 0) + { + if (isfillormix) + count = CVAL(input) + 1; + else + count = CVAL(input) + offset; + } + else if (isfillormix) + { + count <<= 3; + } + } + /* Read preliminary data */ + switch (opcode) + { + case 0: /* Fill */ + if ((lastopcode == opcode) && !((x == width) && (prevline == NULL))) + insertmix = True; + break; + case 8: /* Bicolour */ + colour1 = CVAL(input); + case 3: /* Colour */ + colour2 = CVAL(input); + break; + case 6: /* SetMix/Mix */ + case 7: /* SetMix/FillOrMix */ + mix = CVAL(input); + opcode -= 5; + break; + case 9: /* FillOrMix_1 */ + mask = 0x03; + opcode = 0x02; + fom_mask = 3; + break; + case 0x0a: /* FillOrMix_2 */ + mask = 0x05; + opcode = 0x02; + fom_mask = 5; + break; + } + lastopcode = opcode; + mixmask = 0; + /* Output body */ + while (count > 0) + { + if (x >= width) + { + if (height <= 0) + return False; + x = 0; + height--; + prevline = line; + line = output + height * width; + } + switch (opcode) + { + case 0: /* Fill */ + if (insertmix) + { + if (prevline == NULL) + line[x] = mix; + else + line[x] = prevline[x] ^ mix; + insertmix = False; + count--; + x++; + } + if (prevline == NULL) + { + REPEAT(line[x] = 0) + } + else + { + REPEAT(line[x] = prevline[x]) + } + break; + case 1: /* Mix */ + if (prevline == NULL) + { + REPEAT(line[x] = mix) + } + else + { + REPEAT(line[x] = prevline[x] ^ mix) + } + break; + case 2: /* Fill or Mix */ + if (prevline == NULL) + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + line[x] = mix; + else + line[x] = 0; + ) + } + else + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + line[x] = prevline[x] ^ mix; + else + line[x] = prevline[x]; + ) + } + break; + case 3: /* Colour */ + REPEAT(line[x] = colour2) + break; + case 4: /* Copy */ + REPEAT(line[x] = CVAL(input)) + break; + case 8: /* Bicolour */ + REPEAT + ( + if (bicolour) + { + line[x] = colour2; + bicolour = False; + } + else + { + line[x] = colour1; + bicolour = True; count++; + } + ) + break; + case 0xd: /* White */ + REPEAT(line[x] = 0xff) + break; + case 0xe: /* Black */ + REPEAT(line[x] = 0) + break; + default: + unimpl("bitmap opcode 0x%x\n", opcode); + return False; + } + } + } + return True; +} + +/* 2 byte bitmap decompress */ +static BOOL +bitmap_decompress2(uint8 * output, int width, int height, uint8 * input, int size) +{ + uint8 *end = input + size; + uint16 *prevline = NULL, *line = NULL; + int opcode, count, offset, isfillormix, x = width; + int lastopcode = -1, insertmix = False, bicolour = False; + uint8 code; + uint16 colour1 = 0, colour2 = 0; + uint8 mixmask, mask = 0; + uint16 mix = 0xffff; + int fom_mask = 0; + + while (input < end) + { + fom_mask = 0; + code = CVAL(input); + opcode = code >> 4; + /* Handle different opcode forms */ + switch (opcode) + { + case 0xc: + case 0xd: + case 0xe: + opcode -= 6; + count = code & 0xf; + offset = 16; + break; + case 0xf: + opcode = code & 0xf; + if (opcode < 9) + { + count = CVAL(input); + count |= CVAL(input) << 8; + } + else + { + count = (opcode < 0xb) ? 8 : 1; + } + offset = 0; + break; + default: + opcode >>= 1; + count = code & 0x1f; + offset = 32; + break; + } + /* Handle strange cases for counts */ + if (offset != 0) + { + isfillormix = ((opcode == 2) || (opcode == 7)); + if (count == 0) + { + if (isfillormix) + count = CVAL(input) + 1; + else + count = CVAL(input) + offset; + } + else if (isfillormix) + { + count <<= 3; + } + } + /* Read preliminary data */ + switch (opcode) + { + case 0: /* Fill */ + if ((lastopcode == opcode) && !((x == width) && (prevline == NULL))) + insertmix = True; + break; + case 8: /* Bicolour */ + CVAL2(input, colour1); + case 3: /* Colour */ + CVAL2(input, colour2); + break; + case 6: /* SetMix/Mix */ + case 7: /* SetMix/FillOrMix */ + CVAL2(input, mix); + opcode -= 5; + break; + case 9: /* FillOrMix_1 */ + mask = 0x03; + opcode = 0x02; + fom_mask = 3; + break; + case 0x0a: /* FillOrMix_2 */ + mask = 0x05; + opcode = 0x02; + fom_mask = 5; + break; + } + lastopcode = opcode; + mixmask = 0; + /* Output body */ + while (count > 0) + { + if (x >= width) + { + if (height <= 0) + return False; + x = 0; + height--; + prevline = line; + line = ((uint16 *) output) + height * width; + } + switch (opcode) + { + case 0: /* Fill */ + if (insertmix) + { + if (prevline == NULL) + line[x] = mix; + else + line[x] = prevline[x] ^ mix; + insertmix = False; + count--; + x++; + } + if (prevline == NULL) + { + REPEAT(line[x] = 0) + } + else + { + REPEAT(line[x] = prevline[x]) + } + break; + case 1: /* Mix */ + if (prevline == NULL) + { + REPEAT(line[x] = mix) + } + else + { + REPEAT(line[x] = prevline[x] ^ mix) + } + break; + case 2: /* Fill or Mix */ + if (prevline == NULL) + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + line[x] = mix; + else + line[x] = 0; + ) + } + else + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + line[x] = prevline[x] ^ mix; + else + line[x] = prevline[x]; + ) + } + break; + case 3: /* Colour */ + REPEAT(line[x] = colour2) + break; + case 4: /* Copy */ + REPEAT(CVAL2(input, line[x])) + break; + case 8: /* Bicolour */ + REPEAT + ( + if (bicolour) + { + line[x] = colour2; + bicolour = False; + } + else + { + line[x] = colour1; + bicolour = True; + count++; + } + ) + break; + case 0xd: /* White */ + REPEAT(line[x] = 0xffff) + break; + case 0xe: /* Black */ + REPEAT(line[x] = 0) + break; + default: + unimpl("bitmap opcode 0x%x\n", opcode); + return False; + } + } + } + return True; +} + +/* 3 byte bitmap decompress */ +static BOOL +bitmap_decompress3(uint8 * output, int width, int height, uint8 * input, int size) +{ + uint8 *end = input + size; + uint8 *prevline = NULL, *line = NULL; + int opcode, count, offset, isfillormix, x = width; + int lastopcode = -1, insertmix = False, bicolour = False; + uint8 code; + uint8 colour1[3] = {0, 0, 0}, colour2[3] = {0, 0, 0}; + uint8 mixmask, mask = 0; + uint8 mix[3] = {0xff, 0xff, 0xff}; + int fom_mask = 0; + + while (input < end) + { + fom_mask = 0; + code = CVAL(input); + opcode = code >> 4; + /* Handle different opcode forms */ + switch (opcode) + { + case 0xc: + case 0xd: + case 0xe: + opcode -= 6; + count = code & 0xf; + offset = 16; + break; + case 0xf: + opcode = code & 0xf; + if (opcode < 9) + { + count = CVAL(input); + count |= CVAL(input) << 8; + } + else + { + count = (opcode < + 0xb) ? 8 : 1; + } + offset = 0; + break; + default: + opcode >>= 1; + count = code & 0x1f; + offset = 32; + break; + } + /* Handle strange cases for counts */ + if (offset != 0) + { + isfillormix = ((opcode == 2) || (opcode == 7)); + if (count == 0) + { + if (isfillormix) + count = CVAL(input) + 1; + else + count = CVAL(input) + offset; + } + else if (isfillormix) + { + count <<= 3; + } + } + /* Read preliminary data */ + switch (opcode) + { + case 0: /* Fill */ + if ((lastopcode == opcode) && !((x == width) && (prevline == NULL))) + insertmix = True; + break; + case 8: /* Bicolour */ + colour1[0] = CVAL(input); + colour1[1] = CVAL(input); + colour1[2] = CVAL(input); + case 3: /* Colour */ + colour2[0] = CVAL(input); + colour2[1] = CVAL(input); + colour2[2] = CVAL(input); + break; + case 6: /* SetMix/Mix */ + case 7: /* SetMix/FillOrMix */ + mix[0] = CVAL(input); + mix[1] = CVAL(input); + mix[2] = CVAL(input); + opcode -= 5; + break; + case 9: /* FillOrMix_1 */ + mask = 0x03; + opcode = 0x02; + fom_mask = 3; + break; + case 0x0a: /* FillOrMix_2 */ + mask = 0x05; + opcode = 0x02; + fom_mask = 5; + break; + } + lastopcode = opcode; + mixmask = 0; + /* Output body */ + while (count > 0) + { + if (x >= width) + { + if (height <= 0) + return False; + x = 0; + height--; + prevline = line; + line = output + height * (width * 3); + } + switch (opcode) + { + case 0: /* Fill */ + if (insertmix) + { + if (prevline == NULL) + { + line[x * 3] = mix[0]; + line[x * 3 + 1] = mix[1]; + line[x * 3 + 2] = mix[2]; + } + else + { + line[x * 3] = + prevline[x * 3] ^ mix[0]; + line[x * 3 + 1] = + prevline[x * 3 + 1] ^ mix[1]; + line[x * 3 + 2] = + prevline[x * 3 + 2] ^ mix[2]; + } + insertmix = False; + count--; + x++; + } + if (prevline == NULL) + { + REPEAT + ( + line[x * 3] = 0; + line[x * 3 + 1] = 0; + line[x * 3 + 2] = 0; + ) + } + else + { + REPEAT + ( + line[x * 3] = prevline[x * 3]; + line[x * 3 + 1] = prevline[x * 3 + 1]; + line[x * 3 + 2] = prevline[x * 3 + 2]; + ) + } + break; + case 1: /* Mix */ + if (prevline == NULL) + { + REPEAT + ( + line[x * 3] = mix[0]; + line[x * 3 + 1] = mix[1]; + line[x * 3 + 2] = mix[2]; + ) + } + else + { + REPEAT + ( + line[x * 3] = + prevline[x * 3] ^ mix[0]; + line[x * 3 + 1] = + prevline[x * 3 + 1] ^ mix[1]; + line[x * 3 + 2] = + prevline[x * 3 + 2] ^ mix[2]; + ) + } + break; + case 2: /* Fill or Mix */ + if (prevline == NULL) + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + { + line[x * 3] = mix[0]; + line[x * 3 + 1] = mix[1]; + line[x * 3 + 2] = mix[2]; + } + else + { + line[x * 3] = 0; + line[x * 3 + 1] = 0; + line[x * 3 + 2] = 0; + } + ) + } + else + { + REPEAT + ( + MASK_UPDATE(); + if (mask & mixmask) + { + line[x * 3] = + prevline[x * 3] ^ mix [0]; + line[x * 3 + 1] = + prevline[x * 3 + 1] ^ mix [1]; + line[x * 3 + 2] = + prevline[x * 3 + 2] ^ mix [2]; + } + else + { + line[x * 3] = + prevline[x * 3]; + line[x * 3 + 1] = + prevline[x * 3 + 1]; + line[x * 3 + 2] = + prevline[x * 3 + 2]; + } + ) + } + break; + case 3: /* Colour */ + REPEAT + ( + line[x * 3] = colour2 [0]; + line[x * 3 + 1] = colour2 [1]; + line[x * 3 + 2] = colour2 [2]; + ) + break; + case 4: /* Copy */ + REPEAT + ( + line[x * 3] = CVAL(input); + line[x * 3 + 1] = CVAL(input); + line[x * 3 + 2] = CVAL(input); + ) + break; + case 8: /* Bicolour */ + REPEAT + ( + if (bicolour) + { + line[x * 3] = colour2[0]; + line[x * 3 + 1] = colour2[1]; + line[x * 3 + 2] = colour2[2]; + bicolour = False; + } + else + { + line[x * 3] = colour1[0]; + line[x * 3 + 1] = colour1[1]; + line[x * 3 + 2] = colour1[2]; + bicolour = True; + count++; + } + ) + break; + case 0xd: /* White */ + REPEAT + ( + line[x * 3] = 0xff; + line[x * 3 + 1] = 0xff; + line[x * 3 + 2] = 0xff; + ) + break; + case 0xe: /* Black */ + REPEAT + ( + line[x * 3] = 0; + line[x * 3 + 1] = 0; + line[x * 3 + 2] = 0; + ) + break; + default: + unimpl("bitmap opcode 0x%x\n", opcode); + return False; + } + } + } + return True; +} + +#else + +static uint32 +cvalx(uint8 **input, int Bpp) +{ + uint32 rv = 0; + memcpy(&rv, *input, Bpp); + *input += Bpp; + return rv; +} + +static void +setli(uint8 *input, int offset, uint32 value, int Bpp) +{ + input += offset * Bpp; + memcpy(input, &value, Bpp); +} + +static uint32 +getli(uint8 *input, int offset, int Bpp) +{ + uint32 rv = 0; + input += offset * Bpp; + memcpy(&rv, input, Bpp); + return rv; +} + +static BOOL +bitmap_decompressx(uint8 *output, int width, int height, uint8 *input, int size, int Bpp) +{ + uint8 *end = input + size; + uint8 *prevline = NULL, *line = NULL; + int opcode, count, offset, isfillormix, x = width; + int lastopcode = -1, insertmix = False, bicolour = False; + uint8 code; + uint32 colour1 = 0, colour2 = 0; + uint8 mixmask, mask = 0; + uint32 mix = 0xffffffff; + int fom_mask = 0; + + while (input < end) + { + fom_mask = 0; + code = CVAL(input); + opcode = code >> 4; + + /* Handle different opcode forms */ + switch (opcode) + { + case 0xc: + case 0xd: + case 0xe: + opcode -= 6; + count = code & 0xf; + offset = 16; + break; + + case 0xf: + opcode = code & 0xf; + if (opcode < 9) + { + count = CVAL(input); + count |= CVAL(input) << 8; + } + else + { + count = (opcode < 0xb) ? 8 : 1; + } + offset = 0; + break; + + default: + opcode >>= 1; + count = code & 0x1f; + offset = 32; + break; + } + + /* Handle strange cases for counts */ + if (offset != 0) + { + isfillormix = ((opcode == 2) || (opcode == 7)); + + if (count == 0) + { + if (isfillormix) + count = CVAL(input) + 1; + else + count = CVAL(input) + offset; + } + else if (isfillormix) + { + count <<= 3; + } + } + + /* Read preliminary data */ + switch (opcode) + { + case 0: /* Fill */ + if ((lastopcode == opcode) && !((x == width) && (prevline == NULL))) + insertmix = True; + break; + case 8: /* Bicolour */ + colour1 = cvalx(&input, Bpp); + case 3: /* Colour */ + colour2 = cvalx(&input, Bpp); + break; + case 6: /* SetMix/Mix */ + case 7: /* SetMix/FillOrMix */ + mix = cvalx(&input, Bpp); + opcode -= 5; + break; + case 9: /* FillOrMix_1 */ + mask = 0x03; + opcode = 0x02; + fom_mask = 3; + break; + case 0x0a: /* FillOrMix_2 */ + mask = 0x05; + opcode = 0x02; + fom_mask = 5; + break; + + } + + lastopcode = opcode; + mixmask = 0; + + /* Output body */ + while (count > 0) + { + if (x >= width) + { + if (height <= 0) + return False; + + x = 0; + height--; + + prevline = line; + line = output + height * width * Bpp; + } + + switch (opcode) + { + case 0: /* Fill */ + if (insertmix) + { + if (prevline == NULL) + setli(line, x, mix, Bpp); + else + setli(line, x, + getli(prevline, x, Bpp) ^ mix, Bpp); + + insertmix = False; + count--; + x++; + } + + if (prevline == NULL) + { + REPEAT(setli(line, x, 0, Bpp))} + else + { + REPEAT(setli + (line, x, getli(prevline, x, Bpp), Bpp)); + } + break; + + case 1: /* Mix */ + if (prevline == NULL) + { + REPEAT(setli(line, x, mix, Bpp)); + } + else + { + REPEAT(setli + (line, x, getli(prevline, x, Bpp) ^ mix, + Bpp)); + } + break; + + case 2: /* Fill or Mix */ + if (prevline == NULL) + { + REPEAT(MASK_UPDATE(); + if (mask & mixmask) setli(line, x, mix, Bpp); + else + setli(line, x, 0, Bpp);); + } + else + { + REPEAT(MASK_UPDATE(); + if (mask & mixmask) + setli(line, x, getli(prevline, x, Bpp) ^ mix, + Bpp); + else + setli(line, x, getli(prevline, x, Bpp), + Bpp);); + } + break; + + case 3: /* Colour */ + REPEAT(setli(line, x, colour2, Bpp)); + break; + + case 4: /* Copy */ + REPEAT(setli(line, x, cvalx(&input, Bpp), Bpp)); + break; + + case 8: /* Bicolour */ + REPEAT(if (bicolour) + { + setli(line, x, colour2, Bpp); bicolour = False;} + else + { + setli(line, x, colour1, Bpp); bicolour = True; + count++;} + ); + break; + + case 0xd: /* White */ + REPEAT(setli(line, x, 0xffffffff, Bpp)); + break; + + case 0xe: /* Black */ + REPEAT(setli(line, x, 0, Bpp)); + break; + + default: + unimpl("bitmap opcode 0x%x\n", opcode); + return False; + } + } + } + + return True; +} + +#endif + +/* main decompress function */ +BOOL +bitmap_decompress(uint8 * output, int width, int height, uint8 * input, int size, int Bpp) +{ +#ifdef BITMAP_SPEED_OVER_SIZE + BOOL rv = False; + switch (Bpp) + { + case 1: + rv = bitmap_decompress1(output, width, height, input, size); + break; + case 2: + rv = bitmap_decompress2(output, width, height, input, size); + break; + case 3: + rv = bitmap_decompress3(output, width, height, input, size); + break; + } +#else + BOOL rv; + rv = bitmap_decompressx(output, width, height, input, size, Bpp); +#endif + return rv; +} + +/* *INDENT-ON* */ diff --git a/reactos/base/applications/mstsc/bsops.c b/reactos/base/applications/mstsc/bsops.c new file mode 100644 index 00000000000..9a5aa64e482 --- /dev/null +++ b/reactos/base/applications/mstsc/bsops.c @@ -0,0 +1,838 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Generics backingstore operations + Copyright (C) Jay Sorg 2005-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include "bsops.h" + +/* globals */ +static char * g_bs = 0; +static int g_bs_size = 0; + +static int g_width = 800; +static int g_height = 600; +static int g_bpp = 8; +static int g_Bpp = 1; + +static int g_clip_left = 0; +static int g_clip_top = 0; +static int g_clip_right = 800; +static int g_clip_bottom = 600; + +/* for bs_patblt */ +static unsigned char g_hatch_patterns[] = +{ + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, /* 0 - bsHorizontal */ + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, /* 1 - bsVertical */ + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, /* 2 - bsFDiagonal */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, /* 3 - bsBDiagonal */ + 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, 0x08, 0x08, /* 4 - bsCross */ + 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 /* 5 - bsDiagCross */ +}; + + +/*****************************************************************************/ +/* do a raster op */ +int +bs_do_rop(int rop, int src, int dst) +{ + switch (rop) + { + case 0x0: return 0; + case 0x1: return ~(src | dst); + case 0x2: return (~src) & dst; + case 0x3: return ~src; + case 0x4: return src & (~dst); + case 0x5: return ~(dst); + case 0x6: return src ^ dst; + case 0x7: return ~(src & dst); + case 0x8: return src & dst; + case 0x9: return ~(src) ^ dst; + case 0xa: return dst; + case 0xb: return (~src) | dst; + case 0xc: return src; + case 0xd: return src | (~dst); + case 0xe: return src | dst; + case 0xf: return ~0; + } + return dst; +} + +/*****************************************************************************/ +/* get a pixel from the in memory copy of whats on the screen */ +int +bs_get_pixel(int x, int y) +{ + char * p; + + if (x >= 0 && x < g_width && y >= 0 && y < g_height) + { + p = g_bs + (y * g_width * g_Bpp) + (x * g_Bpp); + if (g_Bpp == 1) + { + return *((unsigned char *) p); + } + else if (g_Bpp == 2) + { + return *((unsigned short *) p); + } + else + { + return *((unsigned int *) p); + } + } + else + { + return 0; + } +} + +/*****************************************************************************/ +/* set a pixel on the screen using the clip */ +void +bs_set_pixel(int x, int y, int pixel, int rop, int use_clip) +{ + char * p; + + if (!use_clip || + (x >= g_clip_left && x < g_clip_right && + y >= g_clip_top && y < g_clip_bottom)) + { + if (x >= 0 && x < g_width && y >= 0 && y < g_height) + { + p = g_bs + (y * g_width * g_Bpp) + (x * g_Bpp); + if (rop != 12) + { + pixel = bs_do_rop(rop, pixel, bs_get_pixel(x, y)); + } + if (g_Bpp == 1) + { + *((unsigned char *) p) = pixel; + } + else if (g_Bpp == 2) + { + *((unsigned short *) p) = pixel; + } + else + { + *((unsigned int *) p) = pixel; + } + } + } +} + +/*****************************************************************************/ +static char * +get_bs_ptr(int x, int y) +{ + char * p; + + if (x >= 0 && x < g_width && y >= 0 && y < g_height) + { + p = g_bs + (y * g_width * g_Bpp) + (x * g_Bpp); + return p; + } + else + { + return 0; + } +} + +/*****************************************************************************/ +void +bs_init(int width, int height, int bpp) +{ + if (g_bs != 0) + { + free(g_bs); + } + g_width = width; + g_height = height; + g_bpp = bpp; + g_Bpp = (bpp + 7) / 8; + g_bs_size = width * height * g_Bpp; + g_bs = malloc(g_bs_size); + memset(g_bs, 0, g_bs_size); + g_clip_left = 0; + g_clip_top = 0; + g_clip_right = width; + g_clip_bottom = height; +} + +/*****************************************************************************/ +void +bs_exit(void) +{ + if (g_bs != 0) + { + free(g_bs); + } +} + +/*****************************************************************************/ +void +bs_set_clip(int x, int y, int cx, int cy) +{ + g_clip_left = x; + g_clip_top = y; + g_clip_right = x + cx; + g_clip_bottom = y + cy; +} + +/*****************************************************************************/ +void +bs_reset_clip(void) +{ + g_clip_left = 0; + g_clip_top = 0; + g_clip_right = g_width; + g_clip_bottom = g_height; +} + +/*****************************************************************************/ +/* check if a certain pixel is set in a bitmap */ +int +bs_is_pixel_on(char * data, int x, int y, int width, int bpp) +{ + int start; + int shift; + + if (bpp == 1) + { + width = (width + 7) / 8; + start = (y * width) + x / 8; + shift = x % 8; + return (data[start] & (0x80 >> shift)) != 0; + } + else if (bpp == 8) + { + return data[y * width + x] != 0; + } + else if (bpp == 24) + { + return data[(y * 3) * width + (x * 3)] != 0 && + data[(y * 3) * width + (x * 3) + 1] != 0 && + data[(y * 3) * width + (x * 3) + 2] != 0; + } + else + { + return 0; + } +} + +/*****************************************************************************/ +void +bs_set_pixel_on(char * data, int x, int y, int width, int bpp, + int pixel) +{ + int start; + int shift; + + if (bpp == 1) + { + width = (width + 7) / 8; + start = (y * width) + x / 8; + shift = x % 8; + if (pixel != 0) + { + data[start] = data[start] | (0x80 >> shift); + } + else + { + data[start] = data[start] & ~(0x80 >> shift); + } + } + else if (bpp == 8) + { + data[y * width + x] = pixel; + } + else if (bpp == 15 || bpp == 16) + { + ((unsigned short *) data)[y * width + x] = pixel; + } +} + +/*****************************************************************************/ +void +bs_copy_mem(char * d, char * s, int n) +{ + while (n & (~7)) + { + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + *(d++) = *(s++); + n = n - 8; + } + while (n > 0) + { + *(d++) = *(s++); + n--; + } +} + +/*****************************************************************************/ +void +bs_copy_memb(char * d, char * s, int n) +{ + d = (d + n) - 1; + s = (s + n) - 1; + while (n & (~7)) + { + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + *(d--) = *(s--); + n = n - 8; + } + while (n > 0) + { + *(d--) = *(s--); + n--; + } +} + +/*****************************************************************************/ +/* return true is the is something to draw */ +int +bs_warp_coords(int * x, int * y, int * cx, int * cy, + int * srcx, int * srcy) +{ + int dx; + int dy; + + if (g_clip_left > *x) + { + dx = g_clip_left - *x; + } + else + { + dx = 0; + } + if (g_clip_top > *y) + { + dy = g_clip_top - *y; + } + else + { + dy = 0; + } + if (*x + *cx > g_clip_right) + { + *cx = (*cx - ((*x + *cx) - g_clip_right)); + } + if (*y + *cy > g_clip_bottom) + { + *cy = (*cy - ((*y + *cy) - g_clip_bottom)); + } + *cx = *cx - dx; + *cy = *cy - dy; + if (*cx <= 0) + { + return 0; + } + if (*cy <= 0) + { + return 0; + } + *x = *x + dx; + *y = *y + dy; + if (srcx != 0) + { + *srcx = *srcx + dx; + } + if (srcy != 0) + { + *srcy = *srcy + dy; + } + return 1; +} + +/*****************************************************************************/ +void +bs_rect(int x, int y, int cx, int cy, int colour, int rop) +{ + int i; + int j; + unsigned char * p8; + unsigned short * p16; + unsigned int * p32; + + if (bs_warp_coords(&x, &y, &cx, &cy, 0, 0)) + { + if (rop == 0) /* black */ + { + rop = 12; + colour = 0; + } + else if (rop == 15) /* white */ + { + rop = 12; + colour = 0xffffff; + } + if (rop == 12) /* copy */ + { + if (g_Bpp == 1) + { + for (i = 0; i < cy; i++) + { + p8 = (unsigned char *) get_bs_ptr(x, y + i); + if (p8 != 0) + { + for (j = 0; j < cx; j++) + { + *p8 = colour; + p8++; + } + } + } + } + else if (g_Bpp == 2) + { + for (i = 0; i < cy; i++) + { + p16 = (unsigned short *) get_bs_ptr(x, y + i); + if (p16 != 0) + { + for (j = 0; j < cx; j++) + { + *p16 = colour; + p16++; + } + } + } + } + else + { + for (i = 0; i < cy; i++) + { + p32 = (unsigned int *) get_bs_ptr(x, y + i); + if (p32 != 0) + { + for (j = 0; j < cx; j++) + { + *p32 = colour; + p32++; + } + } + } + } + } + else /* slow */ + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + bs_set_pixel(j + x, i + y, colour, rop, 0); + } + } + } + } +} + +/*****************************************************************************/ +void +bs_screenblt(int rop, int x, int y, int cx, int cy, + int srcx, int srcy) +{ + int p; + int i; + int j; + char * src; + char * dst; + + if (bs_warp_coords(&x, &y, &cx, &cy, &srcx, &srcy)) + { + if (rop == 12) /* copy */ + { + if (srcy < y) /* copy down - bottom to top */ + { + for (i = cy - 1; i >= 0; i--) + { + src = get_bs_ptr(srcx, srcy + i); + dst = get_bs_ptr(x, y + i); + if (src != 0 && dst != 0) + { + bs_copy_mem(dst, src, cx * g_Bpp); + } + } + } + else if (srcy > y || srcx > x) /* copy up or left - top to bottom */ + { + for (i = 0; i < cy; i++) + { + src = get_bs_ptr(srcx, srcy + i); + dst = get_bs_ptr(x, y + i); + if (src != 0 && dst != 0) + { + bs_copy_mem(dst, src, cx * g_Bpp); + } + } + } + else /* copy straight right */ + { + for (i = 0; i < cy; i++) + { + src = get_bs_ptr(srcx, srcy + i); + dst = get_bs_ptr(x, y + i); + if (src != 0 && dst != 0) + { + bs_copy_memb(dst, src, cx * g_Bpp); + } + } + } + } + else /* slow */ + { + if (srcy < y) /* copy down - bottom to top */ + { + for (i = cy - 1; i >= 0; i--) + { + for (j = 0; j < cx; j++) + { + p = bs_get_pixel(srcx + j, srcy + i); + bs_set_pixel(x + j, y + i, p, rop, 0); + } + } + } + else if (srcy > y || srcx > x) /* copy up or left - top to bottom */ + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + p = bs_get_pixel(srcx + j, srcy + i); + bs_set_pixel(x + j, y + i, p, rop, 0); + } + } + } + else /* copy straight right */ + { + for (i = 0; i < cy; i++) + { + for (j = cx - 1; j >= 0; j--) + { + p = bs_get_pixel(srcx + j, srcy + i); + bs_set_pixel(x + j, y + i, p, rop, 0); + } + } + } + } + } +} + +/*****************************************************************************/ +void +bs_memblt(int opcode, int x, int y, int cx, int cy, + void * srcdata, int srcwidth, int srcheight, + int srcx, int srcy) +{ + int i; + int j; + int p; + char * dst; + char * src; + + if (bs_warp_coords(&x, &y, &cx, &cy, &srcx, &srcy)) + { + if (opcode == 12) /* copy */ + { + if (g_Bpp == 1) + { + src = (char *) (((unsigned char *) srcdata) + srcy * srcwidth + srcx); + } + else if (g_Bpp == 2) + { + src = (char *) (((unsigned short *) srcdata) + srcy * srcwidth + srcx); + } + else + { + src = (char *) (((unsigned int *) srcdata) + srcy * srcwidth + srcx); + } + for (i = 0; i < cy; i++) + { + dst = get_bs_ptr(x, y + i); + if (dst != 0) + { + bs_copy_mem(dst, src, cx * g_Bpp); + src += srcwidth * g_Bpp; + } + } + } + else /* slow */ + { + if (g_Bpp == 1) + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + p = *(((unsigned char *) srcdata) + + ((i + srcy) * srcwidth + (j + srcx))); + bs_set_pixel(x + j, y + i, p, opcode, 0); + } + } + } + else if (g_Bpp == 2) + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + p = *(((unsigned short *) srcdata) + + ((i + srcy) * srcwidth + (j + srcx))); + bs_set_pixel(x + j, y + i, p, opcode, 0); + } + } + } + else + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + p = *(((unsigned int *) srcdata) + + ((i + srcy) * srcwidth + (j + srcx))); + bs_set_pixel(x + j, y + i, p, opcode, 0); + } + } + } + } + } +} + +/*****************************************************************************/ +void +bs_draw_glyph(int x, int y, char * glyph_data, int glyph_width, + int glyph_height, int fgcolour) +{ + int i; + int j; + + for (i = 0; i < glyph_height; i++) + { + for (j = 0; j < glyph_width; j++) + { + if (bs_is_pixel_on(glyph_data, j, i, glyph_width, 8)) + { + bs_set_pixel(x + j, y + i, fgcolour, 12, 1); + } + } + } +} + +/*****************************************************************************/ +/* Bresenham's line drawing algorithm */ +void +bs_line(int opcode, int startx, int starty, int endx, int endy, + int pen_width, int pen_style, int pen_colour) +{ + int dx; + int dy; + int incx; + int incy; + int dpr; + int dpru; + int p; + + if (startx > endx) + { + dx = startx - endx; + incx = -1; + } + else + { + dx = endx - startx; + incx = 1; + } + if (starty > endy) + { + dy = starty - endy; + incy = -1; + } + else + { + dy = endy - starty; + incy = 1; + } + if (dx >= dy) + { + dpr = dy << 1; + dpru = dpr - (dx << 1); + p = dpr - dx; + for (; dx >= 0; dx--) + { + if (startx != endx || starty != endy) + { + bs_set_pixel(startx, starty, pen_colour, opcode, 1); + } + if (p > 0) + { + startx += incx; + starty += incy; + p += dpru; + } + else + { + startx += incx; + p += dpr; + } + } + } + else + { + dpr = dx << 1; + dpru = dpr - (dy << 1); + p = dpr - dy; + for (; dy >= 0; dy--) + { + if (startx != endx || starty != endy) + { + bs_set_pixel(startx, starty, pen_colour, opcode, 1); + } + if (p > 0) + { + startx += incx; + starty += incy; + p += dpru; + } + else + { + starty += incy; + p += dpr; + } + } + } +} + +/*****************************************************************************/ +void +bs_patblt(int opcode, int x, int y, int cx, int cy, + int brush_style, char * brush_pattern, + int brush_x_org, int brush_y_org, + int bgcolour, int fgcolour) +{ + int i; + int j; + char ipattern[8]; + char * b; + + b = 0; + switch (brush_style) + { + case 0: + bs_rect(x, y, cx, cy, fgcolour, opcode); + break; + case 2: /* Hatch */ + b = g_hatch_patterns + brush_pattern[0] * 8; + break; + case 3: + for (i = 0; i < 8; i++) + { + ipattern[i] = ~brush_pattern[7 - i]; + } + b = ipattern; + break; + } + if (b != 0) + { + for (i = 0; i < cy; i++) + { + for (j = 0; j < cx; j++) + { + if (bs_is_pixel_on(b, (x + j + brush_x_org) % 8, + (y + i + brush_y_org) % 8, 8, 1)) + { + bs_set_pixel(x + j, y + i, fgcolour, opcode, 1); + } + else + { + bs_set_pixel(x + j, y + i, bgcolour, opcode, 1); + } + } + } + } +} + +/*****************************************************************************/ +void +bs_copy_box(char * dst, int x, int y, int cx, int cy, int line_size) +{ + char * src; + int i; + + /* shouldn't happen */ + if (cx < 1 || cy < 1) + { + return; + } + /* nothing to draw, memset and leave */ + if (x + cx < 0 || y + cy < 0 || x >= g_width || y >= g_height) + { + memset(dst, 0, cx * cy * g_Bpp); + return; + } + /* check if it goes over an edge */ + if (x < 0 || y < 0 || x + cx > g_width || y + cy > g_height) + { + memset(dst, 0, cx * cy * g_Bpp); + if (x < 0) + { + cx += x; + dst += -x * g_Bpp; + x = 0; + } + if (x + cx > g_width) + { + cx = g_width - x; + } + for (i = 0; i < cy; i++) + { + src = get_bs_ptr(x, y + i); + if (src != 0) + { + bs_copy_mem(dst, src, cx * g_Bpp); + } + dst += line_size; + } + } + else /* whole box is within */ + { + for (i = 0; i < cy; i++) + { + src = get_bs_ptr(x, y + i); + if (src != 0) + { + bs_copy_mem(dst, src, cx * g_Bpp); + } + dst += line_size; + } + } +} + diff --git a/reactos/base/applications/mstsc/bsops.h b/reactos/base/applications/mstsc/bsops.h new file mode 100644 index 00000000000..c0c3112c1cb --- /dev/null +++ b/reactos/base/applications/mstsc/bsops.h @@ -0,0 +1,49 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Generics backingstore operations + Copyright (C) Jay Sorg 2005-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +int bs_get_pixel(int x, int y); +void bs_set_pixel(int x, int y, int pixel, int rop, int use_clip); +int bs_do_rop(int rop, int src, int dst); +void bs_init(int width, int height, int bpp); +void bs_exit(void); +void bs_set_clip(int x, int y, int cx, int cy); +void bs_reset_clip(void); +void bs_set_pixel_on(char * data, int x, int y, int width, int bpp, + int pixel); +int bs_is_pixel_on(char * data, int x, int y, int width, int bpp); +void bs_copy_mem(char * d, char * s, int n); +void bs_copy_memb(char * d, char * s, int n); +int bs_warp_coords(int * x, int * y, int * cx, int * cy, + int * srcx, int * srcy); +void bs_rect(int x, int y, int cx, int cy, int colour, int rop); +void bs_screenblt(int opcode, int x, int y, int cx, int cy, + int srcx, int srcy); +void bs_memblt(int opcode, int x, int y, int cx, int cy, + void * srcdata, int srcwidth, int srcheight, + int srcx, int srcy); +void bs_copy_box(char * dst, int x, int y, int cx, int cy, int line_size); +void bs_draw_glyph(int x, int y, char * glyph_data, int glyph_width, + int glyph_height, int fgcolour); +void bs_line(int opcode, int startx, int starty, int endx, int endy, + int pen_width, int pen_style, int pen_colour); +void bs_patblt(int opcode, int x, int y, int cx, int cy, + int brush_style, char * brush_pattern, + int brush_x_org, int brush_y_org, + int bgcolour, int fgcolour); diff --git a/reactos/base/applications/mstsc/cache.c b/reactos/base/applications/mstsc/cache.c new file mode 100644 index 00000000000..54636379325 --- /dev/null +++ b/reactos/base/applications/mstsc/cache.c @@ -0,0 +1,432 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Cache routines + Copyright (C) Matthew Chapman 1999-2005 + Copyright (C) Jeroen Meijer 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +/* BITMAP CACHE */ +extern int g_pstcache_fd[]; + +#define NUM_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) +#define IS_PERSISTENT(id) (g_pstcache_fd[id] > 0) +#define TO_TOP -1 +#define NOT_SET -1 +#define IS_SET(idx) (idx >= 0) + +/* + * TODO: Test for optimal value of BUMP_COUNT. TO_TOP gives lowest cpu utilisation but using + * a positive value will hopefully result in less frequently used bitmaps having a greater chance + * of being evicted from the cache, and therby reducing the need to load bitmaps from disk. + * (Jeroen) + */ +#define BUMP_COUNT 40 + +struct bmpcache_entry +{ + RD_HBITMAP bitmap; + sint16 previous; + sint16 next; +}; + +static struct bmpcache_entry g_bmpcache[3][0xa00]; +static RD_HBITMAP g_volatile_bc[3]; + +static int g_bmpcache_lru[3] = { NOT_SET, NOT_SET, NOT_SET }; +static int g_bmpcache_mru[3] = { NOT_SET, NOT_SET, NOT_SET }; +static int g_bmpcache_count[3]; + +/* Setup the bitmap cache lru/mru linked list */ +void +cache_rebuild_bmpcache_linked_list(uint8 id, sint16 * idx, int count) +{ + int n = count, c = 0; + sint16 n_idx; + + /* find top, skip evicted bitmaps */ + while (--n >= 0 && g_bmpcache[id][idx[n]].bitmap == NULL); + if (n < 0) + { + g_bmpcache_mru[id] = g_bmpcache_lru[id] = NOT_SET; + return; + } + + g_bmpcache_mru[id] = idx[n]; + g_bmpcache[id][idx[n]].next = NOT_SET; + n_idx = idx[n]; + c++; + + /* link list */ + while (n >= 0) + { + /* skip evicted bitmaps */ + while (--n >= 0 && g_bmpcache[id][idx[n]].bitmap == NULL); + + if (n < 0) + break; + + g_bmpcache[id][n_idx].previous = idx[n]; + g_bmpcache[id][idx[n]].next = n_idx; + n_idx = idx[n]; + c++; + } + + g_bmpcache[id][n_idx].previous = NOT_SET; + g_bmpcache_lru[id] = n_idx; + + if (c != g_bmpcache_count[id]) + { + error("Oops. %d in bitmap cache linked list, %d in ui cache...\n", c, + g_bmpcache_count[id]); + exit(1); + } +} + +/* Move a bitmap to a new position in the linked list. */ +void +cache_bump_bitmap(uint8 id, uint16 idx, int bump) +{ + int p_idx, n_idx, n; + + if (!IS_PERSISTENT(id)) + return; + + if (g_bmpcache_mru[id] == idx) + return; + + DEBUG_RDP5(("bump bitmap: id=%d, idx=%d, bump=%d\n", id, idx, bump)); + + n_idx = g_bmpcache[id][idx].next; + p_idx = g_bmpcache[id][idx].previous; + + if (IS_SET(n_idx)) + { + /* remove */ + --g_bmpcache_count[id]; + if (IS_SET(p_idx)) + g_bmpcache[id][p_idx].next = n_idx; + else + g_bmpcache_lru[id] = n_idx; + if (IS_SET(n_idx)) + g_bmpcache[id][n_idx].previous = p_idx; + else + g_bmpcache_mru[id] = p_idx; + } + else + { + p_idx = NOT_SET; + n_idx = g_bmpcache_lru[id]; + } + + if (bump >= 0) + { + for (n = 0; n < bump && IS_SET(n_idx); n++) + { + p_idx = n_idx; + n_idx = g_bmpcache[id][p_idx].next; + } + } + else + { + p_idx = g_bmpcache_mru[id]; + n_idx = NOT_SET; + } + + /* insert */ + ++g_bmpcache_count[id]; + g_bmpcache[id][idx].previous = p_idx; + g_bmpcache[id][idx].next = n_idx; + + if (p_idx >= 0) + g_bmpcache[id][p_idx].next = idx; + else + g_bmpcache_lru[id] = idx; + + if (n_idx >= 0) + g_bmpcache[id][n_idx].previous = idx; + else + g_bmpcache_mru[id] = idx; +} + +/* Evict the least-recently used bitmap from the cache */ +void +cache_evict_bitmap(uint8 id) +{ + uint16 idx; + int n_idx; + + if (!IS_PERSISTENT(id)) + return; + + idx = g_bmpcache_lru[id]; + n_idx = g_bmpcache[id][idx].next; + DEBUG_RDP5(("evict bitmap: id=%d idx=%d n_idx=%d bmp=0x%x\n", id, idx, n_idx, + g_bmpcache[id][idx].bitmap)); + + ui_destroy_bitmap(g_bmpcache[id][idx].bitmap); + --g_bmpcache_count[id]; + g_bmpcache[id][idx].bitmap = 0; + + g_bmpcache_lru[id] = n_idx; + g_bmpcache[id][n_idx].previous = NOT_SET; + + pstcache_touch_bitmap(id, idx, 0); +} + +/* Retrieve a bitmap from the cache */ +RD_HBITMAP +cache_get_bitmap(uint8 id, uint16 idx) +{ + if ((id < NUM_ELEMENTS(g_bmpcache)) && (idx < NUM_ELEMENTS(g_bmpcache[0]))) + { + if (g_bmpcache[id][idx].bitmap || pstcache_load_bitmap(id, idx)) + { + if (IS_PERSISTENT(id)) + cache_bump_bitmap(id, idx, BUMP_COUNT); + + return g_bmpcache[id][idx].bitmap; + } + } + else if ((id < NUM_ELEMENTS(g_volatile_bc)) && (idx == 0x7fff)) + { + return g_volatile_bc[id]; + } + + error("get bitmap %d:%d\n", id, idx); + return NULL; +} + +/* Store a bitmap in the cache */ +void +cache_put_bitmap(uint8 id, uint16 idx, RD_HBITMAP bitmap) +{ + RD_HBITMAP old; + + if ((id < NUM_ELEMENTS(g_bmpcache)) && (idx < NUM_ELEMENTS(g_bmpcache[0]))) + { + old = g_bmpcache[id][idx].bitmap; + if (old != NULL) + ui_destroy_bitmap(old); + g_bmpcache[id][idx].bitmap = bitmap; + + if (IS_PERSISTENT(id)) + { + if (old == NULL) + g_bmpcache[id][idx].previous = g_bmpcache[id][idx].next = NOT_SET; + + cache_bump_bitmap(id, idx, TO_TOP); + if (g_bmpcache_count[id] > BMPCACHE2_C2_CELLS) + cache_evict_bitmap(id); + } + } + else if ((id < NUM_ELEMENTS(g_volatile_bc)) && (idx == 0x7fff)) + { + old = g_volatile_bc[id]; + if (old != NULL) + ui_destroy_bitmap(old); + g_volatile_bc[id] = bitmap; + } + else + { + error("put bitmap %d:%d\n", id, idx); + } +} + +/* Updates the persistent bitmap cache MRU information on exit */ +void +cache_save_state(void) +{ + uint32 id = 0, t = 0; + int idx; + + for (id = 0; id < NUM_ELEMENTS(g_bmpcache); id++) + if (IS_PERSISTENT(id)) + { + DEBUG_RDP5(("Saving cache state for bitmap cache %d...", id)); + idx = g_bmpcache_lru[id]; + while (idx >= 0) + { + pstcache_touch_bitmap((uint8) id, (uint16) idx, ++t); + idx = g_bmpcache[id][idx].next; + } + DEBUG_RDP5((" %d stamps written.\n", t)); + } +} + + +/* FONT CACHE */ +static FONTGLYPH g_fontcache[12][256]; + +/* Retrieve a glyph from the font cache */ +FONTGLYPH * +cache_get_font(uint8 font, uint16 character) +{ + FONTGLYPH *glyph; + + if ((font < NUM_ELEMENTS(g_fontcache)) && (character < NUM_ELEMENTS(g_fontcache[0]))) + { + glyph = &g_fontcache[font][character]; + if (glyph->pixmap != NULL) + return glyph; + } + + error("get font %d:%d\n", font, character); + return NULL; +} + +/* Store a glyph in the font cache */ +void +cache_put_font(uint8 font, uint16 character, uint16 offset, + uint16 baseline, uint16 width, uint16 height, RD_HGLYPH pixmap) +{ + FONTGLYPH *glyph; + + if ((font < NUM_ELEMENTS(g_fontcache)) && (character < NUM_ELEMENTS(g_fontcache[0]))) + { + glyph = &g_fontcache[font][character]; + if (glyph->pixmap != NULL) + ui_destroy_glyph(glyph->pixmap); + + glyph->offset = offset; + glyph->baseline = baseline; + glyph->width = width; + glyph->height = height; + glyph->pixmap = pixmap; + } + else + { + error("put font %d:%d\n", font, character); + } +} + + +/* TEXT CACHE */ +static DATABLOB g_textcache[256]; + +/* Retrieve a text item from the cache */ +DATABLOB * +cache_get_text(uint8 cache_id) +{ + DATABLOB *text; + + text = &g_textcache[cache_id]; + return text; +} + +/* Store a text item in the cache */ +void +cache_put_text(uint8 cache_id, void *data, int length) +{ + DATABLOB *text; + + text = &g_textcache[cache_id]; + if (text->data != NULL) + xfree(text->data); + text->data = xmalloc(length); + text->size = length; + memcpy(text->data, data, length); +} + + +/* DESKTOP CACHE */ +static uint8 g_deskcache[0x38400 * 4]; + +/* Retrieve desktop data from the cache */ +uint8 * +cache_get_desktop(uint32 offset, int cx, int cy, int bytes_per_pixel) +{ + int length = cx * cy * bytes_per_pixel; + + if (offset > sizeof(g_deskcache)) + offset = 0; + + if ((offset + length) <= sizeof(g_deskcache)) + { + return &g_deskcache[offset]; + } + + error("get desktop %d:%d\n", offset, length); + return NULL; +} + +/* Store desktop data in the cache */ +void +cache_put_desktop(uint32 offset, int cx, int cy, int scanline, int bytes_per_pixel, uint8 * data) +{ + int length = cx * cy * bytes_per_pixel; + + if (offset > sizeof(g_deskcache)) + offset = 0; + + if ((offset + length) <= sizeof(g_deskcache)) + { + cx *= bytes_per_pixel; + while (cy--) + { + memcpy(&g_deskcache[offset], data, cx); + data += scanline; + offset += cx; + } + } + else + { + error("put desktop %d:%d\n", offset, length); + } +} + + +/* CURSOR CACHE */ +static RD_HCURSOR g_cursorcache[0x20]; + +/* Retrieve cursor from cache */ +RD_HCURSOR +cache_get_cursor(uint16 cache_idx) +{ + RD_HCURSOR cursor; + + if (cache_idx < NUM_ELEMENTS(g_cursorcache)) + { + cursor = g_cursorcache[cache_idx]; + if (cursor != NULL) + return cursor; + } + + error("get cursor %d\n", cache_idx); + return NULL; +} + +/* Store cursor in cache */ +void +cache_put_cursor(uint16 cache_idx, RD_HCURSOR cursor) +{ + RD_HCURSOR old; + + if (cache_idx < NUM_ELEMENTS(g_cursorcache)) + { + old = g_cursorcache[cache_idx]; + if (old != NULL) + ui_destroy_cursor(old); + + g_cursorcache[cache_idx] = cursor; + } + else + { + error("put cursor %d\n", cache_idx); + } +} diff --git a/reactos/base/applications/mstsc/channels.c b/reactos/base/applications/mstsc/channels.c new file mode 100644 index 00000000000..389cce2e183 --- /dev/null +++ b/reactos/base/applications/mstsc/channels.c @@ -0,0 +1,181 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - Virtual channels + Copyright (C) Erik Forsberg 2003 + Copyright (C) Matthew Chapman 2003-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +#define MAX_CHANNELS 6 +#define CHANNEL_CHUNK_LENGTH 1600 +#define CHANNEL_FLAG_FIRST 0x01 +#define CHANNEL_FLAG_LAST 0x02 +#define CHANNEL_FLAG_SHOW_PROTOCOL 0x10 + +extern BOOL g_use_rdp5; +extern BOOL g_encryption; + +VCHANNEL g_channels[MAX_CHANNELS]; +unsigned int g_num_channels; + +/* FIXME: We should use the information in TAG_SRV_CHANNELS to map RDP5 + channels to MCS channels. + + The format of TAG_SRV_CHANNELS seems to be + + global_channel_no (uint16le) + number_of_other_channels (uint16le) + ..followed by uint16les for the other channels. +*/ + +VCHANNEL * +channel_register(char *name, uint32 flags, void (*callback) (STREAM)) +{ + VCHANNEL *channel; + + if (!g_use_rdp5) + return NULL; + + if (g_num_channels >= MAX_CHANNELS) + { + error("Channel table full, increase MAX_CHANNELS\n"); + return NULL; + } + + channel = &g_channels[g_num_channels]; + channel->mcs_id = MCS_GLOBAL_CHANNEL + 1 + g_num_channels; + strncpy(channel->name, name, 8); + channel->flags = flags; + channel->process = callback; + g_num_channels++; + return channel; +} + +STREAM +channel_init(VCHANNEL * channel, uint32 length) +{ + STREAM s; + + s = sec_init(g_encryption ? SEC_ENCRYPT : 0, length + 8); + s_push_layer(s, channel_hdr, 8); + return s; +} + +void +channel_send(STREAM s, VCHANNEL * channel) +{ + uint32 length, flags; + uint32 thislength, remaining; + uint8 *data; + + /* first fragment sent in-place */ + s_pop_layer(s, channel_hdr); + length = s->end - s->p - 8; + + DEBUG_CHANNEL(("channel_send, length = %d\n", length)); + + thislength = MIN(length, CHANNEL_CHUNK_LENGTH); +/* Note: In the original clipboard implementation, this number was + 1592, not 1600. However, I don't remember the reason and 1600 seems + to work so.. This applies only to *this* length, not the length of + continuation or ending packets. */ + remaining = length - thislength; + flags = (remaining == 0) ? CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST : CHANNEL_FLAG_FIRST; + if (channel->flags & CHANNEL_OPTION_SHOW_PROTOCOL) + flags |= CHANNEL_FLAG_SHOW_PROTOCOL; + + out_uint32_le(s, length); + out_uint32_le(s, flags); + data = s->end = s->p + thislength; + DEBUG_CHANNEL(("Sending %d bytes with FLAG_FIRST\n", thislength)); + sec_send_to_channel(s, g_encryption ? SEC_ENCRYPT : 0, channel->mcs_id); + + /* subsequent segments copied (otherwise would have to generate headers backwards) */ + while (remaining > 0) + { + thislength = MIN(remaining, CHANNEL_CHUNK_LENGTH); + remaining -= thislength; + flags = (remaining == 0) ? CHANNEL_FLAG_LAST : 0; + if (channel->flags & CHANNEL_OPTION_SHOW_PROTOCOL) + flags |= CHANNEL_FLAG_SHOW_PROTOCOL; + + DEBUG_CHANNEL(("Sending %d bytes with flags %d\n", thislength, flags)); + + s = sec_init(g_encryption ? SEC_ENCRYPT : 0, thislength + 8); + out_uint32_le(s, length); + out_uint32_le(s, flags); + out_uint8p(s, data, thislength); + s_mark_end(s); + sec_send_to_channel(s, g_encryption ? SEC_ENCRYPT : 0, channel->mcs_id); + + data += thislength; + } +} + +void +channel_process(STREAM s, uint16 mcs_channel) +{ + uint32 length, flags; + uint32 thislength; + VCHANNEL *channel = NULL; + unsigned int i; + STREAM in; + + for (i = 0; i < g_num_channels; i++) + { + channel = &g_channels[i]; + if (channel->mcs_id == mcs_channel) + break; + } + + if (i >= g_num_channels) + return; + + in_uint32_le(s, length); + in_uint32_le(s, flags); + if ((flags & CHANNEL_FLAG_FIRST) && (flags & CHANNEL_FLAG_LAST)) + { + /* single fragment - pass straight up */ + channel->process(s); + } + else + { + /* add fragment to defragmentation buffer */ + in = &channel->in; + if (flags & CHANNEL_FLAG_FIRST) + { + if (length > in->size) + { + in->data = (uint8 *) xrealloc(in->data, length); + in->size = length; + } + in->p = in->data; + } + + thislength = MIN(s->end - s->p, in->data + in->size - in->p); + memcpy(in->p, s->p, thislength); + in->p += thislength; + + if (flags & CHANNEL_FLAG_LAST) + { + in->end = in->p; + in->p = in->data; + channel->process(in); + } + } +} diff --git a/reactos/base/applications/mstsc/connectdialog.c b/reactos/base/applications/mstsc/connectdialog.c new file mode 100644 index 00000000000..f6bd661fce7 --- /dev/null +++ b/reactos/base/applications/mstsc/connectdialog.c @@ -0,0 +1,439 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Connection settings dialog + Copyright (C) Ged Murphy 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include "resource.h" + +typedef struct _INFO +{ + HWND hSelf; + HWND hTab; + HWND hGeneralPage; + HWND hDisplayPage; + HBITMAP hHeader; + BITMAP headerbitmap; +} INFO, *PINFO; + +HINSTANCE hInst; + +void OnTabWndSelChange(PINFO pInfo) +{ + switch (TabCtrl_GetCurSel(pInfo->hTab)) + { + case 0: //General + ShowWindow(pInfo->hGeneralPage, SW_SHOW); + ShowWindow(pInfo->hDisplayPage, SW_HIDE); + BringWindowToTop(pInfo->hGeneralPage); + break; + case 1: //Display + ShowWindow(pInfo->hGeneralPage, SW_HIDE); + ShowWindow(pInfo->hDisplayPage, SW_SHOW); + BringWindowToTop(pInfo->hDisplayPage); + break; + } +} + + +INT_PTR CALLBACK +GeneralDlgProc(HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static HICON hLogon, hConn; + HWND hParent = GetParent(hDlg); + PINFO pInfo = (PINFO)GetWindowLongPtr(hParent, + GWLP_USERDATA); + + switch (message) + { + case WM_INITDIALOG: + { + SetWindowPos(hDlg, + NULL, + 15, + 122, + 0, + 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + + hLogon = LoadImage(hInst, + MAKEINTRESOURCE(IDI_LOGON), + IMAGE_ICON, + 32, + 32, + LR_DEFAULTCOLOR); + if (hLogon) + { + SendDlgItemMessage(hDlg, + IDC_LOGONICON, + STM_SETICON, + (WPARAM)hLogon, + 0); + } + + hConn = LoadImage(hInst, + MAKEINTRESOURCE(IDI_CONN), + IMAGE_ICON, + 32, + 32, + LR_DEFAULTCOLOR); + if (hConn) + { + SendDlgItemMessage(hDlg, + IDC_CONNICON, + STM_SETICON, + (WPARAM)hConn, + 0); + } + + return TRUE; + } + + case WM_CLOSE: + { + if (hLogon) + DestroyIcon(hLogon); + + if (hConn) + DestroyIcon(hConn); + + break; + } + } + + return 0; +} + + +INT_PTR CALLBACK +DisplayDlgProc(HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static HICON hRemote, hColor; + static HBITMAP hSpectrum; + static BITMAP bitmap; + PINFO pInfo = (PINFO)GetWindowLongPtr(GetParent(hDlg), + GWLP_USERDATA); + + switch (message) + { + case WM_INITDIALOG: + { + SetWindowPos(hDlg, + NULL, + 15, + 122, + 0, + 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + + hRemote = LoadImage(hInst, + MAKEINTRESOURCE(IDI_REMOTE), + IMAGE_ICON, + 32, + 32, + LR_DEFAULTCOLOR); + if (hRemote) + { + SendDlgItemMessage(hDlg, + IDC_REMICON, + STM_SETICON, + (WPARAM)hRemote, + 0); + } + + hColor = LoadImage(hInst, + MAKEINTRESOURCE(IDI_COLORS), + IMAGE_ICON, + 32, + 32, + LR_DEFAULTCOLOR); + if (hColor) + { + SendDlgItemMessage(hDlg, + IDC_COLORSICON, + STM_SETICON, + (WPARAM)hColor, + 0); + } + + hSpectrum = LoadImage(hInst, + MAKEINTRESOURCE(IDB_SPECT), + IMAGE_BITMAP, + 0, + 0, + LR_DEFAULTCOLOR); + if (hSpectrum) + { + GetObject(hSpectrum, sizeof(BITMAP), &bitmap); + } + + return TRUE; + } + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT lpDrawItem; + lpDrawItem = (LPDRAWITEMSTRUCT) lParam; + if(lpDrawItem->CtlID == IDC_COLORIMAGE) + { + HDC hdcMem; + hdcMem = CreateCompatibleDC(lpDrawItem->hDC); + if (hdcMem != NULL) + { + SelectObject(hdcMem, hSpectrum); + StretchBlt(lpDrawItem->hDC, + lpDrawItem->rcItem.left, + lpDrawItem->rcItem.top, + lpDrawItem->rcItem.right - lpDrawItem->rcItem.left, + lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top, + hdcMem, + 0, + 0, + bitmap.bmWidth, + bitmap.bmHeight, + SRCCOPY); + DeleteDC(hdcMem); + } + } + break; + } + + case WM_CLOSE: + { + if (hRemote) + DestroyIcon(hRemote); + + if (hColor) + DestroyIcon(hColor); + + if (hSpectrum) + DeleteObject(hSpectrum); + + break; + } + + break; + } + return 0; +} + + + +static BOOL +OnCreate(HWND hwnd) +{ + PINFO pInfo; + TCITEM item; + BOOL bRet = FALSE; + + pInfo = HeapAlloc(GetProcessHeap(), + 0, + sizeof(INFO)); + if (pInfo) + { + SetWindowLongPtr(hwnd, + GWLP_USERDATA, + (LONG_PTR)pInfo); + + pInfo->hHeader = LoadImage(hInst, + MAKEINTRESOURCE(IDB_HEADER), + IMAGE_BITMAP, + 0, + 0, + LR_DEFAULTCOLOR); + if (pInfo->hHeader) + { + GetObject(pInfo->hHeader, sizeof(BITMAP), &pInfo->headerbitmap); + } + + pInfo->hTab = GetDlgItem(hwnd, IDC_TAB); + if (pInfo->hTab) + { + pInfo->hGeneralPage = CreateDialog(hInst, + MAKEINTRESOURCE(IDD_GENERAL), + hwnd, + (DLGPROC)GeneralDlgProc); + if (pInfo->hGeneralPage) + { + char str[256]; + LoadString(hInst, IDS_TAB_GENERAL, str, 256); + ZeroMemory(&item, sizeof(TCITEM)); + item.mask = TCIF_TEXT; + item.pszText = str; + item.cchTextMax = 256; + (void)TabCtrl_InsertItem(pInfo->hTab, 0, &item); + } + + pInfo->hDisplayPage = CreateDialog(hInst, + MAKEINTRESOURCE(IDD_DISPLAY), + hwnd, + (DLGPROC)DisplayDlgProc); + if (pInfo->hDisplayPage) + { + char str[256]; + LoadString(hInst, IDS_TAB_DISPLAY, str, 256); + ZeroMemory(&item, sizeof(TCITEM)); + item.mask = TCIF_TEXT; + item.pszText = str; + item.cchTextMax = 256; + (void)TabCtrl_InsertItem(pInfo->hTab, 1, &item); + } + + OnTabWndSelChange(pInfo); + + bRet = TRUE; + } + } + + return bRet; +} + + +static BOOL CALLBACK +DlgProc(HWND hDlg, + UINT Message, + WPARAM wParam, + LPARAM lParam) +{ + PINFO pInfo; + + /* Get the window context */ + pInfo = (PINFO)GetWindowLongPtr(hDlg, + GWLP_USERDATA); + if (pInfo == NULL && Message != WM_INITDIALOG) + { + goto HandleDefaultMessage; + } + + switch(Message) + { + case WM_INITDIALOG: + OnCreate(hDlg); + break; + + case WM_COMMAND: + { + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + if (pInfo) + { + HeapFree(GetProcessHeap(), + 0, + pInfo); + } + + EndDialog(hDlg, LOWORD(wParam)); + } + + switch(LOWORD(wParam)) + { + + break; + } + + break; + } + + case WM_NOTIFY: + { + INT idctrl; + LPNMHDR pnmh; + idctrl = (int)wParam; + pnmh = (LPNMHDR)lParam; + if (//(pnmh->hwndFrom == pInfo->hSelf) && + (pnmh->idFrom == IDC_TAB) && + (pnmh->code == TCN_SELCHANGE)) + { + OnTabWndSelChange(pInfo); + } + + break; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc, hdcMem; + + hdc = BeginPaint(hDlg, &ps); + if (hdc != NULL) + { + HDC hdcMem = CreateCompatibleDC(hdc); + if (hdcMem) + { + SelectObject(hdcMem, pInfo->hHeader); + BitBlt(hdc, + 0, + 0, + pInfo->headerbitmap.bmWidth, + pInfo->headerbitmap.bmHeight, + hdcMem, + 0, + 0, + SRCCOPY); + DeleteDC(hdcMem); + } + + EndPaint(hDlg, &ps); + } + + break; + } + + case WM_CLOSE: + { + if (pInfo) + HeapFree(GetProcessHeap(), + 0, + pInfo); + + EndDialog(hDlg, 0); + } + break; + +HandleDefaultMessage: + default: + return FALSE; + } + + return FALSE; +} + +BOOL +OpenRDPConnectDialog(HINSTANCE hInstance) +{ + INITCOMMONCONTROLSEX iccx; + + hInst = hInstance; + + iccx.dwSize = sizeof(INITCOMMONCONTROLSEX); + iccx.dwICC = ICC_TAB_CLASSES; + InitCommonControlsEx(&iccx); + + return (DialogBox(hInst, + MAKEINTRESOURCE(IDD_CONNECTDIALOG), + NULL, + (DLGPROC)DlgProc) == IDOK); +} diff --git a/reactos/base/applications/mstsc/constants.h b/reactos/base/applications/mstsc/constants.h new file mode 100644 index 00000000000..32db85a75f0 --- /dev/null +++ b/reactos/base/applications/mstsc/constants.h @@ -0,0 +1,435 @@ +/* + rdesktop: A Remote Desktop Protocol client. + Miscellaneous protocol constants + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* TCP port for Remote Desktop Protocol */ +#define TCP_PORT_RDP 3389 + +#define DEFAULT_CODEPAGE "UTF-8" +#define WINDOWS_CODEPAGE "UTF-16LE" + +/* ISO PDU codes */ +enum ISO_PDU_CODE +{ + ISO_PDU_CR = 0xE0, /* Connection Request */ + ISO_PDU_CC = 0xD0, /* Connection Confirm */ + ISO_PDU_DR = 0x80, /* Disconnect Request */ + ISO_PDU_DT = 0xF0, /* Data */ + ISO_PDU_ER = 0x70 /* Error */ +}; + +/* MCS PDU codes */ +enum MCS_PDU_TYPE +{ + MCS_EDRQ = 1, /* Erect Domain Request */ + MCS_DPUM = 8, /* Disconnect Provider Ultimatum */ + MCS_AURQ = 10, /* Attach User Request */ + MCS_AUCF = 11, /* Attach User Confirm */ + MCS_CJRQ = 14, /* Channel Join Request */ + MCS_CJCF = 15, /* Channel Join Confirm */ + MCS_SDRQ = 25, /* Send Data Request */ + MCS_SDIN = 26 /* Send Data Indication */ +}; + +#define MCS_CONNECT_INITIAL 0x7f65 +#define MCS_CONNECT_RESPONSE 0x7f66 + +#define BER_TAG_BOOLEAN 1 +#define BER_TAG_INTEGER 2 +#define BER_TAG_OCTET_STRING 4 +#define BER_TAG_RESULT 10 +#define MCS_TAG_DOMAIN_PARAMS 0x30 + +#define MCS_GLOBAL_CHANNEL 1003 +#define MCS_USERCHANNEL_BASE 1001 + +/* RDP secure transport constants */ +#define SEC_RANDOM_SIZE 32 +#define SEC_MODULUS_SIZE 64 +#define SEC_PADDING_SIZE 8 +#define SEC_EXPONENT_SIZE 4 + +#define SEC_CLIENT_RANDOM 0x0001 +#define SEC_ENCRYPT 0x0008 +#define SEC_LOGON_INFO 0x0040 +#define SEC_LICENCE_NEG 0x0080 +#define SEC_REDIRECT_ENCRYPT 0x0C00 + +#define SEC_TAG_SRV_INFO 0x0c01 +#define SEC_TAG_SRV_CRYPT 0x0c02 +#define SEC_TAG_SRV_CHANNELS 0x0c03 + +#define SEC_TAG_CLI_INFO 0xc001 +#define SEC_TAG_CLI_CRYPT 0xc002 +#define SEC_TAG_CLI_CHANNELS 0xc003 +#define SEC_TAG_CLI_4 0xc004 + +#define SEC_TAG_PUBKEY 0x0006 +#define SEC_TAG_KEYSIG 0x0008 + +#define SEC_RSA_MAGIC 0x31415352 /* RSA1 */ + +/* RDP licensing constants */ +#define LICENCE_TOKEN_SIZE 10 +#define LICENCE_HWID_SIZE 20 +#define LICENCE_SIGNATURE_SIZE 16 + +#define LICENCE_TAG_DEMAND 0x01 +#define LICENCE_TAG_AUTHREQ 0x02 +#define LICENCE_TAG_ISSUE 0x03 +#define LICENCE_TAG_REISSUE 0x04 +#define LICENCE_TAG_PRESENT 0x12 +#define LICENCE_TAG_REQUEST 0x13 +#define LICENCE_TAG_AUTHRESP 0x15 +#define LICENCE_TAG_RESULT 0xff + +#define LICENCE_TAG_USER 0x000f +#define LICENCE_TAG_HOST 0x0010 + +/* RDP PDU codes */ +enum RDP_PDU_TYPE +{ + RDP_PDU_DEMAND_ACTIVE = 1, + RDP_PDU_CONFIRM_ACTIVE = 3, + RDP_PDU_REDIRECT = 4, /* MS Server 2003 Session Redirect */ + RDP_PDU_DEACTIVATE = 6, + RDP_PDU_DATA = 7 +}; + +enum RDP_DATA_PDU_TYPE +{ + RDP_DATA_PDU_UPDATE = 2, + RDP_DATA_PDU_CONTROL = 20, + RDP_DATA_PDU_POINTER = 27, + RDP_DATA_PDU_INPUT = 28, + RDP_DATA_PDU_SYNCHRONISE = 31, + RDP_DATA_PDU_BELL = 34, + RDP_DATA_PDU_CLIENT_WINDOW_STATUS = 35, + RDP_DATA_PDU_LOGON = 38, + RDP_DATA_PDU_FONT2 = 39, + RDP_DATA_PDU_KEYBOARD_INDICATORS = 41, + RDP_DATA_PDU_DISCONNECT = 47 +}; + +enum RDP_CONTROL_PDU_TYPE +{ + RDP_CTL_REQUEST_CONTROL = 1, + RDP_CTL_GRANT_CONTROL = 2, + RDP_CTL_DETACH = 3, + RDP_CTL_COOPERATE = 4 +}; + +enum RDP_UPDATE_PDU_TYPE +{ + RDP_UPDATE_ORDERS = 0, + RDP_UPDATE_BITMAP = 1, + RDP_UPDATE_PALETTE = 2, + RDP_UPDATE_SYNCHRONIZE = 3 +}; + +enum RDP_POINTER_PDU_TYPE +{ + RDP_POINTER_SYSTEM = 1, + RDP_POINTER_MOVE = 3, + RDP_POINTER_COLOR = 6, + RDP_POINTER_CACHED = 7 +}; + +enum RDP_SYSTEM_POINTER_TYPE +{ + RDP_NULL_POINTER = 0, + RDP_DEFAULT_POINTER = 0x7F00 +}; + +enum RDP_INPUT_DEVICE +{ + RDP_INPUT_SYNCHRONIZE = 0, + RDP_INPUT_CODEPOINT = 1, + RDP_INPUT_VIRTKEY = 2, + RDP_INPUT_SCANCODE = 4, + RDP_INPUT_MOUSE = 0x8001 +}; + +/* Device flags */ +#define KBD_FLAG_RIGHT 0x0001 +#define KBD_FLAG_EXT 0x0100 +#define KBD_FLAG_QUIET 0x1000 +#define KBD_FLAG_DOWN 0x4000 +#define KBD_FLAG_UP 0x8000 + +/* These are for synchronization; not for keystrokes */ +#define KBD_FLAG_SCROLL 0x0001 +#define KBD_FLAG_NUMLOCK 0x0002 +#define KBD_FLAG_CAPITAL 0x0004 + +/* See T.128 */ +#define RDP_KEYPRESS 0 +#define RDP_KEYRELEASE (KBD_FLAG_DOWN | KBD_FLAG_UP) + +#define MOUSE_FLAG_MOVE 0x0800 +#define MOUSE_FLAG_BUTTON1 0x1000 +#define MOUSE_FLAG_BUTTON2 0x2000 +#define MOUSE_FLAG_BUTTON3 0x4000 +#define MOUSE_FLAG_BUTTON4 0x0280 +#define MOUSE_FLAG_BUTTON5 0x0380 +#define MOUSE_FLAG_DOWN 0x8000 + +/* Raster operation masks */ +#define ROP2_S(rop3) ((uint8) (rop3 & 0xf)) +#define ROP2_P(rop3) ((uint8) ((rop3 & 0x3) | ((rop3 & 0x30) >> 2))) +#define ROP_MINUS_1(rop) ((uint8) (rop - 1)) + +#define ROP2_COPY 0xc +#define ROP2_XOR 0x6 +#define ROP2_AND 0x8 +#define ROP2_NXOR 0x9 +#define ROP2_OR 0xe + +#define MIX_TRANSPARENT 0 +#define MIX_OPAQUE 1 + +#define TEXT2_VERTICAL 0x04 +#define TEXT2_IMPLICIT_X 0x20 + +#define ALTERNATE 1 +#define WINDING 2 + +/* RDP bitmap cache (version 2) constants */ +#define BMPCACHE2_C0_CELLS 0x78 +#define BMPCACHE2_C1_CELLS 0x78 +#define BMPCACHE2_C2_CELLS 0x150 +#define BMPCACHE2_NUM_PSTCELLS 0x9f6 + +#define PDU_FLAG_FIRST 0x01 +#define PDU_FLAG_LAST 0x02 + +/* RDP capabilities */ +#define RDP_CAPSET_GENERAL 1 /* Maps to generalCapabilitySet in T.128 page 138 */ +#define RDP_CAPLEN_GENERAL 0x18 +#define OS_MAJOR_TYPE_UNIX 4 +#define OS_MINOR_TYPE_XSERVER 7 + +#define RDP_CAPSET_BITMAP 2 +#define RDP_CAPLEN_BITMAP 0x1C + +#define RDP_CAPSET_ORDER 3 +#define RDP_CAPLEN_ORDER 0x58 +#define ORDER_CAP_NEGOTIATE 2 +#define ORDER_CAP_NOSUPPORT 4 + +#define RDP_CAPSET_BMPCACHE 4 +#define RDP_CAPLEN_BMPCACHE 0x28 + +#define RDP_CAPSET_CONTROL 5 +#define RDP_CAPLEN_CONTROL 0x0C + +#define RDP_CAPSET_ACTIVATE 7 +#define RDP_CAPLEN_ACTIVATE 0x0C + +#define RDP_CAPSET_POINTER 8 +#define RDP_CAPLEN_POINTER 0x08 + +#define RDP_CAPSET_SHARE 9 +#define RDP_CAPLEN_SHARE 0x08 + +#define RDP_CAPSET_COLCACHE 10 +#define RDP_CAPLEN_COLCACHE 0x08 + +#define RDP_CAPSET_BMPCACHE2 19 +#define RDP_CAPLEN_BMPCACHE2 0x28 +#define BMPCACHE2_FLAG_PERSIST ((uint32)1<<31) + +#define RDP_SOURCE "MSTSC" + +/* Logon flags */ +#define RDP_LOGON_AUTO 0x0008 +#define RDP_LOGON_NORMAL 0x0033 +#define RDP_LOGON_COMPRESSION 0x0080 /* mppc compression with 8kB histroy buffer */ +#define RDP_LOGON_BLOB 0x0100 +#define RDP_LOGON_COMPRESSION2 0x0200 /* rdp5 mppc compression with 64kB history buffer */ +#define RDP_LOGON_LEAVE_AUDIO 0x2000 + +#define RDP5_DISABLE_NOTHING 0x00 +#define RDP5_NO_WALLPAPER 0x01 +#define RDP5_NO_FULLWINDOWDRAG 0x02 +#define RDP5_NO_MENUANIMATIONS 0x04 +#define RDP5_NO_THEMING 0x08 +#define RDP5_NO_CURSOR_SHADOW 0x20 +#define RDP5_NO_CURSORSETTINGS 0x40 /* disables cursor blinking */ + +/* compression types */ +#define RDP_MPPC_BIG 0x01 +#define RDP_MPPC_COMPRESSED 0x20 +#define RDP_MPPC_RESET 0x40 +#define RDP_MPPC_FLUSH 0x80 +#define RDP_MPPC_DICT_SIZE 65536 + +#define RDP5_COMPRESSED 0x80 + +/* Keymap flags */ +#define MapRightShiftMask (1<<0) +#define MapLeftShiftMask (1<<1) +#define MapShiftMask (MapRightShiftMask | MapLeftShiftMask) + +#define MapRightAltMask (1<<2) +#define MapLeftAltMask (1<<3) +#define MapAltGrMask MapRightAltMask + +#define MapRightCtrlMask (1<<4) +#define MapLeftCtrlMask (1<<5) +#define MapCtrlMask (MapRightCtrlMask | MapLeftCtrlMask) + +#define MapRightWinMask (1<<6) +#define MapLeftWinMask (1<<7) +#define MapWinMask (MapRightWinMask | MapLeftWinMask) + +#define MapNumLockMask (1<<8) +#define MapCapsLockMask (1<<9) + +#define MapLocalStateMask (1<<10) + +#define MapInhibitMask (1<<11) + +#define MASK_ADD_BITS(var, mask) (var |= mask) +#define MASK_REMOVE_BITS(var, mask) (var &= ~mask) +#define MASK_HAS_BITS(var, mask) ((var & mask)>0) +#define MASK_CHANGE_BIT(var, mask, active) (var = ((var & ~mask) | (active ? mask : 0))) + +/* Clipboard constants, "borrowed" from GCC system headers in + the w32 cross compiler */ + +#ifndef CF_TEXT +#define CF_TEXT 1 +#define CF_BITMAP 2 +#define CF_METAFILEPICT 3 +#define CF_SYLK 4 +#define CF_DIF 5 +#define CF_TIFF 6 +#define CF_OEMTEXT 7 +#define CF_DIB 8 +#define CF_PALETTE 9 +#define CF_PENDATA 10 +#define CF_RIFF 11 +#define CF_WAVE 12 +#define CF_UNICODETEXT 13 +#define CF_ENHMETAFILE 14 +#define CF_HDROP 15 +#define CF_LOCALE 16 +#define CF_MAX 17 +#define CF_OWNERDISPLAY 128 +#define CF_DSPTEXT 129 +#define CF_DSPBITMAP 130 +#define CF_DSPMETAFILEPICT 131 +#define CF_DSPENHMETAFILE 142 +#define CF_PRIVATEFIRST 512 +#define CF_PRIVATELAST 767 +#define CF_GDIOBJFIRST 768 +#define CF_GDIOBJLAST 1023 +#endif + +/* Sound format constants */ +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_ADPCM 2 +#define WAVE_FORMAT_ALAW 6 +#define WAVE_FORMAT_MULAW 7 + +/* Virtual channel options */ +#define CHANNEL_OPTION_INITIALIZED 0x80000000 +#define CHANNEL_OPTION_ENCRYPT_RDP 0x40000000 +#define CHANNEL_OPTION_COMPRESS_RDP 0x00800000 +#define CHANNEL_OPTION_SHOW_PROTOCOL 0x00200000 + +/* NT status codes for RDPDR */ +#undef STATUS_SUCCESS +#define STATUS_SUCCESS 0x00000000 +#undef STATUS_NOT_IMPLEMENTED +#define STATUS_NOT_IMPLEMENTED 0x00000001 +#undef STATUS_PENDING +#define STATUS_PENDING 0x00000103 + +#ifndef STATUS_NO_MORE_FILES +#define STATUS_NO_MORE_FILES 0x80000006 +#define STATUS_DEVICE_PAPER_EMPTY 0x8000000e +#define STATUS_DEVICE_POWERED_OFF 0x8000000f +#define STATUS_DEVICE_OFF_LINE 0x80000010 +#define STATUS_DEVICE_BUSY 0x80000011 +#endif + +#ifndef STATUS_INVALID_HANDLE +#define STATUS_INVALID_HANDLE 0xc0000008 +#define STATUS_INVALID_PARAMETER 0xc000000d +#define STATUS_NO_SUCH_FILE 0xc000000f +#define STATUS_INVALID_DEVICE_REQUEST 0xc0000010 +#define STATUS_ACCESS_DENIED 0xc0000022 +#define STATUS_OBJECT_NAME_COLLISION 0xc0000035 +#define STATUS_DISK_FULL 0xc000007f +#define STATUS_FILE_IS_A_DIRECTORY 0xc00000ba +#define STATUS_NOT_SUPPORTED 0xc00000bb +#define STATUS_TIMEOUT 0xc0000102 +#define STATUS_NOTIFY_ENUM_DIR 0xc000010c +#define STATUS_CANCELLED 0xc0000120 +#endif + +/* RDPDR constants */ +#define RDPDR_MAX_DEVICES 0x10 +#define DEVICE_TYPE_SERIAL 0x01 +#define DEVICE_TYPE_PARALLEL 0x02 +#define DEVICE_TYPE_PRINTER 0x04 +#define DEVICE_TYPE_DISK 0x08 +#define DEVICE_TYPE_SCARD 0x20 + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +/* RDP5 disconnect PDU */ +#define exDiscReasonNoInfo 0x0000 +#define exDiscReasonAPIInitiatedDisconnect 0x0001 +#define exDiscReasonAPIInitiatedLogoff 0x0002 +#define exDiscReasonServerIdleTimeout 0x0003 +#define exDiscReasonServerLogonTimeout 0x0004 +#define exDiscReasonReplacedByOtherConnection 0x0005 +#define exDiscReasonOutOfMemory 0x0006 +#define exDiscReasonServerDeniedConnection 0x0007 +#define exDiscReasonServerDeniedConnectionFips 0x0008 +#define exDiscReasonLicenseInternal 0x0100 +#define exDiscReasonLicenseNoLicenseServer 0x0101 +#define exDiscReasonLicenseNoLicense 0x0102 +#define exDiscReasonLicenseErrClientMsg 0x0103 +#define exDiscReasonLicenseHwidDoesntMatchLicense 0x0104 +#define exDiscReasonLicenseErrClientLicense 0x0105 +#define exDiscReasonLicenseCantFinishProtocol 0x0106 +#define exDiscReasonLicenseClientEndedProtocol 0x0107 +#define exDiscReasonLicenseErrClientEncryption 0x0108 +#define exDiscReasonLicenseCantUpgradeLicense 0x0109 +#define exDiscReasonLicenseNoRemoteConnections 0x010a + +/* SeamlessRDP constants */ +#define SEAMLESSRDP_NOTYETMAPPED -1 +#define SEAMLESSRDP_NORMAL 0 +#define SEAMLESSRDP_MINIMIZED 1 +#define SEAMLESSRDP_MAXIMIZED 2 +#define SEAMLESSRDP_POSITION_TIMER 200000 + +#define SEAMLESSRDP_CREATE_MODAL 0x0001 + +#define SEAMLESSRDP_HELLO_RECONNECT 0x0001 +#define SEAMLESSRDP_HELLO_HIDDEN 0x0002 diff --git a/reactos/base/applications/mstsc/iso.c b/reactos/base/applications/mstsc/iso.c new file mode 100644 index 00000000000..163946dffd7 --- /dev/null +++ b/reactos/base/applications/mstsc/iso.c @@ -0,0 +1,231 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - ISO layer + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +/* Send a self-contained ISO PDU */ +static void +iso_send_msg(uint8 code) +{ + STREAM s; + + s = tcp_init(11); + + out_uint8(s, 3); /* version */ + out_uint8(s, 0); /* reserved */ + out_uint16_be(s, 11); /* length */ + + out_uint8(s, 6); /* hdrlen */ + out_uint8(s, code); + out_uint16(s, 0); /* dst_ref */ + out_uint16(s, 0); /* src_ref */ + out_uint8(s, 0); /* class */ + + s_mark_end(s); + tcp_send(s); +} + +static void +iso_send_connection_request(char *username) +{ + STREAM s; + int length = 30 + strlen(username); + + s = tcp_init(length); + + out_uint8(s, 3); /* version */ + out_uint8(s, 0); /* reserved */ + out_uint16_be(s, length); /* length */ + + out_uint8(s, length - 5); /* hdrlen */ + out_uint8(s, ISO_PDU_CR); + out_uint16(s, 0); /* dst_ref */ + out_uint16(s, 0); /* src_ref */ + out_uint8(s, 0); /* class */ + + out_uint8p(s, "Cookie: mstshash=", strlen("Cookie: mstshash=")); + out_uint8p(s, username, strlen(username)); + + out_uint8(s, 0x0d); /* Unknown */ + out_uint8(s, 0x0a); /* Unknown */ + + s_mark_end(s); + tcp_send(s); +} + +/* Receive a message on the ISO layer, return code */ +static STREAM +iso_recv_msg(uint8 * code, uint8 * rdpver) +{ + STREAM s; + uint16 length; + uint8 version; + + s = tcp_recv(NULL, 4); + if (s == NULL) + return NULL; + in_uint8(s, version); + if (rdpver != NULL) + *rdpver = version; + if (version == 3) + { + in_uint8s(s, 1); /* pad */ + in_uint16_be(s, length); + } + else + { + in_uint8(s, length); + if (length & 0x80) + { + length &= ~0x80; + next_be(s, length); + } + } + s = tcp_recv(s, length - 4); + if (s == NULL) + return NULL; + if (version != 3) + return s; + in_uint8s(s, 1); /* hdrlen */ + in_uint8(s, *code); + if (*code == ISO_PDU_DT) + { + in_uint8s(s, 1); /* eot */ + return s; + } + in_uint8s(s, 5); /* dst_ref, src_ref, class */ + return s; +} + +/* Initialise ISO transport data packet */ +STREAM +iso_init(int length) +{ + STREAM s; + + s = tcp_init(length + 7); + s_push_layer(s, iso_hdr, 7); + + return s; +} + +/* Send an ISO data PDU */ +void +iso_send(STREAM s) +{ + uint16 length; + + s_pop_layer(s, iso_hdr); + length = s->end - s->p; + + out_uint8(s, 3); /* version */ + out_uint8(s, 0); /* reserved */ + out_uint16_be(s, length); + + out_uint8(s, 2); /* hdrlen */ + out_uint8(s, ISO_PDU_DT); /* code */ + out_uint8(s, 0x80); /* eot */ + + tcp_send(s); +} + +/* Receive ISO transport data packet */ +STREAM +iso_recv(uint8 * rdpver) +{ + STREAM s; + uint8 code = 0; + + s = iso_recv_msg(&code, rdpver); + if (s == NULL) + return NULL; + if (rdpver != NULL) + if (*rdpver != 3) + return s; + if (code != ISO_PDU_DT) + { + error("expected DT, got 0x%x\n", code); + return NULL; + } + return s; +} + +/* Establish a connection up to the ISO layer */ +BOOL +iso_connect(char *server, char *username) +{ + uint8 code = 0; + + if (!tcp_connect(server)) + return False; + + iso_send_connection_request(username); + + if (iso_recv_msg(&code, NULL) == NULL) + return False; + + if (code != ISO_PDU_CC) + { + error("expected CC, got 0x%x\n", code); + tcp_disconnect(); + return False; + } + + return True; +} + +/* Establish a reconnection up to the ISO layer */ +BOOL +iso_reconnect(char *server) +{ + uint8 code = 0; + + if (!tcp_connect(server)) + return False; + + iso_send_msg(ISO_PDU_CR); + + if (iso_recv_msg(&code, NULL) == NULL) + return False; + + if (code != ISO_PDU_CC) + { + error("expected CC, got 0x%x\n", code); + tcp_disconnect(); + return False; + } + + return True; +} + +/* Disconnect from the ISO layer */ +void +iso_disconnect(void) +{ + iso_send_msg(ISO_PDU_DR); + tcp_disconnect(); +} + +/* reset the state to support reconnecting */ +void +iso_reset_state(void) +{ + tcp_reset_state(); +} diff --git a/reactos/base/applications/mstsc/lang/en-US.rc b/reactos/base/applications/mstsc/lang/en-US.rc new file mode 100644 index 00000000000..8efff59245d --- /dev/null +++ b/reactos/base/applications/mstsc/lang/en-US.rc @@ -0,0 +1,53 @@ +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +IDD_GENERAL DIALOGEX 0, 0, 242, 175 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Logon settings",IDC_STATIC,7,7,228,89 + GROUPBOX "Connection settings",IDC_STATIC,7,103,228,65 + ICON "", IDC_LOGONICON, 15,19,20,20 + LTEXT "Enter the server address",IDC_STATIC,47,24,81,8 + LTEXT "Server:",IDC_STATIC,47,41,25,8 + LTEXT "User name:",IDC_STATIC,47,58,38,8 + COMBOBOX IDC_SERVERCOMBO,79,39,141,30,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Save",IDC_SAVE,67,139,50,14 + PUSHBUTTON "SaveAs...",IDC_SAVEAS,123,139,50,14 + PUSHBUTTON "Open...",IDC_OPEN,177,139,50,14 + ICON "", IDC_CONNICON, 16,114,20,20 + LTEXT "Save the current connection settings or open an existing config",IDC_STATIC,50,115,172,20 +END + +IDD_DISPLAY DIALOGEX 0, 0, 242, 175 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + GROUPBOX "Remote desktop size",IDC_STATIC,7,7,228,68 + GROUPBOX "Colors",IDC_STATIC,7,83,228,85 + ICON "", IDC_REMICON, 15,19,20,20 + ICON "", IDC_COLORSICON, 15,98,20,20 + LTEXT "Set the screen size of your remote desktop. Drag the slider to the far right to go fullscreen",IDC_STATIC,53,22,175,21 + CONTROL "",IDC_GEOSLIDER,"msctls_trackbar32",WS_TABSTOP,56,49,124,15 + COMBOBOX IDC_BPPCOMBO,56,102,128,30,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_COLORIMAGE,"Static",SS_OWNERDRAW | SS_SUNKEN, 56,121,127,13 + LTEXT "Note: Settings on the remote computer might override this setting.",IDC_STATIC,56,143,165,18 + LTEXT "Less",IDC_STATIC,35,52,15,8 + LTEXT "More",IDC_STATIC,189,51,17,8 +END + +IDD_CONNECTDIALOG DIALOGEX 0, 0, 260, 277 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Remote Desktop Connection" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Connect",IDOK,147,256,50,14 + PUSHBUTTON "Cancel",IDCANCEL,203,256,50,14 + CONTROL "",IDC_TAB,"SysTabControl32",0x0,7,54,246,198 +END + + +STRINGTABLE +BEGIN + IDS_TAB_GENERAL "General" + IDS_TAB_DISPLAY "Display" +END diff --git a/reactos/base/applications/mstsc/licence.c b/reactos/base/applications/mstsc/licence.c new file mode 100644 index 00000000000..09cb4548c6c --- /dev/null +++ b/reactos/base/applications/mstsc/licence.c @@ -0,0 +1,345 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + RDP licensing negotiation + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" +//#include + +void * +ssl_rc4_info_create(void); +void +ssl_rc4_info_delete(void * rc4_info); +void +ssl_rc4_set_key(void * rc4_info, char * key, int len); +void +ssl_rc4_crypt(void * rc4_info, char * in_data, char * out_data, int len); +int +ssl_mod_exp(char* out, int out_len, char* in, int in_len, + char* mod, int mod_len, char* exp, int exp_len); + +extern char g_username[64]; +extern char g_hostname[16]; + +static uint8 g_licence_key[16]; +static uint8 g_licence_sign_key[16]; + +BOOL g_licence_issued = False; + +/* Generate a session key and RC4 keys, given client and server randoms */ +static void +licence_generate_keys(uint8 * client_random, uint8 * server_random, uint8 * pre_master_secret) +{ + uint8 master_secret[48]; + uint8 key_block[48]; + + /* Generate master secret and then key material */ + sec_hash_48(master_secret, pre_master_secret, client_random, server_random, 'A'); + sec_hash_48(key_block, master_secret, server_random, client_random, 'A'); + + /* Store first 16 bytes of session key as MAC secret */ + memcpy(g_licence_sign_key, key_block, 16); + + /* Generate RC4 key from next 16 bytes */ + sec_hash_16(g_licence_key, &key_block[16], client_random, server_random); +} + +static void +licence_generate_hwid(uint8 * hwid) +{ + buf_out_uint32(hwid, 2); + strncpy((char *) (hwid + 4), g_hostname, LICENCE_HWID_SIZE - 4); +} + +/* Present an existing licence to the server */ +static void +licence_present(uint8 * client_random, uint8 * rsa_data, + uint8 * licence_data, int licence_size, uint8 * hwid, uint8 * signature) +{ + uint32 sec_flags = SEC_LICENCE_NEG; + uint16 length = + 16 + SEC_RANDOM_SIZE + SEC_MODULUS_SIZE + SEC_PADDING_SIZE + + licence_size + LICENCE_HWID_SIZE + LICENCE_SIGNATURE_SIZE; + STREAM s; + + s = sec_init(sec_flags, length + 4); + + out_uint8(s, LICENCE_TAG_PRESENT); + out_uint8(s, 2); /* version */ + out_uint16_le(s, length); + + out_uint32_le(s, 1); + out_uint16(s, 0); + out_uint16_le(s, 0x0201); + + out_uint8p(s, client_random, SEC_RANDOM_SIZE); + out_uint16(s, 0); + out_uint16_le(s, (SEC_MODULUS_SIZE + SEC_PADDING_SIZE)); + out_uint8p(s, rsa_data, SEC_MODULUS_SIZE); + out_uint8s(s, SEC_PADDING_SIZE); + + out_uint16_le(s, 1); + out_uint16_le(s, licence_size); + out_uint8p(s, licence_data, licence_size); + + out_uint16_le(s, 1); + out_uint16_le(s, LICENCE_HWID_SIZE); + out_uint8p(s, hwid, LICENCE_HWID_SIZE); + + out_uint8p(s, signature, LICENCE_SIGNATURE_SIZE); + + s_mark_end(s); + sec_send(s, sec_flags); +} + +/* Send a licence request packet */ +static void +licence_send_request(uint8 * client_random, uint8 * rsa_data, char *user, char *host) +{ + uint32 sec_flags = SEC_LICENCE_NEG; + uint16 userlen = strlen(user) + 1; + uint16 hostlen = strlen(host) + 1; + uint16 length = 128 + userlen + hostlen; + STREAM s; + + s = sec_init(sec_flags, length + 2); + + out_uint8(s, LICENCE_TAG_REQUEST); + out_uint8(s, 2); /* version */ + out_uint16_le(s, length); + + out_uint32_le(s, 1); + out_uint16(s, 0); + out_uint16_le(s, 0xff01); + + out_uint8p(s, client_random, SEC_RANDOM_SIZE); + out_uint16(s, 0); + out_uint16_le(s, (SEC_MODULUS_SIZE + SEC_PADDING_SIZE)); + out_uint8p(s, rsa_data, SEC_MODULUS_SIZE); + out_uint8s(s, SEC_PADDING_SIZE); + + out_uint16_le(s, LICENCE_TAG_USER); + out_uint16_le(s, userlen); + out_uint8p(s, user, userlen); + + out_uint16_le(s, LICENCE_TAG_HOST); + out_uint16_le(s, hostlen); + out_uint8p(s, host, hostlen); + + s_mark_end(s); + sec_send(s, sec_flags); +} + +/* Process a licence demand packet */ +static void +licence_process_demand(STREAM s) +{ + uint8 null_data[SEC_MODULUS_SIZE]; + uint8 *server_random; + uint8 signature[LICENCE_SIGNATURE_SIZE]; + uint8 hwid[LICENCE_HWID_SIZE]; + uint8 *licence_data; + int licence_size; + void * crypt_key; + + /* Retrieve the server random from the incoming packet */ + in_uint8p(s, server_random, SEC_RANDOM_SIZE); + + /* We currently use null client keys. This is a bit naughty but, hey, + the security of licence negotiation isn't exactly paramount. */ + memset(null_data, 0, sizeof(null_data)); + licence_generate_keys(null_data, server_random, null_data); + + licence_size = load_licence(&licence_data); + if (licence_size > 0) + { + /* Generate a signature for the HWID buffer */ + licence_generate_hwid(hwid); + sec_sign(signature, 16, g_licence_sign_key, 16, hwid, sizeof(hwid)); + + /* Now encrypt the HWID */ + crypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(crypt_key, g_licence_key, 16); + ssl_rc4_crypt(crypt_key, hwid, hwid, sizeof(hwid)); + ssl_rc4_info_delete(crypt_key); + + licence_present(null_data, null_data, licence_data, licence_size, hwid, signature); + xfree(licence_data); + return; + } + + licence_send_request(null_data, null_data, g_username, g_hostname); +} + +/* Send an authentication response packet */ +static void +licence_send_authresp(uint8 * token, uint8 * crypt_hwid, uint8 * signature) +{ + uint32 sec_flags = SEC_LICENCE_NEG; + uint16 length = 58; + STREAM s; + + s = sec_init(sec_flags, length + 2); + + out_uint8(s, LICENCE_TAG_AUTHRESP); + out_uint8(s, 2); /* version */ + out_uint16_le(s, length); + + out_uint16_le(s, 1); + out_uint16_le(s, LICENCE_TOKEN_SIZE); + out_uint8p(s, token, LICENCE_TOKEN_SIZE); + + out_uint16_le(s, 1); + out_uint16_le(s, LICENCE_HWID_SIZE); + out_uint8p(s, crypt_hwid, LICENCE_HWID_SIZE); + + out_uint8p(s, signature, LICENCE_SIGNATURE_SIZE); + + s_mark_end(s); + sec_send(s, sec_flags); +} + +/* Parse an authentication request packet */ +static BOOL +licence_parse_authreq(STREAM s, uint8 ** token, uint8 ** signature) +{ + uint16 tokenlen; + + in_uint8s(s, 6); /* unknown: f8 3d 15 00 04 f6 */ + + in_uint16_le(s, tokenlen); + if (tokenlen != LICENCE_TOKEN_SIZE) + { + error("token len %d\n", tokenlen); + return False; + } + + in_uint8p(s, *token, tokenlen); + in_uint8p(s, *signature, LICENCE_SIGNATURE_SIZE); + + return s_check_end(s); +} + +/* Process an authentication request packet */ +static void +licence_process_authreq(STREAM s) +{ + uint8 *in_token, *in_sig; + uint8 out_token[LICENCE_TOKEN_SIZE], decrypt_token[LICENCE_TOKEN_SIZE]; + uint8 hwid[LICENCE_HWID_SIZE], crypt_hwid[LICENCE_HWID_SIZE]; + uint8 sealed_buffer[LICENCE_TOKEN_SIZE + LICENCE_HWID_SIZE]; + uint8 out_sig[LICENCE_SIGNATURE_SIZE]; + void * crypt_key; + + /* Parse incoming packet and save the encrypted token */ + licence_parse_authreq(s, &in_token, &in_sig); + memcpy(out_token, in_token, LICENCE_TOKEN_SIZE); + + /* Decrypt the token. It should read TEST in Unicode. */ + crypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(crypt_key, g_licence_key, 16); + ssl_rc4_crypt(crypt_key, in_token, decrypt_token, LICENCE_TOKEN_SIZE); + ssl_rc4_info_delete(crypt_key); + + /* Generate a signature for a buffer of token and HWID */ + licence_generate_hwid(hwid); + memcpy(sealed_buffer, decrypt_token, LICENCE_TOKEN_SIZE); + memcpy(sealed_buffer + LICENCE_TOKEN_SIZE, hwid, LICENCE_HWID_SIZE); + sec_sign(out_sig, 16, g_licence_sign_key, 16, sealed_buffer, sizeof(sealed_buffer)); + + /* Now encrypt the HWID */ + crypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(crypt_key, g_licence_key, 16); + ssl_rc4_crypt(crypt_key, hwid, crypt_hwid, LICENCE_HWID_SIZE); + ssl_rc4_info_delete(crypt_key); + + licence_send_authresp(out_token, crypt_hwid, out_sig); +} + +/* Process an licence issue packet */ +static void +licence_process_issue(STREAM s) +{ + void * crypt_key; + uint32 length; + uint16 check; + int i; + + in_uint8s(s, 2); /* 3d 45 - unknown */ + in_uint16_le(s, length); + if (!s_check_rem(s, length)) + return; + + crypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(crypt_key, g_licence_key, 16); + ssl_rc4_crypt(crypt_key, s->p, s->p, length); + ssl_rc4_info_delete(crypt_key); + + in_uint16(s, check); + if (check != 0) + return; + + g_licence_issued = True; + + in_uint8s(s, 2); /* pad */ + + /* advance to fourth string */ + length = 0; + for (i = 0; i < 4; i++) + { + in_uint8s(s, length); + in_uint32_le(s, length); + if (!s_check_rem(s, length)) + return; + } + + g_licence_issued = True; + save_licence(s->p, length); +} + +/* Process a licence packet */ +void +licence_process(STREAM s) +{ + uint8 tag; + + in_uint8(s, tag); + in_uint8s(s, 3); /* version, length */ + + switch (tag) + { + case LICENCE_TAG_DEMAND: + licence_process_demand(s); + break; + + case LICENCE_TAG_AUTHREQ: + licence_process_authreq(s); + break; + + case LICENCE_TAG_ISSUE: + licence_process_issue(s); + break; + + case LICENCE_TAG_REISSUE: + case LICENCE_TAG_RESULT: + break; + + default: + unimpl("licence tag 0x%x\n", tag); + } +} diff --git a/reactos/base/applications/mstsc/manifest.xml b/reactos/base/applications/mstsc/manifest.xml new file mode 100644 index 00000000000..86e87690826 --- /dev/null +++ b/reactos/base/applications/mstsc/manifest.xml @@ -0,0 +1,23 @@ + + + +ReactOS Service Manager + + + + + + diff --git a/reactos/base/applications/mstsc/mcs.c b/reactos/base/applications/mstsc/mcs.c new file mode 100644 index 00000000000..c954d5e040a --- /dev/null +++ b/reactos/base/applications/mstsc/mcs.c @@ -0,0 +1,469 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - Multipoint Communications Service + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +uint16 g_mcs_userid; +extern VCHANNEL g_channels[]; +extern unsigned int g_num_channels; + +/* Parse an ASN.1 BER header */ +static BOOL +ber_parse_header(STREAM s, int tagval, int *length) +{ + int tag, len; + + if (tagval > 0xff) + { + in_uint16_be(s, tag); + } + else + { + in_uint8(s, tag)} + + if (tag != tagval) + { + error("expected tag %d, got %d\n", tagval, tag); + return False; + } + + in_uint8(s, len); + + if (len & 0x80) + { + len &= ~0x80; + *length = 0; + while (len--) + next_be(s, *length); + } + else + *length = len; + + return s_check(s); +} + +/* Output an ASN.1 BER header */ +static void +ber_out_header(STREAM s, int tagval, int length) +{ + if (tagval > 0xff) + { + out_uint16_be(s, tagval); + } + else + { + out_uint8(s, tagval); + } + + if (length >= 0x80) + { + out_uint8(s, 0x82); + out_uint16_be(s, length); + } + else + out_uint8(s, length); +} + +/* Output an ASN.1 BER integer */ +static void +ber_out_integer(STREAM s, int value) +{ + ber_out_header(s, BER_TAG_INTEGER, 2); + out_uint16_be(s, value); +} + +/* Output a DOMAIN_PARAMS structure (ASN.1 BER) */ +static void +mcs_out_domain_params(STREAM s, int max_channels, int max_users, int max_tokens, int max_pdusize) +{ + ber_out_header(s, MCS_TAG_DOMAIN_PARAMS, 32); + ber_out_integer(s, max_channels); + ber_out_integer(s, max_users); + ber_out_integer(s, max_tokens); + ber_out_integer(s, 1); /* num_priorities */ + ber_out_integer(s, 0); /* min_throughput */ + ber_out_integer(s, 1); /* max_height */ + ber_out_integer(s, max_pdusize); + ber_out_integer(s, 2); /* ver_protocol */ +} + +/* Parse a DOMAIN_PARAMS structure (ASN.1 BER) */ +static BOOL +mcs_parse_domain_params(STREAM s) +{ + int length; + + ber_parse_header(s, MCS_TAG_DOMAIN_PARAMS, &length); + in_uint8s(s, length); + + return s_check(s); +} + +/* Send an MCS_CONNECT_INITIAL message (ASN.1 BER) */ +static void +mcs_send_connect_initial(STREAM mcs_data) +{ + int datalen = mcs_data->end - mcs_data->data; + int length = 9 + 3 * 34 + 4 + datalen; + STREAM s; + + s = iso_init(length + 5); + + ber_out_header(s, MCS_CONNECT_INITIAL, length); + ber_out_header(s, BER_TAG_OCTET_STRING, 1); /* calling domain */ + out_uint8(s, 1); + ber_out_header(s, BER_TAG_OCTET_STRING, 1); /* called domain */ + out_uint8(s, 1); + + ber_out_header(s, BER_TAG_BOOLEAN, 1); + out_uint8(s, 0xff); /* upward flag */ + + mcs_out_domain_params(s, 34, 2, 0, 0xffff); /* target params */ + mcs_out_domain_params(s, 1, 1, 1, 0x420); /* min params */ + mcs_out_domain_params(s, 0xffff, 0xfc17, 0xffff, 0xffff); /* max params */ + + ber_out_header(s, BER_TAG_OCTET_STRING, datalen); + out_uint8p(s, mcs_data->data, datalen); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a MCS_CONNECT_RESPONSE message (ASN.1 BER) */ +static BOOL +mcs_recv_connect_response(STREAM mcs_data) +{ + uint8 result; + int length; + STREAM s; + + s = iso_recv(NULL); + if (s == NULL) + return False; + + ber_parse_header(s, MCS_CONNECT_RESPONSE, &length); + + ber_parse_header(s, BER_TAG_RESULT, &length); + in_uint8(s, result); + if (result != 0) + { + error("MCS connect: %d\n", result); + return False; + } + + ber_parse_header(s, BER_TAG_INTEGER, &length); + in_uint8s(s, length); /* connect id */ + mcs_parse_domain_params(s); + + ber_parse_header(s, BER_TAG_OCTET_STRING, &length); + + sec_process_mcs_data(s); + /* + if (length > mcs_data->size) + { + error("MCS data length %d, expected %d\n", length, + mcs_data->size); + length = mcs_data->size; + } + + in_uint8a(s, mcs_data->data, length); + mcs_data->p = mcs_data->data; + mcs_data->end = mcs_data->data + length; + */ + return s_check_end(s); +} + +/* Send an EDrq message (ASN.1 PER) */ +static void +mcs_send_edrq(void) +{ + STREAM s; + + s = iso_init(5); + + out_uint8(s, (MCS_EDRQ << 2)); + out_uint16_be(s, 1); /* height */ + out_uint16_be(s, 1); /* interval */ + + s_mark_end(s); + iso_send(s); +} + +/* Send an AUrq message (ASN.1 PER) */ +static void +mcs_send_aurq(void) +{ + STREAM s; + + s = iso_init(1); + + out_uint8(s, (MCS_AURQ << 2)); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a AUcf message (ASN.1 PER) */ +static BOOL +mcs_recv_aucf(uint16 * mcs_userid) +{ + uint8 opcode, result; + STREAM s; + + s = iso_recv(NULL); + if (s == NULL) + return False; + + in_uint8(s, opcode); + if ((opcode >> 2) != MCS_AUCF) + { + error("expected AUcf, got %d\n", opcode); + return False; + } + + in_uint8(s, result); + if (result != 0) + { + error("AUrq: %d\n", result); + return False; + } + + if (opcode & 2) + in_uint16_be(s, *mcs_userid); + + return s_check_end(s); +} + +/* Send a CJrq message (ASN.1 PER) */ +static void +mcs_send_cjrq(uint16 chanid) +{ + STREAM s; + + DEBUG_RDP5(("Sending CJRQ for channel #%d\n", chanid)); + + s = iso_init(5); + + out_uint8(s, (MCS_CJRQ << 2)); + out_uint16_be(s, g_mcs_userid); + out_uint16_be(s, chanid); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a CJcf message (ASN.1 PER) */ +static BOOL +mcs_recv_cjcf(void) +{ + uint8 opcode, result; + STREAM s; + + s = iso_recv(NULL); + if (s == NULL) + return False; + + in_uint8(s, opcode); + if ((opcode >> 2) != MCS_CJCF) + { + error("expected CJcf, got %d\n", opcode); + return False; + } + + in_uint8(s, result); + if (result != 0) + { + error("CJrq: %d\n", result); + return False; + } + + in_uint8s(s, 4); /* mcs_userid, req_chanid */ + if (opcode & 2) + in_uint8s(s, 2); /* join_chanid */ + + return s_check_end(s); +} + +/* Initialise an MCS transport data packet */ +STREAM +mcs_init(int length) +{ + STREAM s; + + s = iso_init(length + 8); + s_push_layer(s, mcs_hdr, 8); + + return s; +} + +/* Send an MCS transport data packet to a specific channel */ +void +mcs_send_to_channel(STREAM s, uint16 channel) +{ + uint16 length; + + s_pop_layer(s, mcs_hdr); + length = s->end - s->p - 8; + length |= 0x8000; + + out_uint8(s, (MCS_SDRQ << 2)); + out_uint16_be(s, g_mcs_userid); + out_uint16_be(s, channel); + out_uint8(s, 0x70); /* flags */ + out_uint16_be(s, length); + + iso_send(s); +} + +/* Send an MCS transport data packet to the global channel */ +void +mcs_send(STREAM s) +{ + mcs_send_to_channel(s, MCS_GLOBAL_CHANNEL); +} + +/* Receive an MCS transport data packet */ +STREAM +mcs_recv(uint16 * channel, uint8 * rdpver) +{ + uint8 opcode, appid, length; + STREAM s; + + s = iso_recv(rdpver); + if (s == NULL) + return NULL; + if (rdpver != NULL) + if (*rdpver != 3) + return s; + in_uint8(s, opcode); + appid = opcode >> 2; + if (appid != MCS_SDIN) + { + if (appid != MCS_DPUM) + { + error("expected data, got %d\n", opcode); + } + return NULL; + } + in_uint8s(s, 2); /* userid */ + in_uint16_be(s, *channel); + in_uint8s(s, 1); /* flags */ + in_uint8(s, length); + if (length & 0x80) + in_uint8s(s, 1); /* second byte of length */ + return s; +} + +/* Establish a connection up to the MCS layer */ +BOOL +mcs_connect(char *server, STREAM mcs_data, char *username) +{ + unsigned int i; + + if (!iso_connect(server, username)) + return False; + + mcs_send_connect_initial(mcs_data); + if (!mcs_recv_connect_response(mcs_data)) + goto error; + + mcs_send_edrq(); + + mcs_send_aurq(); + if (!mcs_recv_aucf(&g_mcs_userid)) + goto error; + + mcs_send_cjrq((uint16) (g_mcs_userid + MCS_USERCHANNEL_BASE)); + + if (!mcs_recv_cjcf()) + goto error; + + mcs_send_cjrq(MCS_GLOBAL_CHANNEL); + if (!mcs_recv_cjcf()) + goto error; + + for (i = 0; i < g_num_channels; i++) + { + mcs_send_cjrq(g_channels[i].mcs_id); + if (!mcs_recv_cjcf()) + goto error; + } + return True; + + error: + iso_disconnect(); + return False; +} + +/* Establish a connection up to the MCS layer */ +BOOL +mcs_reconnect(char *server, STREAM mcs_data) +{ + unsigned int i; + + if (!iso_reconnect(server)) + return False; + + mcs_send_connect_initial(mcs_data); + if (!mcs_recv_connect_response(mcs_data)) + goto error; + + mcs_send_edrq(); + + mcs_send_aurq(); + if (!mcs_recv_aucf(&g_mcs_userid)) + goto error; + + mcs_send_cjrq((uint16) (g_mcs_userid + MCS_USERCHANNEL_BASE)); + + if (!mcs_recv_cjcf()) + goto error; + + mcs_send_cjrq(MCS_GLOBAL_CHANNEL); + if (!mcs_recv_cjcf()) + goto error; + + for (i = 0; i < g_num_channels; i++) + { + mcs_send_cjrq(g_channels[i].mcs_id); + if (!mcs_recv_cjcf()) + goto error; + } + return True; + + error: + iso_disconnect(); + return False; +} + +/* Disconnect from the MCS layer */ +void +mcs_disconnect(void) +{ + iso_disconnect(); +} + +/* reset the state of the mcs layer */ +void +mcs_reset_state(void) +{ + g_mcs_userid = 0; + iso_reset_state(); +} diff --git a/reactos/base/applications/mstsc/mppc.c b/reactos/base/applications/mstsc/mppc.c new file mode 100644 index 00000000000..cc126c1532a --- /dev/null +++ b/reactos/base/applications/mstsc/mppc.c @@ -0,0 +1,380 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - RDP decompression + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include + +#include "rdesktop.h" + +/* mppc decompression */ +/* http://www.faqs.org/rfcs/rfc2118.html */ + +/* Contacts: */ + +/* hifn contact mentioned in the faq is */ +/* Robert Friend rfriend at hifn dot com */ + +/* if you have questions regarding MPPC */ +/* our contact is */ +/* Guus Dhaeze GDhaeze at hifn dot com */ + +/* Licensing: */ + +/* decompression is alright as long as we */ +/* don't compress data */ + +/* Algorithm: */ + +/* as the rfc states the algorithm seems to */ +/* be LZ77 with a sliding buffer */ +/* that is empty at init. */ + +/* the algorithm is called LZS and is */ +/* patented for another couple of years. */ + +/* more information is available in */ +/* http://www.ietf.org/ietf/IPR/hifn-ipr-draft-friend-tls-lzs-compression.txt */ + + +RDPCOMP g_mppc_dict; + +int +mppc_expand(uint8 * data, uint32 clen, uint8 ctype, uint32 * roff, uint32 * rlen) +{ + int k, walker_len = 0, walker; + uint32 i = 0; + int next_offset, match_off; + int match_len; + int old_offset, match_bits; + BOOL big = ctype & RDP_MPPC_BIG ? True : False; + + uint8 *dict = g_mppc_dict.hist; + + if ((ctype & RDP_MPPC_COMPRESSED) == 0) + { + *roff = 0; + *rlen = clen; + return 0; + } + + if ((ctype & RDP_MPPC_RESET) != 0) + { + g_mppc_dict.roff = 0; + } + + if ((ctype & RDP_MPPC_FLUSH) != 0) + { + memset(dict, 0, RDP_MPPC_DICT_SIZE); + g_mppc_dict.roff = 0; + } + + *roff = 0; + *rlen = 0; + + walker = g_mppc_dict.roff; + + next_offset = walker; + old_offset = next_offset; + *roff = old_offset; + if (clen == 0) + return 0; + clen += i; + + do + { + if (walker_len == 0) + { + if (i >= clen) + break; + walker = data[i++] << 24; + walker_len = 8; + } + if (walker >= 0) + { + if (walker_len < 8) + { + if (i >= clen) + { + if (walker != 0) + return -1; + break; + } + walker |= (data[i++] & 0xff) << (24 - walker_len); + walker_len += 8; + } + if (next_offset >= RDP_MPPC_DICT_SIZE) + return -1; + dict[next_offset++] = (((uint32) walker) >> ((uint32) 24)); + walker <<= 8; + walker_len -= 8; + continue; + } + walker <<= 1; + /* fetch next 8-bits */ + if (--walker_len == 0) + { + if (i >= clen) + return -1; + walker = data[i++] << 24; + walker_len = 8; + } + /* literal decoding */ + if (walker >= 0) + { + if (walker_len < 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + walker_len += 8; + } + if (next_offset >= RDP_MPPC_DICT_SIZE) + return -1; + dict[next_offset++] = (uint8) (walker >> 24 | 0x80); + walker <<= 8; + walker_len -= 8; + continue; + } + + /* decode offset */ + /* length pair */ + walker <<= 1; + if (--walker_len < (big ? 3 : 2)) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + walker_len += 8; + } + + if (big) + { + /* offset decoding where offset len is: + -63: 11111 followed by the lower 6 bits of the value + 64-319: 11110 followed by the lower 8 bits of the value ( value - 64 ) + 320-2367: 1110 followed by lower 11 bits of the value ( value - 320 ) + 2368-65535: 110 followed by lower 16 bits of the value ( value - 2368 ) + */ + switch (((uint32) walker) >> ((uint32) 29)) + { + case 7: /* - 63 */ + for (; walker_len < 9; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + walker <<= 3; + match_off = ((uint32) walker) >> ((uint32) 26); + walker <<= 6; + walker_len -= 9; + break; + + case 6: /* 64 - 319 */ + for (; walker_len < 11; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + + walker <<= 3; + match_off = (((uint32) walker) >> ((uint32) 24)) + 64; + walker <<= 8; + walker_len -= 11; + break; + + case 5: + case 4: /* 320 - 2367 */ + for (; walker_len < 13; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + + walker <<= 2; + match_off = (((uint32) walker) >> ((uint32) 21)) + 320; + walker <<= 11; + walker_len -= 13; + break; + + default: /* 2368 - 65535 */ + for (; walker_len < 17; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + + walker <<= 1; + match_off = (((uint32) walker) >> ((uint32) 16)) + 2368; + walker <<= 16; + walker_len -= 17; + break; + } + } + else + { + /* offset decoding where offset len is: + -63: 1111 followed by the lower 6 bits of the value + 64-319: 1110 followed by the lower 8 bits of the value ( value - 64 ) + 320-8191: 110 followed by the lower 13 bits of the value ( value - 320 ) + */ + switch (((uint32) walker) >> ((uint32) 30)) + { + case 3: /* - 63 */ + if (walker_len < 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + walker_len += 8; + } + walker <<= 2; + match_off = ((uint32) walker) >> ((uint32) 26); + walker <<= 6; + walker_len -= 8; + break; + + case 2: /* 64 - 319 */ + for (; walker_len < 10; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + + walker <<= 2; + match_off = (((uint32) walker) >> ((uint32) 24)) + 64; + walker <<= 8; + walker_len -= 10; + break; + + default: /* 320 - 8191 */ + for (; walker_len < 14; walker_len += 8) + { + if (i >= clen) + return -1; + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + + match_off = (walker >> 18) + 320; + walker <<= 14; + walker_len -= 14; + break; + } + } + if (walker_len == 0) + { + if (i >= clen) + return -1; + walker = data[i++] << 24; + walker_len = 8; + } + + /* decode length of match */ + match_len = 0; + if (walker >= 0) + { /* special case - length of 3 is in bit 0 */ + match_len = 3; + walker <<= 1; + walker_len--; + } + else + { + /* this is how it works len of: + 4-7: 10 followed by 2 bits of the value + 8-15: 110 followed by 3 bits of the value + 16-31: 1110 followed by 4 bits of the value + 32-63: .... and so forth + 64-127: + 128-255: + 256-511: + 512-1023: + 1024-2047: + 2048-4095: + 4096-8191: + + i.e. 4097 is encoded as: 111111111110 000000000001 + meaning 4096 + 1... + */ + match_bits = big ? 14 : 11; /* 11 or 14 bits of value at most */ + do + { + walker <<= 1; + if (--walker_len == 0) + { + if (i >= clen) + return -1; + walker = data[i++] << 24; + walker_len = 8; + } + if (walker >= 0) + break; + if (--match_bits == 0) + { + return -1; + } + } + while (1); + match_len = (big ? 16 : 13) - match_bits; + walker <<= 1; + if (--walker_len < match_len) + { + for (; walker_len < match_len; walker_len += 8) + { + if (i >= clen) + { + return -1; + } + walker |= (data[i++] & 0xff) << (24 - walker_len); + } + } + + match_bits = match_len; + match_len = + ((walker >> (32 - match_bits)) & (~(-1 << match_bits))) | (1 << + match_bits); + walker <<= match_bits; + walker_len -= match_bits; + } + if (next_offset + match_len >= RDP_MPPC_DICT_SIZE) + { + return -1; + } + /* memory areas can overlap - meaning we can't use memXXX functions */ + k = (next_offset - match_off) & (big ? 65535 : 8191); + do + { + dict[next_offset++] = dict[k++]; + } + while (--match_len != 0); + } + while (1); + + /* store history offset */ + g_mppc_dict.roff = next_offset; + + *roff = old_offset; + *rlen = next_offset - old_offset; + + return 0; +} diff --git a/reactos/base/applications/mstsc/mstsc.rbuild b/reactos/base/applications/mstsc/mstsc.rbuild new file mode 100644 index 00000000000..c32200f9944 --- /dev/null +++ b/reactos/base/applications/mstsc/mstsc.rbuild @@ -0,0 +1,33 @@ + + + + . + 0x600 + 0x501 + kernel32 + user32 + gdi32 + comctl32 + ws2_32 + + bitmap.c + bsops.c + cache.c + channels.c + connectdialog.c + iso.c + licence.c + mcs.c + mppc.c + orders.c + pstcache.c + rdp5.c + rdp.c + secure.c + ssl_calls.c + tcp.c + uimain.c + win32.c + + rdc.rc + diff --git a/reactos/base/applications/mstsc/mstsc_vc8_auto.sln b/reactos/base/applications/mstsc/mstsc_vc8_auto.sln new file mode 100644 index 00000000000..40323489b86 --- /dev/null +++ b/reactos/base/applications/mstsc/mstsc_vc8_auto.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mstsc", "mstsc_vc8_auto.vcproj", "{B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug - ReactOS headers|Win32 = Debug - ReactOS headers|Win32 + Debug|Win32 = Debug|Win32 + Release - ReactOS headers|Win32 = Release - ReactOS headers|Win32 + Release|Win32 = Release|Win32 + Speed - ReactOS headers|Win32 = Speed - ReactOS headers|Win32 + Speed|Win32 = Speed|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Debug - ReactOS headers|Win32.ActiveCfg = Debug - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Debug - ReactOS headers|Win32.Build.0 = Debug - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Debug|Win32.ActiveCfg = Debug|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Debug|Win32.Build.0 = Debug|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Release - ReactOS headers|Win32.ActiveCfg = Release - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Release - ReactOS headers|Win32.Build.0 = Release - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Release|Win32.ActiveCfg = Release|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Release|Win32.Build.0 = Release|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Speed - ReactOS headers|Win32.ActiveCfg = Speed - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Speed - ReactOS headers|Win32.Build.0 = Speed - ReactOS headers|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Speed|Win32.ActiveCfg = Speed|Win32 + {B6EF8F33-E4EA-495D-9E94-9A6ADBC235ED}.Speed|Win32.Build.0 = Speed|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/reactos/base/applications/mstsc/mstsc_vc8_auto.vcproj b/reactos/base/applications/mstsc/mstsc_vc8_auto.vcproj new file mode 100644 index 00000000000..79e03c92b5c --- /dev/null +++ b/reactos/base/applications/mstsc/mstsc_vc8_auto.vcproj @@ -0,0 +1,645 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/reactos/base/applications/mstsc/orders.c b/reactos/base/applications/mstsc/orders.c new file mode 100644 index 00000000000..9a83a99a0a6 --- /dev/null +++ b/reactos/base/applications/mstsc/orders.c @@ -0,0 +1,1307 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + RDP order processing + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" +#include "orders.h" + +extern uint8 *g_next_packet; +static RDP_ORDER_STATE g_order_state; +extern BOOL g_use_rdp5; + +/* Read field indicating which parameters are present */ +static void +rdp_in_present(STREAM s, uint32 * present, uint8 flags, int size) +{ + uint8 bits; + int i; + + if (flags & RDP_ORDER_SMALL) + { + size--; + } + + if (flags & RDP_ORDER_TINY) + { + if (size < 2) + size = 0; + else + size -= 2; + } + + *present = 0; + for (i = 0; i < size; i++) + { + in_uint8(s, bits); + *present |= bits << (i * 8); + } +} + +/* Read a co-ordinate (16-bit, or 8-bit delta) */ +static void +rdp_in_coord(STREAM s, sint16 * coord, BOOL delta) +{ + sint8 change; + + if (delta) + { + in_uint8(s, change); + *coord += change; + } + else + { + in_uint16_le(s, *coord); + } +} + +/* Parse a delta co-ordinate in polyline/polygon order form */ +static int +parse_delta(uint8 * buffer, int *offset) +{ + int value = buffer[(*offset)++]; + int two_byte = value & 0x80; + + if (value & 0x40) /* sign bit */ + value |= ~0x3f; + else + value &= 0x3f; + + if (two_byte) + value = (value << 8) | buffer[(*offset)++]; + + return value; +} + +/* Read a colour entry */ +static void +rdp_in_colour(STREAM s, uint32 * colour) +{ + uint32 i; + in_uint8(s, i); + *colour = i; + in_uint8(s, i); + *colour |= i << 8; + in_uint8(s, i); + *colour |= i << 16; +} + +/* Parse bounds information */ +static BOOL +rdp_parse_bounds(STREAM s, BOUNDS * bounds) +{ + uint8 present; + + in_uint8(s, present); + + if (present & 1) + rdp_in_coord(s, &bounds->left, False); + else if (present & 16) + rdp_in_coord(s, &bounds->left, True); + + if (present & 2) + rdp_in_coord(s, &bounds->top, False); + else if (present & 32) + rdp_in_coord(s, &bounds->top, True); + + if (present & 4) + rdp_in_coord(s, &bounds->right, False); + else if (present & 64) + rdp_in_coord(s, &bounds->right, True); + + if (present & 8) + rdp_in_coord(s, &bounds->bottom, False); + else if (present & 128) + rdp_in_coord(s, &bounds->bottom, True); + + return s_check(s); +} + +/* Parse a pen */ +static BOOL +rdp_parse_pen(STREAM s, PEN * pen, uint32 present) +{ + if (present & 1) + in_uint8(s, pen->style); + + if (present & 2) + in_uint8(s, pen->width); + + if (present & 4) + rdp_in_colour(s, &pen->colour); + + return s_check(s); +} + +/* Parse a brush */ +static BOOL +rdp_parse_brush(STREAM s, BRUSH * brush, uint32 present) +{ + if (present & 1) + in_uint8(s, brush->xorigin); + + if (present & 2) + in_uint8(s, brush->yorigin); + + if (present & 4) + in_uint8(s, brush->style); + + if (present & 8) + in_uint8(s, brush->pattern[0]); + + if (present & 16) + in_uint8a(s, &brush->pattern[1], 7); + + return s_check(s); +} + +/* Process a destination blt order */ +static void +process_destblt(STREAM s, DESTBLT_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x01) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x02) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x04) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x08) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x10) + in_uint8(s, os->opcode); + + DEBUG(("DESTBLT(op=0x%x,x=%d,y=%d,cx=%d,cy=%d)\n", + os->opcode, os->x, os->y, os->cx, os->cy)); + + ui_destblt(ROP2_S(os->opcode), os->x, os->y, os->cx, os->cy); +} + +/* Process a pattern blt order */ +static void +process_patblt(STREAM s, PATBLT_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x0001) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x0002) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x0004) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x0008) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x0010) + in_uint8(s, os->opcode); + + if (present & 0x0020) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x0040) + rdp_in_colour(s, &os->fgcolour); + + rdp_parse_brush(s, &os->brush, present >> 7); + + DEBUG(("PATBLT(op=0x%x,x=%d,y=%d,cx=%d,cy=%d,bs=%d,bg=0x%x,fg=0x%x)\n", os->opcode, os->x, + os->y, os->cx, os->cy, os->brush.style, os->bgcolour, os->fgcolour)); + + ui_patblt(ROP2_P(os->opcode), os->x, os->y, os->cx, os->cy, + &os->brush, os->bgcolour, os->fgcolour); +} + +/* Process a screen blt order */ +static void +process_screenblt(STREAM s, SCREENBLT_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x0001) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x0002) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x0004) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x0008) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x0010) + in_uint8(s, os->opcode); + + if (present & 0x0020) + rdp_in_coord(s, &os->srcx, delta); + + if (present & 0x0040) + rdp_in_coord(s, &os->srcy, delta); + + DEBUG(("SCREENBLT(op=0x%x,x=%d,y=%d,cx=%d,cy=%d,srcx=%d,srcy=%d)\n", + os->opcode, os->x, os->y, os->cx, os->cy, os->srcx, os->srcy)); + + ui_screenblt(ROP2_S(os->opcode), os->x, os->y, os->cx, os->cy, os->srcx, os->srcy); +} + +/* Process a line order */ +static void +process_line(STREAM s, LINE_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x0001) + in_uint16_le(s, os->mixmode); + + if (present & 0x0002) + rdp_in_coord(s, &os->startx, delta); + + if (present & 0x0004) + rdp_in_coord(s, &os->starty, delta); + + if (present & 0x0008) + rdp_in_coord(s, &os->endx, delta); + + if (present & 0x0010) + rdp_in_coord(s, &os->endy, delta); + + if (present & 0x0020) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x0040) + in_uint8(s, os->opcode); + + rdp_parse_pen(s, &os->pen, present >> 7); + + DEBUG(("LINE(op=0x%x,sx=%d,sy=%d,dx=%d,dy=%d,fg=0x%x)\n", + os->opcode, os->startx, os->starty, os->endx, os->endy, os->pen.colour)); + + if (os->opcode < 0x01 || os->opcode > 0x10) + { + error("bad ROP2 0x%x\n", os->opcode); + return; + } + + ui_line(ROP_MINUS_1(os->opcode), os->startx, os->starty, os->endx, os->endy, &os->pen); +} + +/* Process an opaque rectangle order */ +static void +process_rect(STREAM s, RECT_ORDER * os, uint32 present, BOOL delta) +{ + uint32 i; + if (present & 0x01) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x02) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x04) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x08) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x10) + { + in_uint8(s, i); + os->colour = (os->colour & 0xffffff00) | i; + } + + if (present & 0x20) + { + in_uint8(s, i); + os->colour = (os->colour & 0xffff00ff) | (i << 8); + } + + if (present & 0x40) + { + in_uint8(s, i); + os->colour = (os->colour & 0xff00ffff) | (i << 16); + } + + DEBUG(("RECT(x=%d,y=%d,cx=%d,cy=%d,fg=0x%x)\n", os->x, os->y, os->cx, os->cy, os->colour)); + + ui_rect(os->x, os->y, os->cx, os->cy, os->colour); +} + +/* Process a desktop save order */ +static void +process_desksave(STREAM s, DESKSAVE_ORDER * os, uint32 present, BOOL delta) +{ + int width, height; + + if (present & 0x01) + in_uint32_le(s, os->offset); + + if (present & 0x02) + rdp_in_coord(s, &os->left, delta); + + if (present & 0x04) + rdp_in_coord(s, &os->top, delta); + + if (present & 0x08) + rdp_in_coord(s, &os->right, delta); + + if (present & 0x10) + rdp_in_coord(s, &os->bottom, delta); + + if (present & 0x20) + in_uint8(s, os->action); + + DEBUG(("DESKSAVE(l=%d,t=%d,r=%d,b=%d,off=%d,op=%d)\n", + os->left, os->top, os->right, os->bottom, os->offset, os->action)); + + width = os->right - os->left + 1; + height = os->bottom - os->top + 1; + + if (os->action == 0) + ui_desktop_save(os->offset, os->left, os->top, width, height); + else + ui_desktop_restore(os->offset, os->left, os->top, width, height); +} + +/* Process a memory blt order */ +static void +process_memblt(STREAM s, MEMBLT_ORDER * os, uint32 present, BOOL delta) +{ + HBITMAP bitmap; + + if (present & 0x0001) + { + in_uint8(s, os->cache_id); + in_uint8(s, os->colour_table); + } + + if (present & 0x0002) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x0004) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x0008) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x0010) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x0020) + in_uint8(s, os->opcode); + + if (present & 0x0040) + rdp_in_coord(s, &os->srcx, delta); + + if (present & 0x0080) + rdp_in_coord(s, &os->srcy, delta); + + if (present & 0x0100) + in_uint16_le(s, os->cache_idx); + + DEBUG(("MEMBLT(op=0x%x,x=%d,y=%d,cx=%d,cy=%d,id=%d,idx=%d)\n", + os->opcode, os->x, os->y, os->cx, os->cy, os->cache_id, os->cache_idx)); + + bitmap = cache_get_bitmap(os->cache_id, os->cache_idx); + if (bitmap == NULL) + return; + + ui_memblt(ROP2_S(os->opcode), os->x, os->y, os->cx, os->cy, bitmap, os->srcx, os->srcy); +} + +/* Process a 3-way blt order */ +static void +process_triblt(STREAM s, TRIBLT_ORDER * os, uint32 present, BOOL delta) +{ + HBITMAP bitmap; + + if (present & 0x000001) + { + in_uint8(s, os->cache_id); + in_uint8(s, os->colour_table); + } + + if (present & 0x000002) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x000004) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x000008) + rdp_in_coord(s, &os->cx, delta); + + if (present & 0x000010) + rdp_in_coord(s, &os->cy, delta); + + if (present & 0x000020) + in_uint8(s, os->opcode); + + if (present & 0x000040) + rdp_in_coord(s, &os->srcx, delta); + + if (present & 0x000080) + rdp_in_coord(s, &os->srcy, delta); + + if (present & 0x000100) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x000200) + rdp_in_colour(s, &os->fgcolour); + + rdp_parse_brush(s, &os->brush, present >> 10); + + if (present & 0x008000) + in_uint16_le(s, os->cache_idx); + + if (present & 0x010000) + in_uint16_le(s, os->unknown); + + DEBUG(("TRIBLT(op=0x%x,x=%d,y=%d,cx=%d,cy=%d,id=%d,idx=%d,bs=%d,bg=0x%x,fg=0x%x)\n", + os->opcode, os->x, os->y, os->cx, os->cy, os->cache_id, os->cache_idx, + os->brush.style, os->bgcolour, os->fgcolour)); + + bitmap = cache_get_bitmap(os->cache_id, os->cache_idx); + if (bitmap == NULL) + return; + + ui_triblt(os->opcode, os->x, os->y, os->cx, os->cy, + bitmap, os->srcx, os->srcy, &os->brush, os->bgcolour, os->fgcolour); +} + +/* Process a polygon order */ +static void +process_polygon(STREAM s, POLYGON_ORDER * os, uint32 present, BOOL delta) +{ + int index, data, next; + uint8 flags = 0; + POINT *points; + + if (present & 0x01) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x02) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x04) + in_uint8(s, os->opcode); + + if (present & 0x08) + in_uint8(s, os->fillmode); + + if (present & 0x10) + rdp_in_colour(s, &os->fgcolour); + + if (present & 0x20) + in_uint8(s, os->npoints); + + if (present & 0x40) + { + in_uint8(s, os->datasize); + in_uint8a(s, os->data, os->datasize); + } + + DEBUG(("POLYGON(x=%d,y=%d,op=0x%x,fm=%d,fg=0x%x,n=%d,sz=%d)\n", + os->x, os->y, os->opcode, os->fillmode, os->fgcolour, os->npoints, os->datasize)); + + DEBUG(("Data: ")); + + for (index = 0; index < os->datasize; index++) + DEBUG(("%02x ", os->data[index])); + + DEBUG(("\n")); + + if (os->opcode < 0x01 || os->opcode > 0x10) + { + error("bad ROP2 0x%x\n", os->opcode); + return; + } + + points = (POINT *) xmalloc((os->npoints + 1) * sizeof(POINT)); + memset(points, 0, (os->npoints + 1) * sizeof(POINT)); + + points[0].x = os->x; + points[0].y = os->y; + + index = 0; + data = ((os->npoints - 1) / 4) + 1; + for (next = 1; (next <= os->npoints) && (next < 256) && (data < os->datasize); next++) + { + if ((next - 1) % 4 == 0) + flags = os->data[index++]; + + if (~flags & 0x80) + points[next].x = parse_delta(os->data, &data); + + if (~flags & 0x40) + points[next].y = parse_delta(os->data, &data); + + flags <<= 2; + } + + if (next - 1 == os->npoints) + ui_polygon(ROP_MINUS_1(os->opcode), os->fillmode, points, os->npoints + 1, NULL, 0, + os->fgcolour); + else + error("polygon parse error\n"); + + xfree(points); +} + +/* Process a polygon2 order */ +static void +process_polygon2(STREAM s, POLYGON2_ORDER * os, uint32 present, BOOL delta) +{ + int index, data, next; + uint8 flags = 0; + POINT *points; + + if (present & 0x0001) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x0002) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x0004) + in_uint8(s, os->opcode); + + if (present & 0x0008) + in_uint8(s, os->fillmode); + + if (present & 0x0010) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x0020) + rdp_in_colour(s, &os->fgcolour); + + rdp_parse_brush(s, &os->brush, present >> 6); + + if (present & 0x0800) + in_uint8(s, os->npoints); + + if (present & 0x1000) + { + in_uint8(s, os->datasize); + in_uint8a(s, os->data, os->datasize); + } + + DEBUG(("POLYGON2(x=%d,y=%d,op=0x%x,fm=%d,bs=%d,bg=0x%x,fg=0x%x,n=%d,sz=%d)\n", + os->x, os->y, os->opcode, os->fillmode, os->brush.style, os->bgcolour, os->fgcolour, + os->npoints, os->datasize)); + + DEBUG(("Data: ")); + + for (index = 0; index < os->datasize; index++) + DEBUG(("%02x ", os->data[index])); + + DEBUG(("\n")); + + if (os->opcode < 0x01 || os->opcode > 0x10) + { + error("bad ROP2 0x%x\n", os->opcode); + return; + } + + points = (POINT *) xmalloc((os->npoints + 1) * sizeof(POINT)); + memset(points, 0, (os->npoints + 1) * sizeof(POINT)); + + points[0].x = os->x; + points[0].y = os->y; + + index = 0; + data = ((os->npoints - 1) / 4) + 1; + for (next = 1; (next <= os->npoints) && (next < 256) && (data < os->datasize); next++) + { + if ((next - 1) % 4 == 0) + flags = os->data[index++]; + + if (~flags & 0x80) + points[next].x = parse_delta(os->data, &data); + + if (~flags & 0x40) + points[next].y = parse_delta(os->data, &data); + + flags <<= 2; + } + + if (next - 1 == os->npoints) + ui_polygon(ROP_MINUS_1(os->opcode), os->fillmode, points, os->npoints + 1, + &os->brush, os->bgcolour, os->fgcolour); + else + error("polygon2 parse error\n"); + + xfree(points); +} + +/* Process a polyline order */ +static void +process_polyline(STREAM s, POLYLINE_ORDER * os, uint32 present, BOOL delta) +{ + int index, next, data; + uint8 flags = 0; + PEN pen; + POINT *points; + + if (present & 0x01) + rdp_in_coord(s, &os->x, delta); + + if (present & 0x02) + rdp_in_coord(s, &os->y, delta); + + if (present & 0x04) + in_uint8(s, os->opcode); + + if (present & 0x10) + rdp_in_colour(s, &os->fgcolour); + + if (present & 0x20) + in_uint8(s, os->lines); + + if (present & 0x40) + { + in_uint8(s, os->datasize); + in_uint8a(s, os->data, os->datasize); + } + + DEBUG(("POLYLINE(x=%d,y=%d,op=0x%x,fg=0x%x,n=%d,sz=%d)\n", + os->x, os->y, os->opcode, os->fgcolour, os->lines, os->datasize)); + + DEBUG(("Data: ")); + + for (index = 0; index < os->datasize; index++) + DEBUG(("%02x ", os->data[index])); + + DEBUG(("\n")); + + if (os->opcode < 0x01 || os->opcode > 0x10) + { + error("bad ROP2 0x%x\n", os->opcode); + return; + } + + points = (POINT *) xmalloc((os->lines + 1) * sizeof(POINT)); + memset(points, 0, (os->lines + 1) * sizeof(POINT)); + + points[0].x = os->x; + points[0].y = os->y; + pen.style = pen.width = 0; + pen.colour = os->fgcolour; + + index = 0; + data = ((os->lines - 1) / 4) + 1; + for (next = 1; (next <= os->lines) && (data < os->datasize); next++) + { + if ((next - 1) % 4 == 0) + flags = os->data[index++]; + + if (~flags & 0x80) + points[next].x = parse_delta(os->data, &data); + + if (~flags & 0x40) + points[next].y = parse_delta(os->data, &data); + + flags <<= 2; + } + + if (next - 1 == os->lines) + ui_polyline(ROP_MINUS_1(os->opcode), points, os->lines + 1, &pen); + else + error("polyline parse error\n"); + + xfree(points); +} + +/* Process an ellipse order */ +static void +process_ellipse(STREAM s, ELLIPSE_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x01) + rdp_in_coord(s, &os->left, delta); + + if (present & 0x02) + rdp_in_coord(s, &os->top, delta); + + if (present & 0x04) + rdp_in_coord(s, &os->right, delta); + + if (present & 0x08) + rdp_in_coord(s, &os->bottom, delta); + + if (present & 0x10) + in_uint8(s, os->opcode); + + if (present & 0x20) + in_uint8(s, os->fillmode); + + if (present & 0x40) + rdp_in_colour(s, &os->fgcolour); + + DEBUG(("ELLIPSE(l=%d,t=%d,r=%d,b=%d,op=0x%x,fm=%d,fg=0x%x)\n", os->left, os->top, + os->right, os->bottom, os->opcode, os->fillmode, os->fgcolour)); + + ui_ellipse(ROP_MINUS_1(os->opcode), os->fillmode, os->left, os->top, os->right - os->left, + os->bottom - os->top, NULL, 0, os->fgcolour); +} + +/* Process an ellipse2 order */ +static void +process_ellipse2(STREAM s, ELLIPSE2_ORDER * os, uint32 present, BOOL delta) +{ + if (present & 0x0001) + rdp_in_coord(s, &os->left, delta); + + if (present & 0x0002) + rdp_in_coord(s, &os->top, delta); + + if (present & 0x0004) + rdp_in_coord(s, &os->right, delta); + + if (present & 0x0008) + rdp_in_coord(s, &os->bottom, delta); + + if (present & 0x0010) + in_uint8(s, os->opcode); + + if (present & 0x0020) + in_uint8(s, os->fillmode); + + if (present & 0x0040) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x0080) + rdp_in_colour(s, &os->fgcolour); + + rdp_parse_brush(s, &os->brush, present >> 8); + + DEBUG(("ELLIPSE2(l=%d,t=%d,r=%d,b=%d,op=0x%x,fm=%d,bs=%d,bg=0x%x,fg=0x%x)\n", + os->left, os->top, os->right, os->bottom, os->opcode, os->fillmode, os->brush.style, + os->bgcolour, os->fgcolour)); + + ui_ellipse(ROP_MINUS_1(os->opcode), os->fillmode, os->left, os->top, os->right - os->left, + os->bottom - os->top, &os->brush, os->bgcolour, os->fgcolour); +} + +/* Process a text order */ +static void +process_text2(STREAM s, TEXT2_ORDER * os, uint32 present, BOOL delta) +{ + int i; + + if (present & 0x000001) + in_uint8(s, os->font); + + if (present & 0x000002) + in_uint8(s, os->flags); + + if (present & 0x000004) + in_uint8(s, os->opcode); + + if (present & 0x000008) + in_uint8(s, os->mixmode); + + if (present & 0x000010) + rdp_in_colour(s, &os->fgcolour); + + if (present & 0x000020) + rdp_in_colour(s, &os->bgcolour); + + if (present & 0x000040) + in_uint16_le(s, os->clipleft); + + if (present & 0x000080) + in_uint16_le(s, os->cliptop); + + if (present & 0x000100) + in_uint16_le(s, os->clipright); + + if (present & 0x000200) + in_uint16_le(s, os->clipbottom); + + if (present & 0x000400) + in_uint16_le(s, os->boxleft); + + if (present & 0x000800) + in_uint16_le(s, os->boxtop); + + if (present & 0x001000) + in_uint16_le(s, os->boxright); + + if (present & 0x002000) + in_uint16_le(s, os->boxbottom); + + rdp_parse_brush(s, &os->brush, present >> 14); + + if (present & 0x080000) + in_uint16_le(s, os->x); + + if (present & 0x100000) + in_uint16_le(s, os->y); + + if (present & 0x200000) + { + in_uint8(s, os->length); + in_uint8a(s, os->text, os->length); + } + + DEBUG(("TEXT2(x=%d,y=%d,cl=%d,ct=%d,cr=%d,cb=%d,bl=%d,bt=%d,br=%d,bb=%d,bs=%d,bg=0x%x,fg=0x%x,font=%d,fl=0x%x,op=0x%x,mix=%d,n=%d)\n", os->x, os->y, os->clipleft, os->cliptop, os->clipright, os->clipbottom, os->boxleft, os->boxtop, os->boxright, os->boxbottom, os->brush.style, os->bgcolour, os->fgcolour, os->font, os->flags, os->opcode, os->mixmode, os->length)); + + DEBUG(("Text: ")); + + for (i = 0; i < os->length; i++) + DEBUG(("%02x ", os->text[i])); + + DEBUG(("\n")); + + ui_draw_text(os->font, os->flags, ROP_MINUS_1(os->opcode), os->mixmode, os->x, os->y, + os->clipleft, os->cliptop, os->clipright - os->clipleft, + os->clipbottom - os->cliptop, os->boxleft, os->boxtop, + os->boxright - os->boxleft, os->boxbottom - os->boxtop, + &os->brush, os->bgcolour, os->fgcolour, os->text, os->length); +} + +/* Process a raw bitmap cache order */ +static void +process_raw_bmpcache(STREAM s) +{ + HBITMAP bitmap; + uint16 cache_idx, bufsize; + uint8 cache_id, width, height, bpp, Bpp; + uint8 *data, *inverted; + int y; + + in_uint8(s, cache_id); + in_uint8s(s, 1); /* pad */ + in_uint8(s, width); + in_uint8(s, height); + in_uint8(s, bpp); + Bpp = (bpp + 7) / 8; + in_uint16_le(s, bufsize); + in_uint16_le(s, cache_idx); + in_uint8p(s, data, bufsize); + + DEBUG(("RAW_BMPCACHE(cx=%d,cy=%d,id=%d,idx=%d)\n", width, height, cache_id, cache_idx)); + inverted = (uint8 *) xmalloc(width * height * Bpp); + for (y = 0; y < height; y++) + { + memcpy(&inverted[(height - y - 1) * (width * Bpp)], &data[y * (width * Bpp)], + width * Bpp); + } + + bitmap = ui_create_bitmap(width, height, inverted); + xfree(inverted); + cache_put_bitmap(cache_id, cache_idx, bitmap); +} + +/* Process a bitmap cache order */ +static void +process_bmpcache(STREAM s) +{ + HBITMAP bitmap; + uint16 cache_idx, size; + uint8 cache_id, width, height, bpp, Bpp; + uint8 *data, *bmpdata; + uint16 bufsize, pad2, row_size, final_size; + uint8 pad1; + + pad2 = row_size = final_size = 0xffff; /* Shut the compiler up */ + + in_uint8(s, cache_id); + in_uint8(s, pad1); /* pad */ + in_uint8(s, width); + in_uint8(s, height); + in_uint8(s, bpp); + Bpp = (bpp + 7) / 8; + in_uint16_le(s, bufsize); /* bufsize */ + in_uint16_le(s, cache_idx); + + if (g_use_rdp5) + { + size = bufsize; + } + else + { + + /* Begin compressedBitmapData */ + in_uint16_le(s, pad2); /* pad */ + in_uint16_le(s, size); + /* in_uint8s(s, 4); *//* row_size, final_size */ + in_uint16_le(s, row_size); + in_uint16_le(s, final_size); + + } + in_uint8p(s, data, size); + + DEBUG(("BMPCACHE(cx=%d,cy=%d,id=%d,idx=%d,bpp=%d,size=%d,pad1=%d,bufsize=%d,pad2=%d,rs=%d,fs=%d)\n", width, height, cache_id, cache_idx, bpp, size, pad1, bufsize, pad2, row_size, final_size)); + + bmpdata = (uint8 *) xmalloc(width * height * Bpp); + + if (bitmap_decompress(bmpdata, width, height, data, size, Bpp)) + { + bitmap = ui_create_bitmap(width, height, bmpdata); + cache_put_bitmap(cache_id, cache_idx, bitmap); + } + else + { + DEBUG(("Failed to decompress bitmap data\n")); + } + + xfree(bmpdata); +} + +/* Process a bitmap cache v2 order */ +static void +process_bmpcache2(STREAM s, uint16 flags, BOOL compressed) +{ + HBITMAP bitmap; + int y; + uint8 cache_id, cache_idx_low, width, height, Bpp; + uint16 cache_idx, bufsize; + uint8 *data, *bmpdata, *bitmap_id; + + bitmap_id = NULL; /* prevent compiler warning */ + cache_id = flags & ID_MASK; + Bpp = ((flags & MODE_MASK) >> MODE_SHIFT) - 2; + + if (flags & PERSIST) + { + in_uint8p(s, bitmap_id, 8); + } + + if (flags & SQUARE) + { + in_uint8(s, width); + height = width; + } + else + { + in_uint8(s, width); + in_uint8(s, height); + } + + in_uint16_be(s, bufsize); + bufsize &= BUFSIZE_MASK; + in_uint8(s, cache_idx); + + if (cache_idx & LONG_FORMAT) + { + in_uint8(s, cache_idx_low); + cache_idx = ((cache_idx ^ LONG_FORMAT) << 8) + cache_idx_low; + } + + in_uint8p(s, data, bufsize); + + DEBUG(("BMPCACHE2(compr=%d,flags=%x,cx=%d,cy=%d,id=%d,idx=%d,Bpp=%d,bs=%d)\n", + compressed, flags, width, height, cache_id, cache_idx, Bpp, bufsize)); + + bmpdata = (uint8 *) xmalloc(width * height * Bpp); + + if (compressed) + { + if (!bitmap_decompress(bmpdata, width, height, data, bufsize, Bpp)) + { + DEBUG(("Failed to decompress bitmap data\n")); + xfree(bmpdata); + return; + } + } + else + { + for (y = 0; y < height; y++) + memcpy(&bmpdata[(height - y - 1) * (width * Bpp)], + &data[y * (width * Bpp)], width * Bpp); + } + + bitmap = ui_create_bitmap(width, height, bmpdata); + + if (bitmap) + { + cache_put_bitmap(cache_id, cache_idx, bitmap); + if (flags & PERSIST) + pstcache_save_bitmap(cache_id, cache_idx, bitmap_id, width, height, + (uint16) (width * height * Bpp), bmpdata); + } + else + { + DEBUG(("process_bmpcache2: ui_create_bitmap failed\n")); + } + + xfree(bmpdata); +} + +/* Process a colourmap cache order */ +static void +process_colcache(STREAM s) +{ + COLOURENTRY *entry; + COLOURMAP map; + RD_HCOLOURMAP hmap; + uint8 cache_id; + int i; + + in_uint8(s, cache_id); + in_uint16_le(s, map.ncolours); + + map.colours = (COLOURENTRY *) xmalloc(sizeof(COLOURENTRY) * map.ncolours); + + for (i = 0; i < map.ncolours; i++) + { + entry = &map.colours[i]; + in_uint8(s, entry->blue); + in_uint8(s, entry->green); + in_uint8(s, entry->red); + in_uint8s(s, 1); /* pad */ + } + + DEBUG(("COLCACHE(id=%d,n=%d)\n", cache_id, map.ncolours)); + + hmap = ui_create_colourmap(&map); + + if (cache_id) + ui_set_colourmap(hmap); + + xfree(map.colours); +} + +/* Process a font cache order */ +static void +process_fontcache(STREAM s) +{ + RD_HGLYPH bitmap; + uint8 font, nglyphs; + uint16 character, offset, baseline, width, height; + int i, datasize; + uint8 *data; + + in_uint8(s, font); + in_uint8(s, nglyphs); + + DEBUG(("FONTCACHE(font=%d,n=%d)\n", font, nglyphs)); + + for (i = 0; i < nglyphs; i++) + { + in_uint16_le(s, character); + in_uint16_le(s, offset); + in_uint16_le(s, baseline); + in_uint16_le(s, width); + in_uint16_le(s, height); + + datasize = (height * ((width + 7) / 8) + 3) & ~3; + in_uint8p(s, data, datasize); + + bitmap = ui_create_glyph(width, height, data); + cache_put_font(font, character, offset, baseline, width, height, bitmap); + } +} + +/* Process a secondary order */ +static void +process_secondary_order(STREAM s) +{ + /* The length isn't calculated correctly by the server. + * For very compact orders the length becomes negative + * so a signed integer must be used. */ + uint16 length; + uint16 flags; + uint8 type; + uint8 *next_order; + + in_uint16_le(s, length); + in_uint16_le(s, flags); /* used by bmpcache2 */ + in_uint8(s, type); + + next_order = s->p + (sint16) length + 7; + + switch (type) + { + case RDP_ORDER_RAW_BMPCACHE: + process_raw_bmpcache(s); + break; + + case RDP_ORDER_COLCACHE: + process_colcache(s); + break; + + case RDP_ORDER_BMPCACHE: + process_bmpcache(s); + break; + + case RDP_ORDER_FONTCACHE: + process_fontcache(s); + break; + + case RDP_ORDER_RAW_BMPCACHE2: + process_bmpcache2(s, flags, False); /* uncompressed */ + break; + + case RDP_ORDER_BMPCACHE2: + process_bmpcache2(s, flags, True); /* compressed */ + break; + + default: + unimpl("secondary order %d\n", type); + } + + s->p = next_order; +} + +/* Process an order PDU */ +void +process_orders(STREAM s, uint16 num_orders) +{ + RDP_ORDER_STATE *os = &g_order_state; + uint32 present; + uint8 order_flags; + int size, processed = 0; + BOOL delta; + + while (processed < num_orders) + { + in_uint8(s, order_flags); + + if (!(order_flags & RDP_ORDER_STANDARD)) + { + error("order parsing failed\n"); + break; + } + + if (order_flags & RDP_ORDER_SECONDARY) + { + process_secondary_order(s); + } + else + { + if (order_flags & RDP_ORDER_CHANGE) + { + in_uint8(s, os->order_type); + } + + switch (os->order_type) + { + case RDP_ORDER_TRIBLT: + case RDP_ORDER_TEXT2: + size = 3; + break; + + case RDP_ORDER_PATBLT: + case RDP_ORDER_MEMBLT: + case RDP_ORDER_LINE: + case RDP_ORDER_POLYGON2: + case RDP_ORDER_ELLIPSE2: + size = 2; + break; + + default: + size = 1; + } + + rdp_in_present(s, &present, order_flags, size); + + if (order_flags & RDP_ORDER_BOUNDS) + { + if (!(order_flags & RDP_ORDER_LASTBOUNDS)) + rdp_parse_bounds(s, &os->bounds); + + ui_set_clip(os->bounds.left, + os->bounds.top, + os->bounds.right - + os->bounds.left + 1, + os->bounds.bottom - os->bounds.top + 1); + } + + delta = order_flags & RDP_ORDER_DELTA; + + switch (os->order_type) + { + case RDP_ORDER_DESTBLT: + process_destblt(s, &os->destblt, present, delta); + break; + + case RDP_ORDER_PATBLT: + process_patblt(s, &os->patblt, present, delta); + break; + + case RDP_ORDER_SCREENBLT: + process_screenblt(s, &os->screenblt, present, delta); + break; + + case RDP_ORDER_LINE: + process_line(s, &os->line, present, delta); + break; + + case RDP_ORDER_RECT: + process_rect(s, &os->rect, present, delta); + break; + + case RDP_ORDER_DESKSAVE: + process_desksave(s, &os->desksave, present, delta); + break; + + case RDP_ORDER_MEMBLT: + process_memblt(s, &os->memblt, present, delta); + break; + + case RDP_ORDER_TRIBLT: + process_triblt(s, &os->triblt, present, delta); + break; + + case RDP_ORDER_POLYGON: + process_polygon(s, &os->polygon, present, delta); + break; + + case RDP_ORDER_POLYGON2: + process_polygon2(s, &os->polygon2, present, delta); + break; + + case RDP_ORDER_POLYLINE: + process_polyline(s, &os->polyline, present, delta); + break; + + case RDP_ORDER_ELLIPSE: + process_ellipse(s, &os->ellipse, present, delta); + break; + + case RDP_ORDER_ELLIPSE2: + process_ellipse2(s, &os->ellipse2, present, delta); + break; + + case RDP_ORDER_TEXT2: + process_text2(s, &os->text2, present, delta); + break; + + default: + unimpl("order %d\n", os->order_type); + return; + } + + if (order_flags & RDP_ORDER_BOUNDS) + ui_reset_clip(); + } + + processed++; + } +#if 0 + /* not true when RDP_COMPRESSION is set */ + if (s->p != g_next_packet) + error("%d bytes remaining\n", (int) (g_next_packet - s->p)); +#endif + +} + +/* Reset order state */ +void +reset_order_state(void) +{ + memset(&g_order_state, 0, sizeof(g_order_state)); + g_order_state.order_type = RDP_ORDER_PATBLT; +} diff --git a/reactos/base/applications/mstsc/orders.h b/reactos/base/applications/mstsc/orders.h new file mode 100644 index 00000000000..b127282251e --- /dev/null +++ b/reactos/base/applications/mstsc/orders.h @@ -0,0 +1,368 @@ +/* + rdesktop: A Remote Desktop Protocol client. + RDP order processing + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define RDP_ORDER_STANDARD 0x01 +#define RDP_ORDER_SECONDARY 0x02 +#define RDP_ORDER_BOUNDS 0x04 +#define RDP_ORDER_CHANGE 0x08 +#define RDP_ORDER_DELTA 0x10 +#define RDP_ORDER_LASTBOUNDS 0x20 +#define RDP_ORDER_SMALL 0x40 +#define RDP_ORDER_TINY 0x80 + +enum RDP_ORDER_TYPE +{ + RDP_ORDER_DESTBLT = 0, + RDP_ORDER_PATBLT = 1, + RDP_ORDER_SCREENBLT = 2, + RDP_ORDER_LINE = 9, + RDP_ORDER_RECT = 10, + RDP_ORDER_DESKSAVE = 11, + RDP_ORDER_MEMBLT = 13, + RDP_ORDER_TRIBLT = 14, + RDP_ORDER_POLYGON = 20, + RDP_ORDER_POLYGON2 = 21, + RDP_ORDER_POLYLINE = 22, + RDP_ORDER_ELLIPSE = 25, + RDP_ORDER_ELLIPSE2 = 26, + RDP_ORDER_TEXT2 = 27 +}; + +enum RDP_SECONDARY_ORDER_TYPE +{ + RDP_ORDER_RAW_BMPCACHE = 0, + RDP_ORDER_COLCACHE = 1, + RDP_ORDER_BMPCACHE = 2, + RDP_ORDER_FONTCACHE = 3, + RDP_ORDER_RAW_BMPCACHE2 = 4, + RDP_ORDER_BMPCACHE2 = 5, + RDP_ORDER_BRUSHCACHE = 7 +}; + +typedef struct _DESTBLT_ORDER +{ + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint8 opcode; + +} +DESTBLT_ORDER; + +typedef struct _PATBLT_ORDER +{ + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint8 opcode; + uint32 bgcolour; + uint32 fgcolour; + BRUSH brush; + +} +PATBLT_ORDER; + +typedef struct _SCREENBLT_ORDER +{ + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint8 opcode; + sint16 srcx; + sint16 srcy; + +} +SCREENBLT_ORDER; + +typedef struct _LINE_ORDER +{ + uint16 mixmode; + sint16 startx; + sint16 starty; + sint16 endx; + sint16 endy; + uint32 bgcolour; + uint8 opcode; + PEN pen; + +} +LINE_ORDER; + +typedef struct _RECT_ORDER +{ + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint32 colour; + +} +RECT_ORDER; + +typedef struct _DESKSAVE_ORDER +{ + uint32 offset; + sint16 left; + sint16 top; + sint16 right; + sint16 bottom; + uint8 action; + +} +DESKSAVE_ORDER; + +typedef struct _TRIBLT_ORDER +{ + uint8 colour_table; + uint8 cache_id; + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint8 opcode; + sint16 srcx; + sint16 srcy; + uint32 bgcolour; + uint32 fgcolour; + BRUSH brush; + uint16 cache_idx; + uint16 unknown; + +} +TRIBLT_ORDER; + +typedef struct _MEMBLT_ORDER +{ + uint8 colour_table; + uint8 cache_id; + sint16 x; + sint16 y; + sint16 cx; + sint16 cy; + uint8 opcode; + sint16 srcx; + sint16 srcy; + uint16 cache_idx; + +} +MEMBLT_ORDER; + +#define MAX_DATA 256 + +typedef struct _POLYGON_ORDER +{ + sint16 x; + sint16 y; + uint8 opcode; + uint8 fillmode; + uint32 fgcolour; + uint8 npoints; + uint8 datasize; + uint8 data[MAX_DATA]; + +} +POLYGON_ORDER; + +typedef struct _POLYGON2_ORDER +{ + sint16 x; + sint16 y; + uint8 opcode; + uint8 fillmode; + uint32 bgcolour; + uint32 fgcolour; + BRUSH brush; + uint8 npoints; + uint8 datasize; + uint8 data[MAX_DATA]; + +} +POLYGON2_ORDER; + +typedef struct _POLYLINE_ORDER +{ + sint16 x; + sint16 y; + uint8 opcode; + uint32 fgcolour; + uint8 lines; + uint8 datasize; + uint8 data[MAX_DATA]; + +} +POLYLINE_ORDER; + +typedef struct _ELLIPSE_ORDER +{ + sint16 left; + sint16 top; + sint16 right; + sint16 bottom; + uint8 opcode; + uint8 fillmode; + uint32 fgcolour; + +} +ELLIPSE_ORDER; + +typedef struct _ELLIPSE2_ORDER +{ + sint16 left; + sint16 top; + sint16 right; + sint16 bottom; + uint8 opcode; + uint8 fillmode; + BRUSH brush; + uint32 bgcolour; + uint32 fgcolour; + +} +ELLIPSE2_ORDER; + +#define MAX_TEXT 256 + +typedef struct _TEXT2_ORDER +{ + uint8 font; + uint8 flags; + uint8 opcode; + uint8 mixmode; + uint32 bgcolour; + uint32 fgcolour; + sint16 clipleft; + sint16 cliptop; + sint16 clipright; + sint16 clipbottom; + sint16 boxleft; + sint16 boxtop; + sint16 boxright; + sint16 boxbottom; + BRUSH brush; + sint16 x; + sint16 y; + uint8 length; + uint8 text[MAX_TEXT]; + +} +TEXT2_ORDER; + +typedef struct _RDP_ORDER_STATE +{ + uint8 order_type; + BOUNDS bounds; + + DESTBLT_ORDER destblt; + PATBLT_ORDER patblt; + SCREENBLT_ORDER screenblt; + LINE_ORDER line; + RECT_ORDER rect; + DESKSAVE_ORDER desksave; + MEMBLT_ORDER memblt; + TRIBLT_ORDER triblt; + POLYGON_ORDER polygon; + POLYGON2_ORDER polygon2; + POLYLINE_ORDER polyline; + ELLIPSE_ORDER ellipse; + ELLIPSE2_ORDER ellipse2; + TEXT2_ORDER text2; + +} +RDP_ORDER_STATE; + +typedef struct _RDP_RAW_BMPCACHE_ORDER +{ + uint8 cache_id; + uint8 pad1; + uint8 width; + uint8 height; + uint8 bpp; + uint16 bufsize; + uint16 cache_idx; + uint8 *data; + +} +RDP_RAW_BMPCACHE_ORDER; + +typedef struct _RDP_BMPCACHE_ORDER +{ + uint8 cache_id; + uint8 pad1; + uint8 width; + uint8 height; + uint8 bpp; + uint16 bufsize; + uint16 cache_idx; + uint16 pad2; + uint16 size; + uint16 row_size; + uint16 final_size; + uint8 *data; + +} +RDP_BMPCACHE_ORDER; + +/* RDP_BMPCACHE2_ORDER */ +#define ID_MASK 0x0007 +#define MODE_MASK 0x0038 +#define SQUARE 0x0080 +#define PERSIST 0x0100 +#define FLAG_51_UNKNOWN 0x0800 + +#define MODE_SHIFT 3 + +#define LONG_FORMAT 0x80 +#define BUFSIZE_MASK 0x3FFF /* or 0x1FFF? */ + +#define MAX_GLYPH 32 + +typedef struct _RDP_FONT_GLYPH +{ + uint16 character; + uint16 unknown; + uint16 baseline; + uint16 width; + uint16 height; + uint8 data[MAX_GLYPH]; + +} +RDP_FONT_GLYPH; + +#define MAX_GLYPHS 256 + +typedef struct _RDP_FONTCACHE_ORDER +{ + uint8 font; + uint8 nglyphs; + RDP_FONT_GLYPH glyphs[MAX_GLYPHS]; + +} +RDP_FONTCACHE_ORDER; + +typedef struct _RDP_COLCACHE_ORDER +{ + uint8 cache_id; + COLOURMAP map; + +} +RDP_COLCACHE_ORDER; diff --git a/reactos/base/applications/mstsc/parse.h b/reactos/base/applications/mstsc/parse.h new file mode 100644 index 00000000000..758e53faa34 --- /dev/null +++ b/reactos/base/applications/mstsc/parse.h @@ -0,0 +1,95 @@ +/* + rdesktop: A Remote Desktop Protocol client. + Parsing primitives + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Parser state */ +typedef struct stream +{ + unsigned char *p; + unsigned char *end; + unsigned char *data; + unsigned int size; + + /* Offsets of various headers */ + unsigned char *iso_hdr; + unsigned char *mcs_hdr; + unsigned char *sec_hdr; + unsigned char *rdp_hdr; + unsigned char *channel_hdr; + +} + *STREAM; + +#define s_push_layer(s,h,n) { (s)->h = (s)->p; (s)->p += n; } +#define s_pop_layer(s,h) (s)->p = (s)->h; +#define s_mark_end(s) (s)->end = (s)->p; +#define s_check(s) ((s)->p <= (s)->end) +#define s_check_rem(s,n) ((s)->p + n <= (s)->end) +#define s_check_end(s) ((s)->p == (s)->end) + +#if defined(L_ENDIAN) && !defined(NEED_ALIGN) +#define in_uint16_le(s,v) { v = *(uint16 *)((s)->p); (s)->p += 2; } +#define in_uint32_le(s,v) { v = *(uint32 *)((s)->p); (s)->p += 4; } +#define out_uint16_le(s,v) { *(uint16 *)((s)->p) = v; (s)->p += 2; } +#define out_uint32_le(s,v) { *(uint32 *)((s)->p) = v; (s)->p += 4; } + +#else +#define in_uint16_le(s,v) { v = *((s)->p++); v += *((s)->p++) << 8; } +#define in_uint32_le(s,v) { in_uint16_le(s,v) \ + v += *((s)->p++) << 16; v += *((s)->p++) << 24; } +#define out_uint16_le(s,v) { *((s)->p++) = (v) & 0xff; *((s)->p++) = ((v) >> 8) & 0xff; } +#define out_uint32_le(s,v) { out_uint16_le(s, (v) & 0xffff); out_uint16_le(s, ((v) >> 16) & 0xffff); } +#endif + +#if defined(B_ENDIAN) && !defined(NEED_ALIGN) +#define in_uint16_be(s,v) { v = *(uint16 *)((s)->p); (s)->p += 2; } +#define in_uint32_be(s,v) { v = *(uint32 *)((s)->p); (s)->p += 4; } +#define out_uint16_be(s,v) { *(uint16 *)((s)->p) = v; (s)->p += 2; } +#define out_uint32_be(s,v) { *(uint32 *)((s)->p) = v; (s)->p += 4; } + +#define B_ENDIAN_PREFERRED +#define in_uint16(s,v) in_uint16_be(s,v) +#define in_uint32(s,v) in_uint32_be(s,v) +#define out_uint16(s,v) out_uint16_be(s,v) +#define out_uint32(s,v) out_uint32_be(s,v) + +#else +#define in_uint16_be(s,v) { v = *((s)->p++); next_be(s,v); } +#define in_uint32_be(s,v) { in_uint16_be(s,v); next_be(s,v); next_be(s,v); } +#define out_uint16_be(s,v) { *((s)->p++) = ((v) >> 8) & 0xff; *((s)->p++) = (v) & 0xff; } +#define out_uint32_be(s,v) { out_uint16_be(s, ((v) >> 16) & 0xffff); out_uint16_be(s, (v) & 0xffff); } +#endif + +#ifndef B_ENDIAN_PREFERRED +#define in_uint16(s,v) in_uint16_le(s,v) +#define in_uint32(s,v) in_uint32_le(s,v) +#define out_uint16(s,v) out_uint16_le(s,v) +#define out_uint32(s,v) out_uint32_le(s,v) +#endif + +#define in_uint8(s,v) v = *((s)->p++); +#define in_uint8p(s,v,n) { v = (s)->p; (s)->p += n; } +#define in_uint8a(s,v,n) { memcpy(v,(s)->p,n); (s)->p += n; } +#define in_uint8s(s,n) (s)->p += n; +#define out_uint8(s,v) *((s)->p++) = v; +#define out_uint8p(s,v,n) { memcpy((s)->p,v,n); (s)->p += n; } +#define out_uint8a(s,v,n) out_uint8p(s,v,n); +#define out_uint8s(s,n) { memset((s)->p,0,n); (s)->p += n; } + +#define next_be(s,v) v = ((v) << 8) + *((s)->p++); diff --git a/reactos/base/applications/mstsc/proto.h b/reactos/base/applications/mstsc/proto.h new file mode 100644 index 00000000000..9e94cef1d43 --- /dev/null +++ b/reactos/base/applications/mstsc/proto.h @@ -0,0 +1,310 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef RDESKTOP_PROTO_H +#define RDESKTOP_PROTO_H + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ +/* bitmap.c */ +BOOL bitmap_decompress(uint8 * output, int width, int height, uint8 * input, int size, int Bpp); +/* cache.c */ +void cache_rebuild_bmpcache_linked_list(uint8 id, sint16 * idx, int count); +void cache_bump_bitmap(uint8 id, uint16 idx, int bump); +void cache_evict_bitmap(uint8 id); +RD_HBITMAP cache_get_bitmap(uint8 id, uint16 idx); +void cache_put_bitmap(uint8 id, uint16 idx, RD_HBITMAP bitmap); +void cache_save_state(void); +FONTGLYPH *cache_get_font(uint8 font, uint16 character); +void cache_put_font(uint8 font, uint16 character, uint16 offset, uint16 baseline, uint16 width, + uint16 height, RD_HGLYPH pixmap); +DATABLOB *cache_get_text(uint8 cache_id); +void cache_put_text(uint8 cache_id, void *data, int length); +uint8 *cache_get_desktop(uint32 offset, int cx, int cy, int bytes_per_pixel); +void cache_put_desktop(uint32 offset, int cx, int cy, int scanline, int bytes_per_pixel, + uint8 * data); +RD_HCURSOR cache_get_cursor(uint16 cache_idx); +void cache_put_cursor(uint16 cache_idx, RD_HCURSOR cursor); +/* channels.c */ +VCHANNEL *channel_register(char *name, uint32 flags, void (*callback) (STREAM)); +STREAM channel_init(VCHANNEL * channel, uint32 length); +void channel_send(STREAM s, VCHANNEL * channel); +void channel_process(STREAM s, uint16 mcs_channel); +/* cliprdr.c */ +void cliprdr_send_simple_native_format_announce(uint32 format); +void cliprdr_send_native_format_announce(uint8 * formats_data, uint32 formats_data_length); +void cliprdr_send_data_request(uint32 format); +void cliprdr_send_data(uint8 * data, uint32 length); +void cliprdr_set_mode(const char *optarg); +BOOL cliprdr_init(void); +/* disk.c */ +int disk_enum_devices(uint32 * id, char *optarg); +RD_NTSTATUS disk_query_information(RD_NTHANDLE handle, uint32 info_class, STREAM out); +RD_NTSTATUS disk_set_information(RD_NTHANDLE handle, uint32 info_class, STREAM in, STREAM out); +RD_NTSTATUS disk_check_notify(RD_NTHANDLE handle); +RD_NTSTATUS disk_create_notify(RD_NTHANDLE handle, uint32 info_class); +RD_NTSTATUS disk_query_volume_information(RD_NTHANDLE handle, uint32 info_class, STREAM out); +RD_NTSTATUS disk_query_directory(RD_NTHANDLE handle, uint32 info_class, char *pattern, STREAM out); +/* mppc.c */ +int mppc_expand(uint8 * data, uint32 clen, uint8 ctype, uint32 * roff, uint32 * rlen); +/* ewmhints.c */ +int get_current_workarea(uint32 * x, uint32 * y, uint32 * width, uint32 * height); +void ewmh_init(void); +/* iso.c */ +STREAM iso_init(int length); +void iso_send(STREAM s); +STREAM iso_recv(uint8 * rdpver); +BOOL iso_connect(char *server, char *username); +BOOL iso_reconnect(char *server); +void iso_disconnect(void); +void iso_reset_state(void); +/* licence.c */ +void licence_process(STREAM s); +/* mcs.c */ +STREAM mcs_init(int length); +void mcs_send_to_channel(STREAM s, uint16 channel); +void mcs_send(STREAM s); +STREAM mcs_recv(uint16 * channel, uint8 * rdpver); +BOOL mcs_connect(char *server, STREAM mcs_data, char *username); +BOOL mcs_reconnect(char *server, STREAM mcs_data); +void mcs_disconnect(void); +void mcs_reset_state(void); +/* orders.c */ +void process_orders(STREAM s, uint16 num_orders); +void reset_order_state(void); +/* parallel.c */ +int parallel_enum_devices(uint32 * id, char *optarg); +/* printer.c */ +int printer_enum_devices(uint32 * id, char *optarg); +/* printercache.c */ +int printercache_load_blob(char *printer_name, uint8 ** data); +void printercache_process(STREAM s); +/* pstcache.c */ +void pstcache_touch_bitmap(uint8 cache_id, uint16 cache_idx, uint32 stamp); +BOOL pstcache_load_bitmap(uint8 cache_id, uint16 cache_idx); +BOOL pstcache_save_bitmap(uint8 cache_id, uint16 cache_idx, uint8 * key, uint8 width, + uint8 height, uint16 length, uint8 * data); +int pstcache_enumerate(uint8 id, HASH_KEY * keylist); +BOOL pstcache_init(uint8 cache_id); +/* rdesktop.c */ +int main(int argc, char *argv[]); +void generate_random(uint8 * random); +void *xmalloc(int size); +char *xstrdup(const char *s); +void *xrealloc(void *oldmem, int size); +void xfree(void *mem); +void error(char *format, ...); +void warning(char *format, ...); +void unimpl(char *format, ...); +void hexdump(unsigned char *p, unsigned int len); +char *next_arg(char *src, char needle); +void toupper_str(char *p); +BOOL str_startswith(const char *s, const char *prefix); +BOOL str_handle_lines(const char *input, char **rest, str_handle_lines_t linehandler, void *data); +BOOL subprocess(char *const argv[], str_handle_lines_t linehandler, void *data); +char *l_to_a(long N, int base); +int load_licence(unsigned char **data); +void save_licence(unsigned char *data, int length); +BOOL rd_pstcache_mkdir(void); +int rd_open_file(char *filename); +void rd_close_file(int fd); +int rd_read_file(int fd, void *ptr, int len); +int rd_write_file(int fd, void *ptr, int len); +int rd_lseek_file(int fd, int offset); +BOOL rd_lock_file(int fd, int start, int len); +/* rdp5.c */ +void rdp5_process(STREAM s); +/* rdp.c */ +void rdp_out_unistr(STREAM s, char *string, int len); +int rdp_in_unistr(STREAM s, char *string, int uni_len); +void rdp_send_input(uint32 time, uint16 message_type, uint16 device_flags, uint16 param1, + uint16 param2); +void rdp_send_client_window_status(int status); +void process_colour_pointer_pdu(STREAM s); +void process_cached_pointer_pdu(STREAM s); +void process_system_pointer_pdu(STREAM s); +void process_bitmap_updates(STREAM s); +void process_palette(STREAM s); +void process_disconnect_pdu(STREAM s, uint32 * ext_disc_reason); +void rdp_main_loop(BOOL * deactivated, uint32 * ext_disc_reason); +BOOL rdp_loop(BOOL * deactivated, uint32 * ext_disc_reason); +BOOL rdp_connect(char *server, uint32 flags, char *domain, char *password, char *command, + char *directory); +BOOL rdp_reconnect(char *server, uint32 flags, char *domain, char *password, char *command, + char *directory, char *cookie); +void rdp_reset_state(void); +void rdp_disconnect(void); +/* rdpdr.c */ +int get_device_index(RD_NTHANDLE handle); +void convert_to_unix_filename(char *filename); +BOOL rdpdr_init(void); +void rdpdr_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv, BOOL * timeout); +struct async_iorequest *rdpdr_remove_iorequest(struct async_iorequest *prev, + struct async_iorequest *iorq); +void rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out); +BOOL rdpdr_abort_io(uint32 fd, uint32 major, RD_NTSTATUS status); +/* rdpsnd.c */ +void rdpsnd_send_completion(uint16 tick, uint8 packet_index); +BOOL rdpsnd_init(void); +/* rdpsnd_oss.c */ +BOOL wave_out_open(void); +void wave_out_close(void); +BOOL wave_out_format_supported(WAVEFORMATEX * pwfx); +BOOL wave_out_set_format(WAVEFORMATEX * pwfx); +void wave_out_volume(uint16 left, uint16 right); +void wave_out_write(STREAM s, uint16 tick, uint8 index); +void wave_out_play(void); +/* secure.c */ +void sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt); +void sec_hash_16(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2); +void buf_out_uint32(uint8 * buffer, uint32 value); +void sec_sign(uint8 * signature, int siglen, uint8 * session_key, int keylen, uint8 * data, + int datalen); +void sec_decrypt(uint8 * data, int length); +STREAM sec_init(uint32 flags, int maxlen); +void sec_send_to_channel(STREAM s, uint32 flags, uint16 channel); +void sec_send(STREAM s, uint32 flags); +void sec_process_mcs_data(STREAM s); +STREAM sec_recv(uint8 * rdpver); +BOOL sec_connect(char *server, char *username); +BOOL sec_reconnect(char *server); +void sec_disconnect(void); +void sec_reset_state(void); +/* serial.c */ +int serial_enum_devices(uint32 * id, char *optarg); +BOOL serial_get_event(RD_NTHANDLE handle, uint32 * result); +BOOL serial_get_timeout(RD_NTHANDLE handle, uint32 length, uint32 * timeout, uint32 * itv_timeout); +/* tcp.c */ +STREAM tcp_init(uint32 maxlen); +void tcp_send(STREAM s); +STREAM tcp_recv(STREAM s, uint32 length); +BOOL tcp_connect(char *server); +void tcp_disconnect(void); +char *tcp_get_address(void); +void tcp_reset_state(void); +/* xclip.c */ +void ui_clip_format_announce(uint8 * data, uint32 length); +void ui_clip_handle_data(uint8 * data, uint32 length); +void ui_clip_request_failed(void); +void ui_clip_request_data(uint32 format); +void ui_clip_sync(void); +void ui_clip_set_mode(const char *optarg); +void xclip_init(void); +/* xkeymap.c */ +BOOL xkeymap_from_locale(const char *locale); +FILE *xkeymap_open(const char *filename); +void xkeymap_init(void); +BOOL handle_special_keys(uint32 keysym, unsigned int state, uint32 ev_time, BOOL pressed); +key_translation xkeymap_translate_key(uint32 keysym, unsigned int keycode, unsigned int state); +void xkeymap_send_keys(uint32 keysym, unsigned int keycode, unsigned int state, uint32 ev_time, + BOOL pressed, uint8 nesting); +uint16 xkeymap_translate_button(unsigned int button); +char *get_ksname(uint32 keysym); +void save_remote_modifiers(uint8 scancode); +void restore_remote_modifiers(uint32 ev_time, uint8 scancode); +void ensure_remote_modifiers(uint32 ev_time, key_translation tr); +unsigned int read_keyboard_state(void); +uint16 ui_get_numlock_state(unsigned int state); +void reset_modifier_keys(void); +void rdp_send_scancode(uint32 time, uint16 flags, uint8 scancode); +/* xwin.c */ +BOOL get_key_state(unsigned int state, uint32 keysym); +BOOL ui_init(void); +void ui_deinit(void); +BOOL ui_create_window(void); +void ui_resize_window(void); +void ui_destroy_window(void); +void xwin_toggle_fullscreen(void); +int ui_select(int rdp_socket); +void ui_move_pointer(int x, int y); +RD_HBITMAP ui_create_bitmap(int width, int height, uint8 * data); +void ui_paint_bitmap(int x, int y, int cx, int cy, int width, int height, uint8 * data); +void ui_destroy_bitmap(RD_HBITMAP bmp); +RD_HGLYPH ui_create_glyph(int width, int height, uint8 * data); +void ui_destroy_glyph(RD_HGLYPH glyph); +RD_HCURSOR ui_create_cursor(unsigned int x, unsigned int y, int width, int height, uint8 * andmask, + uint8 * xormask); +void ui_set_cursor(RD_HCURSOR cursor); +void ui_destroy_cursor(RD_HCURSOR cursor); +void ui_set_null_cursor(void); +RD_HCOLOURMAP ui_create_colourmap(COLOURMAP * colours); +void ui_destroy_colourmap(RD_HCOLOURMAP map); +void ui_set_colourmap(RD_HCOLOURMAP map); +void ui_set_clip(int x, int y, int cx, int cy); +void ui_reset_clip(void); +void ui_bell(void); +void ui_destblt(uint8 opcode, int x, int y, int cx, int cy); +void ui_patblt(uint8 opcode, int x, int y, int cx, int cy, BRUSH * brush, int bgcolour, + int fgcolour); +void ui_screenblt(uint8 opcode, int x, int y, int cx, int cy, int srcx, int srcy); +void ui_memblt(uint8 opcode, int x, int y, int cx, int cy, RD_HBITMAP src, int srcx, int srcy); +void ui_triblt(uint8 opcode, int x, int y, int cx, int cy, RD_HBITMAP src, int srcx, int srcy, + BRUSH * brush, int bgcolour, int fgcolour); +void ui_line(uint8 opcode, int startx, int starty, int endx, int endy, PEN * pen); +void ui_rect(int x, int y, int cx, int cy, int colour); +void ui_polygon(uint8 opcode, uint8 fillmode, POINT * point, int npoints, BRUSH * brush, + int bgcolour, int fgcolour); +void ui_polyline(uint8 opcode, POINT * points, int npoints, PEN * pen); +void ui_ellipse(uint8 opcode, uint8 fillmode, int x, int y, int cx, int cy, BRUSH * brush, + int bgcolour, int fgcolour); +void ui_draw_glyph(int mixmode, int x, int y, int cx, int cy, RD_HGLYPH glyph, int srcx, int srcy, + int bgcolour, int fgcolour); +void ui_draw_text(uint8 font, uint8 flags, uint8 opcode, int mixmode, int x, int y, int clipx, + int clipy, int clipcx, int clipcy, int boxx, int boxy, int boxcx, int boxcy, + BRUSH * brush, int bgcolour, int fgcolour, uint8 * text, uint8 length); +void ui_desktop_save(uint32 offset, int x, int y, int cx, int cy); +void ui_desktop_restore(uint32 offset, int x, int y, int cx, int cy); +void ui_begin_update(void); +void ui_end_update(void); +void ui_seamless_begin(BOOL hidden); +void ui_seamless_hide_desktop(void); +void ui_seamless_unhide_desktop(void); +void ui_seamless_toggle(void); +void ui_seamless_create_window(unsigned long id, unsigned long group, unsigned long parent, + unsigned long flags); +void ui_seamless_destroy_window(unsigned long id, unsigned long flags); +void ui_seamless_move_window(unsigned long id, int x, int y, int width, int height, + unsigned long flags); +void ui_seamless_restack_window(unsigned long id, unsigned long behind, unsigned long flags); +void ui_seamless_settitle(unsigned long id, const char *title, unsigned long flags); +void ui_seamless_setstate(unsigned long id, unsigned int state, unsigned long flags); +void ui_seamless_syncbegin(unsigned long flags); +void ui_seamless_ack(unsigned int serial); +/* lspci.c */ +BOOL lspci_init(void); +/* seamless.c */ +BOOL seamless_init(void); +unsigned int seamless_send_sync(void); +unsigned int seamless_send_state(unsigned long id, unsigned int state, unsigned long flags); +unsigned int seamless_send_position(unsigned long id, int x, int y, int width, int height, + unsigned long flags); +void seamless_select_timeout(struct timeval *tv); +unsigned int seamless_send_zchange(unsigned long id, unsigned long below, unsigned long flags); +unsigned int seamless_send_focus(unsigned long id, unsigned long flags); + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif diff --git a/reactos/base/applications/mstsc/pstcache.c b/reactos/base/applications/mstsc/pstcache.c new file mode 100644 index 00000000000..9e6432bd6df --- /dev/null +++ b/reactos/base/applications/mstsc/pstcache.c @@ -0,0 +1,200 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Persistent Bitmap Cache routines + Copyright (C) Jeroen Meijer 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +#define MAX_CELL_SIZE 0x1000 /* pixels */ + +#define IS_PERSISTENT(id) (id < 8 && g_pstcache_fd[id] > 0) + +extern int g_server_depth; +extern BOOL g_bitmap_cache; +extern BOOL g_bitmap_cache_persist_enable; +extern BOOL g_bitmap_cache_precache; + +int g_pstcache_fd[8]; +int g_pstcache_Bpp; +BOOL g_pstcache_enumerated = False; +uint8 zero_key[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + +/* Update mru stamp/index for a bitmap */ +void +pstcache_touch_bitmap(uint8 cache_id, uint16 cache_idx, uint32 stamp) +{ + int fd; + + if (!IS_PERSISTENT(cache_id) || cache_idx >= BMPCACHE2_NUM_PSTCELLS) + return; + + fd = g_pstcache_fd[cache_id]; + rd_lseek_file(fd, 12 + cache_idx * (g_pstcache_Bpp * MAX_CELL_SIZE + sizeof(CELLHEADER))); + rd_write_file(fd, &stamp, sizeof(stamp)); +} + +/* Load a bitmap from the persistent cache */ +BOOL +pstcache_load_bitmap(uint8 cache_id, uint16 cache_idx) +{ + uint8 *celldata; + int fd; + CELLHEADER cellhdr; + HBITMAP bitmap; + + if (!g_bitmap_cache_persist_enable) + return False; + + if (!IS_PERSISTENT(cache_id) || cache_idx >= BMPCACHE2_NUM_PSTCELLS) + return False; + + fd = g_pstcache_fd[cache_id]; + rd_lseek_file(fd, cache_idx * (g_pstcache_Bpp * MAX_CELL_SIZE + sizeof(CELLHEADER))); + rd_read_file(fd, &cellhdr, sizeof(CELLHEADER)); + celldata = (uint8 *) xmalloc(cellhdr.length); + rd_read_file(fd, celldata, cellhdr.length); + + bitmap = ui_create_bitmap(cellhdr.width, cellhdr.height, celldata); + DEBUG(("Load bitmap from disk: id=%d, idx=%d, bmp=0x%x)\n", cache_id, cache_idx, bitmap)); + cache_put_bitmap(cache_id, cache_idx, bitmap); + + xfree(celldata); + return True; +} + +/* Store a bitmap in the persistent cache */ +BOOL +pstcache_save_bitmap(uint8 cache_id, uint16 cache_idx, uint8 * key, + uint8 width, uint8 height, uint16 length, uint8 * data) +{ + int fd; + CELLHEADER cellhdr; + + if (!IS_PERSISTENT(cache_id) || cache_idx >= BMPCACHE2_NUM_PSTCELLS) + return False; + + memcpy(cellhdr.key, key, sizeof(HASH_KEY)); + cellhdr.width = width; + cellhdr.height = height; + cellhdr.length = length; + cellhdr.stamp = 0; + + fd = g_pstcache_fd[cache_id]; + rd_lseek_file(fd, cache_idx * (g_pstcache_Bpp * MAX_CELL_SIZE + sizeof(CELLHEADER))); + rd_write_file(fd, &cellhdr, sizeof(CELLHEADER)); + rd_write_file(fd, data, length); + + return True; +} + +/* List the bitmap keys from the persistent cache file */ +int +pstcache_enumerate(uint8 id, HASH_KEY * keylist) +{ + int fd, n; + uint16 idx; + sint16 mru_idx[0xa00]; + uint32 mru_stamp[0xa00]; + CELLHEADER cellhdr; + + if (!(g_bitmap_cache && g_bitmap_cache_persist_enable && IS_PERSISTENT(id))) + return 0; + + /* The server disconnects if the bitmap cache content is sent more than once */ + if (g_pstcache_enumerated) + return 0; + + DEBUG_RDP5(("Persistent bitmap cache enumeration... ")); + for (idx = 0; idx < BMPCACHE2_NUM_PSTCELLS; idx++) + { + fd = g_pstcache_fd[id]; + rd_lseek_file(fd, idx * (g_pstcache_Bpp * MAX_CELL_SIZE + sizeof(CELLHEADER))); + if (rd_read_file(fd, &cellhdr, sizeof(CELLHEADER)) <= 0) + break; + + if (memcmp(cellhdr.key, zero_key, sizeof(HASH_KEY)) != 0) + { + memcpy(keylist[idx], cellhdr.key, sizeof(HASH_KEY)); + + /* Pre-cache (not possible for 8 bit colour depth cause it needs a colourmap) */ + if (g_bitmap_cache_precache && cellhdr.stamp && g_server_depth > 8) + pstcache_load_bitmap(id, idx); + + /* Sort by stamp */ + for (n = idx; n > 0 && cellhdr.stamp < mru_stamp[n - 1]; n--) + { + mru_idx[n] = mru_idx[n - 1]; + mru_stamp[n] = mru_stamp[n - 1]; + } + + mru_idx[n] = idx; + mru_stamp[n] = cellhdr.stamp; + } + else + { + break; + } + } + + DEBUG_RDP5(("%d cached bitmaps.\n", idx)); + + cache_rebuild_bmpcache_linked_list(id, mru_idx, idx); + g_pstcache_enumerated = True; + return idx; +} + +/* initialise the persistent bitmap cache */ +BOOL +pstcache_init(uint8 cache_id) +{ + int fd; + char filename[256]; + + if (g_pstcache_enumerated) + return True; + + g_pstcache_fd[cache_id] = 0; + + if (!(g_bitmap_cache && g_bitmap_cache_persist_enable)) + return False; + + if (!rd_pstcache_mkdir()) + { + DEBUG(("failed to get/make cache directory!\n")); + return False; + } + + g_pstcache_Bpp = (g_server_depth + 7) / 8; + sprintf(filename, "cache/pstcache_%d_%d", cache_id, g_pstcache_Bpp); + DEBUG(("persistent bitmap cache file: %s\n", filename)); + + fd = rd_open_file(filename); + if (fd == -1) + return False; + + if (!rd_lock_file(fd, 0, 0)) + { + warning("Persistent bitmap caching is disabled. (The file is already in use)\n"); + rd_close_file(fd); + return False; + } + + g_pstcache_fd[cache_id] = fd; + return True; +} diff --git a/reactos/base/applications/mstsc/rdc.rc b/reactos/base/applications/mstsc/rdc.rc new file mode 100644 index 00000000000..f618084bf79 --- /dev/null +++ b/reactos/base/applications/mstsc/rdc.rc @@ -0,0 +1,9 @@ +#include +#include "resource.h" + +#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Remote Desktop Client\0" +#define REACTOS_STR_INTERNAL_NAME "mstsc\0" +#define REACTOS_STR_ORIGINAL_FILENAME "mstsc.exe\0" +//#include + +#include "rsrc.rc" diff --git a/reactos/base/applications/mstsc/rdesktop.h b/reactos/base/applications/mstsc/rdesktop.h new file mode 100644 index 00000000000..0f39b6e3a9d --- /dev/null +++ b/reactos/base/applications/mstsc/rdesktop.h @@ -0,0 +1,126 @@ +/* + rdesktop: A Remote Desktop Protocol client. + Master include file + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#ifdef _WIN32 +#include /* winsock2.h first */ +#include +#include +#else /* WIN32 */ +#include +#include +#include +#ifdef HAVE_SYS_SELECT_H +#include +#else /* HAVE_SYS_SELECT_H */ +#include +#include +#endif /* HAVE_SYS_SELECT_H */ +#endif /* WIN32 */ +#include /* PATH_MAX */ + +#define VERSION "1.4.1" + +#ifdef WITH_DEBUG +#define DEBUG(args) printf args; +#else +#define DEBUG(args) +#endif + +#ifdef WITH_DEBUG_KBD +#define DEBUG_KBD(args) printf args; +#else +#define DEBUG_KBD(args) +#endif + +#ifdef WITH_DEBUG_RDP5 +#define DEBUG_RDP5(args) printf args; +#else +#define DEBUG_RDP5(args) +#endif + +#ifdef WITH_DEBUG_CLIPBOARD +#define DEBUG_CLIPBOARD(args) printf args; +#else +#define DEBUG_CLIPBOARD(args) +#endif + +#ifdef WITH_DEBUG_CHANNEL +#define DEBUG_CHANNEL(args) printf args; +#else +#define DEBUG_CHANNEL(args) +#endif + +#define STRNCPY(dst,src,n) { strncpy(dst,src,n-1); dst[n-1] = 0; } + +#ifndef MIN +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#endif + +#ifndef MAX +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) +#endif + +/* timeval macros */ +#ifndef timerisset +#define timerisset(tvp)\ + ((tvp)->tv_sec || (tvp)->tv_usec) +#endif +#ifndef timercmp +#define timercmp(tvp, uvp, cmp)\ + ((tvp)->tv_sec cmp (uvp)->tv_sec ||\ + (tvp)->tv_sec == (uvp)->tv_sec &&\ + (tvp)->tv_usec cmp (uvp)->tv_usec) +#endif +#ifndef timerclear +#define timerclear(tvp)\ + ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#endif + +/* If configure does not define the endianess, try + to find it out */ +#if !defined(L_ENDIAN) && !defined(B_ENDIAN) +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define L_ENDIAN +#elif __BYTE_ORDER == __BIG_ENDIAN +#define B_ENDIAN +#else +#error Unknown endianness. Edit rdesktop.h. +#endif +#endif /* B_ENDIAN, L_ENDIAN from configure */ + +/* No need for alignment on x86 and amd64 */ +#if !defined(NEED_ALIGN) +#if !(defined(__x86__) || defined(__x86_64__) || \ + defined(__AMD64__) || defined(_M_IX86) || \ + defined(__i386__)) +#define NEED_ALIGN +#endif +#endif + +#include "parse.h" +#include "constants.h" +#include "types.h" + +#ifndef MAKE_PROTO +#include "proto.h" +#endif diff --git a/reactos/base/applications/mstsc/rdp.c b/reactos/base/applications/mstsc/rdp.c new file mode 100644 index 00000000000..020e51de2d4 --- /dev/null +++ b/reactos/base/applications/mstsc/rdp.c @@ -0,0 +1,1441 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - RDP layer + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +//#include +#ifndef _WIN32 +#include +#include +#endif +#include "rdesktop.h" + +#ifdef HAVE_ICONV +#ifdef HAVE_ICONV_H +#include +#endif + +#ifndef ICONV_CONST +#define ICONV_CONST "" +#endif +#endif + +extern uint16 g_mcs_userid; +extern char g_username[64]; +extern char g_codepage[16]; +extern BOOL g_bitmap_compression; +extern BOOL g_orders; +extern BOOL g_encryption; +extern BOOL g_desktop_save; +extern BOOL g_polygon_ellipse_orders; +extern BOOL g_use_rdp5; +extern uint16 g_server_rdp_version; +extern uint32 g_rdp5_performanceflags; +extern int g_server_depth; +extern int g_width; +extern int g_height; +extern BOOL g_bitmap_cache; +extern BOOL g_bitmap_cache_persist_enable; + +uint8 *g_next_packet; +uint32 g_rdp_shareid; + +extern RDPCOMP g_mppc_dict; + +/* Session Directory support */ +extern BOOL g_redirect; +extern char g_redirect_server[64]; +extern char g_redirect_domain[16]; +extern char g_redirect_password[64]; +extern char g_redirect_username[64]; +extern char g_redirect_cookie[128]; +extern uint32 g_redirect_flags; +/* END Session Directory support */ + +#ifdef WITH_DEBUG +static uint32 g_packetno; +#endif + +#ifdef HAVE_ICONV +static BOOL g_iconv_works = True; +#endif + +/* Receive an RDP packet */ +static STREAM +rdp_recv(uint8 * type) +{ + static STREAM rdp_s; + uint16 length, pdu_type; + uint8 rdpver; + + if ((rdp_s == NULL) || (g_next_packet >= rdp_s->end) || (g_next_packet == NULL)) + { + rdp_s = sec_recv(&rdpver); + if (rdp_s == NULL) + return NULL; + if (rdpver == 0xff) + { + g_next_packet = rdp_s->end; + *type = 0; + return rdp_s; + } + else if (rdpver != 3) + { + /* rdp5_process should move g_next_packet ok */ + rdp5_process(rdp_s); + *type = 0; + return rdp_s; + } + + g_next_packet = rdp_s->p; + } + else + { + rdp_s->p = g_next_packet; + } + + in_uint16_le(rdp_s, length); + /* 32k packets are really 8, keepalive fix */ + if (length == 0x8000) + { + g_next_packet += 8; + *type = 0; + return rdp_s; + } + in_uint16_le(rdp_s, pdu_type); + in_uint8s(rdp_s, 2); /* userid */ + *type = pdu_type & 0xf; + +#ifdef WITH_DEBUG + DEBUG(("RDP packet #%d, (type %x)\n", ++g_packetno, *type)); + hexdump(g_next_packet, length); +#endif /* */ + + g_next_packet += length; + return rdp_s; +} + +/* Initialise an RDP data packet */ +static STREAM +rdp_init_data(int maxlen) +{ + STREAM s; + + s = sec_init(g_encryption ? SEC_ENCRYPT : 0, maxlen + 18); + s_push_layer(s, rdp_hdr, 18); + + return s; +} + +/* Send an RDP data packet */ +static void +rdp_send_data(STREAM s, uint8 data_pdu_type) +{ + uint16 length; + + s_pop_layer(s, rdp_hdr); + length = s->end - s->p; + + out_uint16_le(s, length); + out_uint16_le(s, (RDP_PDU_DATA | 0x10)); + out_uint16_le(s, (g_mcs_userid + 1001)); + + out_uint32_le(s, g_rdp_shareid); + out_uint8(s, 0); /* pad */ + out_uint8(s, 1); /* streamid */ + out_uint16_le(s, (length - 14)); + out_uint8(s, data_pdu_type); + out_uint8(s, 0); /* compress_type */ + out_uint16(s, 0); /* compress_len */ + + sec_send(s, g_encryption ? SEC_ENCRYPT : 0); +} + +/* Output a string in Unicode */ +void +rdp_out_unistr(STREAM s, char *string, int len) +{ +#ifdef HAVE_ICONV + size_t ibl = strlen(string), obl = len + 2; + static iconv_t iconv_h = (iconv_t) - 1; + char *pin = string, *pout = (char *) s->p; + + memset(pout, 0, len + 4); + + if (g_iconv_works) + { + if (iconv_h == (iconv_t) - 1) + { + size_t i = 1, o = 4; + if ((iconv_h = iconv_open(WINDOWS_CODEPAGE, g_codepage)) == (iconv_t) - 1) + { + warning("rdp_out_unistr: iconv_open[%s -> %s] fail %d\n", + g_codepage, WINDOWS_CODEPAGE, (int) iconv_h); + + g_iconv_works = False; + rdp_out_unistr(s, string, len); + return; + } + if (iconv(iconv_h, (ICONV_CONST char **) &pin, &i, &pout, &o) == + (size_t) - 1) + { + iconv_close(iconv_h); + iconv_h = (iconv_t) - 1; + warning("rdp_out_unistr: iconv(1) fail, errno %d\n", errno); + + g_iconv_works = False; + rdp_out_unistr(s, string, len); + return; + } + pin = string; + pout = (char *) s->p; + } + + if (iconv(iconv_h, (ICONV_CONST char **) &pin, &ibl, &pout, &obl) == (size_t) - 1) + { + iconv_close(iconv_h); + iconv_h = (iconv_t) - 1; + warning("rdp_out_unistr: iconv(2) fail, errno %d\n", errno); + + g_iconv_works = False; + rdp_out_unistr(s, string, len); + return; + } + + s->p += len + 2; + + } + else +#endif + { + int i = 0, j = 0; + + len += 2; + + while (i < len) + { + s->p[i++] = string[j++]; + s->p[i++] = 0; + } + + s->p += len; + } +} + +/* Input a string in Unicode + * + * Returns str_len of string + */ +int +rdp_in_unistr(STREAM s, char *string, int uni_len) +{ +#ifdef HAVE_ICONV + size_t ibl = uni_len, obl = uni_len; + char *pin = (char *) s->p, *pout = string; + static iconv_t iconv_h = (iconv_t) - 1; + + if (g_iconv_works) + { + if (iconv_h == (iconv_t) - 1) + { + if ((iconv_h = iconv_open(g_codepage, WINDOWS_CODEPAGE)) == (iconv_t) - 1) + { + warning("rdp_in_unistr: iconv_open[%s -> %s] fail %d\n", + WINDOWS_CODEPAGE, g_codepage, (int) iconv_h); + + g_iconv_works = False; + return rdp_in_unistr(s, string, uni_len); + } + } + + if (iconv(iconv_h, (ICONV_CONST char **) &pin, &ibl, &pout, &obl) == (size_t) - 1) + { + iconv_close(iconv_h); + iconv_h = (iconv_t) - 1; + warning("rdp_in_unistr: iconv fail, errno %d\n", errno); + + g_iconv_works = False; + return rdp_in_unistr(s, string, uni_len); + } + + /* we must update the location of the current STREAM for future reads of s->p */ + s->p += uni_len; + + return pout - string; + } + else +#endif + { + int i = 0; + + while (i < uni_len / 2) + { + in_uint8a(s, &string[i++], 1); + in_uint8s(s, 1); + } + + return i - 1; + } +} + + +/* Parse a logon info packet */ +static void +rdp_send_logon_info(uint32 flags, char *domain, char *user, + char *password, char *program, char *directory) +{ + char *ipaddr = tcp_get_address(); + int len_domain = 2 * strlen(domain); + int len_user = 2 * strlen(user); + int len_password = 2 * strlen(password); + int len_program = 2 * strlen(program); + int len_directory = 2 * strlen(directory); + int len_ip = 2 * strlen(ipaddr); + int len_dll = 2 * strlen("C:\\WINNT\\System32\\mstscax.dll"); + int packetlen = 0; + uint32 sec_flags = g_encryption ? (SEC_LOGON_INFO | SEC_ENCRYPT) : SEC_LOGON_INFO; + STREAM s; + //time_t t = time(NULL); + //time_t tzone; + + if (!g_use_rdp5 || 1 == g_server_rdp_version) + { + DEBUG_RDP5(("Sending RDP4-style Logon packet\n")); + + s = sec_init(sec_flags, 18 + len_domain + len_user + len_password + + len_program + len_directory + 10); + + out_uint32(s, 0); + out_uint32_le(s, flags); + out_uint16_le(s, len_domain); + out_uint16_le(s, len_user); + out_uint16_le(s, len_password); + out_uint16_le(s, len_program); + out_uint16_le(s, len_directory); + rdp_out_unistr(s, domain, len_domain); + rdp_out_unistr(s, user, len_user); + rdp_out_unistr(s, password, len_password); + rdp_out_unistr(s, program, len_program); + rdp_out_unistr(s, directory, len_directory); + } + else + { +#if 0 + flags |= RDP_LOGON_BLOB; + DEBUG_RDP5(("Sending RDP5-style Logon packet\n")); + packetlen = 4 + /* Unknown uint32 */ + 4 + /* flags */ + 2 + /* len_domain */ + 2 + /* len_user */ + (flags & RDP_LOGON_AUTO ? 2 : 0) + /* len_password */ + (flags & RDP_LOGON_BLOB ? 2 : 0) + /* Length of BLOB */ + 2 + /* len_program */ + 2 + /* len_directory */ + (0 < len_domain ? len_domain : 2) + /* domain */ + len_user + (flags & RDP_LOGON_AUTO ? len_password : 0) + 0 + /* We have no 512 byte BLOB. Perhaps we must? */ + (flags & RDP_LOGON_BLOB && !(flags & RDP_LOGON_AUTO) ? 2 : 0) + /* After the BLOB is a unknown int16. If there is a BLOB, that is. */ + (0 < len_program ? len_program : 2) + (0 < len_directory ? len_directory : 2) + 2 + /* Unknown (2) */ + 2 + /* Client ip length */ + len_ip + /* Client ip */ + 2 + /* DLL string length */ + len_dll + /* DLL string */ + 2 + /* Unknown */ + 2 + /* Unknown */ + 64 + /* Time zone #0 */ + 2 + /* Unknown */ + 64 + /* Time zone #1 */ + 32; /* Unknown */ + + s = sec_init(sec_flags, packetlen); + DEBUG_RDP5(("Called sec_init with packetlen %d\n", packetlen)); + + out_uint32(s, 0); /* Unknown */ + out_uint32_le(s, flags); + out_uint16_le(s, len_domain); + out_uint16_le(s, len_user); + if (flags & RDP_LOGON_AUTO) + { + out_uint16_le(s, len_password); + + } + if (flags & RDP_LOGON_BLOB && !(flags & RDP_LOGON_AUTO)) + { + out_uint16_le(s, 0); + } + out_uint16_le(s, len_program); + out_uint16_le(s, len_directory); + if (0 < len_domain) + rdp_out_unistr(s, domain, len_domain); + else + out_uint16_le(s, 0); + rdp_out_unistr(s, user, len_user); + if (flags & RDP_LOGON_AUTO) + { + rdp_out_unistr(s, password, len_password); + } + if (flags & RDP_LOGON_BLOB && !(flags & RDP_LOGON_AUTO)) + { + out_uint16_le(s, 0); + } + if (0 < len_program) + { + rdp_out_unistr(s, program, len_program); + + } + else + { + out_uint16_le(s, 0); + } + if (0 < len_directory) + { + rdp_out_unistr(s, directory, len_directory); + } + else + { + out_uint16_le(s, 0); + } + out_uint16_le(s, 2); + out_uint16_le(s, len_ip + 2); /* Length of client ip */ + rdp_out_unistr(s, ipaddr, len_ip); + out_uint16_le(s, len_dll + 2); + rdp_out_unistr(s, "C:\\WINNT\\System32\\mstscax.dll", len_dll); + + tzone = (mktime(gmtime(&t)) - mktime(localtime(&t))) / 60; + out_uint32_le(s, tzone); + + rdp_out_unistr(s, "GTB, normaltid", 2 * strlen("GTB, normaltid")); + out_uint8s(s, 62 - 2 * strlen("GTB, normaltid")); + + out_uint32_le(s, 0x0a0000); + out_uint32_le(s, 0x050000); + out_uint32_le(s, 3); + out_uint32_le(s, 0); + out_uint32_le(s, 0); + + rdp_out_unistr(s, "GTB, sommartid", 2 * strlen("GTB, sommartid")); + out_uint8s(s, 62 - 2 * strlen("GTB, sommartid")); + + out_uint32_le(s, 0x30000); + out_uint32_le(s, 0x050000); + out_uint32_le(s, 2); + out_uint32(s, 0); + out_uint32_le(s, 0xffffffc4); + out_uint32_le(s, 0xfffffffe); + out_uint32_le(s, g_rdp5_performanceflags); + out_uint32(s, 0); + +#endif + } + s_mark_end(s); + sec_send(s, sec_flags); +} + +/* Send a control PDU */ +static void +rdp_send_control(uint16 action) +{ + STREAM s; + + s = rdp_init_data(8); + + out_uint16_le(s, action); + out_uint16(s, 0); /* userid */ + out_uint32(s, 0); /* control id */ + + s_mark_end(s); + rdp_send_data(s, RDP_DATA_PDU_CONTROL); +} + +/* Send a synchronisation PDU */ +static void +rdp_send_synchronise(void) +{ + STREAM s; + + s = rdp_init_data(4); + + out_uint16_le(s, 1); /* type */ + out_uint16_le(s, 1002); + + s_mark_end(s); + rdp_send_data(s, RDP_DATA_PDU_SYNCHRONISE); +} + +/* Send a single input event */ +void +rdp_send_input(uint32 time, uint16 message_type, uint16 device_flags, uint16 param1, uint16 param2) +{ + STREAM s; + + s = rdp_init_data(16); + + out_uint16_le(s, 1); /* number of events */ + out_uint16(s, 0); /* pad */ + + out_uint32_le(s, time); + out_uint16_le(s, message_type); + out_uint16_le(s, device_flags); + out_uint16_le(s, param1); + out_uint16_le(s, param2); + + s_mark_end(s); + rdp_send_data(s, RDP_DATA_PDU_INPUT); +} + +/* Send a client window information PDU */ +void +rdp_send_client_window_status(int status) +{ + STREAM s; + static int current_status = 1; + + if (current_status == status) + return; + + s = rdp_init_data(12); + + out_uint32_le(s, status); + + switch (status) + { + case 0: /* shut the server up */ + break; + + case 1: /* receive data again */ + out_uint32_le(s, 0); /* unknown */ + out_uint16_le(s, g_width); + out_uint16_le(s, g_height); + break; + } + + s_mark_end(s); + rdp_send_data(s, RDP_DATA_PDU_CLIENT_WINDOW_STATUS); + current_status = status; +} + +/* Send persistent bitmap cache enumeration PDU's */ +static void +rdp_enum_bmpcache2(void) +{ + STREAM s; + HASH_KEY keylist[BMPCACHE2_NUM_PSTCELLS]; + uint32 num_keys, offset, count, flags; + + offset = 0; + num_keys = pstcache_enumerate(2, keylist); + + while (offset < num_keys) + { + count = MIN(num_keys - offset, 169); + + s = rdp_init_data(24 + count * sizeof(HASH_KEY)); + + flags = 0; + if (offset == 0) + flags |= PDU_FLAG_FIRST; + if (num_keys - offset <= 169) + flags |= PDU_FLAG_LAST; + + /* header */ + out_uint32_le(s, 0); + out_uint16_le(s, count); + out_uint16_le(s, 0); + out_uint16_le(s, 0); + out_uint16_le(s, 0); + out_uint16_le(s, 0); + out_uint16_le(s, num_keys); + out_uint32_le(s, 0); + out_uint32_le(s, flags); + + /* list */ + out_uint8a(s, keylist[offset], count * sizeof(HASH_KEY)); + + s_mark_end(s); + rdp_send_data(s, 0x2b); + + offset += 169; + } +} + +/* Send an (empty) font information PDU */ +static void +rdp_send_fonts(uint16 seq) +{ + STREAM s; + + s = rdp_init_data(8); + + out_uint16(s, 0); /* number of fonts */ + out_uint16_le(s, 0); /* pad? */ + out_uint16_le(s, seq); /* unknown */ + out_uint16_le(s, 0x32); /* entry size */ + + s_mark_end(s); + rdp_send_data(s, RDP_DATA_PDU_FONT2); +} + +/* Output general capability set */ +static void +rdp_out_general_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_GENERAL); + out_uint16_le(s, RDP_CAPLEN_GENERAL); + + out_uint16_le(s, 1); /* OS major type */ + out_uint16_le(s, 3); /* OS minor type */ + out_uint16_le(s, 0x200); /* Protocol version */ + out_uint16(s, 0); /* Pad */ + out_uint16(s, 0); /* Compression types */ + out_uint16_le(s, g_use_rdp5 ? 0x40d : 0); + /* Pad, according to T.128. 0x40d seems to + trigger + the server to start sending RDP5 packets. + However, the value is 0x1d04 with W2KTSK and + NT4MS. Hmm.. Anyway, thankyou, Microsoft, + for sending such information in a padding + field.. */ + out_uint16(s, 0); /* Update capability */ + out_uint16(s, 0); /* Remote unshare capability */ + out_uint16(s, 0); /* Compression level */ + out_uint16(s, 0); /* Pad */ +} + +/* Output bitmap capability set */ +static void +rdp_out_bitmap_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_BITMAP); + out_uint16_le(s, RDP_CAPLEN_BITMAP); + + out_uint16_le(s, g_server_depth); /* Preferred colour depth */ + out_uint16_le(s, 1); /* Receive 1 BPP */ + out_uint16_le(s, 1); /* Receive 4 BPP */ + out_uint16_le(s, 1); /* Receive 8 BPP */ + out_uint16_le(s, 800); /* Desktop width */ + out_uint16_le(s, 600); /* Desktop height */ + out_uint16(s, 0); /* Pad */ + out_uint16(s, 1); /* Allow resize */ + out_uint16_le(s, g_bitmap_compression ? 1 : 0); /* Support compression */ + out_uint16(s, 0); /* Unknown */ + out_uint16_le(s, 1); /* Unknown */ + out_uint16(s, 0); /* Pad */ +} + +/* Output order capability set */ +static void +rdp_out_order_caps(STREAM s) +{ + uint8 order_caps[32]; + + memset(order_caps, 0, 32); + order_caps[0] = 1; /* dest blt */ + order_caps[1] = 1; /* pat blt */ + order_caps[2] = 1; /* screen blt */ + order_caps[3] = (g_bitmap_cache ? 1 : 0); /* memblt */ + order_caps[4] = 0; /* triblt */ + order_caps[8] = 1; /* line */ + order_caps[9] = 1; /* line */ + order_caps[10] = 1; /* rect */ + order_caps[11] = (g_desktop_save ? 1 : 0); /* desksave */ + order_caps[13] = 1; /* memblt */ + order_caps[14] = 1; /* triblt */ + order_caps[20] = (g_polygon_ellipse_orders ? 1 : 0); /* polygon */ + order_caps[21] = (g_polygon_ellipse_orders ? 1 : 0); /* polygon2 */ + order_caps[22] = 1; /* polyline */ + order_caps[25] = (g_polygon_ellipse_orders ? 1 : 0); /* ellipse */ + order_caps[26] = (g_polygon_ellipse_orders ? 1 : 0); /* ellipse2 */ + order_caps[27] = 1; /* text2 */ + out_uint16_le(s, RDP_CAPSET_ORDER); + out_uint16_le(s, RDP_CAPLEN_ORDER); + + out_uint8s(s, 20); /* Terminal desc, pad */ + out_uint16_le(s, 1); /* Cache X granularity */ + out_uint16_le(s, 20); /* Cache Y granularity */ + out_uint16(s, 0); /* Pad */ + out_uint16_le(s, 1); /* Max order level */ + out_uint16_le(s, 0x147); /* Number of fonts */ + out_uint16_le(s, 0x2a); /* Capability flags */ + out_uint8p(s, order_caps, 32); /* Orders supported */ + out_uint16_le(s, 0x6a1); /* Text capability flags */ + out_uint8s(s, 6); /* Pad */ + out_uint32_le(s, g_desktop_save == False ? 0 : 0x38400); /* Desktop cache size */ + out_uint32(s, 0); /* Unknown */ + out_uint32_le(s, 0x4e4); /* Unknown */ +} + +/* Output bitmap cache capability set */ +static void +rdp_out_bmpcache_caps(STREAM s) +{ + int Bpp; + out_uint16_le(s, RDP_CAPSET_BMPCACHE); + out_uint16_le(s, RDP_CAPLEN_BMPCACHE); + + Bpp = (g_server_depth + 7) / 8; /* bytes per pixel */ + out_uint8s(s, 24); /* unused */ + out_uint16_le(s, 0x258); /* entries */ + out_uint16_le(s, 0x100 * Bpp); /* max cell size */ + out_uint16_le(s, 0x12c); /* entries */ + out_uint16_le(s, 0x400 * Bpp); /* max cell size */ + out_uint16_le(s, 0x106); /* entries */ + out_uint16_le(s, 0x1000 * Bpp); /* max cell size */ +} + +/* Output bitmap cache v2 capability set */ +static void +rdp_out_bmpcache2_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_BMPCACHE2); + out_uint16_le(s, RDP_CAPLEN_BMPCACHE2); + + out_uint16_le(s, g_bitmap_cache_persist_enable ? 2 : 0); /* version */ + + out_uint16_be(s, 3); /* number of caches in this set */ + + /* max cell size for cache 0 is 16x16, 1 = 32x32, 2 = 64x64, etc */ + out_uint32_le(s, BMPCACHE2_C0_CELLS); + out_uint32_le(s, BMPCACHE2_C1_CELLS); + if (pstcache_init(2)) + { + out_uint32_le(s, BMPCACHE2_NUM_PSTCELLS | BMPCACHE2_FLAG_PERSIST); + } + else + { + out_uint32_le(s, BMPCACHE2_C2_CELLS); + } + out_uint8s(s, 20); /* other bitmap caches not used */ +} + +/* Output control capability set */ +static void +rdp_out_control_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_CONTROL); + out_uint16_le(s, RDP_CAPLEN_CONTROL); + + out_uint16(s, 0); /* Control capabilities */ + out_uint16(s, 0); /* Remote detach */ + out_uint16_le(s, 2); /* Control interest */ + out_uint16_le(s, 2); /* Detach interest */ +} + +/* Output activation capability set */ +static void +rdp_out_activate_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_ACTIVATE); + out_uint16_le(s, RDP_CAPLEN_ACTIVATE); + + out_uint16(s, 0); /* Help key */ + out_uint16(s, 0); /* Help index key */ + out_uint16(s, 0); /* Extended help key */ + out_uint16(s, 0); /* Window activate */ +} + +/* Output pointer capability set */ +static void +rdp_out_pointer_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_POINTER); + out_uint16_le(s, RDP_CAPLEN_POINTER); + + out_uint16(s, 0); /* Colour pointer */ + out_uint16_le(s, 20); /* Cache size */ +} + +/* Output share capability set */ +static void +rdp_out_share_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_SHARE); + out_uint16_le(s, RDP_CAPLEN_SHARE); + + out_uint16(s, 0); /* userid */ + out_uint16(s, 0); /* pad */ +} + +/* Output colour cache capability set */ +static void +rdp_out_colcache_caps(STREAM s) +{ + out_uint16_le(s, RDP_CAPSET_COLCACHE); + out_uint16_le(s, RDP_CAPLEN_COLCACHE); + + out_uint16_le(s, 6); /* cache size */ + out_uint16(s, 0); /* pad */ +} + +static uint8 caps_0x0d[] = { + 0x01, 0x00, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static uint8 caps_0x0c[] = { 0x01, 0x00, 0x00, 0x00 }; + +static uint8 caps_0x0e[] = { 0x01, 0x00, 0x00, 0x00 }; + +static uint8 caps_0x10[] = { + 0xFE, 0x00, 0x04, 0x00, 0xFE, 0x00, 0x04, 0x00, + 0xFE, 0x00, 0x08, 0x00, 0xFE, 0x00, 0x08, 0x00, + 0xFE, 0x00, 0x10, 0x00, 0xFE, 0x00, 0x20, 0x00, + 0xFE, 0x00, 0x40, 0x00, 0xFE, 0x00, 0x80, 0x00, + 0xFE, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x08, + 0x00, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00 +}; + +/* Output unknown capability sets */ +static void +rdp_out_unknown_caps(STREAM s, uint16 id, uint16 length, uint8 * caps) +{ + out_uint16_le(s, id); + out_uint16_le(s, length); + + out_uint8p(s, caps, length - 4); +} + +#define RDP5_FLAG 0x0030 +/* Send a confirm active PDU */ +static void +rdp_send_confirm_active(void) +{ + STREAM s; + uint32 sec_flags = g_encryption ? (RDP5_FLAG | SEC_ENCRYPT) : RDP5_FLAG; + uint16 caplen = + RDP_CAPLEN_GENERAL + RDP_CAPLEN_BITMAP + RDP_CAPLEN_ORDER + + RDP_CAPLEN_BMPCACHE + RDP_CAPLEN_COLCACHE + + RDP_CAPLEN_ACTIVATE + RDP_CAPLEN_CONTROL + + RDP_CAPLEN_POINTER + RDP_CAPLEN_SHARE + + 0x58 + 0x08 + 0x08 + 0x34 /* unknown caps */ + + 4 /* w2k fix, why? */ ; + + s = sec_init(sec_flags, 6 + 14 + caplen + sizeof(RDP_SOURCE)); + + out_uint16_le(s, 2 + 14 + caplen + sizeof(RDP_SOURCE)); + out_uint16_le(s, (RDP_PDU_CONFIRM_ACTIVE | 0x10)); /* Version 1 */ + out_uint16_le(s, (g_mcs_userid + 1001)); + + out_uint32_le(s, g_rdp_shareid); + out_uint16_le(s, 0x3ea); /* userid */ + out_uint16_le(s, sizeof(RDP_SOURCE)); + out_uint16_le(s, caplen); + + out_uint8p(s, RDP_SOURCE, sizeof(RDP_SOURCE)); + out_uint16_le(s, 0xd); /* num_caps */ + out_uint8s(s, 2); /* pad */ + + rdp_out_general_caps(s); + rdp_out_bitmap_caps(s); + rdp_out_order_caps(s); + g_use_rdp5 ? rdp_out_bmpcache2_caps(s) : rdp_out_bmpcache_caps(s); + rdp_out_colcache_caps(s); + rdp_out_activate_caps(s); + rdp_out_control_caps(s); + rdp_out_pointer_caps(s); + rdp_out_share_caps(s); + + rdp_out_unknown_caps(s, 0x0d, 0x58, caps_0x0d); /* international? */ + rdp_out_unknown_caps(s, 0x0c, 0x08, caps_0x0c); + rdp_out_unknown_caps(s, 0x0e, 0x08, caps_0x0e); + rdp_out_unknown_caps(s, 0x10, 0x34, caps_0x10); /* glyph cache? */ + + s_mark_end(s); + sec_send(s, sec_flags); +} + +/* Process a general capability set */ +static void +rdp_process_general_caps(STREAM s) +{ + uint16 pad2octetsB; /* rdp5 flags? */ + + in_uint8s(s, 10); + in_uint16_le(s, pad2octetsB); + + if (!pad2octetsB) + g_use_rdp5 = False; +} + +/* Process a bitmap capability set */ +static void +rdp_process_bitmap_caps(STREAM s) +{ + uint16 width, height, depth; + + in_uint16_le(s, depth); + in_uint8s(s, 6); + + in_uint16_le(s, width); + in_uint16_le(s, height); + + DEBUG(("setting desktop size and depth to: %dx%dx%d\n", width, height, depth)); + + /* + * The server may limit depth and change the size of the desktop (for + * example when shadowing another session). + */ + if (g_server_depth != depth) + { + warning("Remote desktop does not support colour depth %d; falling back to %d\n", + g_server_depth, depth); + g_server_depth = depth; + } + if (g_width != width || g_height != height) + { + warning("Remote desktop changed from %dx%d to %dx%d.\n", g_width, g_height, + width, height); + g_width = width; + g_height = height; + ui_resize_window(); + } +} + +/* Process server capabilities */ +static void +rdp_process_server_caps(STREAM s, uint16 length) +{ + int n; + uint8 *next, *start; + uint16 ncapsets, capset_type, capset_length; + + start = s->p; + + in_uint16_le(s, ncapsets); + in_uint8s(s, 2); /* pad */ + + for (n = 0; n < ncapsets; n++) + { + if (s->p > start + length) + return; + + in_uint16_le(s, capset_type); + in_uint16_le(s, capset_length); + + next = s->p + capset_length - 4; + + switch (capset_type) + { + case RDP_CAPSET_GENERAL: + rdp_process_general_caps(s); + break; + + case RDP_CAPSET_BITMAP: + rdp_process_bitmap_caps(s); + break; + } + + s->p = next; + } +} + +/* Respond to a demand active PDU */ +static void +process_demand_active(STREAM s) +{ + uint8 type; + uint16 len_src_descriptor, len_combined_caps; + + in_uint32_le(s, g_rdp_shareid); + in_uint16_le(s, len_src_descriptor); + in_uint16_le(s, len_combined_caps); + in_uint8s(s, len_src_descriptor); + + DEBUG(("DEMAND_ACTIVE(id=0x%x)\n", g_rdp_shareid)); + rdp_process_server_caps(s, len_combined_caps); + + rdp_send_confirm_active(); + rdp_send_synchronise(); + rdp_send_control(RDP_CTL_COOPERATE); + rdp_send_control(RDP_CTL_REQUEST_CONTROL); + rdp_recv(&type); /* RDP_PDU_SYNCHRONIZE */ + rdp_recv(&type); /* RDP_CTL_COOPERATE */ + rdp_recv(&type); /* RDP_CTL_GRANT_CONTROL */ + rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0, ui_get_numlock_state(read_keyboard_state()), 0); + + if (g_use_rdp5) + { + rdp_enum_bmpcache2(); + rdp_send_fonts(3); + } + else + { + rdp_send_fonts(1); + rdp_send_fonts(2); + } + + rdp_recv(&type); /* RDP_PDU_UNKNOWN 0x28 (Fonts?) */ + reset_order_state(); +} + +/* Process a colour pointer PDU */ +void +process_colour_pointer_pdu(STREAM s) +{ + uint16 x, y, width, height, cache_idx, masklen, datalen; + uint8 *mask, *data; + HCURSOR cursor; + + in_uint16_le(s, cache_idx); + in_uint16_le(s, x); + in_uint16_le(s, y); + in_uint16_le(s, width); + in_uint16_le(s, height); + in_uint16_le(s, masklen); + in_uint16_le(s, datalen); + in_uint8p(s, data, datalen); + in_uint8p(s, mask, masklen); + cursor = ui_create_cursor(x, y, width, height, mask, data); + ui_set_cursor(cursor); + cache_put_cursor(cache_idx, cursor); +} + +/* Process a cached pointer PDU */ +void +process_cached_pointer_pdu(STREAM s) +{ + uint16 cache_idx; + + in_uint16_le(s, cache_idx); + ui_set_cursor(cache_get_cursor(cache_idx)); +} + +/* Process a system pointer PDU */ +void +process_system_pointer_pdu(STREAM s) +{ + uint16 system_pointer_type; + + in_uint16(s, system_pointer_type); + switch (system_pointer_type) + { + case RDP_NULL_POINTER: + ui_set_null_cursor(); + break; + + default: + unimpl("System pointer message 0x%x\n", system_pointer_type); + } +} + +/* Process a pointer PDU */ +static void +process_pointer_pdu(STREAM s) +{ + uint16 message_type; + uint16 x, y; + + in_uint16_le(s, message_type); + in_uint8s(s, 2); /* pad */ + + switch (message_type) + { + case RDP_POINTER_MOVE: + in_uint16_le(s, x); + in_uint16_le(s, y); + if (s_check(s)) + ui_move_pointer(x, y); + break; + + case RDP_POINTER_COLOR: + process_colour_pointer_pdu(s); + break; + + case RDP_POINTER_CACHED: + process_cached_pointer_pdu(s); + break; + + case RDP_POINTER_SYSTEM: + process_system_pointer_pdu(s); + break; + + default: + unimpl("Pointer message 0x%x\n", message_type); + } +} + +/* Process bitmap updates */ +void +process_bitmap_updates(STREAM s) +{ + uint16 num_updates; + uint16 left, top, right, bottom, width, height; + uint16 cx, cy, bpp, Bpp, compress, bufsize, size; + uint8 *data, *bmpdata; + int i; + + in_uint16_le(s, num_updates); + + for (i = 0; i < num_updates; i++) + { + in_uint16_le(s, left); + in_uint16_le(s, top); + in_uint16_le(s, right); + in_uint16_le(s, bottom); + in_uint16_le(s, width); + in_uint16_le(s, height); + in_uint16_le(s, bpp); + Bpp = (bpp + 7) / 8; + in_uint16_le(s, compress); + in_uint16_le(s, bufsize); + + cx = right - left + 1; + cy = bottom - top + 1; + + DEBUG(("BITMAP_UPDATE(l=%d,t=%d,r=%d,b=%d,w=%d,h=%d,Bpp=%d,cmp=%d)\n", + left, top, right, bottom, width, height, Bpp, compress)); + + if (!compress) + { + int y; + bmpdata = (uint8 *) xmalloc(width * height * Bpp); + for (y = 0; y < height; y++) + { + in_uint8a(s, &bmpdata[(height - y - 1) * (width * Bpp)], + width * Bpp); + } + ui_paint_bitmap(left, top, cx, cy, width, height, bmpdata); + xfree(bmpdata); + continue; + } + + + if (compress & 0x400) + { + size = bufsize; + } + else + { + in_uint8s(s, 2); /* pad */ + in_uint16_le(s, size); + in_uint8s(s, 4); /* line_size, final_size */ + } + in_uint8p(s, data, size); + bmpdata = (uint8 *) xmalloc(width * height * Bpp); + if (bitmap_decompress(bmpdata, width, height, data, size, Bpp)) + { + ui_paint_bitmap(left, top, cx, cy, width, height, bmpdata); + } + else + { + DEBUG_RDP5(("Failed to decompress data\n")); + } + + xfree(bmpdata); + } +} + +/* Process a palette update */ +void +process_palette(STREAM s) +{ + COLOURENTRY *entry; + COLOURMAP map; + RD_HCOLOURMAP hmap; + int i; + + in_uint8s(s, 2); /* pad */ + in_uint16_le(s, map.ncolours); + in_uint8s(s, 2); /* pad */ + + map.colours = (COLOURENTRY *) xmalloc(sizeof(COLOURENTRY) * map.ncolours); + + DEBUG(("PALETTE(c=%d)\n", map.ncolours)); + + for (i = 0; i < map.ncolours; i++) + { + entry = &map.colours[i]; + in_uint8(s, entry->red); + in_uint8(s, entry->green); + in_uint8(s, entry->blue); + } + + hmap = ui_create_colourmap(&map); + ui_set_colourmap(hmap); + + xfree(map.colours); +} + +/* Process an update PDU */ +static void +process_update_pdu(STREAM s) +{ + uint16 update_type, count; + + in_uint16_le(s, update_type); + + ui_begin_update(); + switch (update_type) + { + case RDP_UPDATE_ORDERS: + in_uint8s(s, 2); /* pad */ + in_uint16_le(s, count); + in_uint8s(s, 2); /* pad */ + process_orders(s, count); + break; + + case RDP_UPDATE_BITMAP: + process_bitmap_updates(s); + break; + + case RDP_UPDATE_PALETTE: + process_palette(s); + break; + + case RDP_UPDATE_SYNCHRONIZE: + break; + + default: + unimpl("update %d\n", update_type); + } + ui_end_update(); +} + +/* Process a disconnect PDU */ +void +process_disconnect_pdu(STREAM s, uint32 * ext_disc_reason) +{ + in_uint32_le(s, *ext_disc_reason); + + DEBUG(("Received disconnect PDU\n")); +} + +/* Process data PDU */ +static BOOL +process_data_pdu(STREAM s, uint32 * ext_disc_reason) +{ + uint8 data_pdu_type; + uint8 ctype; + uint16 clen; + uint32 len; + + uint32 roff, rlen; + + struct stream *ns = &(g_mppc_dict.ns); + + in_uint8s(s, 6); /* shareid, pad, streamid */ + in_uint16(s, len); + in_uint8(s, data_pdu_type); + in_uint8(s, ctype); + in_uint16(s, clen); + clen -= 18; + + if (ctype & RDP_MPPC_COMPRESSED) + { + if (len > RDP_MPPC_DICT_SIZE) + error("error decompressed packet size exceeds max\n"); + if (mppc_expand(s->p, clen, ctype, &roff, &rlen) == -1) + error("error while decompressing packet\n"); + + /* len -= 18; */ + + /* allocate memory and copy the uncompressed data into the temporary stream */ + ns->data = (uint8 *) xrealloc(ns->data, rlen); + + memcpy((ns->data), (unsigned char *) (g_mppc_dict.hist + roff), rlen); + + ns->size = rlen; + ns->end = (ns->data + ns->size); + ns->p = ns->data; + ns->rdp_hdr = ns->p; + + s = ns; + } + + switch (data_pdu_type) + { + case RDP_DATA_PDU_UPDATE: + process_update_pdu(s); + break; + + case RDP_DATA_PDU_CONTROL: + DEBUG(("Received Control PDU\n")); + break; + + case RDP_DATA_PDU_SYNCHRONISE: + DEBUG(("Received Sync PDU\n")); + break; + + case RDP_DATA_PDU_POINTER: + process_pointer_pdu(s); + break; + + case RDP_DATA_PDU_BELL: + ui_bell(); + break; + + case RDP_DATA_PDU_LOGON: + DEBUG(("Received Logon PDU\n")); + /* User logged on */ + break; + + case RDP_DATA_PDU_DISCONNECT: + process_disconnect_pdu(s, ext_disc_reason); + return True; + + default: + unimpl("data PDU %d\n", data_pdu_type); + } + return False; +} + +/* Process redirect PDU from Session Directory */ +static BOOL +process_redirect_pdu(STREAM s /*, uint32 * ext_disc_reason */ ) +{ + uint32 len; + + /* these 2 bytes are unknown, seem to be zeros */ + in_uint8s(s, 2); + + /* read connection flags */ + in_uint32_le(s, g_redirect_flags); + + /* read length of ip string */ + in_uint32_le(s, len); + + /* read ip string */ + rdp_in_unistr(s, g_redirect_server, len); + + /* read length of cookie string */ + in_uint32_le(s, len); + + /* read cookie string (plain ASCII) */ + in_uint8a(s, g_redirect_cookie, len); + g_redirect_cookie[len] = 0; + + /* read length of username string */ + in_uint32_le(s, len); + + /* read username string */ + rdp_in_unistr(s, g_redirect_username, len); + + /* read length of domain string */ + in_uint32_le(s, len); + + /* read domain string */ + rdp_in_unistr(s, g_redirect_domain, len); + + /* read length of password string */ + in_uint32_le(s, len); + + /* read password string */ + rdp_in_unistr(s, g_redirect_password, len); + + g_redirect = True; + + return True; +} + +/* Process incoming packets */ +/* nevers gets out of here till app is done */ +void +rdp_main_loop(BOOL * deactivated, uint32 * ext_disc_reason) +{ + while (rdp_loop(deactivated, ext_disc_reason)) + ; +} + +/* used in uiports and rdp_main_loop, processes the rdp packets waiting */ +BOOL +rdp_loop(BOOL * deactivated, uint32 * ext_disc_reason) +{ + uint8 type; + BOOL disc = False; /* True when a disconnect PDU was received */ + BOOL cont = True; + STREAM s; + + while (cont) + { + s = rdp_recv(&type); + if (s == NULL) + return False; + switch (type) + { + case RDP_PDU_DEMAND_ACTIVE: + process_demand_active(s); + *deactivated = False; + break; + case RDP_PDU_DEACTIVATE: + DEBUG(("RDP_PDU_DEACTIVATE\n")); + *deactivated = True; + break; + case RDP_PDU_REDIRECT: + return process_redirect_pdu(s); + break; + case RDP_PDU_DATA: + disc = process_data_pdu(s, ext_disc_reason); + break; + case 0: + break; + default: + unimpl("PDU %d\n", type); + } + if (disc) + return False; + cont = g_next_packet < s->end; + } + return True; +} + +/* Establish a connection up to the RDP layer */ +BOOL +rdp_connect(char *server, uint32 flags, char *domain, char *password, + char *command, char *directory) +{ + if (!sec_connect(server, g_username)) + return False; + + rdp_send_logon_info(flags, domain, g_username, password, command, directory); + return True; +} + +/* Establish a reconnection up to the RDP layer */ +BOOL +rdp_reconnect(char *server, uint32 flags, char *domain, char *password, + char *command, char *directory, char *cookie) +{ + if (!sec_reconnect(server)) + return False; + + rdp_send_logon_info(flags, domain, g_username, password, command, directory); + return True; +} + +/* Called during redirection to reset the state to support redirection */ +void +rdp_reset_state(void) +{ + g_next_packet = NULL; /* reset the packet information */ + g_rdp_shareid = 0; + sec_reset_state(); +} + +/* Disconnect from the RDP layer */ +void +rdp_disconnect(void) +{ + sec_disconnect(); +} diff --git a/reactos/base/applications/mstsc/rdp5.c b/reactos/base/applications/mstsc/rdp5.c new file mode 100644 index 00000000000..78d9716842c --- /dev/null +++ b/reactos/base/applications/mstsc/rdp5.c @@ -0,0 +1,121 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - RDP5 short form PDU processing + Copyright (C) Matthew Chapman 1999-2005 + Copyright (C) Erik Forsberg 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +extern uint8 *g_next_packet; + +extern RDPCOMP g_mppc_dict; + +void +rdp5_process(STREAM s) +{ + uint16 length, count, x, y; + uint8 type, ctype; + uint8 *next; + + uint32 roff, rlen; + struct stream *ns = &(g_mppc_dict.ns); + struct stream *ts; + +#if 0 + printf("RDP5 data:\n"); + hexdump(s->p, s->end - s->p); +#endif + + ui_begin_update(); + while (s->p < s->end) + { + in_uint8(s, type); + if (type & RDP5_COMPRESSED) + { + in_uint8(s, ctype); + in_uint16_le(s, length); + type ^= RDP5_COMPRESSED; + } + else + { + ctype = 0; + in_uint16_le(s, length); + } + g_next_packet = next = s->p + length; + + if (ctype & RDP_MPPC_COMPRESSED) + { + if (mppc_expand(s->p, length, ctype, &roff, &rlen) == -1) + error("error while decompressing packet\n"); + + /* allocate memory and copy the uncompressed data into the temporary stream */ + ns->data = (uint8 *) xrealloc(ns->data, rlen); + + memcpy((ns->data), (unsigned char *) (g_mppc_dict.hist + roff), rlen); + + ns->size = rlen; + ns->end = (ns->data + ns->size); + ns->p = ns->data; + ns->rdp_hdr = ns->p; + + ts = ns; + } + else + ts = s; + + switch (type) + { + case 0: /* update orders */ + in_uint16_le(ts, count); + process_orders(ts, count); + break; + case 1: /* update bitmap */ + in_uint8s(ts, 2); /* part length */ + process_bitmap_updates(ts); + break; + case 2: /* update palette */ + in_uint8s(ts, 2); /* uint16 = 2 */ + process_palette(ts); + break; + case 3: /* update synchronize */ + break; + case 5: /* null pointer */ + ui_set_null_cursor(); + break; + case 6: /* default pointer */ + break; + case 8: /* pointer position */ + in_uint16_le(ts, x); + in_uint16_le(ts, y); + if (s_check(ts)) + ui_move_pointer(x, y); + break; + case 9: /* color pointer */ + process_colour_pointer_pdu(ts); + break; + case 10: /* cached pointer */ + process_cached_pointer_pdu(ts); + break; + default: + unimpl("RDP5 opcode %d\n", type); + } + + s->p = next; + } + ui_end_update(); +} diff --git a/reactos/base/applications/mstsc/res/colors.ico b/reactos/base/applications/mstsc/res/colors.ico new file mode 100644 index 0000000000000000000000000000000000000000..da145eef8d92580f81524f7c006df62d46a72669 GIT binary patch literal 17542 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 literal 0 HcmV?d00001 diff --git a/reactos/base/applications/mstsc/res/header.bmp b/reactos/base/applications/mstsc/res/header.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a5dfb1b81a04cf02cc08a1c1e35ec0fbfb5532b2 GIT binary patch literal 86294 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 Nbz{jSUEa=2s@QQF_cXHV~wu9{mI%!Wxy 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
    a>}RO# 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%0D#3?`aA2=q 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 literal 0 HcmV?d00001 diff --git a/reactos/base/applications/mstsc/res/logon.ico b/reactos/base/applications/mstsc/res/logon.ico new file mode 100644 index 0000000000000000000000000000000000000000..dac3ff0b85d840eddcacf8a7e47f5afac10c02f2 GIT binary patch literal 17542 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 O>raaMz;Ng;7*2_DiR9x~9U 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 bI69xlbFRH*{V!k#Cnk8|{|+AQTzUT=W=+9+ literal 0 HcmV?d00001 diff --git a/reactos/base/applications/mstsc/res/remote.ico b/reactos/base/applications/mstsc/res/remote.ico new file mode 100644 index 0000000000000000000000000000000000000000..620914ac3d6a8580c6b32da4b6259536cd0a27bb GIT binary patch literal 17542 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 literal 0 HcmV?d00001 diff --git a/reactos/base/applications/mstsc/res/spectrum.bmp b/reactos/base/applications/mstsc/res/spectrum.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c9dcea8486af25ae7cac202a2e9598e598b7d162 GIT binary patch literal 6552 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 g-49{!*$5 +#include "resource.h" + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +//1 24 DISCARDABLE "manifest.xml" + +IDI_LOGON ICON "res/logon.ico" +IDI_CONN ICON "res/connection.ico" +IDI_REMOTE ICON "res/remote.ico" +IDI_COLORS ICON "res/colors.ico" + +IDB_HEADER BITMAP "res/header.bmp" +IDB_SPECT BITMAP "res/spectrum.bmp" + +#include "lang/en-US.rc" diff --git a/reactos/base/applications/mstsc/secure.c b/reactos/base/applications/mstsc/secure.c new file mode 100644 index 00000000000..5dba480e7c5 --- /dev/null +++ b/reactos/base/applications/mstsc/secure.c @@ -0,0 +1,1003 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - RDP encryption and licensing + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +//#include +//#include +//#include +//#include +//#include + +void * +ssl_sha1_info_create(void); +void +ssl_sha1_info_delete(void * sha1_info); +void +ssl_sha1_clear(void * sha1_info); +void +ssl_sha1_transform(void * sha1_info, char * data, int len); +void +ssl_sha1_complete(void * sha1_info, char * data); +void * +ssl_md5_info_create(void); +void +ssl_md5_info_delete(void * md5_info); +void * +ssl_md5_info_create(void); +void +ssl_md5_info_delete(void * md5_info); +void +ssl_md5_clear(void * md5_info); +void +ssl_md5_transform(void * md5_info, char * data, int len); +void +ssl_md5_complete(void * md5_info, char * data); +void * +ssl_rc4_info_create(void); +void +ssl_rc4_info_delete(void * rc4_info); +void +ssl_rc4_set_key(void * rc4_info, char * key, int len); +void +ssl_rc4_crypt(void * rc4_info, char * in_data, char * out_data, int len); +int +ssl_mod_exp(char* out, int out_len, char* in, int in_len, + char* mod, int mod_len, char* exp, int exp_len); + +extern char g_hostname[16]; +extern int g_width; +extern int g_height; +extern unsigned int g_keylayout; +extern int g_keyboard_type; +extern int g_keyboard_subtype; +extern int g_keyboard_functionkeys; +extern BOOL g_encryption; +extern BOOL g_licence_issued; +extern BOOL g_use_rdp5; +extern BOOL g_console_session; +extern int g_server_depth; +extern uint16 mcs_userid; +extern VCHANNEL g_channels[]; +extern unsigned int g_num_channels; + +static int rc4_key_len; +static void * rc4_decrypt_key = 0; +static void * rc4_encrypt_key = 0; +//static RSA *server_public_key; +static void * server_public_key; + +static uint8 sec_sign_key[16]; +static uint8 sec_decrypt_key[16]; +static uint8 sec_encrypt_key[16]; +static uint8 sec_decrypt_update_key[16]; +static uint8 sec_encrypt_update_key[16]; +static uint8 sec_crypted_random[SEC_MODULUS_SIZE]; + +uint16 g_server_rdp_version = 0; + +/* These values must be available to reset state - Session Directory */ +static int sec_encrypt_use_count = 0; +static int sec_decrypt_use_count = 0; + +/* + * I believe this is based on SSLv3 with the following differences: + * MAC algorithm (5.2.3.1) uses only 32-bit length in place of seq_num/type/length fields + * MAC algorithm uses SHA1 and MD5 for the two hash functions instead of one or other + * key_block algorithm (6.2.2) uses 'X', 'YY', 'ZZZ' instead of 'A', 'BB', 'CCC' + * key_block partitioning is different (16 bytes each: MAC secret, decrypt key, encrypt key) + * encryption/decryption keys updated every 4096 packets + * See http://wp.netscape.com/eng/ssl3/draft302.txt + */ + +/* + * 48-byte transformation used to generate master secret (6.1) and key material (6.2.2). + * Both SHA1 and MD5 algorithms are used. + */ +void +sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt) +{ + uint8 shasig[20]; + uint8 pad[4]; + void * sha; + void * md5; + int i; + + for (i = 0; i < 3; i++) + { + memset(pad, salt + i, i + 1); + sha = ssl_sha1_info_create(); + ssl_sha1_clear(sha); + ssl_sha1_transform(sha, pad, i + 1); + ssl_sha1_transform(sha, in, 48); + ssl_sha1_transform(sha, salt1, 32); + ssl_sha1_transform(sha, salt2, 32); + ssl_sha1_complete(sha, shasig); + ssl_sha1_info_delete(sha); + md5 = ssl_md5_info_create(); + ssl_md5_clear(md5); + ssl_md5_transform(md5, in, 48); + ssl_md5_transform(md5, shasig, 20); + ssl_md5_complete(md5, out + i * 16); + ssl_md5_info_delete(md5); + } +} + +/* + * 16-byte transformation used to generate export keys (6.2.2). + */ +void +sec_hash_16(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2) +{ + void * md5; + + md5 = ssl_md5_info_create(); + ssl_md5_clear(md5); + ssl_md5_transform(md5, in, 16); + ssl_md5_transform(md5, salt1, 32); + ssl_md5_transform(md5, salt2, 32); + ssl_md5_complete(md5, out); + ssl_md5_info_delete(md5); +} + +/* Reduce key entropy from 64 to 40 bits */ +static void +sec_make_40bit(uint8 * key) +{ + key[0] = 0xd1; + key[1] = 0x26; + key[2] = 0x9e; +} + +/* Generate encryption keys given client and server randoms */ +static void +sec_generate_keys(uint8 * client_random, uint8 * server_random, int rc4_key_size) +{ + uint8 pre_master_secret[48]; + uint8 master_secret[48]; + uint8 key_block[48]; + + /* Construct pre-master secret */ + memcpy(pre_master_secret, client_random, 24); + memcpy(pre_master_secret + 24, server_random, 24); + + /* Generate master secret and then key material */ + sec_hash_48(master_secret, pre_master_secret, client_random, server_random, 'A'); + sec_hash_48(key_block, master_secret, client_random, server_random, 'X'); + + /* First 16 bytes of key material is MAC secret */ + memcpy(sec_sign_key, key_block, 16); + + /* Generate export keys from next two blocks of 16 bytes */ + sec_hash_16(sec_decrypt_key, &key_block[16], client_random, server_random); + sec_hash_16(sec_encrypt_key, &key_block[32], client_random, server_random); + + if (rc4_key_size == 1) + { + DEBUG(("40-bit encryption enabled\n")); + sec_make_40bit(sec_sign_key); + sec_make_40bit(sec_decrypt_key); + sec_make_40bit(sec_encrypt_key); + rc4_key_len = 8; + } + else + { + DEBUG(("rc_4_key_size == %d, 128-bit encryption enabled\n", rc4_key_size)); + rc4_key_len = 16; + } + + /* Save initial RC4 keys as update keys */ + memcpy(sec_decrypt_update_key, sec_decrypt_key, 16); + memcpy(sec_encrypt_update_key, sec_encrypt_key, 16); + + /* Initialise RC4 state arrays */ + + ssl_rc4_info_delete(rc4_decrypt_key); + rc4_decrypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(rc4_decrypt_key, sec_decrypt_key, rc4_key_len); + + ssl_rc4_info_delete(rc4_encrypt_key); + rc4_encrypt_key = ssl_rc4_info_create(); + ssl_rc4_set_key(rc4_encrypt_key, sec_encrypt_key, rc4_key_len); +} + +static uint8 pad_54[40] = { + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54 +}; + +static uint8 pad_92[48] = { + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92 +}; + +/* Output a uint32 into a buffer (little-endian) */ +void +buf_out_uint32(uint8 * buffer, uint32 value) +{ + buffer[0] = (value) & 0xff; + buffer[1] = (value >> 8) & 0xff; + buffer[2] = (value >> 16) & 0xff; + buffer[3] = (value >> 24) & 0xff; +} + +/* Generate a MAC hash (5.2.3.1), using a combination of SHA1 and MD5 */ +void +sec_sign(uint8 * signature, int siglen, uint8 * session_key, int keylen, uint8 * data, int datalen) +{ + uint8 shasig[20]; + uint8 md5sig[16]; + uint8 lenhdr[4]; + void * sha; + void * md5; + + buf_out_uint32(lenhdr, datalen); + + sha = ssl_sha1_info_create(); + ssl_sha1_clear(sha); + ssl_sha1_transform(sha, session_key, keylen); + ssl_sha1_transform(sha, pad_54, 40); + ssl_sha1_transform(sha, lenhdr, 4); + ssl_sha1_transform(sha, data, datalen); + ssl_sha1_complete(sha, shasig); + ssl_sha1_info_delete(sha); + + md5 = ssl_md5_info_create(); + ssl_md5_clear(md5); + ssl_md5_transform(md5, session_key, keylen); + ssl_md5_transform(md5, pad_92, 48); + ssl_md5_transform(md5, shasig, 20); + ssl_md5_complete(md5, md5sig); + ssl_md5_info_delete(md5); + + memcpy(signature, md5sig, siglen); +} + +/* Update an encryption key */ +static void +sec_update(uint8 * key, uint8 * update_key) +{ + uint8 shasig[20]; + void * sha; + void * md5; + void * update; + + sha = ssl_sha1_info_create(); + ssl_sha1_clear(sha); + ssl_sha1_transform(sha, update_key, rc4_key_len); + ssl_sha1_transform(sha, pad_54, 40); + ssl_sha1_transform(sha, key, rc4_key_len); + ssl_sha1_complete(sha, shasig); + ssl_sha1_info_delete(sha); + + md5 = ssl_md5_info_create(); + ssl_md5_clear(md5); + ssl_md5_transform(md5, update_key, rc4_key_len); + ssl_md5_transform(md5, pad_92, 48); + ssl_md5_transform(md5, shasig, 20); + ssl_md5_complete(md5, key); + ssl_md5_info_delete(md5); + + + update = ssl_rc4_info_create(); + ssl_rc4_set_key(update, key, rc4_key_len); + ssl_rc4_crypt(update, key, key, rc4_key_len); + ssl_rc4_info_delete(update); + + if (rc4_key_len == 8) + sec_make_40bit(key); +} + +/* Encrypt data using RC4 */ +static void +sec_encrypt(uint8 * data, int length) +{ + if (sec_encrypt_use_count == 4096) + { + sec_update(sec_encrypt_key, sec_encrypt_update_key); + ssl_rc4_set_key(rc4_encrypt_key, sec_encrypt_key, rc4_key_len); + sec_encrypt_use_count = 0; + } + ssl_rc4_crypt(rc4_encrypt_key, data, data, length); + sec_encrypt_use_count++; +} + +/* Decrypt data using RC4 */ +void +sec_decrypt(uint8 * data, int length) +{ + if (sec_decrypt_use_count == 4096) + { + sec_update(sec_decrypt_key, sec_decrypt_update_key); + ssl_rc4_set_key(rc4_decrypt_key, sec_decrypt_key, rc4_key_len); + sec_decrypt_use_count = 0; + } + ssl_rc4_crypt(rc4_decrypt_key, data, data, length); + sec_decrypt_use_count++; +} + +/*static void +reverse(uint8 * p, int len) +{ + int i, j; + uint8 temp; + + for (i = 0, j = len - 1; i < j; i++, j--) + { + temp = p[i]; + p[i] = p[j]; + p[j] = temp; + } +}*/ + +/* Perform an RSA public key encryption operation */ +static void +sec_rsa_encrypt(uint8 * out, uint8 * in, int len, uint8 * modulus, uint8 * exponent) +{ + ssl_mod_exp(out, 64, in, 32, modulus, 64, exponent, 4); +/* + BN_CTX *ctx; + BIGNUM mod, exp, x, y; + uint8 inr[SEC_MODULUS_SIZE]; + int outlen; + + reverse(modulus, SEC_MODULUS_SIZE); + reverse(exponent, SEC_EXPONENT_SIZE); + memcpy(inr, in, len); + reverse(inr, len); + + ctx = BN_CTX_new(); + BN_init(&mod); + BN_init(&exp); + BN_init(&x); + BN_init(&y); + + BN_bin2bn(modulus, SEC_MODULUS_SIZE, &mod); + BN_bin2bn(exponent, SEC_EXPONENT_SIZE, &exp); + BN_bin2bn(inr, len, &x); + BN_mod_exp(&y, &x, &exp, &mod, ctx); + outlen = BN_bn2bin(&y, out); + reverse(out, outlen); + if (outlen < SEC_MODULUS_SIZE) + memset(out + outlen, 0, SEC_MODULUS_SIZE - outlen); + + BN_free(&y); + BN_clear_free(&x); + BN_free(&exp); + BN_free(&mod); + BN_CTX_free(ctx);*/ +} + +/* Initialise secure transport packet */ +STREAM +sec_init(uint32 flags, int maxlen) +{ + int hdrlen; + STREAM s; + + if (!g_licence_issued) + hdrlen = (flags & SEC_ENCRYPT) ? 12 : 4; + else + hdrlen = (flags & SEC_ENCRYPT) ? 12 : 0; + s = mcs_init(maxlen + hdrlen); + s_push_layer(s, sec_hdr, hdrlen); + + return s; +} + +/* Transmit secure transport packet over specified channel */ +void +sec_send_to_channel(STREAM s, uint32 flags, uint16 channel) +{ + int datalen; + + s_pop_layer(s, sec_hdr); + if (!g_licence_issued || (flags & SEC_ENCRYPT)) + out_uint32_le(s, flags); + + if (flags & SEC_ENCRYPT) + { + flags &= ~SEC_ENCRYPT; + datalen = s->end - s->p - 8; + +#ifdef WITH_DEBUG + DEBUG(("Sending encrypted packet:\n")); + hexdump(s->p + 8, datalen); +#endif + + sec_sign(s->p, 8, sec_sign_key, rc4_key_len, s->p + 8, datalen); + sec_encrypt(s->p + 8, datalen); + } + + mcs_send_to_channel(s, channel); +} + +/* Transmit secure transport packet */ + +void +sec_send(STREAM s, uint32 flags) +{ + sec_send_to_channel(s, flags, MCS_GLOBAL_CHANNEL); +} + + +/* Transfer the client random to the server */ +static void +sec_establish_key(void) +{ + uint32 length = SEC_MODULUS_SIZE + SEC_PADDING_SIZE; + uint32 flags = SEC_CLIENT_RANDOM; + STREAM s; + + s = sec_init(flags, 76); + + out_uint32_le(s, length); + out_uint8p(s, sec_crypted_random, SEC_MODULUS_SIZE); + out_uint8s(s, SEC_PADDING_SIZE); + + s_mark_end(s); + sec_send(s, flags); +} + +/* Output connect initial data blob */ +static void +sec_out_mcs_data(STREAM s) +{ + int hostlen = 2 * strlen(g_hostname); + int length = 158 + 76 + 12 + 4; + unsigned int i; + + if (g_num_channels > 0) + length += g_num_channels * 12 + 8; + + if (hostlen > 30) + hostlen = 30; + + /* Generic Conference Control (T.124) ConferenceCreateRequest */ + out_uint16_be(s, 5); + out_uint16_be(s, 0x14); + out_uint8(s, 0x7c); + out_uint16_be(s, 1); + + out_uint16_be(s, (length | 0x8000)); /* remaining length */ + + out_uint16_be(s, 8); /* length? */ + out_uint16_be(s, 16); + out_uint8(s, 0); + out_uint16_le(s, 0xc001); + out_uint8(s, 0); + + out_uint32_le(s, 0x61637544); /* OEM ID: "Duca", as in Ducati. */ + out_uint16_be(s, ((length - 14) | 0x8000)); /* remaining length */ + + /* Client information */ + out_uint16_le(s, SEC_TAG_CLI_INFO); + out_uint16_le(s, 212); /* length */ + out_uint16_le(s, g_use_rdp5 ? 4 : 1); /* RDP version. 1 == RDP4, 4 == RDP5. */ + out_uint16_le(s, 8); + out_uint16_le(s, g_width); + out_uint16_le(s, g_height); + out_uint16_le(s, 0xca01); + out_uint16_le(s, 0xaa03); + out_uint32_le(s, g_keylayout); + out_uint32_le(s, 2600); /* Client build. We are now 2600 compatible :-) */ + + /* Unicode name of client, padded to 32 bytes */ + rdp_out_unistr(s, g_hostname, hostlen); + out_uint8s(s, 30 - hostlen); + + /* See + http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceddk40/html/cxtsksupportingremotedesktopprotocol.asp */ + out_uint32_le(s, g_keyboard_type); + out_uint32_le(s, g_keyboard_subtype); + out_uint32_le(s, g_keyboard_functionkeys); + out_uint8s(s, 64); /* reserved? 4 + 12 doublewords */ + out_uint16_le(s, 0xca01); /* colour depth? */ + out_uint16_le(s, 1); + + out_uint32(s, 0); + out_uint8(s, g_server_depth); + out_uint16_le(s, 0x0700); + out_uint8(s, 0); + out_uint32_le(s, 1); + out_uint8s(s, 64); /* End of client info */ + + out_uint16_le(s, SEC_TAG_CLI_4); + out_uint16_le(s, 12); + out_uint32_le(s, g_console_session ? 0xb : 9); + out_uint32(s, 0); + + /* Client encryption settings */ + out_uint16_le(s, SEC_TAG_CLI_CRYPT); + out_uint16_le(s, 12); /* length */ + out_uint32_le(s, g_encryption ? 0x3 : 0); /* encryption supported, 128-bit supported */ + out_uint32(s, 0); /* Unknown */ + + DEBUG_RDP5(("g_num_channels is %d\n", g_num_channels)); + if (g_num_channels > 0) + { + out_uint16_le(s, SEC_TAG_CLI_CHANNELS); + out_uint16_le(s, g_num_channels * 12 + 8); /* length */ + out_uint32_le(s, g_num_channels); /* number of virtual channels */ + for (i = 0; i < g_num_channels; i++) + { + DEBUG_RDP5(("Requesting channel %s\n", g_channels[i].name)); + out_uint8a(s, g_channels[i].name, 8); + out_uint32_be(s, g_channels[i].flags); + } + } + + s_mark_end(s); +} + +/* Parse a public key structure */ +static BOOL +sec_parse_public_key(STREAM s, uint8 ** modulus, uint8 ** exponent) +{ + uint32 magic, modulus_len; + + in_uint32_le(s, magic); + if (magic != SEC_RSA_MAGIC) + { + error("RSA magic 0x%x\n", magic); + return False; + } + + in_uint32_le(s, modulus_len); + if (modulus_len != SEC_MODULUS_SIZE + SEC_PADDING_SIZE) + { + error("modulus len 0x%x\n", modulus_len); + return False; + } + + in_uint8s(s, 8); /* modulus_bits, unknown */ + in_uint8p(s, *exponent, SEC_EXPONENT_SIZE); + in_uint8p(s, *modulus, SEC_MODULUS_SIZE); + in_uint8s(s, SEC_PADDING_SIZE); + + return s_check(s); +} + +/* Parse a crypto information structure */ +static BOOL +sec_parse_crypt_info(STREAM s, uint32 * rc4_key_size, + uint8 ** server_random, uint8 ** modulus, uint8 ** exponent) +{ + uint32 crypt_level, random_len, rsa_info_len; + uint32 /*cacert_len, cert_len,*/ flags; + //X509 *cacert, *server_cert; + uint16 tag, length; + uint8 *next_tag, *end; + + in_uint32_le(s, *rc4_key_size); /* 1 = 40-bit, 2 = 128-bit */ + in_uint32_le(s, crypt_level); /* 1 = low, 2 = medium, 3 = high */ + if (crypt_level == 0) /* no encryption */ + return False; + in_uint32_le(s, random_len); + in_uint32_le(s, rsa_info_len); + + if (random_len != SEC_RANDOM_SIZE) + { + error("random len %d, expected %d\n", random_len, SEC_RANDOM_SIZE); + return False; + } + + in_uint8p(s, *server_random, random_len); + + /* RSA info */ + end = s->p + rsa_info_len; + if (end > s->end) + return False; + + in_uint32_le(s, flags); /* 1 = RDP4-style, 0x80000002 = X.509 */ + if (flags & 1) + { + DEBUG_RDP5(("We're going for the RDP4-style encryption\n")); + in_uint8s(s, 8); /* unknown */ + + while (s->p < end) + { + in_uint16_le(s, tag); + in_uint16_le(s, length); + + next_tag = s->p + length; + + switch (tag) + { + case SEC_TAG_PUBKEY: + if (!sec_parse_public_key(s, modulus, exponent)) + return False; + DEBUG_RDP5(("Got Public key, RDP4-style\n")); + + break; + + case SEC_TAG_KEYSIG: + /* Is this a Microsoft key that we just got? */ + /* Care factor: zero! */ + /* Actually, it would probably be a good idea to check if the public key is signed with this key, and then store this + key as a known key of the hostname. This would prevent some MITM-attacks. */ + break; + + default: + unimpl("crypt tag 0x%x\n", tag); + } + + s->p = next_tag; + } + } + else + { +#if 0 + uint32 certcount; + + DEBUG_RDP5(("We're going for the RDP5-style encryption\n")); + in_uint32_le(s, certcount); /* Number of certificates */ + + if (certcount < 2) + { + error("Server didn't send enough X509 certificates\n"); + return False; + } + + for (; certcount > 2; certcount--) + { /* ignore all the certificates between the root and the signing CA */ + uint32 ignorelen; + X509 *ignorecert; + + DEBUG_RDP5(("Ignored certs left: %d\n", certcount)); + + in_uint32_le(s, ignorelen); + DEBUG_RDP5(("Ignored Certificate length is %d\n", ignorelen)); + ignorecert = d2i_X509(NULL, &(s->p), ignorelen); + + if (ignorecert == NULL) + { /* XXX: error out? */ + DEBUG_RDP5(("got a bad cert: this will probably screw up the rest of the communication\n")); + } + +#ifdef WITH_DEBUG_RDP5 + DEBUG_RDP5(("cert #%d (ignored):\n", certcount)); + X509_print_fp(stdout, ignorecert); +#endif + } + + /* Do da funky X.509 stuffy + + "How did I find out about this? I looked up and saw a + bright light and when I came to I had a scar on my forehead + and knew about X.500" + - Peter Gutman in a early version of + http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt + */ + + in_uint32_le(s, cacert_len); + DEBUG_RDP5(("CA Certificate length is %d\n", cacert_len)); + cacert = d2i_X509(NULL, &(s->p), cacert_len); + /* Note: We don't need to move s->p here - d2i_X509 is + "kind" enough to do it for us */ + if (NULL == cacert) + { + error("Couldn't load CA Certificate from server\n"); + return False; + } + + /* Currently, we don't use the CA Certificate. + FIXME: + *) Verify the server certificate (server_cert) with the + CA certificate. + *) Store the CA Certificate with the hostname of the + server we are connecting to as key, and compare it + when we connect the next time, in order to prevent + MITM-attacks. + */ + + X509_free(cacert); + + in_uint32_le(s, cert_len); + DEBUG_RDP5(("Certificate length is %d\n", cert_len)); + server_cert = d2i_X509(NULL, &(s->p), cert_len); + if (NULL == server_cert) + { + error("Couldn't load Certificate from server\n"); + return False; + } + + in_uint8s(s, 16); /* Padding */ + + /* Note: Verifying the server certificate must be done here, + before sec_parse_public_key since we'll have to apply + serious violence to the key after this */ + + if (!sec_parse_x509_key(server_cert)) + { + DEBUG_RDP5(("Didn't parse X509 correctly\n")); + X509_free(server_cert); + return False; + } + X509_free(server_cert); + return True; /* There's some garbage here we don't care about */ +#endif + } + return s_check_end(s); +} + +/* Process crypto information blob */ +static void +sec_process_crypt_info(STREAM s) +{ + uint8 *server_random, *modulus, *exponent; + uint8 client_random[SEC_RANDOM_SIZE]; + uint32 rc4_key_size; + uint8 inr[SEC_MODULUS_SIZE]; + + if (!sec_parse_crypt_info(s, &rc4_key_size, &server_random, &modulus, &exponent)) + { + DEBUG(("Failed to parse crypt info\n")); + return; + } + + DEBUG(("Generating client random\n")); + /* Generate a client random, and hence determine encryption keys */ + /* This is what the MS client do: */ + memset(inr, 0, SEC_RANDOM_SIZE); + /* *ARIGL!* Plaintext attack, anyone? + I tried doing: + generate_random(inr); + ..but that generates connection errors now and then (yes, + "now and then". Something like 0 to 3 attempts needed before a + successful connection. Nice. Not! + */ + + generate_random(client_random); + if (NULL != server_public_key) + { /* Which means we should use + RDP5-style encryption */ +#if 0 + memcpy(inr + SEC_RANDOM_SIZE, client_random, SEC_RANDOM_SIZE); + reverse(inr + SEC_RANDOM_SIZE, SEC_RANDOM_SIZE); + + RSA_public_encrypt(SEC_MODULUS_SIZE, + inr, sec_crypted_random, server_public_key, RSA_NO_PADDING); + + reverse(sec_crypted_random, SEC_MODULUS_SIZE); + + RSA_free(server_public_key); + server_public_key = NULL; +#endif + } + else + { /* RDP4-style encryption */ + sec_rsa_encrypt(sec_crypted_random, + client_random, SEC_RANDOM_SIZE, modulus, exponent); + } + sec_generate_keys(client_random, server_random, rc4_key_size); +} + + +/* Process SRV_INFO, find RDP version supported by server */ +static void +sec_process_srv_info(STREAM s) +{ + in_uint16_le(s, g_server_rdp_version); + DEBUG_RDP5(("Server RDP version is %d\n", g_server_rdp_version)); + if (1 == g_server_rdp_version) + { + g_use_rdp5 = 0; + g_server_depth = 8; + } +} + + +/* Process connect response data blob */ +void +sec_process_mcs_data(STREAM s) +{ + uint16 tag, length; + uint8 *next_tag; + uint8 len; + + in_uint8s(s, 21); /* header (T.124 ConferenceCreateResponse) */ + in_uint8(s, len); + if (len & 0x80) + in_uint8(s, len); + + while (s->p < s->end) + { + in_uint16_le(s, tag); + in_uint16_le(s, length); + + if (length <= 4) + return; + + next_tag = s->p + length - 4; + + switch (tag) + { + case SEC_TAG_SRV_INFO: + sec_process_srv_info(s); + break; + + case SEC_TAG_SRV_CRYPT: + sec_process_crypt_info(s); + break; + + case SEC_TAG_SRV_CHANNELS: + /* FIXME: We should parse this information and + use it to map RDP5 channels to MCS + channels */ + break; + + default: + unimpl("response tag 0x%x\n", tag); + } + + s->p = next_tag; + } +} + +/* Receive secure transport packet */ +STREAM +sec_recv(uint8 * rdpver) +{ + uint32 sec_flags; + uint16 channel; + STREAM s; + + while ((s = mcs_recv(&channel, rdpver)) != NULL) + { + if (rdpver != NULL) + { + if (*rdpver != 3) + { + if (*rdpver & 0x80) + { + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); + } + return s; + } + } + if (g_encryption || !g_licence_issued) + { + in_uint32_le(s, sec_flags); + + if (sec_flags & SEC_ENCRYPT) + { + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); + } + + if (sec_flags & SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } + + if (sec_flags & 0x0400) /* SEC_REDIRECT_ENCRYPT */ + { + uint8 swapbyte; + + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); + + /* Check for a redirect packet, starts with 00 04 */ + if (s->p[0] == 0 && s->p[1] == 4) + { + /* for some reason the PDU and the length seem to be swapped. + This isn't good, but we're going to do a byte for byte + swap. So the first foure value appear as: 00 04 XX YY, + where XX YY is the little endian length. We're going to + use 04 00 as the PDU type, so after our swap this will look + like: XX YY 04 00 */ + swapbyte = s->p[0]; + s->p[0] = s->p[2]; + s->p[2] = swapbyte; + + swapbyte = s->p[1]; + s->p[1] = s->p[3]; + s->p[3] = swapbyte; + + swapbyte = s->p[2]; + s->p[2] = s->p[3]; + s->p[3] = swapbyte; + } +#ifdef WITH_DEBUG + /* warning! this debug statement will show passwords in the clear! */ + hexdump(s->p, s->end - s->p); +#endif + } + + } + + if (channel != MCS_GLOBAL_CHANNEL) + { + channel_process(s, channel); + *rdpver = 0xff; + return s; + } + + return s; + } + + return NULL; +} + +/* Establish a secure connection */ +BOOL +sec_connect(char *server, char *username) +{ + struct stream mcs_data; + + /* We exchange some RDP data during the MCS-Connect */ + mcs_data.size = 512; + mcs_data.p = mcs_data.data = (uint8 *) xmalloc(mcs_data.size); + sec_out_mcs_data(&mcs_data); + + if (!mcs_connect(server, &mcs_data, username)) + return False; + + /* sec_process_mcs_data(&mcs_data); */ + if (g_encryption) + sec_establish_key(); + xfree(mcs_data.data); + return True; +} + +/* Establish a secure connection */ +BOOL +sec_reconnect(char *server) +{ + struct stream mcs_data; + + /* We exchange some RDP data during the MCS-Connect */ + mcs_data.size = 512; + mcs_data.p = mcs_data.data = (uint8 *) xmalloc(mcs_data.size); + sec_out_mcs_data(&mcs_data); + + if (!mcs_reconnect(server, &mcs_data)) + return False; + + /* sec_process_mcs_data(&mcs_data); */ + if (g_encryption) + sec_establish_key(); + xfree(mcs_data.data); + return True; +} + +/* Disconnect a connection */ +void +sec_disconnect(void) +{ + mcs_disconnect(); +} + +/* reset the state of the sec layer */ +void +sec_reset_state(void) +{ + g_server_rdp_version = 0; + sec_encrypt_use_count = 0; + sec_decrypt_use_count = 0; + mcs_reset_state(); +} diff --git a/reactos/base/applications/mstsc/ssl_calls.c b/reactos/base/applications/mstsc/ssl_calls.c new file mode 100644 index 00000000000..376421850e1 --- /dev/null +++ b/reactos/base/applications/mstsc/ssl_calls.c @@ -0,0 +1,1644 @@ +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + xrdp: A Remote Desktop Protocol server. + Copyright (C) Jay Sorg 2004-2005 + + ssl calls + +*/ + +#include "rdesktop.h" + +#define APP_CC + +/*****************************************************************************/ +static void * g_malloc(int size, int zero) +{ + void * p; + + p = xmalloc(size); + if (zero) + { + memset(p, 0, size); + } + return p; +} + +/*****************************************************************************/ +static void g_free(void * in) +{ + xfree(in); +} + +/*****************************************************************************/ +/*****************************************************************************/ +/* rc4 stuff */ +/* An implementation of the ARC4 algorithm + * + * Copyright (C) 2001-2003 Christophe Devine + */ +struct rc4_state +{ + int x; + int y; + int m[256]; +}; + +/*****************************************************************************/ +void* APP_CC +ssl_rc4_info_create(void) +{ + return g_malloc(sizeof(struct rc4_state), 1);; +} + +/*****************************************************************************/ +void APP_CC +ssl_rc4_info_delete(void* rc4_info) +{ + g_free(rc4_info); +} + +/*****************************************************************************/ +void APP_CC +ssl_rc4_set_key(void* rc4_info, char* key, int len) +{ + int i; + int j; + int k; + int a; + int* m; + struct rc4_state* s; + + s = (struct rc4_state*)rc4_info; + s->x = 0; + s->y = 0; + m = s->m; + for (i = 0; i < 256; i++) + { + m[i] = i; + } + j = 0; + k = 0; + for (i = 0; i < 256; i++) + { + a = m[i]; + j = (unsigned char)(j + a + key[k]); + m[i] = m[j]; + m[j] = a; + k++; + if (k >= len) + { + k = 0; + } + } +} + +/*****************************************************************************/ +void APP_CC +ssl_rc4_crypt(void* rc4_info, char* in_data, char* out_data, int len) +{ + int i; + int x; + int y; + int a; + int b; + int* m; + struct rc4_state* s; + + s = (struct rc4_state*)rc4_info; + x = s->x; + y = s->y; + m = s->m; + for (i = 0; i < len; i++) + { + x = (unsigned char)(x + 1); + a = m[x]; + y = (unsigned char)(y + a); + b = m[y]; + m[x] = b; + m[y] = a; + out_data[i] = in_data[i] ^ (m[(unsigned char)(a + b)]); + } + s->x = x; + s->y = y; +} + +/*****************************************************************************/ +/*****************************************************************************/ +/* sha1 stuff */ +/* FIPS-180-1 compliant SHA-1 implementation + * + * Copyright (C) 2001-2003 Christophe Devine + */ +struct sha1_context +{ + int total[2]; + int state[5]; + char buffer[64]; +}; + +/*****************************************************************************/ +void* APP_CC +ssl_sha1_info_create(void) +{ + return g_malloc(sizeof(struct sha1_context), 1); +} + +/*****************************************************************************/ +void APP_CC +ssl_sha1_info_delete(void* sha1_info) +{ + g_free(sha1_info); +} + +/*****************************************************************************/ +void APP_CC +ssl_sha1_clear(void* sha1_info) +{ + struct sha1_context* ctx; + + ctx = (struct sha1_context*)sha1_info; + memset(ctx, 0, sizeof(struct sha1_context)); + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; +} + +#undef GET_UINT32 +#define GET_UINT32(n, b, i) \ +{ \ + (n) = ((b)[(i) + 0] << 24) | \ + ((b)[(i) + 1] << 16) | \ + ((b)[(i) + 2] << 8) | \ + ((b)[(i) + 3] << 0); \ +} + +#undef PUT_UINT32 +#define PUT_UINT32(n, b, i) \ +{ \ + (b)[(i) + 0] = ((n) >> 24); \ + (b)[(i) + 1] = ((n) >> 16); \ + (b)[(i) + 2] = ((n) >> 8); \ + (b)[(i) + 3] = ((n) >> 0); \ +} + +/*****************************************************************************/ +static void APP_CC +sha1_process(struct sha1_context* ctx, char* in_data) +{ + int temp; + int W[16]; + int A; + int B; + int C; + int D; + int E; + unsigned char* data; + + data = (unsigned char*)in_data; + + GET_UINT32(W[0], data, 0); + GET_UINT32(W[1], data, 4); + GET_UINT32(W[2], data, 8); + GET_UINT32(W[3], data, 12); + GET_UINT32(W[4], data, 16); + GET_UINT32(W[5], data, 20); + GET_UINT32(W[6], data, 24); + GET_UINT32(W[7], data, 28); + GET_UINT32(W[8], data, 32); + GET_UINT32(W[9], data, 36); + GET_UINT32(W[10], data, 40); + GET_UINT32(W[11], data, 44); + GET_UINT32(W[12], data, 48); + GET_UINT32(W[13], data, 52); + GET_UINT32(W[14], data, 56); + GET_UINT32(W[15], data, 60); + +#define S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define R(t) \ +( \ + temp = W[(t - 3) & 0x0F] ^ \ + W[(t - 8) & 0x0F] ^ \ + W[(t - 14) & 0x0F] ^ \ + W[(t - 0) & 0x0F], \ + (W[t & 0x0F] = S(temp, 1)) \ +) + +#undef P +#define P(a, b, c, d, e, x) \ +{ \ + e += S(a, 5) + F(b, c, d) + K + x; \ + b = S(b, 30); \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + +#define F(x, y, z) (z ^ (x & (y ^ z))) +#define K 0x5A827999 + + P(A, B, C, D, E, W[0]); + P(E, A, B, C, D, W[1]); + P(D, E, A, B, C, W[2]); + P(C, D, E, A, B, W[3]); + P(B, C, D, E, A, W[4]); + P(A, B, C, D, E, W[5]); + P(E, A, B, C, D, W[6]); + P(D, E, A, B, C, W[7]); + P(C, D, E, A, B, W[8]); + P(B, C, D, E, A, W[9]); + P(A, B, C, D, E, W[10]); + P(E, A, B, C, D, W[11]); + P(D, E, A, B, C, W[12]); + P(C, D, E, A, B, W[13]); + P(B, C, D, E, A, W[14]); + P(A, B, C, D, E, W[15]); + P(E, A, B, C, D, R(16)); + P(D, E, A, B, C, R(17)); + P(C, D, E, A, B, R(18)); + P(B, C, D, E, A, R(19)); + +#undef K +#undef F + +#define F(x, y, z) (x ^ y ^ z) +#define K 0x6ED9EBA1 + + P(A, B, C, D, E, R(20)); + P(E, A, B, C, D, R(21)); + P(D, E, A, B, C, R(22)); + P(C, D, E, A, B, R(23)); + P(B, C, D, E, A, R(24)); + P(A, B, C, D, E, R(25)); + P(E, A, B, C, D, R(26)); + P(D, E, A, B, C, R(27)); + P(C, D, E, A, B, R(28)); + P(B, C, D, E, A, R(29)); + P(A, B, C, D, E, R(30)); + P(E, A, B, C, D, R(31)); + P(D, E, A, B, C, R(32)); + P(C, D, E, A, B, R(33)); + P(B, C, D, E, A, R(34)); + P(A, B, C, D, E, R(35)); + P(E, A, B, C, D, R(36)); + P(D, E, A, B, C, R(37)); + P(C, D, E, A, B, R(38)); + P(B, C, D, E, A, R(39)); + +#undef K +#undef F + +#define F(x, y, z) ((x & y) | (z & (x | y))) +#define K 0x8F1BBCDC + + P(A, B, C, D, E, R(40)); + P(E, A, B, C, D, R(41)); + P(D, E, A, B, C, R(42)); + P(C, D, E, A, B, R(43)); + P(B, C, D, E, A, R(44)); + P(A, B, C, D, E, R(45)); + P(E, A, B, C, D, R(46)); + P(D, E, A, B, C, R(47)); + P(C, D, E, A, B, R(48)); + P(B, C, D, E, A, R(49)); + P(A, B, C, D, E, R(50)); + P(E, A, B, C, D, R(51)); + P(D, E, A, B, C, R(52)); + P(C, D, E, A, B, R(53)); + P(B, C, D, E, A, R(54)); + P(A, B, C, D, E, R(55)); + P(E, A, B, C, D, R(56)); + P(D, E, A, B, C, R(57)); + P(C, D, E, A, B, R(58)); + P(B, C, D, E, A, R(59)); + +#undef K +#undef F + +#define F(x, y, z) (x ^ y ^ z) +#define K 0xCA62C1D6 + + P(A, B, C, D, E, R(60)); + P(E, A, B, C, D, R(61)); + P(D, E, A, B, C, R(62)); + P(C, D, E, A, B, R(63)); + P(B, C, D, E, A, R(64)); + P(A, B, C, D, E, R(65)); + P(E, A, B, C, D, R(66)); + P(D, E, A, B, C, R(67)); + P(C, D, E, A, B, R(68)); + P(B, C, D, E, A, R(69)); + P(A, B, C, D, E, R(70)); + P(E, A, B, C, D, R(71)); + P(D, E, A, B, C, R(72)); + P(C, D, E, A, B, R(73)); + P(B, C, D, E, A, R(74)); + P(A, B, C, D, E, R(75)); + P(E, A, B, C, D, R(76)); + P(D, E, A, B, C, R(77)); + P(C, D, E, A, B, R(78)); + P(B, C, D, E, A, R(79)); + +#undef K +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; +} + +/*****************************************************************************/ +void APP_CC +ssl_sha1_transform(void* sha1_info, char* data, int len) +{ + int left; + int fill; + struct sha1_context* ctx; + + ctx = (struct sha1_context*)sha1_info; + if (len == 0) + { + return; + } + left = ctx->total[0] & 0x3F; + fill = 64 - left; + ctx->total[0] += len; + ctx->total[0] &= 0xFFFFFFFF; + if (ctx->total[0] < len) + { + ctx->total[1]++; + } + if (left && (len >= fill)) + { + memcpy(ctx->buffer + left, data, fill); + sha1_process(ctx, ctx->buffer); + len -= fill; + data += fill; + left = 0; + } + while (len >= 64) + { + sha1_process(ctx, data); + len -= 64; + data += 64; + } + if (len != 0) + { + memcpy(ctx->buffer + left, data, len); + } +} + +static unsigned char sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*****************************************************************************/ +void APP_CC +ssl_sha1_complete(void* sha1_info, char* data) +{ + int last; + int padn; + int high; + int low; + char msglen[8]; + struct sha1_context* ctx; + + ctx = (struct sha1_context*)sha1_info; + high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); + low = (ctx->total[0] << 3); + PUT_UINT32(high, msglen, 0); + PUT_UINT32(low, msglen, 4); + last = ctx->total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + ssl_sha1_transform(ctx, sha1_padding, padn); + ssl_sha1_transform(ctx, msglen, 8); + PUT_UINT32(ctx->state[0], data, 0); + PUT_UINT32(ctx->state[1], data, 4); + PUT_UINT32(ctx->state[2], data, 8); + PUT_UINT32(ctx->state[3], data, 12); + PUT_UINT32(ctx->state[4], data, 16); +} + +/*****************************************************************************/ +/*****************************************************************************/ +/* md5 stuff */ +/* RFC 1321 compliant MD5 implementation + * + * Copyright (C) 2001-2003 Christophe Devine + */ + +struct md5_context +{ + int total[2]; + int state[4]; + char buffer[64]; +}; + +/*****************************************************************************/ +void* APP_CC +ssl_md5_info_create(void) +{ + return g_malloc(sizeof(struct md5_context), 1); +} + +/*****************************************************************************/ +void APP_CC +ssl_md5_info_delete(void* md5_info) +{ + g_free(md5_info); +} + +/*****************************************************************************/ +void APP_CC +ssl_md5_clear(void* md5_info) +{ + struct md5_context* ctx; + + ctx = (struct md5_context*)md5_info; + memset(ctx, 0, sizeof(struct md5_context)); + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +#undef GET_UINT32 +#define GET_UINT32(n, b, i) \ +{ \ + (n) = ((b)[(i) + 0] << 0) | \ + ((b)[(i) + 1] << 8) | \ + ((b)[(i) + 2] << 16) | \ + ((b)[(i) + 3] << 24); \ +} + +#undef PUT_UINT32 +#define PUT_UINT32(n, b, i) \ +{ \ + (b)[(i) + 0] = ((n) >> 0); \ + (b)[(i) + 1] = ((n) >> 8); \ + (b)[(i) + 2] = ((n) >> 16); \ + (b)[(i) + 3] = ((n) >> 24); \ +} + +/*****************************************************************************/ +static void +md5_process(struct md5_context* ctx, char* in_data) +{ + int X[16]; + int A; + int B; + int C; + int D; + unsigned char* data; + + data = (unsigned char*)in_data; + GET_UINT32(X[0], data, 0); + GET_UINT32(X[1], data, 4); + GET_UINT32(X[2], data, 8); + GET_UINT32(X[3], data, 12); + GET_UINT32(X[4], data, 16); + GET_UINT32(X[5], data, 20); + GET_UINT32(X[6], data, 24); + GET_UINT32(X[7], data, 28); + GET_UINT32(X[8], data, 32); + GET_UINT32(X[9], data, 36); + GET_UINT32(X[10], data, 40); + GET_UINT32(X[11], data, 44); + GET_UINT32(X[12], data, 48); + GET_UINT32(X[13], data, 52); + GET_UINT32(X[14], data, 56); + GET_UINT32(X[15], data, 60); + +#define S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#undef P +#define P(a, b, c, d, k, s, t) \ +{ \ + a += F(b, c, d) + X[k] + t; \ + a = S(a, s) + b; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + +#define F(x, y, z) (z ^ (x & (y ^ z))) + + P(A, B, C, D, 0, 7, 0xD76AA478); + P(D, A, B, C, 1, 12, 0xE8C7B756); + P(C, D, A, B, 2, 17, 0x242070DB); + P(B, C, D, A, 3, 22, 0xC1BDCEEE); + P(A, B, C, D, 4, 7, 0xF57C0FAF); + P(D, A, B, C, 5, 12, 0x4787C62A); + P(C, D, A, B, 6, 17, 0xA8304613); + P(B, C, D, A, 7, 22, 0xFD469501); + P(A, B, C, D, 8, 7, 0x698098D8); + P(D, A, B, C, 9, 12, 0x8B44F7AF); + P(C, D, A, B, 10, 17, 0xFFFF5BB1); + P(B, C, D, A, 11, 22, 0x895CD7BE); + P(A, B, C, D, 12, 7, 0x6B901122); + P(D, A, B, C, 13, 12, 0xFD987193); + P(C, D, A, B, 14, 17, 0xA679438E); + P(B, C, D, A, 15, 22, 0x49B40821); + +#undef F + +#define F(x, y, z) (y ^ (z & (x ^ y))) + + P(A, B, C, D, 1, 5, 0xF61E2562); + P(D, A, B, C, 6, 9, 0xC040B340); + P(C, D, A, B, 11, 14, 0x265E5A51); + P(B, C, D, A, 0, 20, 0xE9B6C7AA); + P(A, B, C, D, 5, 5, 0xD62F105D); + P(D, A, B, C, 10, 9, 0x02441453); + P(C, D, A, B, 15, 14, 0xD8A1E681); + P(B, C, D, A, 4, 20, 0xE7D3FBC8); + P(A, B, C, D, 9, 5, 0x21E1CDE6); + P(D, A, B, C, 14, 9, 0xC33707D6); + P(C, D, A, B, 3, 14, 0xF4D50D87); + P(B, C, D, A, 8, 20, 0x455A14ED); + P(A, B, C, D, 13, 5, 0xA9E3E905); + P(D, A, B, C, 2, 9, 0xFCEFA3F8); + P(C, D, A, B, 7, 14, 0x676F02D9); + P(B, C, D, A, 12, 20, 0x8D2A4C8A); + +#undef F + +#define F(x, y, z) (x ^ y ^ z) + + P(A, B, C, D, 5, 4, 0xFFFA3942); + P(D, A, B, C, 8, 11, 0x8771F681); + P(C, D, A, B, 11, 16, 0x6D9D6122); + P(B, C, D, A, 14, 23, 0xFDE5380C); + P(A, B, C, D, 1, 4, 0xA4BEEA44); + P(D, A, B, C, 4, 11, 0x4BDECFA9); + P(C, D, A, B, 7, 16, 0xF6BB4B60); + P(B, C, D, A, 10, 23, 0xBEBFBC70); + P(A, B, C, D, 13, 4, 0x289B7EC6); + P(D, A, B, C, 0, 11, 0xEAA127FA); + P(C, D, A, B, 3, 16, 0xD4EF3085); + P(B, C, D, A, 6, 23, 0x04881D05); + P(A, B, C, D, 9, 4, 0xD9D4D039); + P(D, A, B, C, 12, 11, 0xE6DB99E5); + P(C, D, A, B, 15, 16, 0x1FA27CF8); + P(B, C, D, A, 2, 23, 0xC4AC5665); + +#undef F + +#define F(x, y, z) (y ^ (x | ~z)) + + P(A, B, C, D, 0, 6, 0xF4292244); + P(D, A, B, C, 7, 10, 0x432AFF97); + P(C, D, A, B, 14, 15, 0xAB9423A7); + P(B, C, D, A, 5, 21, 0xFC93A039); + P(A, B, C, D, 12, 6, 0x655B59C3); + P(D, A, B, C, 3, 10, 0x8F0CCC92); + P(C, D, A, B, 10, 15, 0xFFEFF47D); + P(B, C, D, A, 1, 21, 0x85845DD1); + P(A, B, C, D, 8, 6, 0x6FA87E4F); + P(D, A, B, C, 15, 10, 0xFE2CE6E0); + P(C, D, A, B, 6, 15, 0xA3014314); + P(B, C, D, A, 13, 21, 0x4E0811A1); + P(A, B, C, D, 4, 6, 0xF7537E82); + P(D, A, B, C, 11, 10, 0xBD3AF235); + P(C, D, A, B, 2, 15, 0x2AD7D2BB); + P(B, C, D, A, 9, 21, 0xEB86D391); + +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; +} + +/*****************************************************************************/ +void APP_CC +ssl_md5_transform(void* md5_info, char* data, int len) +{ + int left; + int fill; + struct md5_context* ctx; + + ctx = (struct md5_context*)md5_info; + if (len == 0) + { + return; + } + left = ctx->total[0] & 0x3F; + fill = 64 - left; + ctx->total[0] += len; + ctx->total[0] &= 0xFFFFFFFF; + if (ctx->total[0] < len) + { + ctx->total[1]++; + } + if (left && (len >= fill)) + { + memcpy(ctx->buffer + left, data, fill); + md5_process(ctx, ctx->buffer); + len -= fill; + data += fill; + left = 0; + } + while (len >= 64) + { + md5_process(ctx, data); + len -= 64; + data += 64; + } + if (len != 0) + { + memcpy(ctx->buffer + left, data, len); + } +} + +static unsigned char md5_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*****************************************************************************/ +void APP_CC +ssl_md5_complete(void* md5_info, char* data) +{ + int last; + int padn; + int high; + int low; + char msglen[8]; + struct md5_context* ctx; + + ctx = (struct md5_context*)md5_info; + high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); + low = (ctx->total[0] << 3); + PUT_UINT32(low, msglen, 0); + PUT_UINT32(high, msglen, 4); + last = ctx->total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + ssl_md5_transform(ctx, md5_padding, padn); + ssl_md5_transform(ctx, msglen, 8); + PUT_UINT32(ctx->state[0], data, 0); + PUT_UINT32(ctx->state[1], data, 4); + PUT_UINT32(ctx->state[2], data, 8); + PUT_UINT32(ctx->state[3], data, 12); +} + +/*****************************************************************************/ +/*****************************************************************************/ +/* big number stuff */ +/******************* SHORT COPYRIGHT NOTICE************************* +This source code is part of the BigDigits multiple-precision +arithmetic library Version 1.0 originally written by David Ireland, +copyright (c) 2001 D.I. Management Services Pty Limited, all rights +reserved. It is provided "as is" with no warranties. You may use +this software under the terms of the full copyright notice +"bigdigitsCopyright.txt" that should have been included with +this library. To obtain a copy send an email to + or visit . +This notice must be retained in any copy. +****************** END OF COPYRIGHT NOTICE*************************/ +/************************* COPYRIGHT NOTICE************************* +This source code is part of the BigDigits multiple-precision +arithmetic library Version 1.0 originally written by David Ireland, +copyright (c) 2001 D.I. Management Services Pty Limited, all rights +reserved. You are permitted to use compiled versions of this code as +part of your own executable files and to distribute unlimited copies +of such executable files for any purposes including commercial ones +provided you keep the copyright notices intact in the source code +and that you ensure that the following characters remain in any +object or executable files you distribute: + +"Contains multiple-precision arithmetic code originally written +by David Ireland, copyright (c) 2001 by D.I. Management Services +Pty Limited , and is used with permission." + +David Ireland and DI Management Services Pty Limited make no +representations concerning either the merchantability of this +software or the suitability of this software for any particular +purpose. It is provided "as is" without express or implied warranty +of any kind. + +Please forward any comments and bug reports to . +The latest version of the source code can be downloaded from +www.di-mgt.com.au/crypto.html. +****************** END OF COPYRIGHT NOTICE*************************/ + +typedef unsigned int DIGIT_T; +#define HIBITMASK 0x80000000 +#define MAX_DIG_LEN 51 +#define MAX_DIGIT 0xffffffff +#define BITS_PER_DIGIT 32 +#define MAX_HALF_DIGIT 0xffff +#define B_J (MAX_HALF_DIGIT + 1) +#define LOHALF(x) ((DIGIT_T)((x) & 0xffff)) +#define HIHALF(x) ((DIGIT_T)((x) >> 16 & 0xffff)) +#define TOHIGH(x) ((DIGIT_T)((x) << 16)) + +#define mpNEXTBITMASK(mask, n) \ +{ \ + if (mask == 1) \ + { \ + mask = HIBITMASK; \ + n--; \ + } \ + else \ + { \ + mask >>= 1; \ + } \ +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +mpAdd(DIGIT_T* w, DIGIT_T* u, DIGIT_T* v, unsigned int ndigits) +{ + /* Calculates w = u + v + where w, u, v are multiprecision integers of ndigits each + Returns carry if overflow. Carry = 0 or 1. + + Ref: Knuth Vol 2 Ch 4.3.1 p 266 Algorithm A. */ + DIGIT_T k; + unsigned int j; + + /* Step A1. Initialise */ + k = 0; + for (j = 0; j < ndigits; j++) + { + /* Step A2. Add digits w_j = (u_j + v_j + k) + Set k = 1 if carry (overflow) occurs */ + w[j] = u[j] + k; + if (w[j] < k) + { + k = 1; + } + else + { + k = 0; + } + w[j] += v[j]; + if (w[j] < v[j]) + { + k++; + } + } /* Step A3. Loop on j */ + return k; /* w_n = k */ +} + +/*****************************************************************************/ +static void APP_CC +mpSetDigit(DIGIT_T* a, DIGIT_T d, unsigned int ndigits) +{ /* Sets a = d where d is a single digit */ + unsigned int i; + + for (i = 1; i < ndigits; i++) + { + a[i] = 0; + } + a[0] = d; +} + +/*****************************************************************************/ +static int APP_CC +mpCompare(DIGIT_T* a, DIGIT_T* b, unsigned int ndigits) +{ + /* Returns sign of (a - b) */ + if (ndigits == 0) + { + return 0; + } + while (ndigits--) + { + if (a[ndigits] > b[ndigits]) + { + return 1; /* GT */ + } + if (a[ndigits] < b[ndigits]) + { + return -1; /* LT */ + } + } + return 0; /* EQ */ +} + +/*****************************************************************************/ +static void APP_CC +mpSetZero(DIGIT_T* a, unsigned int ndigits) +{ /* Sets a = 0 */ + unsigned int i; + + for (i = 0; i < ndigits; i++) + { + a[i] = 0; + } +} + +/*****************************************************************************/ +static void APP_CC +mpSetEqual(DIGIT_T* a, DIGIT_T* b, unsigned int ndigits) +{ /* Sets a = b */ + unsigned int i; + + for (i = 0; i < ndigits; i++) + { + a[i] = b[i]; + } +} + +/*****************************************************************************/ +static unsigned int APP_CC +mpSizeof(DIGIT_T* a, unsigned int ndigits) +{ /* Returns size of significant digits in a */ + while (ndigits--) + { + if (a[ndigits] != 0) + { + return (++ndigits); + } + } + return 0; +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +mpShiftLeft(DIGIT_T* a, DIGIT_T* b, unsigned int x, unsigned int ndigits) +{ /* Computes a = b << x */ + unsigned int i; + unsigned int y; + DIGIT_T mask; + DIGIT_T carry; + DIGIT_T nextcarry; + + /* Check input - NB unspecified result */ + if (x >= BITS_PER_DIGIT) + { + return 0; + } + /* Construct mask */ + mask = HIBITMASK; + for (i = 1; i < x; i++) + { + mask = (mask >> 1) | mask; + } + if (x == 0) + { + mask = 0x0; + } + y = BITS_PER_DIGIT - x; + carry = 0; + for (i = 0; i < ndigits; i++) + { + nextcarry = (b[i] & mask) >> y; + a[i] = b[i] << x | carry; + carry = nextcarry; + } + return carry; +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +mpShiftRight(DIGIT_T* a, DIGIT_T* b, unsigned int x, unsigned int ndigits) +{ /* Computes a = b >> x */ + unsigned int i; + unsigned int y; + DIGIT_T mask; + DIGIT_T carry; + DIGIT_T nextcarry; + + /* Check input - NB unspecified result */ + if (x >= BITS_PER_DIGIT) + { + return 0; + } + /* Construct mask */ + mask = 0x1; + for (i = 1; i < x; i++) + { + mask = (mask << 1) | mask; + } + if (x == 0) + { + mask = 0x0; + } + y = BITS_PER_DIGIT - x; + carry = 0; + i = ndigits; + while (i--) + { + nextcarry = (b[i] & mask) << y; + a[i] = b[i] >> x | carry; + carry = nextcarry; + } + return carry; +} + +/*****************************************************************************/ +static void APP_CC +spMultSub(DIGIT_T* uu, DIGIT_T qhat, DIGIT_T v1, DIGIT_T v0) +{ + /* Compute uu = uu - q(v1v0) + where uu = u3u2u1u0, u3 = 0 + and u_n, v_n are all half-digits + even though v1, v2 are passed as full digits. */ + DIGIT_T p0; + DIGIT_T p1; + DIGIT_T t; + + p0 = qhat * v0; + p1 = qhat * v1; + t = p0 + TOHIGH(LOHALF(p1)); + uu[0] -= t; + if (uu[0] > MAX_DIGIT - t) + { + uu[1]--; /* Borrow */ + } + uu[1] -= HIHALF(p1); +} + +/*****************************************************************************/ +static int APP_CC +spMultiply(DIGIT_T* p, DIGIT_T x, DIGIT_T y) +{ /* Computes p = x * y */ + /* Ref: Arbitrary Precision Computation + http://numbers.computation.free.fr/Constants/constants.html + + high p1 p0 low + +--------+--------+--------+--------+ + | x1*y1 | x0*y0 | + +--------+--------+--------+--------+ + +-+--------+--------+ + |1| (x0*y1 + x1*y1) | + +-+--------+--------+ + ^carry from adding (x0*y1+x1*y1) together + +-+ + |1|< carry from adding LOHALF t + +-+ to high half of p0 */ + DIGIT_T x0; + DIGIT_T y0; + DIGIT_T x1; + DIGIT_T y1; + DIGIT_T t; + DIGIT_T u; + DIGIT_T carry; + + /* Split each x,y into two halves + x = x0 + B * x1 + y = y0 + B * y1 + where B = 2^16, half the digit size + Product is + xy = x0y0 + B(x0y1 + x1y0) + B^2(x1y1) */ + + x0 = LOHALF(x); + x1 = HIHALF(x); + y0 = LOHALF(y); + y1 = HIHALF(y); + + /* Calc low part - no carry */ + p[0] = x0 * y0; + + /* Calc middle part */ + t = x0 * y1; + u = x1 * y0; + t += u; + if (t < u) + { + carry = 1; + } + else + { + carry = 0; + } + /* This carry will go to high half of p[1] + + high half of t into low half of p[1] */ + carry = TOHIGH(carry) + HIHALF(t); + + /* Add low half of t to high half of p[0] */ + t = TOHIGH(t); + p[0] += t; + if (p[0] < t) + { + carry++; + } + + p[1] = x1 * y1; + p[1] += carry; + + return 0; +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +spDivide(DIGIT_T* q, DIGIT_T* r, DIGIT_T* u, DIGIT_T v) +{ /* Computes quotient q = u / v, remainder r = u mod v + where u is a double digit + and q, v, r are single precision digits. + Returns high digit of quotient (max value is 1) + Assumes normalised such that v1 >= b/2 + where b is size of HALF_DIGIT + i.e. the most significant bit of v should be one + + In terms of half-digits in Knuth notation: + (q2q1q0) = (u4u3u2u1u0) / (v1v0) + (r1r0) = (u4u3u2u1u0) mod (v1v0) + for m = 2, n = 2 where u4 = 0 + q2 is either 0 or 1. + We set q = (q1q0) and return q2 as "overflow' */ + DIGIT_T qhat; + DIGIT_T rhat; + DIGIT_T t; + DIGIT_T v0; + DIGIT_T v1; + DIGIT_T u0; + DIGIT_T u1; + DIGIT_T u2; + DIGIT_T u3; + DIGIT_T uu[2]; + DIGIT_T q2; + + /* Check for normalisation */ + if (!(v & HIBITMASK)) + { + *q = *r = 0; + return MAX_DIGIT; + } + + /* Split up into half-digits */ + v0 = LOHALF(v); + v1 = HIHALF(v); + u0 = LOHALF(u[0]); + u1 = HIHALF(u[0]); + u2 = LOHALF(u[1]); + u3 = HIHALF(u[1]); + + /* Do three rounds of Knuth Algorithm D Vol 2 p272 */ + + /* ROUND 1. Set j = 2 and calculate q2 */ + /* Estimate qhat = (u4u3)/v1 = 0 or 1 + then set (u4u3u2) -= qhat(v1v0) + where u4 = 0. */ + qhat = u3 / v1; + if (qhat > 0) + { + rhat = u3 - qhat * v1; + t = TOHIGH(rhat) | u2; + if (qhat * v0 > t) + { + qhat--; + } + } + uu[1] = 0; /* (u4) */ + uu[0] = u[1]; /* (u3u2) */ + if (qhat > 0) + { + /* (u4u3u2) -= qhat(v1v0) where u4 = 0 */ + spMultSub(uu, qhat, v1, v0); + if (HIHALF(uu[1]) != 0) + { /* Add back */ + qhat--; + uu[0] += v; + uu[1] = 0; + } + } + q2 = qhat; + /* ROUND 2. Set j = 1 and calculate q1 */ + /* Estimate qhat = (u3u2) / v1 + then set (u3u2u1) -= qhat(v1v0) */ + t = uu[0]; + qhat = t / v1; + rhat = t - qhat * v1; + /* Test on v0 */ + t = TOHIGH(rhat) | u1; + if ((qhat == B_J) || (qhat * v0 > t)) + { + qhat--; + rhat += v1; + t = TOHIGH(rhat) | u1; + if ((rhat < B_J) && (qhat * v0 > t)) + { + qhat--; + } + } + /* Multiply and subtract + (u3u2u1)' = (u3u2u1) - qhat(v1v0) */ + uu[1] = HIHALF(uu[0]); /* (0u3) */ + uu[0] = TOHIGH(LOHALF(uu[0])) | u1; /* (u2u1) */ + spMultSub(uu, qhat, v1, v0); + if (HIHALF(uu[1]) != 0) + { /* Add back */ + qhat--; + uu[0] += v; + uu[1] = 0; + } + /* q1 = qhat */ + *q = TOHIGH(qhat); + /* ROUND 3. Set j = 0 and calculate q0 */ + /* Estimate qhat = (u2u1) / v1 + then set (u2u1u0) -= qhat(v1v0) */ + t = uu[0]; + qhat = t / v1; + rhat = t - qhat * v1; + /* Test on v0 */ + t = TOHIGH(rhat) | u0; + if ((qhat == B_J) || (qhat * v0 > t)) + { + qhat--; + rhat += v1; + t = TOHIGH(rhat) | u0; + if ((rhat < B_J) && (qhat * v0 > t)) + { + qhat--; + } + } + /* Multiply and subtract + (u2u1u0)" = (u2u1u0)' - qhat(v1v0) */ + uu[1] = HIHALF(uu[0]); /* (0u2) */ + uu[0] = TOHIGH(LOHALF(uu[0])) | u0; /* (u1u0) */ + spMultSub(uu, qhat, v1, v0); + if (HIHALF(uu[1]) != 0) + { /* Add back */ + qhat--; + uu[0] += v; + uu[1] = 0; + } + /* q0 = qhat */ + *q |= LOHALF(qhat); + /* Remainder is in (u1u0) i.e. uu[0] */ + *r = uu[0]; + return q2; +} + +/*****************************************************************************/ +static int APP_CC +QhatTooBig(DIGIT_T qhat, DIGIT_T rhat, DIGIT_T vn2, DIGIT_T ujn2) +{ /* Returns true if Qhat is too big + i.e. if (Qhat * Vn-2) > (b.Rhat + Uj+n-2) */ + DIGIT_T t[2]; + + spMultiply(t, qhat, vn2); + if (t[1] < rhat) + { + return 0; + } + else if (t[1] > rhat) + { + return 1; + } + else if (t[0] > ujn2) + { + return 1; + } + return 0; +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +mpShortDiv(DIGIT_T* q, DIGIT_T* u, DIGIT_T v, unsigned int ndigits) +{ + /* Calculates quotient q = u div v + Returns remainder r = u mod v + where q, u are multiprecision integers of ndigits each + and d, v are single precision digits. + + Makes no assumptions about normalisation. + + Ref: Knuth Vol 2 Ch 4.3.1 Exercise 16 p625 */ + unsigned int j; + unsigned int shift; + DIGIT_T t[2]; + DIGIT_T r; + DIGIT_T bitmask; + DIGIT_T overflow; + DIGIT_T* uu; + + if (ndigits == 0) + { + return 0; + } + if (v == 0) + { + return 0; /* Divide by zero error */ + } + /* Normalise first */ + /* Requires high bit of V + to be set, so find most signif. bit then shift left, + i.e. d = 2^shift, u' = u * d, v' = v * d. */ + bitmask = HIBITMASK; + for (shift = 0; shift < BITS_PER_DIGIT; shift++) + { + if (v & bitmask) + { + break; + } + bitmask >>= 1; + } + v <<= shift; + overflow = mpShiftLeft(q, u, shift, ndigits); + uu = q; + /* Step S1 - modified for extra digit. */ + r = overflow; /* New digit Un */ + j = ndigits; + while (j--) + { + /* Step S2. */ + t[1] = r; + t[0] = uu[j]; + overflow = spDivide(&q[j], &r, t, v); + } + /* Unnormalise */ + r >>= shift; + return r; +} + +/*****************************************************************************/ +static DIGIT_T APP_CC +mpMultSub(DIGIT_T wn, DIGIT_T* w, DIGIT_T* v, DIGIT_T q, unsigned int n) +{ /* Compute w = w - qv + where w = (WnW[n-1]...W[0]) + return modified Wn. */ + DIGIT_T k; + DIGIT_T t[2]; + unsigned int i; + + if (q == 0) /* No change */ + { + return wn; + } + k = 0; + for (i = 0; i < n; i++) + { + spMultiply(t, q, v[i]); + w[i] -= k; + if (w[i] > MAX_DIGIT - k) + { + k = 1; + } + else + { + k = 0; + } + w[i] -= t[0]; + if (w[i] > MAX_DIGIT - t[0]) + { + k++; + } + k += t[1]; + } + /* Cope with Wn not stored in array w[0..n-1] */ + wn -= k; + return wn; +} + +/*****************************************************************************/ +static int APP_CC +mpDivide(DIGIT_T* q, DIGIT_T* r, DIGIT_T* u, unsigned int udigits, + DIGIT_T* v, unsigned int vdigits) +{ /* Computes quotient q = u / v and remainder r = u mod v + where q, r, u are multiple precision digits + all of udigits and the divisor v is vdigits. + + Ref: Knuth Vol 2 Ch 4.3.1 p 272 Algorithm D. + + Do without extra storage space, i.e. use r[] for + normalised u[], unnormalise v[] at end, and cope with + extra digit Uj+n added to u after normalisation. + + WARNING: this trashes q and r first, so cannot do + u = u / v or v = u mod v. */ + unsigned int shift; + int n; + int m; + int j; + int qhatOK; + int cmp; + DIGIT_T bitmask; + DIGIT_T overflow; + DIGIT_T qhat; + DIGIT_T rhat; + DIGIT_T t[2]; + DIGIT_T* uu; + DIGIT_T* ww; + + /* Clear q and r */ + mpSetZero(q, udigits); + mpSetZero(r, udigits); + /* Work out exact sizes of u and v */ + n = (int)mpSizeof(v, vdigits); + m = (int)mpSizeof(u, udigits); + m -= n; + /* Catch special cases */ + if (n == 0) + { + return -1; /* Error: divide by zero */ + } + if (n == 1) + { /* Use short division instead */ + r[0] = mpShortDiv(q, u, v[0], udigits); + return 0; + } + if (m < 0) + { /* v > u, so just set q = 0 and r = u */ + mpSetEqual(r, u, udigits); + return 0; + } + if (m == 0) + { /* u and v are the same length */ + cmp = mpCompare(u, v, (unsigned int)n); + if (cmp < 0) + { /* v > u, as above */ + mpSetEqual(r, u, udigits); + return 0; + } + else if (cmp == 0) + { /* v == u, so set q = 1 and r = 0 */ + mpSetDigit(q, 1, udigits); + return 0; + } + } + /* In Knuth notation, we have: + Given + u = (Um+n-1 ... U1U0) + v = (Vn-1 ... V1V0) + Compute + q = u/v = (QmQm-1 ... Q0) + r = u mod v = (Rn-1 ... R1R0) */ + /* Step D1. Normalise */ + /* Requires high bit of Vn-1 + to be set, so find most signif. bit then shift left, + i.e. d = 2^shift, u' = u * d, v' = v * d. */ + bitmask = HIBITMASK; + for (shift = 0; shift < BITS_PER_DIGIT; shift++) + { + if (v[n - 1] & bitmask) + { + break; + } + bitmask >>= 1; + } + /* Normalise v in situ - NB only shift non-zero digits */ + overflow = mpShiftLeft(v, v, shift, n); + /* Copy normalised dividend u*d into r */ + overflow = mpShiftLeft(r, u, shift, n + m); + uu = r; /* Use ptr to keep notation constant */ + t[0] = overflow; /* New digit Um+n */ + /* Step D2. Initialise j. Set j = m */ + for (j = m; j >= 0; j--) + { + /* Step D3. Calculate Qhat = (b.Uj+n + Uj+n-1)/Vn-1 */ + qhatOK = 0; + t[1] = t[0]; /* This is Uj+n */ + t[0] = uu[j+n-1]; + overflow = spDivide(&qhat, &rhat, t, v[n - 1]); + /* Test Qhat */ + if (overflow) + { /* Qhat = b */ + qhat = MAX_DIGIT; + rhat = uu[j + n - 1]; + rhat += v[n - 1]; + if (rhat < v[n - 1]) /* Overflow */ + { + qhatOK = 1; + } + } + if (!qhatOK && QhatTooBig(qhat, rhat, v[n - 2], uu[j + n - 2])) + { /* Qhat.Vn-2 > b.Rhat + Uj+n-2 */ + qhat--; + rhat += v[n - 1]; + if (!(rhat < v[n - 1])) + { + if (QhatTooBig(qhat, rhat, v[n - 2], uu[j + n - 2])) + { + qhat--; + } + } + } + /* Step D4. Multiply and subtract */ + ww = &uu[j]; + overflow = mpMultSub(t[1], ww, v, qhat, (unsigned int)n); + /* Step D5. Test remainder. Set Qj = Qhat */ + q[j] = qhat; + if (overflow) + { /* Step D6. Add back if D4 was negative */ + q[j]--; + overflow = mpAdd(ww, ww, v, (unsigned int)n); + } + t[0] = uu[j + n - 1]; /* Uj+n on next round */ + } /* Step D7. Loop on j */ + /* Clear high digits in uu */ + for (j = n; j < m+n; j++) + { + uu[j] = 0; + } + /* Step D8. Unnormalise. */ + mpShiftRight(r, r, shift, n); + mpShiftRight(v, v, shift, n); + return 0; +} + +/*****************************************************************************/ +static int APP_CC +mpModulo(DIGIT_T* r, DIGIT_T* u, unsigned int udigits, + DIGIT_T* v, unsigned int vdigits) +{ + /* Calculates r = u mod v + where r, v are multiprecision integers of length vdigits + and u is a multiprecision integer of length udigits. + r may overlap v. + + Note that r here is only vdigits long, + whereas in mpDivide it is udigits long. + + Use remainder from mpDivide function. */ + /* Double-length temp variable for divide fn */ + DIGIT_T qq[MAX_DIG_LEN * 2]; + /* Use a double-length temp for r to allow overlap of r and v */ + DIGIT_T rr[MAX_DIG_LEN * 2]; + + /* rr[2n] = u[2n] mod v[n] */ + mpDivide(qq, rr, u, udigits, v, vdigits); + mpSetEqual(r, rr, vdigits); + mpSetZero(rr, udigits); + mpSetZero(qq, udigits); + return 0; +} + +/*****************************************************************************/ +static int APP_CC +mpMultiply(DIGIT_T* w, DIGIT_T* u, DIGIT_T* v, unsigned int ndigits) +{ + /* Computes product w = u * v + where u, v are multiprecision integers of ndigits each + and w is a multiprecision integer of 2*ndigits + Ref: Knuth Vol 2 Ch 4.3.1 p 268 Algorithm M. */ + DIGIT_T k; + DIGIT_T t[2]; + unsigned int i; + unsigned int j; + unsigned int m; + unsigned int n; + + n = ndigits; + m = n; + /* Step M1. Initialise */ + for (i = 0; i < 2 * m; i++) + { + w[i] = 0; + } + for (j = 0; j < n; j++) + { + /* Step M2. Zero multiplier? */ + if (v[j] == 0) + { + w[j + m] = 0; + } + else + { + /* Step M3. Initialise i */ + k = 0; + for (i = 0; i < m; i++) + { + /* Step M4. Multiply and add */ + /* t = u_i * v_j + w_(i+j) + k */ + spMultiply(t, u[i], v[j]); + t[0] += k; + if (t[0] < k) + { + t[1]++; + } + t[0] += w[i + j]; + if (t[0] < w[i+j]) + { + t[1]++; + } + w[i + j] = t[0]; + k = t[1]; + } + /* Step M5. Loop on i, set w_(j+m) = k */ + w[j + m] = k; + } + } /* Step M6. Loop on j */ + return 0; +} + +/*****************************************************************************/ +static int APP_CC +mpModMult(DIGIT_T* a, DIGIT_T* x, DIGIT_T* y, + DIGIT_T* m, unsigned int ndigits) +{ /* Computes a = (x * y) mod m */ + /* Double-length temp variable */ + DIGIT_T p[MAX_DIG_LEN * 2]; + + /* Calc p[2n] = x * y */ + mpMultiply(p, x, y, ndigits); + /* Then modulo */ + mpModulo(a, p, ndigits * 2, m, ndigits); + mpSetZero(p, ndigits * 2); + return 0; +} + +/*****************************************************************************/ +int APP_CC +ssl_mod_exp(char* out, int out_len, char* in, int in_len, + char* mod, int mod_len, char* exp, int exp_len) +{ + /* Computes y = x ^ e mod m */ + /* Binary left-to-right method */ + DIGIT_T mask; + DIGIT_T* e; + DIGIT_T* x; + DIGIT_T* y; + DIGIT_T* m; + unsigned int n; + int max_size; + char* l_out; + char* l_in; + char* l_mod; + char* l_exp; + + if (in_len > out_len || in_len == 0 || + out_len == 0 || mod_len == 0 || exp_len == 0) + { + return 0; + } + max_size = out_len; + if (in_len > max_size) + { + max_size = in_len; + } + if (mod_len > max_size) + { + max_size = mod_len; + } + if (exp_len > max_size) + { + max_size = exp_len; + } + l_out = (char*)g_malloc(max_size, 1); + l_in = (char*)g_malloc(max_size, 1); + l_mod = (char*)g_malloc(max_size, 1); + l_exp = (char*)g_malloc(max_size, 1); + memcpy(l_in, in, in_len); + memcpy(l_mod, mod, mod_len); + memcpy(l_exp, exp, exp_len); + e = (DIGIT_T*)l_exp; + x = (DIGIT_T*)l_in; + y = (DIGIT_T*)l_out; + m = (DIGIT_T*)l_mod; + /* Find second-most significant bit in e */ + n = mpSizeof(e, max_size / 4); + for (mask = HIBITMASK; mask > 0; mask >>= 1) + { + if (e[n - 1] & mask) + { + break; + } + } + mpNEXTBITMASK(mask, n); + /* Set y = x */ + mpSetEqual(y, x, max_size / 4); + /* For bit j = k - 2 downto 0 step -1 */ + while (n) + { + mpModMult(y, y, y, m, max_size / 4); /* Square */ + if (e[n - 1] & mask) + { + mpModMult(y, y, x, m, max_size / 4); /* Multiply */ + } + /* Move to next bit */ + mpNEXTBITMASK(mask, n); + } + memcpy(out, l_out, out_len); + g_free(l_out); + g_free(l_in); + g_free(l_mod); + g_free(l_exp); + return out_len; +} + diff --git a/reactos/base/applications/mstsc/tcp.c b/reactos/base/applications/mstsc/tcp.c new file mode 100644 index 00000000000..597bacd1942 --- /dev/null +++ b/reactos/base/applications/mstsc/tcp.c @@ -0,0 +1,309 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - TCP layer + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _WIN32 +#include /* select read write close */ +#include /* socket connect setsockopt */ +#include /* timeval */ +#include /* gethostbyname */ +#include /* sockaddr_in */ +#include /* TCP_NODELAY */ +#include /* inet_addr */ +#include /* errno */ +#endif /* _WIN32 */ + +#include "rdesktop.h" + +#ifdef _WIN32 +#define socklen_t int +#define TCP_CLOSE(_sck) closesocket(_sck) +#define TCP_STRERROR "tcp error" +#define TCP_SLEEP(_n) Sleep(_n) +#define TCP_BLOCKS (WSAGetLastError() == WSAEWOULDBLOCK) +#else /* _WIN32 */ +#define TCP_CLOSE(_sck) close(_sck) +#define TCP_STRERROR strerror(errno) +#define TCP_SLEEP(_n) sleep(_n) +#define TCP_BLOCKS (errno == EWOULDBLOCK) +#endif /* _WIN32 */ + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +static int sock; +static struct stream in; +static struct stream out; +int g_tcp_port_rdp = TCP_PORT_RDP; + +/* Initialise TCP transport data packet */ +STREAM +tcp_init(uint32 maxlen) +{ + if (maxlen > out.size) + { + out.data = (uint8 *) xrealloc(out.data, maxlen); + out.size = maxlen; + } + + out.p = out.data; + out.end = out.data + out.size; + return &out; +} + +/* Send TCP transport data packet */ +void +tcp_send(STREAM s) +{ + int length = s->end - s->data; + int sent, total = 0; + + while (total < length) + { + sent = send(sock, s->data + total, length - total, 0); + if (sent <= 0) + { + if (sent == -1 && TCP_BLOCKS) + { + TCP_SLEEP(0); + sent = 0; + } + else + { + error("send: %s\n", TCP_STRERROR); + return; + } + } + total += sent; + } +} + +/* Receive a message on the TCP layer */ +STREAM +tcp_recv(STREAM s, uint32 length) +{ + unsigned int new_length, end_offset, p_offset; + int rcvd = 0; + + if (s == NULL) + { + /* read into "new" stream */ + if (length > in.size) + { + in.data = (uint8 *) xrealloc(in.data, length); + in.size = length; + } + in.end = in.p = in.data; + s = ∈ + } + else + { + /* append to existing stream */ + new_length = (s->end - s->data) + length; + if (new_length > s->size) + { + p_offset = s->p - s->data; + end_offset = s->end - s->data; + s->data = (uint8 *) xrealloc(s->data, new_length); + s->size = new_length; + s->p = s->data + p_offset; + s->end = s->data + end_offset; + } + } + + while (length > 0) + { + if (!ui_select(sock)) + /* User quit */ + return NULL; + + rcvd = recv(sock, s->end, length, 0); + if (rcvd < 0) + { + if (rcvd == -1 && TCP_BLOCKS) + { + TCP_SLEEP(0); + rcvd = 0; + } + else + { + error("recv: %s\n", TCP_STRERROR); + return NULL; + } + } + else if (rcvd == 0) + { + error("Connection closed\n"); + return NULL; + } + + s->end += rcvd; + length -= rcvd; + } + + return s; +} + +/* Establish a connection on the TCP layer */ +BOOL +tcp_connect(char *server) +{ + int true_value = 1; + +#ifdef IPv6 + + int n; + struct addrinfo hints, *res, *ressave; + char tcp_port_rdp_s[10]; + + snprintf(tcp_port_rdp_s, 10, "%d", g_tcp_port_rdp); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((n = getaddrinfo(server, tcp_port_rdp_s, &hints, &res))) + { + error("getaddrinfo: %s\n", gai_strerror(n)); + return False; + } + + ressave = res; + sock = -1; + while (res) + { + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (!(sock < 0)) + { + if (connect(sock, res->ai_addr, res->ai_addrlen) == 0) + break; + TCP_CLOSE(sock); + sock = -1; + } + res = res->ai_next; + } + freeaddrinfo(ressave); + + if (sock == -1) + { + error("%s: unable to connect\n", server); + return False; + } + +#else /* no IPv6 support */ + + struct hostent *nslookup; + struct sockaddr_in servaddr; + + if ((nslookup = gethostbyname(server)) != NULL) + { + memcpy(&servaddr.sin_addr, nslookup->h_addr, sizeof(servaddr.sin_addr)); + } + else if ((servaddr.sin_addr.s_addr = inet_addr(server)) == INADDR_NONE) + { + error("%s: unable to resolve host\n", server); + return False; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + error("socket: %s\n", TCP_STRERROR); + return False; + } + + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons((uint16) g_tcp_port_rdp); + + if (connect(sock, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) < 0) + { + error("connect: %s\n", TCP_STRERROR); + TCP_CLOSE(sock); + return False; + } + +#endif /* IPv6 */ + + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *) &true_value, sizeof(true_value)); + + in.size = 4096; + in.data = (uint8 *) xmalloc(in.size); + + out.size = 4096; + out.data = (uint8 *) xmalloc(out.size); + + return True; +} + +/* Disconnect on the TCP layer */ +void +tcp_disconnect(void) +{ + TCP_CLOSE(sock); +} + +char * +tcp_get_address() +{ + static char ipaddr[32]; + struct sockaddr_in sockaddr; + socklen_t len = sizeof(sockaddr); + if (getsockname(sock, (struct sockaddr *) &sockaddr, &len) == 0) + { + unsigned char *ip = (unsigned char *) &sockaddr.sin_addr; + sprintf(ipaddr, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + } + else + strcpy(ipaddr, "127.0.0.1"); + return ipaddr; +} + +/* reset the state of the tcp layer */ +/* Support for Session Directory */ +void +tcp_reset_state(void) +{ + sock = -1; /* reset socket */ + + /* Clear the incoming stream */ + if (in.data != NULL) + xfree(in.data); + in.p = NULL; + in.end = NULL; + in.data = NULL; + in.size = 0; + in.iso_hdr = NULL; + in.mcs_hdr = NULL; + in.sec_hdr = NULL; + in.rdp_hdr = NULL; + in.channel_hdr = NULL; + + /* Clear the outgoing stream */ + if (out.data != NULL) + xfree(out.data); + out.p = NULL; + out.end = NULL; + out.data = NULL; + out.size = 0; + out.iso_hdr = NULL; + out.mcs_hdr = NULL; + out.sec_hdr = NULL; + out.rdp_hdr = NULL; + out.channel_hdr = NULL; +} diff --git a/reactos/base/applications/mstsc/types.h b/reactos/base/applications/mstsc/types.h new file mode 100644 index 00000000000..f8b62821ea8 --- /dev/null +++ b/reactos/base/applications/mstsc/types.h @@ -0,0 +1,270 @@ +/* + rdesktop: A Remote Desktop Protocol client. + Common data types + Copyright (C) Matthew Chapman 1999-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +typedef int BOOL; + +#ifndef True +#define True (1) +#define False (0) +#endif + +typedef unsigned char uint8; +typedef signed char sint8; +typedef unsigned short uint16; +typedef signed short sint16; +typedef unsigned int uint32; +typedef signed int sint32; + +typedef void *RD_HBITMAP; +typedef void *RD_HGLYPH; +typedef void *RD_HCOLOURMAP; +typedef void *RD_HCURSOR; + +typedef struct _RD_POINT +{ + sint16 x, y; +} +RD_POINT; + +typedef struct _COLOURENTRY +{ + uint8 red; + uint8 green; + uint8 blue; + +} +COLOURENTRY; + +typedef struct _COLOURMAP +{ + uint16 ncolours; + COLOURENTRY *colours; + +} +COLOURMAP; + +typedef struct _BOUNDS +{ + sint16 left; + sint16 top; + sint16 right; + sint16 bottom; + +} +BOUNDS; + +typedef struct _PEN +{ + uint8 style; + uint8 width; + uint32 colour; + +} +PEN; + +typedef struct _BRUSH +{ + uint8 xorigin; + uint8 yorigin; + uint8 style; + uint8 pattern[8]; + +} +BRUSH; + +typedef struct _FONTGLYPH +{ + sint16 offset; + sint16 baseline; + uint16 width; + uint16 height; + RD_HBITMAP pixmap; + +} +FONTGLYPH; + +typedef struct _DATABLOB +{ + void *data; + int size; + +} +DATABLOB; + +typedef struct _key_translation +{ + /* For normal scancode translations */ + uint8 scancode; + uint16 modifiers; + /* For sequences. If keysym is nonzero, the fields above are not used. */ + uint32 seq_keysym; /* Really KeySym */ + struct _key_translation *next; +} +key_translation; + +typedef struct _VCHANNEL +{ + uint16 mcs_id; + char name[8]; + uint32 flags; + struct stream in; + void (*process) (STREAM); +} +VCHANNEL; + +/* PSTCACHE */ +typedef uint8 HASH_KEY[8]; + +/* Header for an entry in the persistent bitmap cache file */ +typedef struct _PSTCACHE_CELLHEADER +{ + HASH_KEY key; + uint8 width, height; + uint16 length; + uint32 stamp; +} +CELLHEADER; + +#define MAX_CBSIZE 256 + +/* RDPSND */ +typedef struct +{ + uint16 wFormatTag; + uint16 nChannels; + uint32 nSamplesPerSec; + uint32 nAvgBytesPerSec; + uint16 nBlockAlign; + uint16 wBitsPerSample; + uint16 cbSize; + uint8 cb[MAX_CBSIZE]; +} RD_WAVEFORMATEX; + +typedef struct _RDPCOMP +{ + uint32 roff; + uint8 hist[RDP_MPPC_DICT_SIZE]; + struct stream ns; +} +RDPCOMP; + +/* RDPDR */ +typedef uint32 RD_NTSTATUS; +typedef uint32 RD_NTHANDLE; + +typedef struct _DEVICE_FNS +{ + RD_NTSTATUS(*create) (uint32 device, uint32 desired_access, uint32 share_mode, + uint32 create_disposition, uint32 flags_and_attributes, char *filename, + RD_NTHANDLE * handle); + RD_NTSTATUS(*close) (RD_NTHANDLE handle); + RD_NTSTATUS(*read) (RD_NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, + uint32 * result); + RD_NTSTATUS(*write) (RD_NTHANDLE handle, uint8 * data, uint32 length, uint32 offset, + uint32 * result); + RD_NTSTATUS(*device_control) (RD_NTHANDLE handle, uint32 request, STREAM in, STREAM out); +} +DEVICE_FNS; + + +typedef struct rdpdr_device_info +{ + uint32 device_type; + RD_NTHANDLE handle; + char name[8]; + char *local_path; + void *pdevice_data; +} +RDPDR_DEVICE; + +typedef struct rdpdr_serial_device_info +{ + int dtr; + int rts; + uint32 control, xonoff, onlimit, offlimit; + uint32 baud_rate, + queue_in_size, + queue_out_size, + wait_mask, + read_interval_timeout, + read_total_timeout_multiplier, + read_total_timeout_constant, + write_total_timeout_multiplier, write_total_timeout_constant, posix_wait_mask; + uint8 stop_bits, parity, word_length; + uint8 chars[6]; + struct termios *ptermios, *pold_termios; + int event_txempty, event_cts, event_dsr, event_rlsd, event_pending; +} +SERIAL_DEVICE; + +typedef struct rdpdr_parallel_device_info +{ + char *driver, *printer; + uint32 queue_in_size, + queue_out_size, + wait_mask, + read_interval_timeout, + read_total_timeout_multiplier, + read_total_timeout_constant, + write_total_timeout_multiplier, + write_total_timeout_constant, posix_wait_mask, bloblen; + uint8 *blob; +} +PARALLEL_DEVICE; + +typedef struct rdpdr_printer_info +{ + FILE *printer_fp; + char *driver, *printer; + uint32 bloblen; + uint8 *blob; + BOOL default_printer; +} +PRINTER; + +typedef struct notify_data +{ + time_t modify_time; + time_t status_time; + time_t total_time; + unsigned int num_entries; +} +NOTIFY; + +#ifndef PATH_MAX +#define PATH_MAX 256 +#endif + +#ifndef _WIN32 +typedef struct fileinfo +{ + uint32 device_id, flags_and_attributes, accessmask; + char path[PATH_MAX]; + DIR *pdir; + struct dirent *pdirent; + char pattern[PATH_MAX]; + BOOL delete_on_close; + NOTIFY notify; + uint32 info_class; +} +FILEINFO; +#endif + +typedef BOOL(*str_handle_lines_t) (const char *line, void *data); diff --git a/reactos/base/applications/mstsc/uimain.c b/reactos/base/applications/mstsc/uimain.c new file mode 100644 index 00000000000..d445514ed47 --- /dev/null +++ b/reactos/base/applications/mstsc/uimain.c @@ -0,0 +1,979 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Main ui file + Copyright (C) Jay Sorg 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" +#include "bsops.h" + +char g_username[256] = ""; +char g_hostname[256] = ""; +char g_servername[256] = ""; +char g_password[256] = ""; +char g_shell[256] = ""; +char g_directory[256] = ""; +char g_domain[256] = ""; +BOOL g_desktop_save = False; /* desktop save order */ +BOOL g_polygon_ellipse_orders = False; /* polygon / ellipse orders */ +BOOL g_bitmap_compression = True; +uint32 g_rdp5_performanceflags = + RDP5_NO_WALLPAPER | RDP5_NO_FULLWINDOWDRAG | RDP5_NO_MENUANIMATIONS; +BOOL g_bitmap_cache_persist_enable = False; +BOOL g_bitmap_cache_precache = True; +BOOL g_bitmap_cache = True; +BOOL g_encryption = True; +int g_server_depth = 8; +BOOL g_use_rdp5 = False; +int g_width = 800; +int g_height = 600; +uint32 g_keylayout = 0x409; /* Defaults to US keyboard layout */ +int g_keyboard_type = 0x4; /* Defaults to US keyboard layout */ +int g_keyboard_subtype = 0x0; /* Defaults to US keyboard layout */ +int g_keyboard_functionkeys = 0xc; /* Defaults to US keyboard layout */ +BOOL g_console_session = False; + +/* can't be static, hardware file or bsops need these */ +int g_tcp_sck = 0; +int pal_entries[256]; + +/* Session Directory redirection */ +BOOL g_redirect = False; +char g_redirect_server[64]; +char g_redirect_domain[16]; +char g_redirect_password[64]; +char g_redirect_username[64]; +char g_redirect_cookie[128]; +uint32 g_redirect_flags = 0; + +extern int g_tcp_port_rdp; + +static int g_deactivated = 0; +static uint32 g_ext_disc_reason = 0; + +struct bitmap +{ + uint8 * data; + uint32 width; + uint32 height; +}; + +/* in ui specific file eg win32.c, qt.c, df.c, ... */ +int +mi_create_window(void); +int +mi_main_loop(void); +void +mi_error(char * msg); +void +mi_warning(char * msg); +void +mi_paint_rect(char * data, int width, int height, int x, int y, int cx, int cy); +void +mi_begin_update(void); +void +mi_end_update(void); +void +mi_fill_rect(int x, int y, int cx, int cy, int colour); +void +mi_screen_copy(int x, int y, int cx, int cy, int srcx, int srcy); +void +mi_set_clip(int x, int y, int cx, int cy); +void +mi_reset_clip(void); +void +mi_line(int x1, int y1, int x2, int y2, int colour); +mi_create_cursor(unsigned int x, unsigned int y, + int width, int height, + unsigned char * andmask, unsigned char * xormask); +void +mi_destroy_cursor(void * cursor); +void +mi_set_cursor(void * cursor); +void +mi_set_null_cursor(void); +int +mi_read_keyboard_state(void); + +/*****************************************************************************/ +/* put part of the screen from the backing store to the display */ +void +ui_invalidate(int x, int y, int cx, int cy) +{ + char * data; + + if (cx < 1 || cy < 1) + { + return; + } + if (bs_warp_coords(&x, &y, &cx, &cy, 0, 0)) + { + cx = (cx + 3) & ~3; + data = (char *) xmalloc(cx * cy * 4); + bs_copy_box(data, x, y, cx, cy, cx * ((g_server_depth + 7) / 8)); + mi_paint_rect(data, cx, cy, x, y, cx, cy); + xfree(data); + } +} + +/*****************************************************************************/ +void +ui_bell(void) +{ +} + +/*****************************************************************************/ +int +ui_select(int in) +{ + if (g_tcp_sck == 0) + { + g_tcp_sck = in; + } + return 1; +} + +/*****************************************************************************/ +void * +ui_create_cursor(uint32 x, uint32 y, + int width, int height, + uint8 * andmask, uint8 * xormask) +{ + int i; + int j; + char am[32 * 4]; + char xm[32 * 4]; + + if (width != 32 || height != 32) + { + return 0; + } + memset(am, 0, 32 * 4); + memset(xm, 0, 32 * 4); + for (i = 0; i < 32; i++) + { + for (j = 0; j < 32; j++) + { + if (bs_is_pixel_on(andmask, j, i, 32, 1)) + { + bs_set_pixel_on(am, j, 31 - i, 32, 1, 1); + } + if (bs_is_pixel_on(xormask, j, i, 32, 24)) + { + bs_set_pixel_on(xm, j, 31 - i, 32, 1, 1); + } + } + } + return (void *) mi_create_cursor(x, y, width, height, am, xm); +} + +/*****************************************************************************/ +void +ui_destroy_cursor(void * cursor) +{ + mi_destroy_cursor(cursor); +} + +/*****************************************************************************/ +void +ui_set_cursor(void * cursor) +{ + mi_set_cursor(cursor); +} + +/*****************************************************************************/ +void +ui_set_null_cursor(void) +{ + mi_set_null_cursor(); +} + +/*****************************************************************************/ +void * +ui_create_glyph(int width, int height, uint8 * data) +{ + int i; + int j; + char * glyph_data; + struct bitmap * the_glyph; + + glyph_data = (char *) xmalloc(width * height); + memset(glyph_data, 0, width * height); + the_glyph = (struct bitmap *) xmalloc(sizeof(struct bitmap)); + memset(the_glyph, 0, sizeof(struct bitmap)); + the_glyph->width = width; + the_glyph->height = height; + the_glyph->data = glyph_data; + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + if (bs_is_pixel_on(data, j, i, width, 1)) + { + bs_set_pixel_on(glyph_data, j, i, width, 8, 255); + } + } + } + return the_glyph; +} + +/*****************************************************************************/ +void +ui_destroy_glyph(void * glyph) +{ + struct bitmap * the_glyph; + + the_glyph = glyph; + if (the_glyph != 0) + { + xfree(the_glyph->data); + } + xfree(the_glyph); +} + +/*****************************************************************************/ +void * +ui_create_bitmap(int width, int height, uint8 * data) +{ + struct bitmap * b; + int size; + + size = width * height * ((g_server_depth + 7) / 8); + b = (struct bitmap *) xmalloc(sizeof(struct bitmap)); + b->data = (uint8 *) xmalloc(size); + memcpy(b->data, data, size); + b->width = width; + b->height = height; + return b; +} + +/*****************************************************************************/ +void +ui_destroy_bitmap(void * bmp) +{ + struct bitmap * b; + + b = (struct bitmap *) bmp; + if (b != 0) + { + xfree(b->data); + } + xfree(b); +} + +/*****************************************************************************/ +void +ui_paint_bitmap(int x, int y, int cx, int cy, + int width, int height, uint8 * data) +{ + struct bitmap b; + + b.width = width; + b.height = height; + b.data = data; + ui_memblt(12, x, y, cx, cy, &b, 0, 0); +} + +/*****************************************************************************/ +void +ui_set_clip(int x, int y, int cx, int cy) +{ + bs_set_clip(x, y, cx, cy); + mi_set_clip(x, y, cx, cy); +} + +/*****************************************************************************/ +void +ui_reset_clip(void) +{ + bs_reset_clip(); + mi_reset_clip(); +} + +/*****************************************************************************/ +void * +ui_create_colourmap(COLOURMAP * colours) +{ + int i; + int n; + + n = MIN(256, colours->ncolours); + memset(pal_entries, 0, sizeof(pal_entries)); + for (i = 0; i < n; i++) + { + pal_entries[i] = (colours->colours[i].red << 16) | + (colours->colours[i].green << 8) | + colours->colours[i].blue; + } + return 0; +} + +/*****************************************************************************/ +void +ui_set_colourmap(void * map) +{ +} + +/*****************************************************************************/ +static void +draw_glyph(int x, int y, void * glyph, int fgcolor) +{ + struct bitmap * b; + + b = glyph; + bs_draw_glyph(x, y, b->data, b->width, b->height, fgcolor); +} + +/*****************************************************************************/ +#define DO_GLYPH(ttext,idx) \ +{ \ + glyph = cache_get_font(font, ttext[idx]); \ + if (!(flags & TEXT2_IMPLICIT_X)) \ + { \ + xyoffset = ttext[++idx]; \ + if (xyoffset & 0x80) \ + { \ + if (flags & TEXT2_VERTICAL) \ + { \ + y += ttext[idx + 1] | (ttext[idx + 2] << 8); \ + } \ + else \ + { \ + x += ttext[idx + 1] | (ttext[idx + 2] << 8); \ + } \ + idx += 2; \ + } \ + else \ + { \ + if (flags & TEXT2_VERTICAL) \ + { \ + y += xyoffset; \ + } \ + else \ + { \ + x += xyoffset; \ + } \ + } \ + } \ + if (glyph != NULL) \ + { \ + draw_glyph(x + glyph->offset, y + glyph->baseline, glyph->pixmap, \ + fgcolour); \ + if (flags & TEXT2_IMPLICIT_X) \ + { \ + x += glyph->width; \ + } \ + } \ +} + +/*****************************************************************************/ +void +ui_draw_text(uint8 font, uint8 flags, uint8 opcode, int mixmode, + int x, int y, + int clipx, int clipy, int clipcx, int clipcy, + int boxx, int boxy, int boxcx, int boxcy, BRUSH * brush, + int bgcolour, int fgcolour, uint8 * text, uint8 length) +{ + int i; + int j; + int xyoffset; + DATABLOB * entry; + FONTGLYPH * glyph; + + if (boxx + boxcx > g_width) + { + boxcx = g_width - boxx; + } + if (boxcx > 1) + { + bs_rect(boxx, boxy, boxcx, boxcy, bgcolour, 0xc); + } + else + { + if (mixmode == MIX_OPAQUE) + { + bs_rect(clipx, clipy, clipcx, clipcy, bgcolour, 0xc); + } + } + /* Paint text, character by character */ + for (i = 0; i < length;) + { + switch (text[i]) + { + case 0xff: + if (i + 2 < length) + { + cache_put_text(text[i + 1], text, text[i + 2]); + } + else + { + error("this shouldn't be happening\n"); + exit(1); + } + /* this will move pointer from start to first character after */ + /* FF command */ + length -= i + 3; + text = &(text[i + 3]); + i = 0; + break; + case 0xfe: + entry = cache_get_text(text[i + 1]); + if (entry != NULL) + { + if ((((uint8 *) (entry->data))[1] == 0) && + (!(flags & TEXT2_IMPLICIT_X))) + { + if (flags & TEXT2_VERTICAL) + { + y += text[i + 2]; + } + else + { + x += text[i + 2]; + } + } + for (j = 0; j < entry->size; j++) + { + DO_GLYPH(((uint8 *) (entry->data)), j); + } + } + if (i + 2 < length) + { + i += 3; + } + else + { + i += 2; + } + length -= i; + /* this will move pointer from start to first character after */ + /* FE command */ + text = &(text[i]); + i = 0; + break; + default: + DO_GLYPH(text, i); + i++; + break; + } + } + if (boxcx > 1) + { + ui_invalidate(boxx, boxy, boxcx, boxcy); + } + else + { + ui_invalidate(clipx, clipy, clipcx, clipcy); + } +} + +/*****************************************************************************/ +void +ui_line(uint8 opcode, int startx, int starty, int endx, int endy, + PEN * pen) +{ + int x; + int y; + int cx; + int cy; + + bs_line(opcode, startx, starty, endx, endy, pen->width, pen->style, + pen->colour); + if (pen->style == 0 && pen->width < 2 && opcode == 12) + { + mi_line(startx, starty, endx, endy, pen->colour); + } + else + { + x = MIN(startx, endx); + y = MIN(starty, endy); + cx = (MAX(startx, endx) + 1) - x; + cy = (MAX(starty, endy) + 1) - y; + ui_invalidate(x, y, cx, cy); + } +} + +/*****************************************************************************/ +void +ui_triblt(uint8 opcode, int x, int y, int cx, int cy, + void * src, int srcx, int srcy, + BRUSH* brush, int bgcolour, int fgcolour) +{ + /* not used */ +} + +/*****************************************************************************/ +void +ui_memblt(uint8 opcode, int x, int y, int cx, int cy, + void * src, int srcx, int srcy) +{ + struct bitmap* b; + + b = (struct bitmap*)src; + bs_memblt(opcode, x, y, cx, cy, b->data, b->width, b->height, + srcx, srcy); + ui_invalidate(x, y, cx, cy); +} + +/*****************************************************************************/ +void +ui_desktop_restore(uint32 offset, int x, int y, int cx, int cy) +{ +} + +/*****************************************************************************/ +void +ui_desktop_save(uint32 offset, int x, int y, int cx, int cy) +{ +} + +/*****************************************************************************/ +void +ui_rect(int x, int y, int cx, int cy, int colour) +{ + bs_rect(x, y, cx, cy, colour, 12); + mi_fill_rect(x, y, cx, cy, colour); +} + +/*****************************************************************************/ +void +ui_screenblt(uint8 opcode, int x, int y, int cx, int cy, + int srcx, int srcy) +{ + bs_screenblt(opcode, x, y, cx, cy, srcx, srcy); + if (opcode == 12) + { + mi_screen_copy(x, y, cx, cy, srcx, srcy); + } + else + { + ui_invalidate(x, y, cx, cy); + } +} + +/*****************************************************************************/ +void +ui_patblt(uint8 opcode, int x, int y, int cx, int cy, + BRUSH * brush, int bgcolour, int fgcolour) +{ + bs_patblt(opcode, x, y, cx, cy, brush->style, brush->pattern, + brush->xorigin, brush->yorigin, bgcolour, fgcolour); + ui_invalidate(x, y, cx, cy); +} + +/*****************************************************************************/ +void +ui_destblt(uint8 opcode, int x, int y, int cx, int cy) +{ + bs_rect(x, y, cx, cy, 0, opcode); + ui_invalidate(x, y, cx, cy); + /* todo */ +} + +/*****************************************************************************/ +void +ui_move_pointer(int x, int y) +{ +} + +/*****************************************************************************/ +uint16 +ui_get_numlock_state(uint32 state) +{ + return (uint16) state; +} + +/*****************************************************************************/ +/* get the num, caps, and scroll lock state */ +/* scroll lock is 1, num lock is 2 and caps lock is 4 */ +/* just returning 0, the hardware specific file is responsable for this */ +uint32 +read_keyboard_state(void) +{ + return (uint32) mi_read_keyboard_state(); +} + +/*****************************************************************************/ +void +ui_set_modifier_state(int code) + +{ + + //error("%8.8x", code); + + rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0, (uint16) code, 0); + +} + +/*****************************************************************************/ +void +ui_resize_window(void) +{ +} + +/*****************************************************************************/ +void +ui_begin_update(void) +{ + mi_begin_update(); +} + +/*****************************************************************************/ +void +ui_end_update(void) +{ + mi_end_update(); +} + +/*****************************************************************************/ +void +ui_polygon(uint8 opcode, uint8 fillmode, POINT * point, int npoints, + BRUSH * brush, int bgcolour, int fgcolour) +{ + /* not used */ +} + +/*****************************************************************************/ +void +ui_polyline(uint8 opcode, POINT * points, int npoints, PEN * pen) +{ + int i, x, y, dx, dy; + if (npoints > 0) + { + x = points[0].x; + y = points[0].y; + for (i = 1; i < npoints; i++) + { + dx = points[i].x; + dy = points[i].y; + ui_line(opcode, x, y, x + dx, y + dy, pen); + x = x + dx; + y = y + dy; + } + } +} + +/*****************************************************************************/ +void +ui_ellipse(uint8 opcode, uint8 fillmode, + int x, int y, int cx, int cy, + BRUSH * brush, int bgcolour, int fgcolour) +{ + /* not used */ +} + +/*****************************************************************************/ +/* get a 32 byte random */ +void +generate_random(uint8 * random) +{ + int i; + + rand(); + rand(); + for (i = 0; i < 32; i++) + { + random[i] = rand() >> 16; /* higher bits are more random */ + } +} + +/*****************************************************************************/ +void +save_licence(uint8 * data, int length) +{ +} + +/*****************************************************************************/ +int +load_licence(uint8 ** data) +{ + return 0; +} + +/*****************************************************************************/ +void * +xrealloc(void * in, int size) +{ + if (size < 1) + { + size = 1; + } + return realloc(in, size); +} + +/*****************************************************************************/ +void * +xmalloc(int size) +{ + if (size < 1) + { + size = 1; + } + return malloc(size); +} + +/*****************************************************************************/ +void +xfree(void * in) +{ + if (in != 0) + { + free(in); + } +} + +/*****************************************************************************/ +char * +xstrdup(const char * s) +{ + int len; + char * p; + + if (s == 0) + { + return 0; + } + len = strlen(s); + p = (char *) xmalloc(len + 1); + strcpy(p, s); + return p; +} + +/*****************************************************************************/ +void +warning(char * format, ...) +{ + va_list ap; + char text[512]; + char text1[512]; + + sprintf(text1, "WARNING: "); + va_start(ap, format); + vsprintf(text, format, ap); + va_end(ap); + strcat(text1, text); + mi_warning(text1); +} + +/*****************************************************************************/ +void +unimpl(char * format, ...) +{ + va_list ap; + char text[512]; + char text1[512]; + + sprintf(text1, "UNIMPL: "); + va_start(ap, format); + vsprintf(text, format, ap); + va_end(ap); + strcat(text1, text); + mi_warning(text1); +} + +/*****************************************************************************/ +void +error(char * format, ...) +{ + va_list ap; + char text[512]; + char text1[512]; + + sprintf(text1, "ERROR: "); + va_start(ap, format); + vsprintf(text, format, ap); + va_end(ap); + strcat(text1, text); + mi_error(text1); +} + +/*****************************************************************************/ +BOOL +rd_pstcache_mkdir(void) +{ + return 0; +} + +/*****************************************************************************/ +int +rd_open_file(char * filename) +{ + return 0; +} + +/*****************************************************************************/ +void +rd_close_file(int fd) +{ + return; +} + +/*****************************************************************************/ +int +rd_read_file(int fd, void * ptr, int len) +{ + return 0; +} + +/*****************************************************************************/ +int +rd_write_file(int fd, void * ptr, int len) +{ + return 0; +} + +/*****************************************************************************/ +int +rd_lseek_file(int fd, int offset) +{ + return 0; +} + +/*****************************************************************************/ +BOOL +rd_lock_file(int fd, int start, int len) +{ + return False; +} + + +/*****************************************************************************/ +void +ui_mouse_move(int x, int y) +{ + rdp_send_input(0, RDP_INPUT_MOUSE, MOUSE_FLAG_MOVE, (uint16) x, (uint16) y); +} + + +/*****************************************************************************/ +void +ui_mouse_button(int button, int x, int y, int down) +{ + uint16 flags; + + flags = 0; + if (down) + { + flags |= MOUSE_FLAG_DOWN; + } + switch (button) + { + case 1: + flags |= MOUSE_FLAG_BUTTON1; + break; + case 2: + flags |= MOUSE_FLAG_BUTTON2; + break; + case 3: + flags |= MOUSE_FLAG_BUTTON3; + break; + case 4: + flags |= MOUSE_FLAG_BUTTON4; + break; + case 5: + flags |= MOUSE_FLAG_BUTTON5; + break; + } + rdp_send_input(0, RDP_INPUT_MOUSE, flags, (uint16) x, (uint16) y); +} + + +/*****************************************************************************/ +void +ui_key_down(int key, int ext) + +{ + rdp_send_input(0, RDP_INPUT_SCANCODE, (uint16) (RDP_KEYPRESS | ext), + (uint16) key, 0); +} + +/*****************************************************************************/ +void +ui_key_up(int key, int ext) +{ + rdp_send_input(0, RDP_INPUT_SCANCODE, (uint16) (RDP_KEYRELEASE | ext), + (uint16) key, 0); +} + +/*****************************************************************************/ +/* returns boolean, non zero is good */ +int +ui_read_wire(void) +{ + return rdp_loop(&g_deactivated, &g_ext_disc_reason); +} + +/*****************************************************************************/ +/* called after the command line parameters are processed */ +/* returns boolean, non zero is ok */ +int +ui_main(void) +{ + uint32 flags; + + /* try to connect */ + flags = RDP_LOGON_NORMAL; + if (g_password[0] != 0) + { + flags |= RDP_LOGON_AUTO; + } + if (!rdp_connect(g_servername, flags, g_domain, g_password, + g_shell, g_directory)) + { + return 0; + } + /* init backingstore */ + bs_init(g_width, g_height, g_server_depth); + /* create the window */ + if (!mi_create_window()) + { + return 0; + } + /* if all ok, enter main loop */ + return mi_main_loop(); +} + +/*****************************************************************************/ +/* produce a hex dump */ +void +hexdump(uint8 * p, uint32 len) +{ + uint8 * line = p; + int i, thisline, offset = 0; + + while (offset < (int)len) + { + printf("%04x ", offset); + thisline = len - offset; + if (thisline > 16) + thisline = 16; + + for (i = 0; i < thisline; i++) + printf("%02x ", line[i]); + + for (; i < 16; i++) + printf(" "); + + for (i = 0; i < thisline; i++) + printf("%c", (line[i] >= 0x20 && line[i] < 0x7f) ? line[i] : '.'); + + printf("\n"); + offset += thisline; + line += thisline; + } +} + diff --git a/reactos/base/applications/mstsc/uimain.h b/reactos/base/applications/mstsc/uimain.h new file mode 100644 index 00000000000..09e3cb33c20 --- /dev/null +++ b/reactos/base/applications/mstsc/uimain.h @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + main ui header + Copyright (C) Jay Sorg 2005-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* in uimain.c */ +int +ui_main(void); +void +ui_invalidate(int x, int y, int cx, int cy); +int +ui_read_wire(void); +void +ui_mouse_move(int x, int y); +void +ui_mouse_button(int button, int x, int y, int down); +void +ui_key_down(int key, int ext); +void +ui_key_up(int key, int ext); + +void + +ui_set_modifier_state(int code); + +#define SPLIT_COLOUR15(c, r, g, b) \ +{ \ + r = ((c >> 7) & 0xf8) | ((c >> 12) & 0x7); \ + g = ((c >> 2) & 0xf8) | ((c >> 8) & 0x7); \ + b = ((c << 3) & 0xf8) | ((c >> 2) & 0x7); \ +} + +#define SPLIT_COLOUR16(c, r, g, b) \ +{ \ + r = ((c >> 8) & 0xf8) | ((c >> 13) & 0x7); \ + g = ((c >> 3) & 0xfc) | ((c >> 9) & 0x3); \ + b = ((c << 3) & 0xf8) | ((c >> 2) & 0x7); \ +} + +#define MAKE_COLOUR15(c, r, g, b) \ +{ \ + c = ( \ + (((r & 0xff) >> 3) << 10) | \ + (((g & 0xff) >> 3) << 5) | \ + (((b & 0xff) >> 3) << 0) \ + ); \ +} + +#define MAKE_COLOUR32(c, r, g, b) \ +{ \ + c = ( \ + ((r & 0xff) << 16) | \ + ((g & 0xff) << 8) | \ + ((b & 0xff) << 0) \ + ); \ +} + +#undef UI_MAX +#define UI_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#undef UI_MIN +#define UI_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* in connectdialog.c */ +BOOL OpenRDPConnectDialog(); + diff --git a/reactos/base/applications/mstsc/win32.c b/reactos/base/applications/mstsc/win32.c new file mode 100644 index 00000000000..a4569c3563f --- /dev/null +++ b/reactos/base/applications/mstsc/win32.c @@ -0,0 +1,1712 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + win32 calls + Copyright (C) Jay Sorg 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifdef _WIN32_WCE +#define MYWINCE +#endif + +#include /* winsock2.h first */ +#include +#ifdef MYWINCE +#include /* aygshell.lib */ +#endif /* MYWINCE */ +#include +#include +#include "uimain.h" + +extern char g_username[]; +extern char g_hostname[]; +extern char g_servername[]; +extern char g_password[]; +extern char g_shell[]; +extern char g_directory[]; +extern char g_domain[]; +extern int g_width; +extern int g_height; +extern int g_tcp_sck; +extern int g_server_depth; +extern int g_tcp_port_rdp; /* in tcp.c */ +extern int pal_entries[]; + +static HWND g_Wnd = 0; +static HINSTANCE g_Instance = 0; +static HCURSOR g_cursor = 0; +static int g_block = 0; +static int g_xoff = 0; /* offset from window to client coords */ +static int g_yoff = 0; +static int g_xscroll = 0; /* current scroll position */ +static int g_yscroll = 0; +static int g_screen_width = 0; +static int g_screen_height = 0; +static int g_wnd_cwidth = 0; /* set from WM_SIZE */ +static int g_wnd_cheight = 0; +static int g_fullscreen = 0; +static int g_workarea = 0; +static int g_mousex = 0; /* in client coords */ +static int g_mousey = 0; +static int g_width_height_set = 0; + +static int g_clip_left = 0; +static int g_clip_top = 0; +static int g_clip_right = 800; +static int g_clip_bottom = 600; +static RECT g_wnd_clip; /* this client area of whats actually visable */ + /* set from WM_SIZE */ +#ifdef MYWINCE +static int g_sip_up = 0; +#endif + +/*****************************************************************************/ +static void +str_to_uni(TCHAR * sizex, char * size1) +{ + int len; + int i; + + len = strlen(size1); + for (i = 0; i < len; i++) + { + sizex[i] = size1[i]; + } + sizex[len] = 0; +} + +/*****************************************************************************/ +/* returns non zero if it processed something */ +static int +check_sck(void) +{ + fd_set rfds; + struct timeval tm; + int count; + int rv; + + rv = 0; + if (g_block == 0) + { + g_block = 1; + /* see if there really is data */ + FD_ZERO(&rfds); + FD_SET((unsigned int)g_tcp_sck, &rfds); + ZeroMemory(&tm, sizeof(tm)); + count = select(g_tcp_sck + 1, &rfds, 0, 0, &tm); + if (count > 0) + { + if (ui_read_wire()) + { + rv = 1; + } + else + { + PostQuitMessage(0); + } + } + g_block = 0; + } + return rv; +} + +/*****************************************************************************/ +void +mi_error(char * msg) +{ +#ifdef WITH_DEBUG + printf(msg); +#else /* WITH_DEBUG */ + TCHAR lmsg[512]; + TCHAR ltitle[512]; + + str_to_uni(lmsg, msg); + str_to_uni(ltitle, "Error"); + MessageBox(g_Wnd, lmsg, ltitle, MB_OK); +#endif /* WITH_DEBUG */ +} + +/*****************************************************************************/ +static int +get_scan_code_from_ascii(int code) +{ + int rv; + + rv = 0; + switch (code & 0xff) + { + case 0x30: rv = 0x0b; break; // 0 + case 0x31: rv = 0x02; break; // 1 + case 0x32: rv = 0x03; break; // 2 + case 0x33: rv = 0x04; break; // 3 + case 0x34: rv = 0x05; break; // 4 + case 0x35: rv = 0x06; break; // 5 + case 0x36: rv = 0x07; break; // 6 + case 0x37: rv = 0x08; break; // 7 + case 0x38: rv = 0x09; break; // 8 + case 0x39: rv = 0x0a; break; // 9 + + case 0xbd: rv = 0x0c; break; // - + case 0xbb: rv = 0x0d; break; // = + case 0x08: rv = 0x0e; break; // backspace + case 0x09: rv = 0x0f; break; // tab + case 0xdb: rv = 0x1b; break; // ] + case 0xdd: rv = 0x1a; break; // [ + case 0x14: rv = 0x3a; break; // capslock + case 0xba: rv = 0x27; break; // ; + case 0xde: rv = 0x28; break; // ' + case 0x10: rv = 0x2a; break; // shift + case 0xbc: rv = 0x33; break; // , + case 0xbe: rv = 0x34; break; // . + case 0xbf: rv = 0x35; break; // / + case 0x0d: rv = 0x1c; break; // enter + case 0x27: rv = 0x4d; break; // arrow right + case 0x25: rv = 0x4b; break; // arrow left + case 0x26: rv = 0x48; break; // arrow up + case 0x28: rv = 0x50; break; // arrow down + case 0x20: rv = 0x39; break; // space + case 0xdc: rv = 0x2b; break; // backslash + case 0xc0: rv = 0x29; break; // ` + case 0x11: rv = 0x1d; break; // ctl + + case 0x41: rv = 0x1e; break; // a + case 0x42: rv = 0x30; break; // b + case 0x43: rv = 0x2e; break; // c + case 0x44: rv = 0x20; break; // d + case 0x45: rv = 0x12; break; // e + case 0x46: rv = 0x21; break; // f + case 0x47: rv = 0x22; break; // g + case 0x48: rv = 0x23; break; // h + case 0x49: rv = 0x17; break; // i + case 0x4a: rv = 0x24; break; // j + case 0x4b: rv = 0x25; break; // k + case 0x4c: rv = 0x26; break; // l + case 0x4d: rv = 0x32; break; // m + case 0x4e: rv = 0x31; break; // n + case 0x4f: rv = 0x18; break; // o + case 0x50: rv = 0x19; break; // p + case 0x51: rv = 0x10; break; // q + case 0x52: rv = 0x13; break; // r + case 0x53: rv = 0x1f; break; // s + case 0x54: rv = 0x14; break; // t + case 0x55: rv = 0x16; break; // u + case 0x56: rv = 0x2f; break; // v + case 0x57: rv = 0x11; break; // w + case 0x58: rv = 0x2d; break; // x + case 0x59: rv = 0x15; break; // y + case 0x5a: rv = 0x2c; break; // z + } + return rv; +} + +/*****************************************************************************/ +static void +mi_scroll(int dx, int dy) +{ + HRGN rgn; + + rgn = CreateRectRgn(0, 0, 0, 0); + ScrollWindowEx(g_Wnd, dx, dy, 0, 0, rgn, 0, SW_ERASE); + InvalidateRgn(g_Wnd, rgn, 0); + DeleteObject(rgn); +} + +/*****************************************************************************/ +int +mi_read_keyboard_state(void) +{ + short keydata; + int code; + + code = 0; + keydata = GetKeyState(VK_SCROLL); + if (keydata & 0x0001) + { + code |= 1; + } + keydata = GetKeyState(VK_NUMLOCK); + if (keydata & 0x0001) + { + code |= 2; + } + keydata = GetKeyState(VK_CAPITAL); + if (keydata & 0x0001) + { + code |= 4; + } + return code; +} + +/*****************************************************************************/ +static void +mi_check_modifier(void) +{ + int code; + + code = mi_read_keyboard_state(); + ui_set_modifier_state(code); +} + +/*****************************************************************************/ +static LRESULT +handle_WM_SETCURSOR(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (g_mousex >= g_wnd_clip.left && + g_mousey >= g_wnd_clip.top && + g_mousex < g_wnd_clip.right && + g_mousey < g_wnd_clip.bottom) + { + SetCursor(g_cursor); + } + /* need default behavoir here */ + return DefWindowProc(hWnd, message, wParam, lParam); +} + +/*****************************************************************************/ +static LRESULT +handle_WM_NCHITTEST(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + POINT pt; + + pt.x = LOWORD(lParam); + pt.y = HIWORD(lParam); + if (ScreenToClient(g_Wnd, &pt)) + { + g_mousex = pt.x; + g_mousey = pt.y; + } + return DefWindowProc(hWnd, message, wParam, lParam); +} + +/*****************************************************************************/ +static LRESULT +handle_WM_MOUSEMOVE(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_move(g_mousex + g_xscroll, g_mousey + g_yscroll); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_LBUTTONDOWN(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(1, g_mousex + g_xscroll, g_mousey + g_yscroll, 1); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_LBUTTONUP(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(1, g_mousex + g_xscroll, g_mousey + g_yscroll, 0); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_RBUTTONDOWN(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(2, g_mousex + g_xscroll, g_mousey + g_yscroll, 1); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_RBUTTONUP(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(2, g_mousex + g_xscroll, g_mousey + g_yscroll, 0); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_MBUTTONDOWN(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(3, g_mousex + g_xscroll, g_mousey + g_yscroll, 1); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_MBUTTONUP(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + g_mousex = LOWORD(lParam); + g_mousey = HIWORD(lParam); + ui_mouse_button(3, g_mousex + g_xscroll, g_mousey + g_yscroll, 0); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_MOUSEWHEEL(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int delta; + + delta = ((signed short)HIWORD(wParam)); /* GET_WHEEL_DELTA_WPARAM(wParam); */ + if (delta > 0) + { + ui_mouse_button(4, 0, 0, 1); + ui_mouse_button(4, 0, 0, 0); + } + else + { + ui_mouse_button(5, 0, 0, 1); + ui_mouse_button(5, 0, 0, 0); + } + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_KEY(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int scancode; + int ext; + int down; + + ext = HIWORD(lParam); + scancode = ext; + down = !(ext & 0x8000); + scancode &= 0xff; + if (scancode == 0) + { + scancode = get_scan_code_from_ascii(wParam); + } + ext &= 0x0100; + if (scancode == 0x0045) /* num lock */ + { + ext &= ~0x0100; + } + if (down) + { + ui_key_down(scancode, ext); + } + else + { + ui_key_up(scancode, ext); + } + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_PAINT(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + RECT rect; + HBRUSH brush; + + BeginPaint(hWnd, &ps); + /* paint the area outside the rdp screen with one colour */ + rect = ps.rcPaint; + rect.left = UI_MAX(rect.left, g_width); + if (!IsRectEmpty(&rect)) + { + brush = CreateSolidBrush(RGB(0, 0, 255)); + FillRect(ps.hdc, &rect, brush); + DeleteObject(brush); + } + rect = ps.rcPaint; + rect.top = UI_MAX(rect.top, g_height); + if (!IsRectEmpty(&rect)) + { + brush = CreateSolidBrush(RGB(0, 0, 255)); + FillRect(ps.hdc, &rect, brush); + DeleteObject(brush); + } + rect = ps.rcPaint; + EndPaint(hWnd, &ps); + ui_invalidate(rect.left + g_xscroll, + rect.top + g_yscroll, + (rect.right - rect.left) + 1, + (rect.bottom - rect.top) + 1); + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_SIZE(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int oldxscroll; + int oldyscroll; + + if (wParam == SIZE_MINIMIZED) + { + return DefWindowProc(hWnd, message, wParam, lParam); + } + g_wnd_cwidth = LOWORD(lParam); /* client width / height */ + g_wnd_cheight = HIWORD(lParam); + g_wnd_clip.left = 0; + g_wnd_clip.top = 0; + g_wnd_clip.right = g_wnd_clip.left + g_wnd_cwidth; + g_wnd_clip.bottom = g_wnd_clip.top + g_wnd_cheight; + if (g_wnd_cwidth < g_width || g_wnd_cheight < g_height) + { + SetScrollRange(g_Wnd, SB_HORZ, 0, g_width - g_wnd_cwidth, 1); + SetScrollRange(g_Wnd, SB_VERT, 0, g_height - g_wnd_cheight, 1); + } + oldxscroll = g_xscroll; + oldyscroll = g_yscroll; + if (g_wnd_cwidth >= g_width) + { + g_xscroll = 0; + } + else + { + g_xscroll = UI_MIN(g_xscroll, g_width - g_wnd_cwidth); + } + if (g_wnd_cheight >= g_height) + { + g_yscroll = 0; + } + else + { + g_yscroll = UI_MIN(g_yscroll, g_height - g_wnd_cheight); + } + mi_scroll(oldxscroll - g_xscroll, oldyscroll - g_yscroll); + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) + { + /* check the caps, num, and scroll lock here */ + mi_check_modifier(); + } + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_SIZING(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + LPRECT prect; + int width; + int height; + int style; + + prect = (LPRECT) lParam; /* total window rect */ + width = (prect->right - prect->left) - (g_xoff * 2); + height = (prect->bottom - prect->top) - (g_yoff + g_xoff); + if (height < g_height || width < g_width) + { + style = GetWindowLong(g_Wnd, GWL_STYLE); + if (!(style & WS_HSCROLL)) + { + style |= WS_HSCROLL | WS_VSCROLL; + SetWindowLong(g_Wnd, GWL_STYLE, style); + g_xscroll = 0; + g_yscroll = 0; + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + } + } + else if (height >= g_height && width >= g_width) + { + style = GetWindowLong(g_Wnd, GWL_STYLE); + if (style & WS_HSCROLL) + { + style &= ~WS_HSCROLL; + style &= ~WS_VSCROLL; + SetWindowLong(g_Wnd, GWL_STYLE, style); + g_xscroll = 0; + g_yscroll = 0; + } + } + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_HSCROLL(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int code; + int oldxscroll; + + code = (int) LOWORD(wParam); /* scroll bar value */ + if (code == SB_LINELEFT) + { + oldxscroll = g_xscroll; + g_xscroll--; + g_xscroll = UI_MAX(g_xscroll, 0); + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_LINERIGHT) + { + oldxscroll = g_xscroll; + g_xscroll++; + g_xscroll = UI_MIN(g_xscroll, g_width - g_wnd_cwidth); + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_PAGELEFT) + { + oldxscroll = g_xscroll; + g_xscroll -= g_wnd_cwidth / 2; + g_xscroll = UI_MAX(g_xscroll, 0); + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_PAGERIGHT) + { + oldxscroll = g_xscroll; + g_xscroll += g_wnd_cwidth / 2; + g_xscroll = UI_MIN(g_xscroll, g_width - g_wnd_cwidth); + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_BOTTOM) + { + oldxscroll = g_xscroll; + g_xscroll = g_width - g_wnd_cwidth; + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_TOP) + { + oldxscroll = g_xscroll; + g_xscroll = 0; + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + else if (code == SB_THUMBPOSITION) + { + oldxscroll = g_xscroll; + g_xscroll = (signed short) HIWORD(wParam); + SetScrollPos(g_Wnd, SB_HORZ, g_xscroll, 1); + mi_scroll(oldxscroll - g_xscroll, 0); + } + return 0; +} + +/*****************************************************************************/ +static LRESULT +handle_WM_VSCROLL(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int code; + int oldyscroll; + + code = (int) LOWORD(wParam); /* scroll bar value */ + if (code == SB_LINELEFT) + { + oldyscroll = g_yscroll; + g_yscroll--; + g_yscroll = UI_MAX(g_yscroll, 0); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_LINERIGHT) + { + oldyscroll = g_yscroll; + g_yscroll++; + g_yscroll = UI_MIN(g_yscroll, g_height - g_wnd_cheight); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_PAGELEFT) + { + oldyscroll = g_yscroll; + g_yscroll -= g_wnd_cheight / 2; + g_yscroll = UI_MAX(g_yscroll, 0); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_PAGERIGHT) + { + oldyscroll = g_yscroll; + g_yscroll += g_wnd_cheight / 2; + g_yscroll = UI_MIN(g_yscroll, g_height - g_wnd_cheight); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_BOTTOM) + { + oldyscroll = g_yscroll; + g_yscroll = g_height - g_wnd_cheight; + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_TOP) + { + oldyscroll = g_yscroll; + g_yscroll = 0; + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + else if (code == SB_THUMBPOSITION) + { + oldyscroll = g_yscroll; + g_yscroll = (signed short) HIWORD(wParam); + SetScrollPos(g_Wnd, SB_VERT, g_yscroll, 1); + mi_scroll(0, oldyscroll - g_yscroll); + } + return 0; +} + +#ifdef MYWINCE +/*****************************************************************************/ +static LRESULT +handle_WM_SETTINGCHANGE(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + SIPINFO si; + SHMENUBARINFO mb; + int x; + int y; + int w; + int h; + int style; + + ZeroMemory(&si, sizeof(SIPINFO)); + si.cbSize = sizeof(SIPINFO); + SHSipInfo(SPI_GETSIPINFO, lParam, &si, 0); + x = si.rcVisibleDesktop.left; + y = si.rcVisibleDesktop.top; + w = si.rcVisibleDesktop.right - x; + h = si.rcVisibleDesktop.bottom - y; + /* get rid of menu */ + DestroyWindow(SHFindMenuBar(g_Wnd)); + if (si.fdwFlags & SIPF_ON) + { + g_sip_up = 1; /* used for WM_SETFOCUS */ + ZeroMemory(&mb, sizeof(SHMENUBARINFO)); + mb.cbSize = sizeof(SHMENUBARINFO); + mb.hwndParent = g_Wnd; + mb.dwFlags = SHCMBF_EMPTYBAR; + SHCreateMenuBar(&mb); + MoveWindow(g_Wnd, x, y, w, h, FALSE); + SHFullScreen(g_Wnd, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON | + SHFS_SHOWSTARTICON); + } + else + { + g_sip_up = 0; + if (g_fullscreen) + { + MoveWindow(g_Wnd, 0, 0, g_screen_width, g_screen_height, FALSE); + } + else + { + MoveWindow(g_Wnd, x, y, w, h, FALSE); + } + if ((g_fullscreen && g_width <= g_screen_width && + g_height <= g_screen_height) || + (!g_fullscreen && g_width <= w && g_height <= h)) + { + style = GetWindowLong(g_Wnd, GWL_STYLE); + if (style & WS_HSCROLL) + { + style &= ~WS_HSCROLL; + style &= ~WS_VSCROLL; + SetWindowLong(g_Wnd, GWL_STYLE, style); + g_xscroll = 0; + g_yscroll = 0; + } + } + if (g_fullscreen) + { + SHFullScreen(g_Wnd, SHFS_HIDETASKBAR | SHFS_SHOWSIPBUTTON | + SHFS_SHOWSTARTICON); + } + else + { + SHFullScreen(g_Wnd, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON | + SHFS_SHOWSTARTICON); + } + } + return 0; +} +#endif /* MYWINCE */ + +/*****************************************************************************/ +LRESULT CALLBACK +WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_SETCURSOR: + return handle_WM_SETCURSOR(hWnd, message, wParam, lParam); + case 0x0084: /* WinCE don't have this WM_NCHITTEST: */ + return handle_WM_NCHITTEST(hWnd, message, wParam, lParam); + case WM_MOUSEMOVE: + return handle_WM_MOUSEMOVE(hWnd, message, wParam, lParam); + case WM_LBUTTONDOWN: + return handle_WM_LBUTTONDOWN(hWnd, message, wParam, lParam); + case WM_LBUTTONUP: + return handle_WM_LBUTTONUP(hWnd, message, wParam, lParam); + case WM_RBUTTONDOWN: + return handle_WM_RBUTTONDOWN(hWnd, message, wParam, lParam); + case WM_RBUTTONUP: + return handle_WM_RBUTTONUP(hWnd, message, wParam, lParam); + case WM_MBUTTONDOWN: + return handle_WM_MBUTTONDOWN(hWnd, message, wParam, lParam); + case WM_MBUTTONUP: + return handle_WM_MBUTTONUP(hWnd, message, wParam, lParam); + /* some windows compilers don't have these defined like vc6 */ + case 0x020a: /* WM_MOUSEWHEEL: */ + return handle_WM_MOUSEWHEEL(hWnd, message, wParam, lParam); + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + return handle_WM_KEY(hWnd, message, wParam, lParam); + case WM_CHAR: + case WM_DEADCHAR: + case WM_SYSCHAR: + case WM_SYSDEADCHAR: + break; + case WM_PAINT: + return handle_WM_PAINT(hWnd, message, wParam, lParam); + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_APP + 1: + case WM_TIMER: + check_sck(); + break; + case WM_SIZE: + return handle_WM_SIZE(hWnd, message, wParam, lParam); + case 532: /* not defined in wince WM_SIZING: */ + return handle_WM_SIZING(hWnd, message, wParam, lParam); + case WM_HSCROLL: + return handle_WM_HSCROLL(hWnd, message, wParam, lParam); + case WM_VSCROLL: + return handle_WM_VSCROLL(hWnd, message, wParam, lParam); +#ifdef MYWINCE + case WM_SETTINGCHANGE: + return handle_WM_SETTINGCHANGE(hWnd, message, wParam, lParam); +#endif /* MYWINCE */ + case WM_SETFOCUS: + mi_check_modifier(); +#ifdef MYWINCE + if (g_sip_up) + { + SHSipPreference(hWnd, SIP_UP); + } +#endif + return DefWindowProc(hWnd, message, wParam, lParam); + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +/*****************************************************************************/ +static HRGN +mi_clip(HDC dc) +{ + HRGN rgn; + + rgn = CreateRectRgn(g_clip_left + g_xoff - g_xscroll, + g_clip_top + g_yoff - g_yscroll, + g_clip_right + g_xoff - g_xscroll, + g_clip_bottom + g_yoff - g_yscroll); + SelectClipRgn(dc, rgn); + IntersectClipRect(dc, g_wnd_clip.left + g_xoff, g_wnd_clip.top + g_yoff, + g_wnd_clip.right + g_xoff, g_wnd_clip.bottom + g_yoff); + return rgn; +} + +/*****************************************************************************/ +/* returns non zero if ok */ +int +mi_create_window(void) +{ + RECT rc; + WNDCLASS wc; + TCHAR classname[512]; + TCHAR caption[512]; + DWORD style; + int x; + int y; + int w; + int h; + + if (g_Wnd != 0 || g_Instance != 0) + { + return 0; + } + g_Instance = GetModuleHandle(NULL); + ZeroMemory(&wc, sizeof(wc)); + wc.lpfnWndProc = WndProc; /* points to window procedure */ + /* name of window class */ + str_to_uni(classname, "rdesktop"); + wc.lpszClassName = classname; + str_to_uni(caption, "ReactOS Remote Desktop"); + /* Register the window class. */ + if (!RegisterClass(&wc)) + { + return 0; /* Failed to register window class */ + } + rc.left = 0; + rc.right = rc.left + UI_MIN(g_width, g_screen_width); + rc.top = 0; + rc.bottom = rc.top + UI_MIN(g_height, g_screen_height); +#ifdef MYWINCE + SHInitExtraControls(); + x = CW_USEDEFAULT; + y = CW_USEDEFAULT; + w = CW_USEDEFAULT; + h = CW_USEDEFAULT; + style = WS_VISIBLE; + if (g_fullscreen) + { + x = 0; + y = 0; + w = g_screen_width; + h = g_screen_height; + } +#else /* MYWINCE */ + if (g_fullscreen) + { + style = WS_POPUP; + } + else + { + style = WS_OVERLAPPED | WS_CAPTION | WS_POPUP | WS_MINIMIZEBOX | + WS_SYSMENU | WS_SIZEBOX | WS_MAXIMIZEBOX; + } + if (g_screen_width < g_width || g_screen_height < g_height) + { + style |= WS_HSCROLL | WS_VSCROLL; + } + AdjustWindowRectEx(&rc, style, 0, 0); + x = CW_USEDEFAULT; + y = CW_USEDEFAULT; + w = rc.right - rc.left; + h = rc.bottom - rc.top; +#endif /* MYWINCE */ + g_Wnd = CreateWindow(wc.lpszClassName, caption, + style, x, y, w, h, + (HWND) NULL, (HMENU) NULL, g_Instance, + (LPVOID) NULL); + g_clip_left = 0; + g_clip_top = 0; + g_clip_right = g_clip_left + g_width; + g_clip_bottom = g_clip_top + g_height; + if (g_workarea) + { + ShowWindow(g_Wnd, SW_SHOWMAXIMIZED); + } + else + { + ShowWindow(g_Wnd, SW_SHOWNORMAL); + } + UpdateWindow(g_Wnd); + +#ifdef MYWINCE + if (g_fullscreen) + { + SHFullScreen(g_Wnd, SHFS_HIDETASKBAR | SHFS_SHOWSIPBUTTON | + SHFS_SHOWSTARTICON); + } + else + { + SHFullScreen(g_Wnd, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON | + SHFS_SHOWSTARTICON); + } +#endif /* MYWINCE */ + + /* WinCE doesn't have WSAAsyncSelect */ +#ifdef MYWINCE + SetTimer(g_Wnd, 1, 1000 / 60, 0); /* 60 per second */ +#else /* MYWINCE */ + WSAAsyncSelect(g_tcp_sck, g_Wnd, WM_APP + 1, FD_READ); + SetTimer(g_Wnd, 1, 333, 0); +#endif /* MYWINCE */ + return 1; +} + +/*****************************************************************************/ +int +mi_main_loop(void) +{ + MSG msg; + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return msg.wParam; +} + +/*****************************************************************************/ +void +mi_warning(char * msg) +{ +} + +/*****************************************************************************/ +static void +mi_show_error(char * caption) +{ + LPVOID lpMsgBuf; + TCHAR lcaption[512]; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, 0, NULL); +#ifdef WITH_DEBUG + printf(lpMsgBuf); +#else /* WITH_DEBUG */ + str_to_uni(lcaption, caption); + MessageBox(g_Wnd, (LPTSTR) lpMsgBuf, lcaption, + MB_OK | MB_ICONINFORMATION); +#endif /* WITH_DEBUG */ + LocalFree(lpMsgBuf); +} + +/*****************************************************************************/ +void +mi_paint_rect(char * data, int width, int height, int x, int y, int cx, int cy) +{ + HBITMAP bitmap; + BITMAPINFO bi; + HDC dc; + HDC maindc; + HGDIOBJ save; + HRGN rgn; + void * bits; + int i; + int j; + int colour; + int red; + int green; + int blue; + + ZeroMemory(&bi, sizeof(bi)); + bi.bmiHeader.biSize = sizeof(bi.bmiHeader); + bi.bmiHeader.biWidth = width; + bi.bmiHeader.biHeight = -height; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + maindc = GetWindowDC(g_Wnd); + bitmap = CreateDIBSection(maindc, &bi, DIB_RGB_COLORS, (void **) &bits, 0, 0); + if (bitmap == 0) + { + mi_show_error("CreateDIBSection failed"); + } + + if (g_server_depth == 8) + { + for (i = cy - 1; i >= 0; i--) + { + for (j = cx - 1; j >= 0; j--) + { + colour = ((unsigned char*)data)[i * cx + j]; + red = (pal_entries[colour & 0xff] & 0xff0000) >> 16; + green = (pal_entries[colour & 0xff] & 0xff00) >> 8; + blue = pal_entries[colour & 0xff] & 0xff; + MAKE_COLOUR32(colour, red, green, blue); + ((unsigned int*)bits)[i * cx + j] = colour; + } + } + } + else if (g_server_depth == 15) + { + for (i = cy - 1; i >= 0; i--) + { + for (j = cx - 1; j >= 0; j--) + { + colour = ((unsigned short*)data)[i * cx + j]; + SPLIT_COLOUR15(colour, red, green, blue); + MAKE_COLOUR32(colour, red, green, blue); + ((unsigned int*)bits)[i * cx + j] = colour; + } + } + } + else if (g_server_depth == 16) + { + for (i = cy - 1; i >= 0; i--) + { + for (j = cx - 1; j >= 0; j--) + { + colour = ((unsigned short*)data)[i * cx + j]; + SPLIT_COLOUR16(colour, red, green, blue); + MAKE_COLOUR32(colour, red, green, blue); + ((unsigned int*)bits)[i * cx + j] = colour; + } + } + } + dc = CreateCompatibleDC(maindc); + if (dc == 0) + { + mi_show_error("CreateCompatibleDC failed"); + } + save = SelectObject(dc, bitmap); + rgn = mi_clip(maindc); + BitBlt(maindc, x + g_xoff - g_xscroll, y + g_yoff - g_yscroll, cx, cy, dc, + 0, 0, SRCCOPY); + SelectObject(dc, save); + DeleteObject(bitmap); + DeleteDC(dc); + ReleaseDC(g_Wnd, maindc); + DeleteObject(rgn); + +} + +/*****************************************************************************/ +static int +mi_process_a_param(char * param1, int state) +{ + char * p; + + if (state == 0) + { + if (strcmp(param1, "-g") == 0 || strcmp(param1, "geometry") == 0) + { + state = 1; + } + if (strcmp(param1, "-t") == 0 || strcmp(param1, "port") == 0) + { + state = 2; + } + if (strcmp(param1, "-a") == 0 || strcmp(param1, "bpp") == 0) + { + state = 3; + } + if (strcmp(param1, "-f") == 0 || strcmp(param1, "fullscreen") == 0) + { + g_fullscreen = 1; + } + if (strcmp(param1, "-u") == 0 || strcmp(param1, "username") == 0) + { + state = 5; + } + if (strcmp(param1, "-p") == 0 || strcmp(param1, "password") == 0) + { + state = 6; + } + if (strcmp(param1, "-d") == 0 || strcmp(param1, "domain") == 0) + { + state = 7; + } + if (strcmp(param1, "-s") == 0 || strcmp(param1, "shell") == 0) + { + state = 8; + } + if (strcmp(param1, "-c") == 0 || strcmp(param1, "directory") == 0) + { + state = 9; + } + if (strcmp(param1, "-n") == 0 || strcmp(param1, "hostname") == 0) + { + state = 10; + } + } + else + { + if (state == 1) /* -g */ + { + state = 0; + if (strcmp(param1, "workarea") == 0) + { + g_workarea = 1; + return state; + } + g_width = strtol(param1, &p, 10); + if (g_width <= 0) + { + mi_error("invalid geometry\r\n"); + } + if (*p == 'x') + { + g_height = strtol(p + 1, &p, 10); + } + if (g_height <= 0) + { + mi_error("invalid geometry\r\n"); + } + g_width_height_set = 1; + } + if (state == 2) /* -t */ + { + state = 0; + g_tcp_port_rdp = atol(param1); + } + if (state == 3) /* -a */ + { + state = 0; + g_server_depth = atol(param1); + if (g_server_depth != 8 && g_server_depth != 15 && g_server_depth != 16) + { + mi_error("invalid server bpp\r\n"); + } + } + if (state == 5) /* -u */ + { + state = 0; + strcpy(g_username, param1); + } + if (state == 6) /* -p */ + { + state = 0; + strcpy(g_password, param1); + } + if (state == 7) /* -d */ + { + state = 0; + strcpy(g_domain, param1); + } + if (state == 8) /* -s */ + { + state = 0; + strcpy(g_shell, param1); + } + if (state == 9) /* -c */ + { + state = 0; + strcpy(g_directory, param1); + } + if (state == 10) /* -n */ + { + state = 0; + strcpy(g_hostname, param1); + } + } + return state; +} + +/*****************************************************************************/ +static int +mi_post_param(void) +{ + /* after parameters */ + if (g_fullscreen) + { + g_xoff = 0; + g_yoff = 0; + if (!g_width_height_set) + { + g_width = g_screen_width; + g_height = g_screen_height; + } + } + else if (g_workarea) + { +#ifdef MYWINCE + g_xoff = 0; + g_yoff = 0; + g_width = g_screen_width; + g_height = g_screen_height - 26; /* start menu size is 26 */ +#else /* MYWINCE */ + g_xoff = GetSystemMetrics(SM_CXEDGE) * 2; + g_yoff = GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CYEDGE) * 2; + g_width = g_screen_width; + g_height = g_screen_height; + g_height = (g_height - g_yoff) - g_xoff - 20; /* todo */ +#endif /* MYWINCE */ + g_width_height_set = 1; + } + else + { +#ifdef MYWINCE + g_xoff = 0; + g_yoff = 0; +#else /* MYWINCE */ + g_xoff = GetSystemMetrics(SM_CXEDGE) * 2; + g_yoff = GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CYEDGE) * 2; +#endif /* MYWINCE */ + } + g_width = g_width & (~3); + return 1; +} + +/*****************************************************************************/ +static int +mi_check_config_file(void) +{ + HANDLE fd; + DWORD count; + TCHAR filename[256]; + char buffer[256]; + char vname[256]; + char value[256]; + int index; + int mode; + int vnameindex; + int valueindex; + int state; + int rv; + + rv = 0; + mode = 0; + vnameindex = 0; + valueindex = 0; + vname[vnameindex] = 0; + value[valueindex] = 0; +#ifdef MYWINCE + str_to_uni(filename, "\\My Documents\\winrdesktop.ini"); +#else /* MYWINCE */ + str_to_uni(filename, ".\\winrdesktop.ini"); +#endif /* MYWINCE */ + fd = CreateFile(filename, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + count = 255; + while (ReadFile(fd, buffer, count, &count, NULL)) + { + if (count == 0) + { + break; + } + buffer[count] = 0; + index = 0; + while (index < (int) count) + { + if (buffer[index] == '=') + { + mode = 1; + } + else if (buffer[index] == 10 || buffer[index] == 13) + { + mode = 0; + vname[vnameindex] = 0; + value[valueindex] = 0; + if (strlen(vname) > 0 || strlen(value) > 0) + { + if (strcmp(vname, "server") == 0) + { + strcpy(g_servername, value); + rv = 1; + } + else + { + state = mi_process_a_param(vname, 0); + mi_process_a_param(value, state); + } + } + vnameindex = 0; + valueindex = 0; + } + else if (mode == 0) + { + vname[vnameindex] = buffer[index]; + vnameindex++; + } + else + { + value[valueindex] = buffer[index]; + valueindex++; + } + index++; + } + count = 255; + } + CloseHandle(fd); + if (rv) + { + mi_post_param(); + } + return rv; +} + +/*****************************************************************************/ +/* process the command line parameters */ +/* returns boolean, non zero is ok */ +static int +mi_process_cl(LPTSTR lpCmdLine) +{ + char param[256]; + char param1[256]; +#ifndef MYWINCE + TCHAR l_username[256]; +#endif + DWORD size; + int len; + int i; + int i1; + int state; + + strcpy(g_hostname, "test"); + strcpy(g_username, "pda"); + /* get username and convert it from unicode */ + size = 255; +#ifndef MYWINCE + /* WinCE doesn't have GetUserName */ + if (GetUserName(l_username, &size)) + { + for (i = size; i >= 0; i--) + { + g_username[i] = (char) l_username[i]; + } + g_username[size] = 0; + } + else + { + mi_show_error("GetUserName"); + } +#endif /* MYWINCE */ + /* get computer name */ + if (gethostname(g_hostname, 255) != 0) + { + mi_show_error("gethostname"); + } + /* defaults */ + strcpy(g_servername, "127.0.0.1"); + g_server_depth = 8; + g_screen_width = GetSystemMetrics(SM_CXSCREEN); + g_screen_height = GetSystemMetrics(SM_CYSCREEN); + /* process parameters */ + i1 = 0; + state = 0; + len = lstrlen(lpCmdLine); + if (len == 0) + { + return mi_check_config_file(); + } + for (i = 0; i < len; i++) + { + if (lpCmdLine[i] != 32 && lpCmdLine[i] != 9) /* space or tab */ + { + param[i1] = (char) lpCmdLine[i]; + i1++; + } + else + { + param[i1] = 0; + i1 = 0; + strcpy(param1, param); + state = mi_process_a_param(param1, state); + strcpy(g_servername, param1); + } + } + if (i1 > 0) + { + param[i1] = 0; + strcpy(param1, param); + state = mi_process_a_param(param1, state); + strcpy(g_servername, param1); + } + if (state == 0) + { + mi_post_param(); + } + return (state == 0); +} + +/*****************************************************************************/ +/* display the command line options available */ +static void +mi_show_params(void) +{ + char text1[512 * 4]; + TCHAR textx[512 * 4]; + TCHAR lcaption[64]; + + strcpy(text1, ""); + strcat(text1, "WinRDesktop - an RDP client based on rdesktop\r\n"); + strcat(text1, "You can't run this application without " ); + strcat(text1, "correct parameters\r\n"); + strcat(text1, "\r\n"); + strcat(text1, "command line options\r\n"); + strcat(text1, "\r\n"); + strcat(text1, "WinRDesktop [-g widthxheight] [-t port] [-a bpp]\r\n"); + strcat(text1, " [-f] [-u username] [-p password] [-d domain]\r\n"); + strcat(text1, " [-s shell] [-c working directory] [-n host name]\r\n"); + strcat(text1, " server-name-or-ip\r\n"); + strcat(text1, "\r\n"); + strcat(text1, "You can use a config file in the current directory\r\n"); + strcat(text1, "called WinRDesktop.ini\r\n"); + strcat(text1, "The file should look like this...\r\n"); + strcat(text1, "[main]\r\n"); + strcat(text1, "server=192.168.1.1\r\n"); + strcat(text1, "port=3389\r\n"); + strcat(text1, "username=user1\r\n"); + strcat(text1, "password=password1\r\n"); + strcat(text1, "bpp=16\r\n"); + strcat(text1, "geometry=800x600\r\n"); +#ifdef WITH_DEBUG + printf(text1); +#else /* WITH_DEBUG */ + str_to_uni(lcaption, "WinRDesktop"); + str_to_uni(textx, text1); + MessageBox(g_Wnd, textx, lcaption, MB_OK); +#endif /* WITH_DEBUG */ +} + +#ifdef WITH_DEBUG +/*****************************************************************************/ +int +main(int argc, char ** argv) +{ + WSADATA d; + + WSAStartup(MAKEWORD(2, 0), &d); + if (!mi_process_cl(argv[0])) + { + mi_show_params(); + WSACleanup(); + return 0; + } + return ui_main(); +} +#else /* WITH_DEBUG */ +/*****************************************************************************/ +int WINAPI +WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, int nCmdShow) +{ + WSADATA d; + int ret = 1; + + WSAStartup(MAKEWORD(2, 0), &d); + if (OpenRDPConnectDialog(hInstance)) + { + if (!mi_process_cl(lpCmdLine)) + { + mi_show_params(); + WSACleanup(); + return 0; + } + ui_main(); + ret = 0; + } + + return ret; +} +#endif /* WITH_DEBUG */ + +/*****************************************************************************/ +void +mi_begin_update(void) +{ +} + +/*****************************************************************************/ +void +mi_end_update(void) +{ +} + +/*****************************************************************************/ +void +mi_fill_rect(int x, int y, int cx, int cy, int colour) +{ + HBRUSH brush; + RECT rect; + HDC maindc; + HRGN rgn; + int red; + int green; + int blue; + + if (g_server_depth == 8) + { + red = (pal_entries[colour & 0xff] & 0xff0000) >> 16; + green = (pal_entries[colour & 0xff] & 0xff00) >> 8; + blue = pal_entries[colour & 0xff] & 0xff; + } + else if (g_server_depth == 15) + { + SPLIT_COLOUR15(colour, red, green, blue); + } + else if (g_server_depth == 16) + { + SPLIT_COLOUR16(colour, red, green, blue); + } + else + { + red = 0; + green = 0; + blue = 0; + } + maindc = GetWindowDC(g_Wnd); + rgn = mi_clip(maindc); + brush = CreateSolidBrush(RGB(red, green, blue)); + rect.left = x + g_xoff - g_xscroll; + rect.top = y + g_yoff - g_yscroll; + rect.right = rect.left + cx; + rect.bottom = rect.top + cy; + FillRect(maindc, &rect, brush); + DeleteObject(brush); + ReleaseDC(g_Wnd, maindc); + DeleteObject(rgn); +} + +/*****************************************************************************/ +void +mi_line(int x1, int y1, int x2, int y2, int colour) +{ + HPEN pen; + HDC maindc; + HGDIOBJ save; + HRGN rgn; + int red; + int green; + int blue; + + if (g_server_depth == 8) + { + red = (pal_entries[colour & 0xff] & 0xff0000) >> 16; + green = (pal_entries[colour & 0xff] & 0xff00) >> 8; + blue = pal_entries[colour & 0xff] & 0xff; + } + else if (g_server_depth == 15) + { + SPLIT_COLOUR15(colour, red, green, blue); + } + else if (g_server_depth == 16) + { + SPLIT_COLOUR16(colour, red, green, blue); + } + else + { + red = 0; + green = 0; + blue = 0; + } + maindc = GetWindowDC(g_Wnd); + rgn = mi_clip(maindc); + pen = CreatePen(PS_SOLID, 0, RGB(red, green, blue)); + save = SelectObject(maindc, pen); + MoveToEx(maindc, x1 + g_xoff - g_xscroll, y1 + g_yoff - g_yscroll, 0); + LineTo(maindc, x2 + g_xoff - g_xscroll, y2 + g_yoff - g_yscroll); + SelectObject(maindc, save); + DeleteObject(pen); + ReleaseDC(g_Wnd, maindc); + DeleteObject(rgn); +} + +/*****************************************************************************/ +void +mi_screen_copy(int x, int y, int cx, int cy, int srcx, int srcy) +{ + RECT rect; + RECT clip_rect; + RECT draw_rect; + HRGN rgn; + int ok_to_ScrollWindowEx; + + /* WinCE can't scroll in 2 directions at once */ +#ifdef MYWINCE + ok_to_ScrollWindowEx = cx == 0 || cy == 0; +#else /* MYWINCE */ + ok_to_ScrollWindowEx = 1; +#endif /* MYWINCE */ + if (!ok_to_ScrollWindowEx) + { + rgn = CreateRectRgn(x - g_xscroll, y - g_yscroll, + (x - g_xscroll) + cx, + (y - g_yscroll) + cy); + InvalidateRgn(g_Wnd, rgn, 0); + DeleteObject(rgn); + } + else + { + /* this is all in client coords */ + rect.left = srcx - g_xscroll; + rect.top = srcy - g_yscroll; + rect.right = rect.left + cx; + rect.bottom = rect.top + cy; + clip_rect.left = g_clip_left - g_xscroll; + clip_rect.top = g_clip_top - g_yscroll; + clip_rect.right = g_clip_right - g_xscroll; + clip_rect.bottom = g_clip_bottom - g_yscroll; + if (IntersectRect(&draw_rect, &clip_rect, &g_wnd_clip)) + { + rgn = CreateRectRgn(0, 0, 0, 0); + ScrollWindowEx(g_Wnd, x - srcx, y - srcy, &rect, &draw_rect, + rgn, 0, SW_ERASE); + InvalidateRgn(g_Wnd, rgn, 0); + DeleteObject(rgn); + } + } +} + +/*****************************************************************************/ +void +mi_set_clip(int x, int y, int cx, int cy) +{ + g_clip_left = x; + g_clip_top = y; + g_clip_right = g_clip_left + cx; + g_clip_bottom = g_clip_top + cy; +} + +/*****************************************************************************/ +void +mi_reset_clip(void) +{ + g_clip_left = 0; + g_clip_top = 0; + g_clip_right = g_clip_left + g_width; + g_clip_bottom = g_clip_top + g_height; +} + +/*****************************************************************************/ +void * +mi_create_cursor(unsigned int x, unsigned int y, + int width, int height, + unsigned char * andmask, unsigned char * xormask) +{ +#ifdef MYWINCE + return (void *) 1; +#else /* MYWINCE */ + HCURSOR hCur; + + hCur = CreateCursor(g_Instance, x, y, width, height, andmask, xormask); + if (hCur == 0) + { + hCur = LoadCursor(NULL, IDC_ARROW); + } + return hCur; +#endif /* MYWINCE */ +} + +/*****************************************************************************/ +void +mi_destroy_cursor(void * cursor) +{ +#ifdef MYWINCE +#else /* MYWINCE */ + if (g_cursor == cursor) + { + g_cursor = 0; + } + DestroyCursor(cursor); +#endif /* MYWINCE */ +} + +/*****************************************************************************/ +void +mi_set_cursor(void * cursor) +{ +#ifdef MYWINCE +#else /* MYWINCE */ + g_cursor = cursor; + SetCursor(g_cursor); +#endif /* MYWINCE */ +} + +/*****************************************************************************/ +void +mi_set_null_cursor(void) +{ +} +