diff --git a/client/tp-player/main.cpp b/client/tp-player/main.cpp new file mode 100644 index 0000000..23849ce --- /dev/null +++ b/client/tp-player/main.cpp @@ -0,0 +1,15 @@ +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ +//#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) +// QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +//#endif + + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/client/tp-player/mainwindow.cpp b/client/tp-player/mainwindow.cpp new file mode 100644 index 0000000..2259562 --- /dev/null +++ b/client/tp-player/mainwindow.cpp @@ -0,0 +1,199 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "rle.h" + +#include +#include +#include + +bool rdpimg2QImage(QImage& out, int w, int h, int bitsPerPixel, bool isCompressed, uint8_t* dat, uint32_t len) { + switch(bitsPerPixel) { + case 15: + if(isCompressed) { + uint8_t* _dat = (uint8_t*)calloc(1, w*h*2); + if(!bitmap_decompress1(_dat, w, h, dat, len)) { + free(_dat); + return false; + } + out = QImage(_dat, w, h, QImage::Format_RGB555); + free(_dat); + } + else { + out = QImage(dat, w, h, QImage::Format_RGB555).transformed(QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) ; + } + break; + case 16: + if(isCompressed) { + uint8_t* _dat = (uint8_t*)calloc(1, w*h*2); + if(!bitmap_decompress2(_dat, w, h, dat, len)) { + free(_dat); + return false; + } + out = QImage(_dat, w, h, QImage::Format_RGB16); + free(_dat); + } + else { + out = QImage(dat, w, h, QImage::Format_RGB16).transformed(QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) ; + } + break; + case 24: + qDebug() << "--------NOT support 24"; + break; + case 32: + qDebug() << "--------NOT support 32"; + break; + } + + return true; +} + + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + m_shown = false; + m_show_bg = true; + m_bg = QImage(":/tp-player/res/bg"); + m_pt_normal = QImage(":/tp-player/res/cursor.png"); + + qDebug() << m_pt_normal.width() << "x" << m_pt_normal.height(); + + ui->setupUi(this); + + //qRegisterMetaType("update_data"); + + // frame-less window. +//#ifdef __APPLE__ +// setWindowFlags(Qt::FramelessWindowHint | Qt::MSWindowsFixedSizeDialogHint | Qt::Window); +// OSXCode::fixWin(winId()); +//#else +// setWindowFlags(Qt::FramelessWindowHint | Qt::MSWindowsFixedSizeDialogHint | windowFlags()); +//#endif //__APPLE__ + + setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint); // 禁止最大化按钮 + //setFixedSize(this->width(),this->height()); // 禁止拖动窗口大小 + + resize(m_bg.width(), m_bg.height()); + + connect(&m_thr_play, SIGNAL(signal_update_data(update_data*)), this, SLOT(on_update_data(update_data*))); +} + +MainWindow::~MainWindow() +{ + m_thr_play.stop(); + m_thr_play.wait(); + delete ui; +} + +void MainWindow::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + if(m_show_bg) { + //qDebug() << "draw bg."; + painter.setBrush(Qt::black); + painter.drawRect(this->rect()); + + int x = (rect().width() - m_bg.width()) / 2; + int y = (rect().height() - m_bg.height()) / 2; + painter.drawImage(x, y, m_bg); + //painter.drawPixmap(rect(), m_bg1); + } + + else { + painter.drawImage(m_img_update_x, m_img_update_y, m_img_update, 0, 0, m_img_update_w, m_img_update_h, Qt::AutoColor); + //qDebug() << "draw pt (" << m_pt.x << "," << m_pt.y << ")"; + painter.drawImage(m_pt.x, m_pt.y, m_pt_normal); + } + + + if(!m_shown) { + m_shown = true; + m_thr_play.start(); + } +} + +void MainWindow::on_update_data(update_data* dat) { + if(!dat) + return; +// qDebug() << "slot-event: " << dat->data_type(); + + if(dat->data_type() == TYPE_DATA) { + m_show_bg = false; + + if(dat->data_len() <= sizeof(TS_RECORD_PKG)) { + qDebug() << "invalid record package(1)."; + delete dat; + return; + } + + TS_RECORD_PKG* pkg = (TS_RECORD_PKG*)dat->data_buf(); + + if(pkg->type == TS_RECORD_TYPE_RDP_POINTER) { + if(dat->data_len() != sizeof(TS_RECORD_PKG) + sizeof(TS_RECORD_RDP_POINTER)) { + qDebug() << "invalid record package(2)."; + delete dat; + return; + } + + memcpy(&m_pt, dat->data_buf() + sizeof(TS_RECORD_PKG), sizeof(TS_RECORD_RDP_POINTER)); + update(); + //update(m_pt.x - 8, m_pt.y - 8, 32, 32); + } + else if(pkg->type == TS_RECORD_TYPE_RDP_IMAGE) { + if(dat->data_len() <= sizeof(TS_RECORD_PKG) + sizeof(TS_RECORD_RDP_IMAGE_INFO)) { + qDebug() << "invalid record package(3)."; + delete dat; + return; + } + + TS_RECORD_RDP_IMAGE_INFO* info = (TS_RECORD_RDP_IMAGE_INFO*)(dat->data_buf() + sizeof(TS_RECORD_PKG)); + uint8_t* img_dat = dat->data_buf() + sizeof(TS_RECORD_PKG) + sizeof(TS_RECORD_RDP_IMAGE_INFO); + uint32_t img_len = dat->data_len() - sizeof(TS_RECORD_PKG) - sizeof(TS_RECORD_RDP_IMAGE_INFO); + + rdpimg2QImage(m_img_update, info->width, info->height, info->bitsPerPixel, (info->format == TS_RDP_IMG_BMP) ? true : false, img_dat, img_len); + m_img_update_x = info->destLeft; + m_img_update_y = info->destTop; + m_img_update_w = info->destRight - info->destLeft + 1; + m_img_update_h = info->destBottom - info->destTop + 1; + + qDebug() << "img " << ((info->format == TS_RDP_IMG_BMP) ? "+" : " ") << " (" << m_img_update_x << "," << m_img_update_y << "), [" << m_img_update.width() << "x" << m_img_update.height() << "]"; + + + update(m_img_update_x, m_img_update_y, m_img_update_w, m_img_update_h); + } + + delete dat; + return; + } + + + if(dat->data_type() == TYPE_HEADER_INFO) { + if(dat->data_len() != sizeof(TS_RECORD_HEADER)) { + qDebug() << "invalid record header."; + delete dat; + return; + } + memcpy(&m_rec_hdr, dat->data_buf(), sizeof(TS_RECORD_HEADER)); + delete dat; + + qDebug() << "resize (" << m_rec_hdr.basic.width << "," << m_rec_hdr.basic.height << ")"; + if(m_rec_hdr.basic.width > 0 && m_rec_hdr.basic.height > 0) { + resize(m_rec_hdr.basic.width, m_rec_hdr.basic.height); + } + + QString title; + if (m_rec_hdr.basic.conn_port == 3389) + title.sprintf("[%s] %s@%s [Teleport-RDP录像回放]", m_rec_hdr.basic.acc_username, m_rec_hdr.basic.user_username, m_rec_hdr.basic.conn_ip); + else + title.sprintf("[%s] %s@%s:%d [Teleport-RDP录像回放]", m_rec_hdr.basic.acc_username, m_rec_hdr.basic.user_username, m_rec_hdr.basic.conn_ip, m_rec_hdr.basic.conn_port); + + setWindowTitle(title); + + return; + } + + + delete dat; +} diff --git a/client/tp-player/mainwindow.h b/client/tp-player/mainwindow.h new file mode 100644 index 0000000..cd593c0 --- /dev/null +++ b/client/tp-player/mainwindow.h @@ -0,0 +1,48 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "thr_play.h" +#include "update_data.h" +#include "record_format.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + void paintEvent(QPaintEvent *); + + +private slots: + void on_update_data(update_data*); + +private: + Ui::MainWindow *ui; + QImage m_bg; + QImage m_pt_normal; + QImage m_img_update; + //QPixmap m_bg1; + bool m_shown; + + ThreadPlay m_thr_play; + + bool m_show_bg; + TS_RECORD_HEADER m_rec_hdr; + TS_RECORD_RDP_POINTER m_pt; + + int m_img_update_x; + int m_img_update_y; + int m_img_update_w; + int m_img_update_h; +}; + +#endif // MAINWINDOW_H diff --git a/client/tp-player/mainwindow.ui b/client/tp-player/mainwindow.ui new file mode 100644 index 0000000..db843d5 --- /dev/null +++ b/client/tp-player/mainwindow.ui @@ -0,0 +1,36 @@ + + + MainWindow + + + + 0 + 0 + 500 + 360 + + + + Teleport Replayer + + + + + + 0 + 0 + 500 + 17 + + + + + + false + + + + + + + diff --git a/client/tp-player/record_format.h b/client/tp-player/record_format.h new file mode 100644 index 0000000..09a1c66 --- /dev/null +++ b/client/tp-player/record_format.h @@ -0,0 +1,96 @@ +#ifndef RECORD_FORMAT_H +#define RECORD_FORMAT_H + +#include + + +#define TYPE_HEADER_INFO 0 +#define TYPE_DATA 1 + + +#define TS_RECORD_TYPE_RDP_POINTER 0x12 // 鼠标坐标位置改变,用于绘制虚拟鼠标 +#define TS_RECORD_TYPE_RDP_IMAGE 0x13 // 服务端返回的图像,用于展示 + +#define TS_RDP_BTN_FREE 0 +#define TS_RDP_BTN_PRESSED 1 +#define TS_RDP_IMG_RAW 0 // 未压缩,原始数据(根据bitsPerPixel,多个字节对应一个点的颜色) +#define TS_RDP_IMG_BMP 1 // 压缩的BMP数据 + +#pragma pack(push,1) + +// 录像文件头(随着录像数据写入,会改变的部分) +typedef struct TS_RECORD_HEADER_INFO { + uint32_t magic; // "TPPR" 标志 TelePort Protocol Record + uint16_t ver; // 录像文件版本,目前为3 + uint32_t packages; // 总包数 + uint32_t time_ms; // 总耗时(毫秒) + //uint32_t file_size; // 数据文件大小 +}TS_RECORD_HEADER_INFO; +#define ts_record_header_info_size sizeof(TS_RECORD_HEADER_INFO) + +// 录像文件头(固定不变部分) +typedef struct TS_RECORD_HEADER_BASIC { + uint16_t protocol_type; // 协议:1=RDP, 2=SSH, 3=Telnet + uint16_t protocol_sub_type; // 子协议:100=RDP-DESKTOP, 200=SSH-SHELL, 201=SSH-SFTP, 300=Telnet + uint64_t timestamp; // 本次录像的起始时间(UTC时间戳) + uint16_t width; // 初始屏幕尺寸:宽 + uint16_t height; // 初始屏幕尺寸:高 + char user_username[64]; // teleport账号 + char acc_username[64]; // 远程主机用户名 + + char host_ip[40]; // 远程主机IP + char conn_ip[40]; // 远程主机IP + uint16_t conn_port; // 远程主机端口 + + char client_ip[40]; // 客户端IP + +// // RDP专有 +// uint8_t rdp_security; // 0 = RDP, 1 = TLS + +// uint8_t _reserve[512 - 2 - 2 - 8 - 2 - 2 - 64 - 64 - 40 - 40 - 2 - 40 - 1 - ts_record_header_info_size]; + uint8_t _reserve[512 - 2 - 2 - 8 - 2 - 2 - 64 - 64 - 40 - 40 - 2 - 40 - ts_record_header_info_size]; +}TS_RECORD_HEADER_BASIC; +#define ts_record_header_basic_size sizeof(TS_RECORD_HEADER_BASIC) + +typedef struct TS_RECORD_HEADER { + TS_RECORD_HEADER_INFO info; + TS_RECORD_HEADER_BASIC basic; +}TS_RECORD_HEADER; + +// header部分(header-info + header-basic) = 512B +#define ts_record_header_size sizeof(TS_RECORD_HEADER) + +// 一个数据包的头 +typedef struct TS_RECORD_PKG { + uint8_t type; // 包的数据类型 + uint32_t size; // 这个包的总大小(不含包头) + uint32_t time_ms; // 这个包距起始时间的时间差(毫秒,意味着一个连接不能持续超过49天) + uint8_t _reserve[3]; // 保留 +}TS_RECORD_PKG; + + +typedef struct TS_RECORD_RDP_POINTER { + uint16_t x; + uint16_t y; + uint8_t button; + uint8_t pressed; +}TS_RECORD_RDP_POINTER; + +// RDP图像更新 +typedef struct TS_RECORD_RDP_IMAGE_INFO { + uint16_t destLeft; + uint16_t destTop; + uint16_t destRight; + uint16_t destBottom; + uint16_t width; + uint16_t height; + uint16_t bitsPerPixel; + uint8_t format; + uint8_t _reserved; +}TS_RECORD_RDP_IMAGE_INFO; + + +#pragma pack(pop) + + +#endif // RECORD_FORMAT_H diff --git a/client/tp-player/res/bg.png b/client/tp-player/res/bg.png new file mode 100644 index 0000000..e35d671 Binary files /dev/null and b/client/tp-player/res/bg.png differ diff --git a/client/tp-player/res/cursor.png b/client/tp-player/res/cursor.png new file mode 100644 index 0000000..5728f4f Binary files /dev/null and b/client/tp-player/res/cursor.png differ diff --git a/client/tp-player/res/tp-player.ico b/client/tp-player/res/tp-player.ico new file mode 100644 index 0000000..d3fce7a Binary files /dev/null and b/client/tp-player/res/tp-player.ico differ diff --git a/client/tp-player/rle.c b/client/tp-player/rle.c new file mode 100644 index 0000000..6906e4e --- /dev/null +++ b/client/tp-player/rle.c @@ -0,0 +1,974 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Bitmap decompression routines + Copyright (C) Matthew Chapman 1999-2008 + + 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 3 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, see . +*/ + +/* three seperate function for speed when decompressing the bitmaps + when modifing one function make the change in the others + jay.sorg@gmail.com */ + +/* indent is confused by this file */ +/* *INDENT-OFF* */ + + +#include +#include "rle.h" + +/* Specific rename for RDPY integration */ +#define unimpl(str, code) + +//#define RD_BOOL int +//#define False 0 +//#define True 1 +/* end specific rename */ + +#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; \ + } \ +} + +/* 1 byte bitmap decompress */ +RD_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 */ +RD_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 */ +RD_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; +} + +/* decompress a colour plane */ +static int +process_plane(uint8 * in, int width, int height, uint8 * out, int size) +{ + int indexw; + int indexh; + int code; + int collen; + int replen; + int color; + int x; + int revcode; + uint8 * last_line; + uint8 * this_line; + uint8 * org_in; + uint8 * org_out; + + org_in = in; + org_out = out; + last_line = 0; + indexh = 0; + while (indexh < height) + { + out = (org_out + width * height * 4) - ((indexh + 1) * width * 4); + color = 0; + this_line = out; + indexw = 0; + if (last_line == 0) + { + while (indexw < width) + { + code = CVAL(in); + replen = code & 0xf; + collen = (code >> 4) & 0xf; + revcode = (replen << 4) | collen; + if ((revcode <= 47) && (revcode >= 16)) + { + replen = revcode; + collen = 0; + } + while (collen > 0) + { + color = CVAL(in); + *out = color; + out += 4; + indexw++; + collen--; + } + while (replen > 0) + { + *out = color; + out += 4; + indexw++; + replen--; + } + } + } + else + { + while (indexw < width) + { + code = CVAL(in); + replen = code & 0xf; + collen = (code >> 4) & 0xf; + revcode = (replen << 4) | collen; + if ((revcode <= 47) && (revcode >= 16)) + { + replen = revcode; + collen = 0; + } + while (collen > 0) + { + x = CVAL(in); + if (x & 1) + { + x = x >> 1; + x = x + 1; + color = -x; + } + else + { + x = x >> 1; + color = x; + } + x = last_line[indexw * 4] + color; + *out = x; + out += 4; + indexw++; + collen--; + } + while (replen > 0) + { + x = last_line[indexw * 4] + color; + *out = x; + out += 4; + indexw++; + replen--; + } + } + } + indexh++; + last_line = this_line; + } + return (int) (in - org_in); +} + +/* 4 byte bitmap decompress */ +RD_BOOL +bitmap_decompress4(uint8 * output, int width, int height, uint8 * input, int size) +{ + int code; + int bytes_pro; + int total_pro; + + code = CVAL(input); + if (code != 0x10) + { + return False; + } + total_pro = 1; + bytes_pro = process_plane(input, width, height, output + 3, size - total_pro); + total_pro += bytes_pro; + input += bytes_pro; + bytes_pro = process_plane(input, width, height, output + 2, size - total_pro); + total_pro += bytes_pro; + input += bytes_pro; + bytes_pro = process_plane(input, width, height, output + 1, size - total_pro); + total_pro += bytes_pro; + input += bytes_pro; + bytes_pro = process_plane(input, width, height, output + 0, size - total_pro); + total_pro += bytes_pro; + return size == total_pro; +} + +int +bitmap_decompress_15(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size) { + uint8 * temp = (uint8*)malloc(input_width * input_height * 2); + RD_BOOL rv = bitmap_decompress2(temp, input_width, input_height, input, size); + + // convert to rgba + for (int y = 0; y < output_height; y++) { + for (int x = 0; x < output_width; x++) { + uint16 a = ((uint16*)temp)[y * input_width + x]; + uint8 r = (a & 0x7c00) >> 10; + uint8 g = (a & 0x03e0) >> 5; + uint8 b = (a & 0x001f); + r = r * 255 / 31; + g = g * 255 / 31; + b = b * 255 / 31; + ((uint32*)output)[y * output_width + x] = 0xff << 24 | b << 16 | g << 8 | r; + } + } + + free(temp); + return rv; +} + +int +bitmap_decompress_16(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size) { + uint8 * temp = (uint8*)malloc(input_width * input_height * 2); + RD_BOOL rv = bitmap_decompress2(temp, input_width, input_height, input, size); + + // convert to rgba + for (int y = 0; y < output_height; y++) { + for (int x = 0; x < output_width; x++) { + uint16 a = ((uint16*)temp)[y * input_width + x]; + uint8 r = (a & 0xf800) >> 11; + uint8 g = (a & 0x07e0) >> 5; + uint8 b = (a & 0x001f); + r = r * 255 / 31; + g = g * 255 / 63; + b = b * 255 / 31; + ((uint32*)output)[y * output_width + x] = 0xff << 24 | b << 16 | g << 8 | r; + } + } + free(temp); + return rv; +} + +int +bitmap_decompress_24(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size) { + uint8 * temp = (uint8*)malloc(input_width * input_height * 3); + RD_BOOL rv = bitmap_decompress3(temp, input_width, input_height, input, size); + + // convert to rgba + for (int y = 0; y < output_height; y++) { + for (int x = 0; x < output_width; x++) { + uint8 r = temp[(y * input_width + x) * 3]; + uint8 g = temp[(y * input_width + x) * 3 + 1]; + uint8 b = temp[(y * input_width + x) * 3 + 2]; + ((uint32*)output)[y * output_width + x] = 0xff << 24 | b << 16 | g << 8 | r; + } + } + free(temp); + + return rv; +} + +int +bitmap_decompress_32(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size) { + uint8 * temp = (uint8*)malloc(input_width * input_height * 4); + RD_BOOL rv = bitmap_decompress4(temp, input_width, input_height, input, size); + + // convert to rgba + for (int y = 0; y < output_height; y++) { + for (int x = 0; x < output_width; x++) { + uint8 r = temp[(y * input_width + x) * 4]; + uint8 g = temp[(y * input_width + x) * 4 + 1]; + uint8 b = temp[(y * input_width + x) * 4 + 2]; + uint8 a = temp[(y * input_width + x) * 4 + 3]; + ((uint32*)output)[y * output_width + x] = 0xff << 24 | r << 16 | g << 8 | b; + } + } + free(temp); + + return rv; +} diff --git a/client/tp-player/rle.h b/client/tp-player/rle.h new file mode 100644 index 0000000..3257d8f --- /dev/null +++ b/client/tp-player/rle.h @@ -0,0 +1,31 @@ +#ifndef RLE_H +#define RLE_H + +#define RD_BOOL int +#define False 0 +#define True 1 + +#define uint8 unsigned char +#define uint16 unsigned short +#define uint32 unsigned int + +#ifdef __cplusplus +extern "C" { +#endif + +RD_BOOL bitmap_decompress1(uint8 * output, int width, int height, uint8 * input, int size); +RD_BOOL bitmap_decompress2(uint8 * output, int width, int height, uint8 * input, int size); +RD_BOOL bitmap_decompress3(uint8 * output, int width, int height, uint8 * input, int size); +RD_BOOL bitmap_decompress4(uint8 * output, int width, int height, uint8 * input, int size); + +//int bitmap_decompress_15(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size); +//int bitmap_decompress_16(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size); +//int bitmap_decompress_24(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size); +//int bitmap_decompress_32(uint8 * output, int output_width, int output_height, int input_width, int input_height, uint8* input, int size); + + +#ifdef __cplusplus +} +#endif + +#endif // RLE_H diff --git a/client/tp-player/thr_play.cpp b/client/tp-player/thr_play.cpp new file mode 100644 index 0000000..6f5b64f --- /dev/null +++ b/client/tp-player/thr_play.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include "thr_play.h" +#include "record_format.h" + +static QString REPLAY_PATH = "E:\\work\\tp4a\\teleport\\server\\share\\replay\\rdp\\000000197\\"; + + +ThreadPlay::ThreadPlay() +{ + m_need_stop = false; +} + +void ThreadPlay::stop() { + m_need_stop = true; +} + +void ThreadPlay::run() { + qint64 read_len = 0; + uint32_t total_pkg = 0; + + QString hdr_filename(REPLAY_PATH); + hdr_filename += "tp-rdp.tpr"; + + QFile f_hdr(hdr_filename); + if(!f_hdr.open(QFile::ReadOnly)) { + qDebug() << "Can not open " << hdr_filename << " for read."; + return; + } + else { + update_data* dat = new update_data; + dat->data_type(TYPE_HEADER_INFO); + dat->alloc_data(sizeof(TS_RECORD_HEADER)); + + read_len = f_hdr.read((char*)(dat->data_buf()), dat->data_len()); + if(read_len != sizeof(TS_RECORD_HEADER)) { + delete dat; + qDebug() << "invaid .tpr file."; + return; + } + + TS_RECORD_HEADER* hdr = (TS_RECORD_HEADER*)dat->data_buf(); + total_pkg = hdr->info.packages; + + emit signal_update_data(dat); + } + + + + QString dat_filename(REPLAY_PATH); + dat_filename += "tp-rdp.dat"; + + QFile f_dat(dat_filename); + if(!f_dat.open(QFile::ReadOnly)) { + qDebug() << "Can not open " << dat_filename << " for read."; + return; + } + + for(uint32_t i = 0; i < total_pkg; ++i) { + if(m_need_stop) { + qDebug() << "stop, user cancel."; + break; + } + + TS_RECORD_PKG pkg; + read_len = f_dat.read((char*)(&pkg), sizeof(pkg)); + if(read_len != sizeof(TS_RECORD_PKG)) { + qDebug() << "invaid .dat file."; + return; + } + + update_data* dat = new update_data; + dat->data_type(TYPE_DATA); + dat->alloc_data(sizeof(TS_RECORD_PKG) + pkg.size); + memcpy(dat->data_buf(), &pkg, sizeof(TS_RECORD_PKG)); + read_len = f_dat.read((char*)(dat->data_buf()+sizeof(TS_RECORD_PKG)), pkg.size); + if(read_len != pkg.size) { + delete dat; + qDebug() << "invaid .dat file."; + return; + } + + emit signal_update_data(dat); + msleep(5); + } +} diff --git a/client/tp-player/thr_play.h b/client/tp-player/thr_play.h new file mode 100644 index 0000000..3c3bb2d --- /dev/null +++ b/client/tp-player/thr_play.h @@ -0,0 +1,25 @@ +#ifndef THR_PLAY_H +#define THR_PLAY_H + +#include +#include "update_data.h" + + +class ThreadPlay : public QThread +{ + Q_OBJECT +public: + ThreadPlay(); + + virtual void run(); + void stop(); + + +signals: + void signal_update_data(update_data*); + +private: + bool m_need_stop; +}; + +#endif // THR_PLAY_H diff --git a/client/tp-player/tp-player.pro b/client/tp-player/tp-player.pro new file mode 100644 index 0000000..cb73260 --- /dev/null +++ b/client/tp-player/tp-player.pro @@ -0,0 +1,26 @@ +TEMPLATE = app +TARGET = tp-player + +QT += core gui widgets + +HEADERS += \ + mainwindow.h \ + thr_play.h \ + update_data.h \ + record_format.h \ + rle.h + +SOURCES += main.cpp \ + mainwindow.cpp \ + thr_play.cpp \ + update_data.cpp \ + rle.c + +RESOURCES += \ + tp-player.qrc + +RC_FILE += \ + tp-player.rc + +FORMS += \ + mainwindow.ui diff --git a/client/tp-player/tp-player.qrc b/client/tp-player/tp-player.qrc new file mode 100644 index 0000000..7b8956f --- /dev/null +++ b/client/tp-player/tp-player.qrc @@ -0,0 +1,7 @@ + + + res/logo.png + res/bg.png + res/cursor.png + + diff --git a/client/tp-player/tp-player.rc b/client/tp-player/tp-player.rc new file mode 100644 index 0000000..df678b3 --- /dev/null +++ b/client/tp-player/tp-player.rc @@ -0,0 +1,2 @@ +IDI_ICON1 ICON DISCARDABLE "res\\tp-player.ico" + diff --git a/client/tp-player/update_data.cpp b/client/tp-player/update_data.cpp new file mode 100644 index 0000000..d84f364 --- /dev/null +++ b/client/tp-player/update_data.cpp @@ -0,0 +1,30 @@ +#include "update_data.h" + +update_data::update_data(QObject *parent) : QObject(parent) +{ + m_data_type = 0xff; + m_data_buf = nullptr; + m_data_len = 0; +} + +update_data::~update_data() { + if(m_data_buf) + delete m_data_buf; +} + +void update_data::alloc_data(uint32_t len) { + if(m_data_buf) + delete m_data_buf; + + m_data_buf = new uint8_t[len]; + memset(m_data_buf, 0, len); + m_data_len = len; +} + +void update_data::attach_data(const uint8_t* dat, uint32_t len) { + if(m_data_buf) + delete m_data_buf; + m_data_buf = new uint8_t[len]; + memcpy(m_data_buf, dat, len); + m_data_len = len; +} diff --git a/client/tp-player/update_data.h b/client/tp-player/update_data.h new file mode 100644 index 0000000..505c040 --- /dev/null +++ b/client/tp-player/update_data.h @@ -0,0 +1,33 @@ +#ifndef UPDATE_DATA_H +#define UPDATE_DATA_H + +#include + +class update_data : public QObject +{ + Q_OBJECT +public: + explicit update_data(QObject *parent = nullptr); + virtual ~update_data(); + + void alloc_data(uint32_t len); + void attach_data(const uint8_t* dat, uint32_t len); + + void data_type(int dt) {m_data_type = dt;} + int data_type() const {return m_data_type;} + + uint8_t* data_buf() {return m_data_buf;} + uint32_t data_len() const {return m_data_len;} + +signals: + +public slots: + + +private: + int m_data_type; + uint8_t* m_data_buf; + uint32_t m_data_len; +}; + +#endif // UPDATE_DATA_H diff --git a/resource/icon-tp-player.psd b/resource/icon-tp-player.psd new file mode 100644 index 0000000..3ae9008 Binary files /dev/null and b/resource/icon-tp-player.psd differ