; VBoxCtrl - утилита для управления драйвером VBoxGuest. ; Показывает версию, список сервисов и синхронизирует буфер обмена ; между хостом и гостем (KolibriOS) ; ; Окно опрашивает драйвер раз в секунду этого хватает. ; Clipboard гоняется в обе стороны автоматически, раз в секунду. ; ; ; Слоты не показатель, что текст обновился, разные программы по разному работают format binary as "" use32 org 0 db 'MENUET01' dd 0x01 dd START dd I_END dd MEM dd stacktop dd 0, 0 __DEBUG__ = 1 __DEBUG_LEVEL__ = 1 include '../../../PROGRAMS/macros.inc' include '../../../PROGRAMS/KOSfuncs.inc' include '../../../PROGRAMS/proc32.inc' include '../../../PROGRAMS/debug-fdo.inc' include '../../../PROGRAMS/dll.inc' ; IOCTL-коды драйвера IOCTL_GET_VERSION = 0 IOCTL_GET_SERVICES = 1 IOCTL_SVC_ENABLE = 2 IOCTL_SVC_DISABLE = 3 IOCTL_CLIP_STATUS = 10 IOCTL_CLIP_READ = 11 IOCTL_CLIP_WRITE = 12 VBOX_SHCL_FMT_UNICODETEXT = 0x01 WINDOW_WIDTH = 420 WINDOW_HEIGHT = 460 MAX_SERVICES = 8 MAX_CLIP_BUF = 65536 PREVIEW_SIZE = 1024 PREVIEW_COLS = 55 PREVIEW_LINES = 5 BTN_CLOSE = 1 BTN_SVC_BASE = 20 ; кнопки сервисов идут с 20 ; цвета кнопок Enable/Disable (бледно-зелёный / бледно-красный) COLOR_ENABLED = 0xAADDAA COLOR_DISABLED = 0xDDAAAA ; структура IOCTL-запроса virtual at 0 IOCTL: .handle dd ? .io_code dd ? .input dd ? .inp_size dd ? .output dd ? .out_size dd ? .size = $ end virtual ; смещения внутри SERVICE_INFO (24 байта на запись) SVC_ID = 0 SVC_NAME = 4 SVC_ENABLED = 20 SVC_SIZE = 24 ; ========== Старт ========== START: mcall SF_SYS_MISC, SSF_HEAP_INIT ; Грузим iconv, он нужен для перекодировки clipboard. ; Если не загрузился, то работаем без него (clipboard не будет работать). stdcall dll.Load, @IMPORT test eax, eax jnz .no_iconv invoke iconv_open, sz_cp866, sz_utf16le mov [cd_u16_cp866], eax ; UTF-16LE -> CP866 invoke iconv_open, sz_utf8, sz_utf16le mov [cd_u16_utf8], eax ; UTF-16LE -> UTF-8 invoke iconv_open, sz_utf16le, sz_cp866 mov [cd_cp866_u16], eax ; CP866 -> UTF-16LE invoke iconv_open, sz_utf16le, sz_cp1251 mov [cd_cp1251_u16], eax ; CP1251 -> UTF-16LE invoke iconv_open, sz_utf16le, sz_utf8 mov [cd_utf8_u16], eax ; UTF-8 -> UTF-16LE mov byte [iconv_loaded], 1 jmp .iconv_ok .no_iconv: DEBUGF 1, "[VBoxCtrl] iconv.obj not loaded!\n" mov byte [iconv_loaded], 0 .iconv_ok: ; Загружаем драйвер VBOXGUEST mcall SF_SYS_MISC, SSF_LOAD_DRIVER, drv_name test eax, eax jz .no_driver mov [driver_handle], eax mov byte [driver_loaded], 1 ; Хэндл нужно вбить во все IOCTL-структуры, они статические mov [ioctl_ver + IOCTL.handle], eax mov [ioctl_svcl + IOCTL.handle], eax mov [ioctl_svc_en + IOCTL.handle], eax mov [ioctl_svc_di + IOCTL.handle], eax mov [ioctl_clstat + IOCTL.handle], eax mov [ioctl_clread + IOCTL.handle], eax mov [ioctl_clwrit + IOCTL.handle], eax call get_driver_version call refresh_services jmp .events .no_driver: mov byte [driver_loaded], 0 .events: mcall SF_SET_EVENTS_MASK, 0x07 ; ========== главный цикл ========== red: call draw_window still: mcall SF_WAIT_EVENT_TIMEOUT, 100 ; таймаут ~1 сек test eax, eax jz .timeout dec eax jz red dec eax jz key dec eax jz button jmp still .timeout: ; Раз в секунду обновляем список сервисов и дёргаем clipboard хоста cmp byte [driver_loaded], 1 jne still call refresh_services call poll_host_clipboard ; Проверяем по fingerprint, не изменился ли clipboard гостя call get_clip_fingerprint test eax, eax jz .skip_guest_check cmp eax, [guest_clip_fp] je .skip_guest_check mov [guest_clip_fp], eax call guest_to_host_clipboard .skip_guest_check: call draw_window jmp still ; --- обработка клавиш --- key: mcall SF_GET_KEY ; съедаем клавишу cmp byte [driver_loaded], 1 jne still call guest_to_host_clipboard jmp still ; --- обработка кнопок --- button: mcall SF_GET_BUTTON shr eax, 8 cmp eax, BTN_CLOSE je .close ; Кнопки переключения сервисов sub eax, BTN_SVC_BASE jl still cmp eax, [svc_count] jge still call toggle_service call refresh_services call draw_window jmp still .close: mcall -1 ; ========== займемся художеством =) ========== draw_window: mcall 12, 1 ; Берём системные цвета и делаем из них варианты с альфой для текста mcall 48, 3, sc, sizeof.system_colors mov eax, [sc.work_text] mov [wc_work_text], eax or eax, 0x90000000 mov [wc_text_90], eax mov eax, [sc.work_text] or eax, 0x80000000 mov [wc_text_80], eax mov eax, [sc.work_button_text] or eax, 0x80000000 mov [wc_btn_text], eax mov edx, [sc.work] or edx, 0x13000000 ; тип окна 3 = skinned mcall 0, <100, WINDOW_WIDTH>, <100, WINDOW_HEIGHT>, , 0, title_str cmp byte [driver_loaded], 1 je .draw_normal ; Драйвер не загрузился — показываем ошибку и выходим mcall 4, <10, 30>, 0x90FF0000, str_no_driver jmp .draw_end .draw_normal: ; строка "Version: N" mcall 4, <10, 30>, [wc_text_80], str_version mcall 47, 0x00020000, [driver_version], <65, 30>, [wc_work_text] ; строка "Connected: Yes/No" mcall 4, <120, 30>, [wc_text_80], str_connected cmp dword [clip_connected], 0 je .not_connected mcall 4, <185, 30>, 0x80008000, str_yes jmp .show_fmt .not_connected: mcall 4, <185, 30>, 0x80FF0000, str_no .show_fmt: ; формат clipboard хоста mcall 4, <260, 30>, [wc_text_80], str_formats mcall 47, 0x00040100, [clip_host_fmt], <302, 30>, [wc_work_text] ; заголовок списка сервисов mcall 4, <10, 60>, [wc_text_90], str_services mov dword [tmp_idx], 0 .svc_loop: mov eax, [tmp_idx] cmp eax, [svc_count] jae .svc_done imul eax, 26 add eax, 85 mov [tmp_y], eax mov eax, [tmp_idx] imul eax, SVC_SIZE add eax, svc_out_buf + 4 mov [tmp_ptr], eax ; имя сервиса mov ebx, 15 shl 16 mov eax, [tmp_y] add eax, 4 add ebx, eax mov eax, [tmp_ptr] lea edx, [eax + SVC_NAME] mcall 4, , [wc_text_80] ; статус (Enabled/Disabled) mov ebx, 170 shl 16 mov eax, [tmp_y] add eax, 4 add ebx, eax mov eax, [tmp_ptr] cmp dword [eax + SVC_ENABLED], 0 je .svc_show_dis mcall 4, , 0x80008000, str_enabled jmp .svc_btn .svc_show_dis: mcall 4, , 0x80800000, str_disabled .svc_btn: ; кнопка Enable/Disable справа mov ebx, (WINDOW_WIDTH - 115) shl 16 + 85 mov ecx, [tmp_y] shl ecx, 16 or ecx, 20 mov edx, [tmp_idx] add edx, BTN_SVC_BASE mov eax, [tmp_ptr] cmp dword [eax + SVC_ENABLED], 0 je .svc_btn_en ; сервис включён — кнопка "Disable" (красноватая) mcall 8, , , , COLOR_DISABLED mov ebx, (WINDOW_WIDTH - 108) shl 16 mov eax, [tmp_y] add eax, 4 add ebx, eax mcall 4, , [wc_btn_text], str_disable jmp .svc_next .svc_btn_en: ; сервис выключен — кнопка "Enable" (зеленоватая) mcall 8, , , , COLOR_ENABLED mov ebx, (WINDOW_WIDTH - 108) shl 16 mov eax, [tmp_y] add eax, 4 add ebx, eax mcall 4, , [wc_btn_text], str_enable .svc_next: inc dword [tmp_idx] jmp .svc_loop .svc_done: ; --- блок превью clipboard --- mov eax, [svc_count] imul eax, 26 add eax, 100 mov [tmp_y], eax mov ebx, 10 shl 16 add ebx, eax mcall 4, , [wc_text_90], str_clipboard ; размер данных, если есть что показать cmp dword [rhc_conv_len], 0 je .skip_size call format_size_str mov ebx, 250 shl 16 add ebx, [tmp_y] mcall 4, , [wc_text_90], size_str_buf .skip_size: ; фоновый прямоугольник под текст превью add dword [tmp_y], 20 mov ecx, [tmp_y] shl ecx, 16 or ecx, (PREVIEW_LINES * 16 + 10) mcall 13, <8, (WINDOW_WIDTH - 26)>, , [sc.work_light] cmp dword [preview_len], 0 je .no_preview mov dword [tmp_idx], 0 ; счётчик строк mov dword [tmp_ptr], preview_buf mov eax, [preview_len] mov [tmp_rem], eax .preview_line: cmp dword [tmp_idx], PREVIEW_LINES jae .draw_end cmp dword [tmp_rem], 0 jbe .draw_end ; сколько символов смотрим: min(tmp_rem, PREVIEW_COLS) mov esi, [tmp_rem] cmp esi, PREVIEW_COLS jbe @f mov esi, PREVIEW_COLS @@: ; ищем перевод строки в текущем фрагменте mov edi, [tmp_ptr] mov ecx, esi mov al, 0x0A repne scasb jne .no_lf ; нашли LF — печатаем до него (без LF и без CR перед ним) mov eax, edi sub eax, [tmp_ptr] mov [tmp_skip], eax dec eax mov esi, eax test esi, esi jz .print_line mov edi, [tmp_ptr] cmp byte [edi + esi - 1], 0x0D jne .print_line dec esi jmp .print_line .no_lf: ; LF не нашли — берём всё что есть mov [tmp_skip], esi .print_line: mov ebx, 12 shl 16 mov eax, [tmp_y] mov ecx, [tmp_idx] imul ecx, 16 add eax, ecx add eax, 5 add ebx, eax test esi, esi jz .skip_print mcall 4, , [wc_work_text], [tmp_ptr] .skip_print: mov eax, [tmp_skip] add [tmp_ptr], eax sub [tmp_rem], eax inc dword [tmp_idx] jmp .preview_line .no_preview: mov ebx, 12 shl 16 mov eax, [tmp_y] add eax, 5 add ebx, eax mcall 4, , 0x80808080, str_no_data .draw_end: mcall SF_REDRAW, SSF_END_DRAW ret ; --- обёртка над mcall 68,17 (вызов ioctl драйвера) --- ; ecx = указатель на IOCTL-структуру, возвращает eax = код результата call_ioctl: mcall SF_SYS_MISC, SSF_CONTROL_DRIVER ret ; --- запрашиваем версию драйвера --- get_driver_version: mov ecx, ioctl_ver call call_ioctl test eax, eax jnz .err mov eax, [version_buf] mov [driver_version], eax ret .err: mov dword [driver_version], 0 ret ; --- обновляем список сервисов из драйвера --- refresh_services: mov ecx, ioctl_svcl call call_ioctl test eax, eax jnz .err mov eax, dword [svc_out_buf] ; первый dd = количество cmp eax, MAX_SERVICES jbe @f mov eax, MAX_SERVICES ; больше MAX_SERVICES не бывает @@: mov [svc_count], eax ret .err: mov dword [svc_count], 0 ret ; --- включить/выключить сервис; eax = индекс сервиса --- toggle_service: push ebx ecx cmp byte [driver_loaded], 1 jne .done cmp eax, [svc_count] jae .done imul eax, SVC_SIZE add eax, svc_out_buf + 4 mov ebx, [eax + SVC_ID] mov [svc_id_buf], ebx cmp dword [eax + SVC_ENABLED], 0 je .enable mov ecx, ioctl_svc_di call call_ioctl jmp .done .enable: mov ecx, ioctl_svc_en call call_ioctl .done: pop ecx ebx ret ; --- опрос буфера обмена хоста (вызывается раз в секунду) --- poll_host_clipboard: mov ecx, ioctl_clstat call call_ioctl test eax, eax jnz .done ; разбираем ответ: connected, host_formats, host_new mov eax, dword [clip_status_buf] mov [clip_connected], eax mov eax, dword [clip_status_buf + 4] mov [clip_host_fmt], eax mov eax, dword [clip_status_buf + 8] ; читаем только если есть новые данные и это текст test eax, eax jz .done test dword [clip_host_fmt], VBOX_SHCL_FMT_UNICODETEXT jz .done call read_host_clipboard .done: ret ; --- читаем clipboard хоста через IOCTL и пишем в гостевой clipboard --- ; Данные приходят в UTF-16LE, мы конвертируем в CP866. read_host_clipboard: pushad ; Начинаем с буфера 64KB. Если не влезло — попробуем больше. mov dword [rhc_bufsize], MAX_CLIP_BUF mcall SF_SYS_MISC, SSF_MEM_ALLOC, 4 + MAX_CLIP_BUF test eax, eax jz .done mov [rhc_buf], eax .rhc_retry: mov eax, [rhc_buf] mov [ioctl_clread + IOCTL.output], eax mov eax, [rhc_bufsize] add eax, 4 mov [ioctl_clread + IOCTL.out_size], eax mov ecx, ioctl_clread call call_ioctl test eax, eax jz .rhc_ok ; eax=1 означает overflow — нужен буфер побольше cmp eax, 1 jne .free mov esi, [rhc_buf] mov ecx, [esi] ; сколько реально нужно test ecx, ecx jz .free cmp ecx, [rhc_bufsize] jbe .free ; ограничиваем 100MB — больше смысла нет cmp ecx, 100*1024*1024 jbe @f mov ecx, 100*1024*1024 @@: mov [rhc_target_size], ecx .rhc_alloc_retry: mov ecx, [rhc_target_size] add ecx, 4 mcall SF_SYS_MISC, SSF_MEM_ALLOC test eax, eax jnz .rhc_alloc_ok ; не хватает памяти — уменьшаем запрос вдвое и пробуем снова mov ecx, [rhc_target_size] shr ecx, 1 cmp ecx, 1024*1024 ; ниже 1MB не опускаемся jb .rhc_alloc_failed mov [rhc_target_size], ecx DEBUGF 2, "[VBoxCtrl] Clipboard alloc failed, reducing to %u bytes\n", ecx jmp .rhc_alloc_retry .rhc_alloc_failed: DEBUGF 2, "[VBoxCtrl] Failed to allocate clipboard buffer (minimum 1MB)\n" jmp .free .rhc_alloc_ok: push eax ; сохраняем новый буфер (free затирает eax) mov ecx, [rhc_buf] mcall SF_SYS_MISC, SSF_MEM_FREE ; освобождаем старый буфер pop eax mov [rhc_buf], eax jmp .rhc_retry .rhc_ok: ; [buf+0] = реальный размер, [buf+4..] = данные в UTF-16LE mov esi, [rhc_buf] mov ecx, [esi] DEBUGF 2, "[VBoxCtrl] read_host: actual_size=%u\n", ecx test ecx, ecx jz .free cmp ecx, 1024*1024 jbe @f DEBUGF 2, "[VBoxCtrl] read_host: Large clipboard (%u bytes) - conversion in progress\n", ecx @@: mov [rhc_u16_size], ecx add esi, 4 mov [rhc_u16_ptr], esi ; превью: конвертируем в CP866 только первые PREVIEW_SIZE байт mov eax, [cd_u16_cp866] mov edi, preview_buf mov edx, PREVIEW_SIZE call iconv_do mov [preview_len], eax mov byte [preview_buf + eax], 0 ; для гостевого clipboard конвертируем всё целиком ; CP866-буфер нужен максимум u16_size/2 байт mov ecx, [rhc_u16_size] shr ecx, 1 add ecx, 4 mcall SF_SYS_MISC, SSF_MEM_ALLOC test eax, eax jnz .cb_alloc_ok DEBUGF 2, "[VBoxCtrl] read_host: CP866 buffer alloc FAILED (need %u)\n", [rhc_u16_size] jmp .free .cb_alloc_ok: mov [rhc_conv_buf], eax mov esi, [rhc_u16_ptr] mov ecx, [rhc_u16_size] mov edi, [rhc_conv_buf] mov edx, [rhc_u16_size] shr edx, 1 mov eax, [cd_u16_cp866] call iconv_do mov [rhc_conv_len], eax mov ebx, [rhc_u16_size] cmp ebx, 1024*1024 jbe @f DEBUGF 2, "[VBoxCtrl] read_host: Conversion complete (%u -> %u bytes)\n", ebx, eax @@: DEBUGF 2, "[VBoxCtrl] read_host: cp866_len=%u (u16_size=%u)\n", eax, [rhc_u16_size] test eax, eax jz .conv_failed ; пишем CP866 в гостевой clipboard mov esi, [rhc_conv_buf] mov ecx, [rhc_conv_len] mov edx, 1 ; encoding = 1 (CP866) call write_to_guest_clipboard jmp .conv_ok .conv_failed: DEBUGF 2, "[VBoxCtrl] read_host: CP866 conversion FAILED, skipping clipboard write\n" .conv_ok: ; обновляем fingerprint чтобы не словить feedback loop call get_clip_fingerprint mov [guest_clip_fp], eax mov ecx, [rhc_conv_buf] mcall SF_SYS_MISC, SSF_MEM_FREE mov dword [rhc_conv_buf], 0 ; обнуляем, чтоб при следующем вызове не было мусора .free: mov ecx, [rhc_buf] mcall SF_SYS_MISC, SSF_MEM_FREE .done: popad ret ; --- записать текст в гостевой clipboard (fn54.2) --- ; esi = указатель на данные, ecx = длина, edx = кодировка (0=UTF-8, 1=cp866, 2=cp1251) write_to_guest_clipboard: pushad DEBUGF 2, "[VBoxCtrl] write_guest_cb: len=%u enc=%u\n", ecx, edx mov [wgc_ptr], esi mov [wgc_len], ecx mov [wgc_enc], edx ; буфер = 12 байт заголовка + данные lea ecx, [ecx + 12] mov [wgc_total], ecx mcall SF_SYS_MISC, SSF_MEM_ALLOC test eax, eax jnz .alloc_ok DEBUGF 2, "[VBoxCtrl] write_guest_cb: alloc FAILED (size=%u)\n", [wgc_total] jmp .done .alloc_ok: mov [wgc_buf], eax ; заполняем заголовок слота clipboard mov ecx, [wgc_total] mov dword [eax], ecx ; total_size mov dword [eax + 4], 0 ; type = text mov ecx, [wgc_enc] mov dword [eax + 8], ecx ; encoding ; копируем данные lea edi, [eax + 12] mov esi, [wgc_ptr] mov ecx, [wgc_len] rep movsb ; удаляем предыдущий слот чтобы не копилось mcall SF_CLIPBOARD, SSF_DEL_SLOT DEBUGF 2, "[VBoxCtrl] write_guest_cb: DEL_SLOT rc=%d\n", eax mcall SF_CLIPBOARD, SSF_WRITE_CB, [wgc_total], [wgc_buf] DEBUGF 2, "[VBoxCtrl] write_guest_cb: WRITE_CB rc=%d (total=%u)\n", eax, [wgc_total] mov ecx, [wgc_buf] mcall SF_SYS_MISC, SSF_MEM_FREE .done: popad ret ; --- отправить гостевой clipboard на хост через IOCTL --- guest_to_host_clipboard: pushad DEBUGF 1, "[VBoxCtrl] guest->host: enter\n" mcall SF_CLIPBOARD, 0 ; сколько слотов в clipboard? cmp eax, -1 je .done_noslots test eax, eax jz .done_noslots DEBUGF 1, "[VBoxCtrl] guest->host: slot_count=%u\n", eax ; читаем последний слот dec eax mov ecx, eax mcall SF_CLIPBOARD, 1 DEBUGF 1, "[VBoxCtrl] guest->host: read slot rc=0x%x\n", eax cmp eax, -1 je .done cmp eax, 1 je .done mov [gth_slot], eax ; проверяем: тип должен быть 0 (текст), кодировка 0/1/2 DEBUGF 1, "[VBoxCtrl] guest->host: type=%u enc=%u\n", [eax+4], [eax+8] cmp dword [eax + 4], 0 jne .bad_type mov ebx, [eax + 8] cmp ebx, 0 ; UTF-8 je .enc_ok cmp ebx, 1 ; CP866 je .enc_ok cmp ebx, 2 ; CP1251 je .enc_ok jmp .bad_type .enc_ok: mov ecx, [eax] ; total_size sub ecx, 12 ; минус заголовок jbe .free_slot mov [gth_data_len], ecx DEBUGF 1, "[VBoxCtrl] guest->host: data_len=%u enc=%u\n", ecx, ebx ; обновляем превью сразу (без конвертации — cp866 и так читается) push ecx eax lea esi, [eax + 12] mov edi, preview_buf cmp ecx, PREVIEW_SIZE jbe @f mov ecx, PREVIEW_SIZE @@: mov [preview_len], ecx rep movsb mov byte [edi], 0 pop eax ecx ; выделяем буфер под UTF-16LE: 4 (format tag) + len*2 + 2 (null-терминатор) push ebx lea ecx, [ecx * 2 + 6] mcall SF_SYS_MISC, SSF_MEM_ALLOC pop ebx test eax, eax jz .alloc_fail mov [gth_u16buf], eax ; format tag в начале — нужен для IOCTL_CLIP_WRITE mov dword [eax], VBOX_SHCL_FMT_UNICODETEXT lea edi, [eax + 4] ; конвертируем в UTF-16LE, дескриптор зависит от кодировки mov esi, [gth_slot] mov ecx, [gth_data_len] add esi, 12 cmp ebx, 1 je .gth_enc_cp866 cmp ebx, 2 je .gth_enc_cp1251 mov eax, [cd_utf8_u16] ; по умолчанию UTF-8 jmp .gth_do_conv .gth_enc_cp866: mov eax, [cd_cp866_u16] jmp .gth_do_conv .gth_enc_cp1251: mov eax, [cd_cp1251_u16] .gth_do_conv: mov edx, [gth_data_len] shl edx, 1 add edx, 2 call iconv_do add edi, eax DEBUGF 1, "[VBoxCtrl] guest->host: utf16_bytes=%u\n", eax ; null-терминатор и отправляем mov word [edi], 0 add eax, 2 add eax, 4 ; + format dword mov ecx, [gth_u16buf] mov [ioctl_clwrit + IOCTL.input], ecx mov [ioctl_clwrit + IOCTL.inp_size], eax DEBUGF 1, "[VBoxCtrl] guest->host: ioctl inp_size=%u\n", eax mov ecx, ioctl_clwrit call call_ioctl DEBUGF 1, "[VBoxCtrl] guest->host: ioctl rc=0x%x\n", eax test eax, eax jnz @f mov eax, [gth_data_len] mov [gth_last_len], eax @@: mov ecx, [gth_u16buf] mcall SF_SYS_MISC, SSF_MEM_FREE jmp .free_slot .alloc_fail: DEBUGF 1, "[VBoxCtrl] guest->host: alloc failed\n" jmp .free_slot .bad_type: DEBUGF 1, "[VBoxCtrl] guest->host: bad type/enc, skipping\n" .free_slot: mov ecx, [gth_slot] mcall SF_SYS_MISC, SSF_MEM_FREE jmp .done .done_noslots: DEBUGF 1, "[VBoxCtrl] guest->host: no slots\n" .done: popad ret ; --- fingerprint последнего слота clipboard --- ; Возвращает: eax = FNV-1a хэш (0 если clipboard пуст или ошибка) ; Хэшируем total_size + первые 64 байта данных — этого достаточно ; чтобы отличить разные данные и не гонять clipboard туда-обратно. get_clip_fingerprint: push ebx mcall SF_CLIPBOARD, SSF_GET_SLOT_COUNT cmp eax, -1 je .zero test eax, eax jz .zero dec eax mov ecx, eax mcall SF_CLIPBOARD, SSF_READ_CB cmp eax, -1 je .zero cmp eax, 1 je .zero ; FNV-1a 32-bit: basis=0x811c9dc5, prime=0x01000193 push esi ecx edx mov ebx, 0x811c9dc5 ; хэшируем total_size (4 байта) mov ecx, [eax] mov esi, 4 .fnv_size_loop: movzx edx, cl xor ebx, edx imul ebx, ebx, 0x01000193 shr ecx, 8 dec esi jnz .fnv_size_loop ; хэшируем до 64 байт данных (пропускаем 12-байтный заголовок) mov ecx, [eax] sub ecx, 12 jbe .fnv_data_done cmp ecx, 64 jbe @f mov ecx, 64 @@: lea esi, [eax + 12] .fnv_data_loop: movzx edx, byte [esi] xor ebx, edx imul ebx, ebx, 0x01000193 inc esi dec ecx jnz .fnv_data_loop .fnv_data_done: ; 0 зарезервирован под "нет данных", не должны вернуть его как хэш test ebx, ebx jnz @f inc ebx @@: pop edx ecx esi mov ecx, eax ; сохраняем указатель на буфер push ebx mcall SF_SYS_MISC, SSF_MEM_FREE ; освобождаем буфер слота pop eax pop ebx ret .zero: xor eax, eax pop ebx ret ; --- обёртка вызова iconv --- ; in: eax = дескриптор, esi = вход, ecx = размер входа, edi = выход, edx = размер выхода ; out: eax = сколько байт записано в выход (0 при ошибке) iconv_do: cmp byte [iconv_loaded], 0 jne .ic_do xor eax, eax ret .ic_do: push ebx esi edi ebp mov [ic_in_ptr], esi mov [ic_in_left], ecx mov [ic_out_ptr], edi mov [ic_out_left], edx mov ebx, edx ; запоминаем исходный размер выхода mov ebp, eax ; дескриптор ; iconv(cd, &in_ptr, &in_left, &out_ptr, &out_left) — stdcall lea eax, [ic_out_left] push eax lea eax, [ic_out_ptr] push eax lea eax, [ic_in_left] push eax lea eax, [ic_in_ptr] push eax push ebp call [iconv] add esp, 20 test eax, eax jnz .ic_err sub ebx, [ic_out_left] ; записано = исходный - оставшийся mov eax, ebx pop ebp edi esi ebx ret .ic_err: DEBUGF 2, "[VBoxCtrl] iconv error: %d (cd=%x, in=%d, out=%d)\n", eax, ebp, [ic_in_left], [ic_out_left] xor eax, eax pop ebp edi esi ebx ret ; --- форматируем строку "NNNNN bytes" для отображения размера clipboard --- format_size_str: push ebx ecx edx edi mov eax, [rhc_conv_len] mov edi, size_str_buf mov ebx, 10 xor ecx, ecx .push_digits: xor edx, edx div ebx push edx inc ecx test eax, eax jnz .push_digits .pop_digits: pop eax add al, '0' stosb dec ecx jnz .pop_digits mov dword [edi], ' byt' mov word [edi + 4], 'es' mov byte [edi + 6], 0 pop edi edx ecx ebx ret ; ========== данные ========== align 4 title_str db 'VBoxCtrl',0 str_version db 'Version: ',0 str_connected db 'Connected: ',0 str_yes db 'Yes',0 str_no db 'No',0 str_services db 'Services',0 str_clipboard db 'Host Clipboard',0 str_enable db 'Enable',0 str_disable db 'Disable',0 str_enabled db 'Enabled ',0 str_disabled db 'Disabled',0 str_formats db 'Fmt: 0x',0 str_no_driver db 'VBOXGUEST driver not loaded!',0 str_no_data db '(no data)',0 drv_name db 'VBOXGUEST',0 ; имена кодировок для iconv_open sz_utf16le db 'UTF-16LE',0 sz_cp866 db 'CP866',0 sz_cp1251 db 'CP1251',0 sz_utf8 db 'UTF-8',0 ; IOCTL-структуры (24 байта каждая), handle вписывается при старте align 4 ioctl_ver: dd 0, IOCTL_GET_VERSION, 0, 0, version_buf, 4 ioctl_svcl: dd 0, IOCTL_GET_SERVICES, 0, 0, svc_out_buf, 4 + MAX_SERVICES * SVC_SIZE ioctl_svc_en: dd 0, IOCTL_SVC_ENABLE, svc_id_buf, 4, 0, 0 ioctl_svc_di: dd 0, IOCTL_SVC_DISABLE, svc_id_buf, 4, 0, 0 ioctl_clstat: dd 0, IOCTL_CLIP_STATUS, 0, 0, clip_status_buf, 12 ioctl_clread: dd 0, IOCTL_CLIP_READ, clip_read_fmt, 4, 0, 0 ; output устанавливается динамически ioctl_clwrit: dd 0, IOCTL_CLIP_WRITE, 0, 0, 0, 0 ; input устанавливается динамически clip_read_fmt dd VBOX_SHCL_FMT_UNICODETEXT include_debug_strings ; ========== импорт библиотек align 16 @IMPORT: library libiconv, 'iconv.obj' import libiconv, \ iconv_open, 'iconv_open', \ iconv, 'iconv' I_END: ; ========== BSS (неинициализированные данные) ========== align 4 driver_handle rd 1 driver_loaded rb 1 rb 3 ; выравнивание driver_version rd 1 svc_count rd 1 svc_id_buf rd 1 svc_out_buf rb 4 + MAX_SERVICES * SVC_SIZE clip_status_buf rb 12 clip_connected rd 1 clip_host_fmt rd 1 version_buf rd 1 preview_len rd 1 preview_buf rb PREVIEW_SIZE + 1 rb 3 ; выравнивание size_str_buf rb 16 ; "NNNNN bytes\0" ; временные переменные для draw_window tmp_idx rd 1 tmp_y rd 1 tmp_ptr rd 1 tmp_rem rd 1 tmp_skip rd 1 ; временные переменные для работы с clipboard rhc_buf rd 1 rhc_bufsize rd 1 rhc_target_size rd 1 ; целевой размер при повторной аллокации rhc_u16_ptr rd 1 ; указатель на UTF-16LE данные в rhc_buf rhc_u16_size rd 1 ; размер UTF-16LE данных в байтах rhc_conv_buf rd 1 ; буфер после конвертации (CP866) rhc_conv_len rd 1 ; длина после конвертации wgc_ptr rd 1 wgc_len rd 1 wgc_enc rd 1 ; кодировка (0=UTF-8, 1=cp866, 2=cp1251) wgc_total rd 1 wgc_buf rd 1 gth_slot rd 1 gth_u16buf rd 1 gth_data_len rd 1 gth_last_len rd 1 guest_clip_fp rd 1 ; fingerprint для обнаружения изменений clipboard ; дескрипторы iconv и параметры вызова iconv_loaded rb 1 rb 3 cd_u16_cp866 rd 1 ; UTF-16LE -> CP866 cd_u16_utf8 rd 1 ; UTF-16LE -> UTF-8 cd_cp866_u16 rd 1 ; CP866 -> UTF-16LE cd_cp1251_u16 rd 1 ; CP1251 -> UTF-16LE cd_utf8_u16 rd 1 ; UTF-8 -> UTF-16LE ic_in_ptr rd 1 ic_in_left rd 1 ic_out_ptr rd 1 ic_out_left rd 1 ; цвета, вычисляются при каждой перерисовке окна wc_work_text rd 1 wc_text_90 rd 1 wc_text_80 rd 1 wc_btn_text rd 1 sc system_colors ; стек align 4 rb 4096 stacktop: MEM: