1103 lines
35 KiB
NASM
1103 lines
35 KiB
NASM
; 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:
|