Files
VBoxGuest/vboxctrl/vboxctrl.asm
2026-03-04 22:03:47 +03:00

1103 lines
35 KiB
NASM
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; 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: