From 1493edddb6636aeaf4bae5f98e548ba1e5767117 Mon Sep 17 00:00:00 2001 From: lex <Алексей Михайлов> Date: Wed, 4 Mar 2026 17:53:08 +0300 Subject: [PATCH 1/2] Add vboxctrl.asm --- drivers/vboxguest/vboxctrl/vboxctrl.asm | 1102 +++++++++++++++++++++++ 1 file changed, 1102 insertions(+) create mode 100644 drivers/vboxguest/vboxctrl/vboxctrl.asm diff --git a/drivers/vboxguest/vboxctrl/vboxctrl.asm b/drivers/vboxguest/vboxctrl/vboxctrl.asm new file mode 100644 index 000000000..621486675 --- /dev/null +++ b/drivers/vboxguest/vboxctrl/vboxctrl.asm @@ -0,0 +1,1102 @@ +; 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: -- 2.49.1 From ab59015ded77777cc8ed200a1d3ac5558edb78de Mon Sep 17 00:00:00 2001 From: lex <Алексей Михайлов> Date: Wed, 4 Mar 2026 21:16:17 +0300 Subject: [PATCH 2/2] Add VBoxGuest driver code --- drivers/vboxguest/common/common.inc | 2 + drivers/vboxguest/common/errors.inc | 82 + drivers/vboxguest/common/utils.inc | 133 + drivers/vboxguest/config.inc | 41 + drivers/vboxguest/core/core.inc | 13 + .../vboxguest/core/dispatcher/dispatcher.inc | 345 ++ .../core/dispatcher/dispatcher_macros.inc | 114 + drivers/vboxguest/core/irq.inc | 109 + drivers/vboxguest/core/mmio.inc | 74 + drivers/vboxguest/core/pci.inc | 117 + drivers/vboxguest/core/ports.inc | 54 + drivers/vboxguest/core/state.inc | 70 + drivers/vboxguest/core/timer.inc | 57 + .../vboxguest/data/clipboard/constants.inc | 80 + drivers/vboxguest/data/clipboard/structs.inc | 73 + drivers/vboxguest/data/core/constants.inc | 135 + drivers/vboxguest/data/core/structs.inc | 297 ++ drivers/vboxguest/data/data.inc | 26 + drivers/vboxguest/data/display/constants.inc | 33 + drivers/vboxguest/data/display/structs.inc | 17 + .../vboxguest/data/guest_props/constants.inc | 39 + .../vboxguest/data/guest_props/structs.inc | 16 + .../vboxguest/data/heartbeat/constants.inc | 11 + drivers/vboxguest/data/heartbeat/structs.inc | 20 + drivers/vboxguest/data/hgcm/constants.inc | 107 + drivers/vboxguest/data/hgcm/structs.inc | 70 + drivers/vboxguest/data/mouse/constants.inc | 40 + drivers/vboxguest/data/mouse/structs.inc | 43 + drivers/vboxguest/data/seamless/constants.inc | 26 + drivers/vboxguest/data/seamless/structs.inc | 29 + .../data/shared_folders/constants.inc | 113 + .../vboxguest/data/shared_folders/structs.inc | 304 ++ drivers/vboxguest/data/timesync/constants.inc | 16 + drivers/vboxguest/data/timesync/structs.inc | 12 + drivers/vboxguest/hgcm/async.inc | 63 + drivers/vboxguest/hgcm/call.inc | 395 +++ drivers/vboxguest/hgcm/core.inc | 5 + drivers/vboxguest/hgcm/hgcm.inc | 276 ++ drivers/vboxguest/hgcm/params.inc | 78 + .../services/clipboard/clipboard.inc | 220 ++ .../services/clipboard/clipboard_listener.inc | 614 ++++ .../vboxguest/services/display/display.inc | 206 ++ .../services/guest_props/guest_props.inc | 263 ++ .../services/heartbeat/heartbeat.inc | 243 ++ drivers/vboxguest/services/mouse/mouse.inc | 382 ++ .../vboxguest/services/seamless/seamless.inc | 265 ++ drivers/vboxguest/services/services.inc | 11 + .../shared_folders/shared_folders.inc | 1119 ++++++ .../services/shared_folders/vboxsf.inc | 3153 +++++++++++++++++ .../vboxguest/services/timesync/timesync.inc | 444 +++ drivers/vboxguest/sys/init.inc | 70 + drivers/vboxguest/sys/ioctl.inc | 183 + drivers/vboxguest/sys/shutdown.inc | 47 + drivers/vboxguest/vboxguest.asm | 92 + drivers/vboxguest/vmmdev/capabilities.inc | 27 + drivers/vboxguest/vmmdev/core.inc | 25 + drivers/vboxguest/vmmdev/event_filter.inc | 28 + drivers/vboxguest/vmmdev/guest_info.inc | 77 + drivers/vboxguest/vmmdev/hypervisor.inc | 51 + drivers/vboxguest/vmmdev/packets.inc | 169 + drivers/vboxguest/vmmdev/vmmdev.inc | 12 + 61 files changed, 11236 insertions(+) create mode 100644 drivers/vboxguest/common/common.inc create mode 100644 drivers/vboxguest/common/errors.inc create mode 100644 drivers/vboxguest/common/utils.inc create mode 100644 drivers/vboxguest/config.inc create mode 100644 drivers/vboxguest/core/core.inc create mode 100644 drivers/vboxguest/core/dispatcher/dispatcher.inc create mode 100644 drivers/vboxguest/core/dispatcher/dispatcher_macros.inc create mode 100644 drivers/vboxguest/core/irq.inc create mode 100644 drivers/vboxguest/core/mmio.inc create mode 100644 drivers/vboxguest/core/pci.inc create mode 100644 drivers/vboxguest/core/ports.inc create mode 100644 drivers/vboxguest/core/state.inc create mode 100644 drivers/vboxguest/core/timer.inc create mode 100644 drivers/vboxguest/data/clipboard/constants.inc create mode 100644 drivers/vboxguest/data/clipboard/structs.inc create mode 100644 drivers/vboxguest/data/core/constants.inc create mode 100644 drivers/vboxguest/data/core/structs.inc create mode 100644 drivers/vboxguest/data/data.inc create mode 100644 drivers/vboxguest/data/display/constants.inc create mode 100644 drivers/vboxguest/data/display/structs.inc create mode 100644 drivers/vboxguest/data/guest_props/constants.inc create mode 100644 drivers/vboxguest/data/guest_props/structs.inc create mode 100644 drivers/vboxguest/data/heartbeat/constants.inc create mode 100644 drivers/vboxguest/data/heartbeat/structs.inc create mode 100644 drivers/vboxguest/data/hgcm/constants.inc create mode 100644 drivers/vboxguest/data/hgcm/structs.inc create mode 100644 drivers/vboxguest/data/mouse/constants.inc create mode 100644 drivers/vboxguest/data/mouse/structs.inc create mode 100644 drivers/vboxguest/data/seamless/constants.inc create mode 100644 drivers/vboxguest/data/seamless/structs.inc create mode 100644 drivers/vboxguest/data/shared_folders/constants.inc create mode 100644 drivers/vboxguest/data/shared_folders/structs.inc create mode 100644 drivers/vboxguest/data/timesync/constants.inc create mode 100644 drivers/vboxguest/data/timesync/structs.inc create mode 100644 drivers/vboxguest/hgcm/async.inc create mode 100644 drivers/vboxguest/hgcm/call.inc create mode 100644 drivers/vboxguest/hgcm/core.inc create mode 100644 drivers/vboxguest/hgcm/hgcm.inc create mode 100644 drivers/vboxguest/hgcm/params.inc create mode 100644 drivers/vboxguest/services/clipboard/clipboard.inc create mode 100644 drivers/vboxguest/services/clipboard/clipboard_listener.inc create mode 100644 drivers/vboxguest/services/display/display.inc create mode 100644 drivers/vboxguest/services/guest_props/guest_props.inc create mode 100644 drivers/vboxguest/services/heartbeat/heartbeat.inc create mode 100644 drivers/vboxguest/services/mouse/mouse.inc create mode 100644 drivers/vboxguest/services/seamless/seamless.inc create mode 100644 drivers/vboxguest/services/services.inc create mode 100644 drivers/vboxguest/services/shared_folders/shared_folders.inc create mode 100644 drivers/vboxguest/services/shared_folders/vboxsf.inc create mode 100644 drivers/vboxguest/services/timesync/timesync.inc create mode 100644 drivers/vboxguest/sys/init.inc create mode 100644 drivers/vboxguest/sys/ioctl.inc create mode 100644 drivers/vboxguest/sys/shutdown.inc create mode 100644 drivers/vboxguest/vboxguest.asm create mode 100644 drivers/vboxguest/vmmdev/capabilities.inc create mode 100644 drivers/vboxguest/vmmdev/core.inc create mode 100644 drivers/vboxguest/vmmdev/event_filter.inc create mode 100644 drivers/vboxguest/vmmdev/guest_info.inc create mode 100644 drivers/vboxguest/vmmdev/hypervisor.inc create mode 100644 drivers/vboxguest/vmmdev/packets.inc create mode 100644 drivers/vboxguest/vmmdev/vmmdev.inc diff --git a/drivers/vboxguest/common/common.inc b/drivers/vboxguest/common/common.inc new file mode 100644 index 000000000..c356e6a1c --- /dev/null +++ b/drivers/vboxguest/common/common.inc @@ -0,0 +1,2 @@ +include 'errors.inc' +include 'utils.inc' \ No newline at end of file diff --git a/drivers/vboxguest/common/errors.inc b/drivers/vboxguest/common/errors.inc new file mode 100644 index 000000000..be7965676 --- /dev/null +++ b/drivers/vboxguest/common/errors.inc @@ -0,0 +1,82 @@ +; ============================================================================= +; VBoxGuest Driver for KolibriOS - Dual-level Error Handling +; VMMDev transport errors + HGCM service errors +; На базе версии v1_04 +; ============================================================================= + +; ============================================================================= +; VMMDev Error Codes (Generic VERR_*) +; These appear in vmmdev_request_header.rc +; ============================================================================= + +; Success codes (positive or zero) +VINF_SUCCESS equ 0 +VINF_HGCM_ASYNC_EXECUTE equ 2903 ; Запрос выполняется асинхронно +VBOX_HGCM_REQ_DONE equ 0x00000001 ; Флаг завершения в поле flags +HGCM_TIMEOUT_DEFAULT equ 500000 ; Базовый таймаут + +; err.h +VINF_NOT_SUPPORTED equ 37 +VERR_ACCESS_DENIED equ -38 +VERR_INTERRUPTED equ -39 +VINF_INTERRUPTED equ 39 + +; ============================================================================= +; Error Codes +; ============================================================================= +VERR_TIMEOUT equ -78 +VERR_NOT_READY equ -25 + +VERR_GENERAL_FAILURE equ -1 +VERR_INVALID_PARAMETER equ -2 +VERR_INVALID_MAGIC equ -3 +VERR_INVALID_POINTER equ -6 +VERR_NO_MEMORY equ -8 +VERR_NOT_IMPLEMENTED equ -12 +VERR_INVALID_FLAGS equ -13 ; 0xFFFFFFF3 +VERR_INVALID_FUNCTION equ -36 +VERR_NOT_SUPPORTED equ -37 +VERR_TOO_MUCH_DATA equ -42 +VERR_NOT_FOUND equ -78 +VERR_INVALID_STATE equ -79 +VERR_OUT_OF_RESOURCES equ -80 +VERR_ALREADY_EXISTS equ -105 +VERR_TRY_AGAIN equ -116 +VERR_INTERNAL_ERROR equ -225 + +; VERR_WRONG_TYPE-22409 +; VERR_WRONG_PARAMETER_TYPE-22416 + +VERR_NO_DATA equ -125 ; Нэт доступных данных + + +; ============================================================================= +; HGCM Error Codes (Range -2900..-2909) +; These appear in VMMDevHGCMRequestHeader.result +; ============================================================================= + +VERR_HGCM_SERVICE_NOT_FOUND equ -2900 +VERR_HGCM_CLIENT_REJECTED equ -2901 +VERR_HGCM_INVALID_CMD_ADDRESS equ -2902 +VERR_HGCM_INTERNAL equ -2904 +VERR_HGCM_INVALID_CLIENT_ID equ -2905 +VERR_HGCM_PROTOCOL_ERROR equ -2906 +VERR_HGCM_TOO_MANY_CLIENTS equ -2908 +VERR_HGCM_TOO_MANY_PARMS equ -2909 + +; ============================================================================= +; Clipboard-specific Error Codes +; ============================================================================= + +VERR_SHCLPB_NO_DATA equ -7153 +VERR_SHCLPB_FORMAT_NOT_SUPPORTED equ -7154 + +; ============================================================================= +; Error Code Ranges +; ============================================================================= + +VMMDEV_ERROR_RANGE_START equ -1000 +VMMDEV_ERROR_RANGE_END equ 0 +HGCM_ERROR_RANGE_START equ -2910 +HGCM_ERROR_RANGE_END equ -2900 + diff --git a/drivers/vboxguest/common/utils.inc b/drivers/vboxguest/common/utils.inc new file mode 100644 index 000000000..43828faa8 --- /dev/null +++ b/drivers/vboxguest/common/utils.inc @@ -0,0 +1,133 @@ +; ============================================================================= +; VBoxGuest Driver for KolibriOS - Common Utilities +; Файл: common/utils.inc +; ============================================================================= + +; sf_utf8_to_cp866 — Конвертировать UTF-8 строку в CP866 (кириллица) +proc sf_utf8_to_cp866 uses ebx ecx edx esi edi, src:dword, dst:dword, src_len:dword + mov esi, [src] + mov edi, [dst] + mov ecx, [src_len] + xor edx, edx + +.u8_loop: + test ecx, ecx + jle .u8_done + + movzx eax, byte [esi] + + cmp al, 0x80 + jb .u8_ascii + + cmp al, 0xC0 + jb .u8_skip1 + cmp al, 0xE0 + jb .u8_two_byte + + cmp al, 0xF0 + jb .u8_skip3 + + mov byte [edi], '?' + inc edi + inc edx + add esi, 4 + sub ecx, 4 + jmp .u8_loop + +.u8_skip3: + mov byte [edi], '?' + inc edi + inc edx + add esi, 3 + sub ecx, 3 + jmp .u8_loop + +.u8_ascii: + mov [edi], al + inc esi + inc edi + inc edx + dec ecx + jmp .u8_loop + +.u8_two_byte: + cmp ecx, 2 + jb .u8_done + + movzx eax, byte [esi] + movzx ebx, byte [esi + 1] + and eax, 0x1F + shl eax, 6 + and ebx, 0x3F + or eax, ebx + + cmp eax, 0x0401 + je .u8_yo_upper + cmp eax, 0x0451 + je .u8_yo_lower + + cmp eax, 0x0410 + jb .u8_not_cyrillic + cmp eax, 0x041F + jbe .u8_upper1 + + cmp eax, 0x042F + jbe .u8_upper2 + + cmp eax, 0x043F + jbe .u8_lower1 + + cmp eax, 0x044F + jbe .u8_lower2 + + jmp .u8_not_cyrillic + +.u8_upper1: + sub eax, 0x0410 + add eax, 0x80 + jmp .u8_store2 + +.u8_upper2: + sub eax, 0x0420 + add eax, 0x90 + jmp .u8_store2 + +.u8_lower1: + sub eax, 0x0430 + add eax, 0xA0 + jmp .u8_store2 + +.u8_lower2: + sub eax, 0x0440 + add eax, 0xE0 + jmp .u8_store2 + +.u8_yo_upper: + mov eax, 0xF0 + jmp .u8_store2 + +.u8_yo_lower: + mov eax, 0xF1 + jmp .u8_store2 + +.u8_not_cyrillic: + mov eax, '?' + +.u8_store2: + mov [edi], al + add esi, 2 + sub ecx, 2 + inc edi + inc edx + jmp .u8_loop + +.u8_skip1: + inc esi + dec ecx + jmp .u8_loop + +.u8_done: + mov byte [edi], 0 + mov eax, edx + ret +endp diff --git a/drivers/vboxguest/config.inc b/drivers/vboxguest/config.inc new file mode 100644 index 000000000..37886f382 --- /dev/null +++ b/drivers/vboxguest/config.inc @@ -0,0 +1,41 @@ +; ============================================================================= +; VBoxGuest Конфигурация +; ============================================================================= + +; ============================================================================= +; Автозапуск сервисов +; ============================================================================= +AUTOSTART_MOUSE = 1 ; Абсолютная мыщъх +AUTOSTART_HEARTBEAT = 1 ; Heartbeat +AUTOSTART_DISPLAY = 1 ; Разрешение экрана +AUTOSTART_TIMESYNC = 0 ; Синхронизация времени +AUTOSTART_SHARED_FOLDERS = 1 ; Общие папки +AUTOSTART_CLIPBOARD = 1 ; Буфер обмена +AUTOSTART_GUEST_PROPS = 1 ; Guest Properties +AUTOSTART_SEAMLESS = 0 ; Seamless mode + +; ============================================================================= +; Настройки сервисов +; ============================================================================= +SHFL_MAX_FOLDERS = 10 ; Максимум общих папок + +; ============================================================================= +; Отладка +; ============================================================================= +; Уровни отладки: +; __DEBUG_LEVEL__ = 1 -> Полное логирование (очень много вывода) +; __DEBUG_LEVEL__ = 2 -> Только ошибки + +__DEBUG__ = 1 ; Включена +__DEBUG_LEVEL__ = 2 ; + +__DEBUG_IRQ__ = 0 ; Логирование прерываний +__DEBUG_HGCM__ = 0 ; Логирование HGCM +__DEBUG_SF__ = 0 ; SharedFolder +__DEBUG_CB__ = 0 ; ClipBoard +__DEBUG_EVENTS__ = 0 ; Логирование событий +__DEBUG_MOUSE__ = 0 ; Логирование мыщъх +__DEBUG_SEAMLESS__ = 0 +__DEBUG_HB__ = 0 ; Heartbeat мониторинг гостя +__DEBUG_DISPLAY__ = 0 ; Display разрешение экрана +__DEBUG_DISPATCHER__ = 0 ; Логирование диспетчера \ No newline at end of file diff --git a/drivers/vboxguest/core/core.inc b/drivers/vboxguest/core/core.inc new file mode 100644 index 000000000..69928bb3a --- /dev/null +++ b/drivers/vboxguest/core/core.inc @@ -0,0 +1,13 @@ +; ============================================================================= +; Модуль : Core Aggregator +; Назначение : Подключение всех модулей ядра драйвера +; Файл : core/core.inc +; ============================================================================= + +include 'core/state.inc' +include 'core/pci.inc' +include 'core/mmio.inc' +include 'core/ports.inc' +include 'core/irq.inc' +include 'core/timer.inc' +include 'core/dispatcher/dispatcher.inc' \ No newline at end of file diff --git a/drivers/vboxguest/core/dispatcher/dispatcher.inc b/drivers/vboxguest/core/dispatcher/dispatcher.inc new file mode 100644 index 000000000..1e8c06898 --- /dev/null +++ b/drivers/vboxguest/core/dispatcher/dispatcher.inc @@ -0,0 +1,345 @@ +; ============================================================================= +; Модуль : Service Dispatcher +; Файл : core/dispatcher.inc +; Назначение : Функции диспетчера сервисов +; ============================================================================= + +include 'dispatcher_macros.inc' + +; ============================================================================= +; Данные диспетчера +; ============================================================================= +align 4 +dispatcher_active_events dd 0 ; OR всех event_mask включенных сервисов +dispatcher_active_caps dd 0 ; OR всех caps_mask включенных сервисов + + +; dispatcher_find_by_id — Найти сервис по ID +; +; Вход : svc_id — числовой идентификатор сервиса +; Выход: eax = SERVICE_ENTRY* или 0 если не найден +proc dispatcher_find_by_id stdcall uses ecx esi, svc_id:dword + mov ecx, [services_count] + test ecx, ecx + jz .not_found + + mov esi, services_table + mov eax, [svc_id] + +.loop: + cmp [esi + SERVICE_ENTRY.id], eax + je .found + + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.not_found: + xor eax, eax + ret + +.found: + mov eax, esi + ret +endp + +; Включить сервис по ID +proc dispatcher_enable_by_id stdcall, svc_id:dword + stdcall dispatcher_find_by_id, [svc_id] + test eax, eax + jz .not_found + + stdcall dispatcher_enable_entry, eax + ret + +.not_found: + mov eax, -1 + ret +endp + +; Выключить сервис по ID +proc dispatcher_disable_by_id stdcall, svc_id:dword + stdcall dispatcher_find_by_id, [svc_id] + test eax, eax + jz .not_found + + stdcall dispatcher_disable_entry, eax + ret + +.not_found: + mov eax, -1 + ret +endp + +; Включить сервис по указателю на SERVICE_ENTRY +proc dispatcher_enable_entry stdcall uses ebx esi, entry_ptr:dword + mov esi, [entry_ptr] + + ; Уже включен? + cmp dword [esi + SERVICE_ENTRY.enabled], 1 + je .already_enabled + + ; Вызвать fn_enable если есть + mov eax, [esi + SERVICE_ENTRY.fn_enable] + test eax, eax + jz .no_enable_fn + + push esi + call eax + pop esi + + test eax, eax + jnz .enable_failed + +.no_enable_fn: + ; Включить + mov dword [esi + SERVICE_ENTRY.enabled], 1 + + ; Добавить маски + mov eax, [esi + SERVICE_ENTRY.event_mask] + or [dispatcher_active_events], eax + + mov eax, [esi + SERVICE_ENTRY.caps_mask] + or [dispatcher_active_caps], eax + + DEBUGF 2, "[VBoxGuest] [Dispatcher] Enabled service ID=%d\n", [esi + SERVICE_ENTRY.id] + +.already_enabled: + xor eax, eax + ret + +.enable_failed: + DEBUGF 2, "[VBoxGuest] [Dispatcher] Enable failed: 0x%x\n", eax + ret +endp + +; Выключить сервис по указателю +proc dispatcher_disable_entry stdcall uses ebx esi, entry_ptr:dword + mov esi, [entry_ptr] + + ; Уже выключен? + cmp dword [esi + SERVICE_ENTRY.enabled], 0 + je .already_disabled + + ; Вызвать fn_disable если есть + mov eax, [esi + SERVICE_ENTRY.fn_disable] + test eax, eax + jz .no_disable_fn + + push esi + call eax + pop esi + +.no_disable_fn: + ; Выключить + mov dword [esi + SERVICE_ENTRY.enabled], 0 + + ; Пересчитать активные маски + call dispatcher_recalc_masks + + DEBUGF 2, "[VBoxGuest] [Dispatcher] Disabled service ID=%d\n", [esi + SERVICE_ENTRY.id] + +.already_disabled: + xor eax, eax + ret +endp + +; dispatcher_disable_all — Выключить все сервисы +proc dispatcher_disable_all uses ecx esi + DEBUGF 2, "[VBoxGuest] [Dispatcher] Disabling all services\n" + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + stdcall dispatcher_disable_entry, esi + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + ret +endp + +; Инициализация всех сервисов +proc dispatcher_init_all uses ebx ecx esi + DEBUGF 2, "[VBoxGuest] [Dispatcher] Initializing %d services...\n", [services_count] + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + mov eax, [esi + SERVICE_ENTRY.fn_init] + test eax, eax + jz .next + + DEBUGF 2, "[VBoxGuest] [Dispatcher] Init service ID=%d, name=%s\n", [esi + SERVICE_ENTRY.id], [esi + SERVICE_ENTRY.name_ptr] + push ecx esi + call eax + pop esi ecx + + test eax, eax + jnz .init_failed + +.next: + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + DEBUGF 2, "[VBoxGuest] [Dispatcher] All services initialized\n" + xor eax, eax + ret + +.init_failed: + DEBUGF 2, "[VBoxGuest] [Dispatcher] Service ID=%d init failed: 0x%x\n", [esi + SERVICE_ENTRY.id], eax + jmp .next +endp + +; Включить сервисы с autostart=1 +proc dispatcher_enable_autostart uses ecx esi + DEBUGF 2, "[VBoxGuest] [Dispatcher] Enabling autostart services...\n" + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + cmp dword [esi + SERVICE_ENTRY.autostart], 1 + jne .next + + push ecx esi + stdcall dispatcher_enable_entry, esi + pop esi ecx + +.next: + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + DEBUGF 2, "[VBoxGuest] [Dispatcher] Autostart done, events=0x%x, caps=0x%x\n", \ + [dispatcher_active_events], [dispatcher_active_caps] + ret +endp + +; Пересчитать активные маски +proc dispatcher_recalc_masks uses ebx ecx edx esi + xor edx, edx ; events + xor ebx, ebx ; caps + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + cmp dword [esi + SERVICE_ENTRY.enabled], 1 + jne .next + + or edx, [esi + SERVICE_ENTRY.event_mask] + or ebx, [esi + SERVICE_ENTRY.caps_mask] + +.next: + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + mov [dispatcher_active_events], edx + mov [dispatcher_active_caps], ebx + ret +endp + +; Разослать события включенным сервисам +proc dispatcher_dispatch stdcall uses ebx ecx edx esi, event_mask:dword + mov edx, [event_mask] + test edx, edx + jz .done + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + ; Включен? + cmp dword [esi + SERVICE_ENTRY.enabled], 0 + je .next + + ; Есть совпадение событий? + mov eax, [esi + SERVICE_ENTRY.event_mask] + test eax, edx + jz .next + + ; Есть обработчик? + mov eax, [esi + SERVICE_ENTRY.fn_on_event] + test eax, eax + jz .next + + ; Вызвать fn_on_event(event_mask) + push ecx edx esi + mov eax, edx + call [esi + SERVICE_ENTRY.fn_on_event] + pop esi edx ecx + +.next: + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + ret +endp + +; Вызвать fn_on_tick у включенных сервисов +proc dispatcher_tick_all uses eax ecx esi + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov esi, services_table + +.loop: + cmp dword [esi + SERVICE_ENTRY.enabled], 0 + je .next + + mov eax, [esi + SERVICE_ENTRY.fn_on_tick] + test eax, eax + jz .next + + push ecx esi + call eax + pop esi ecx + +.next: + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + ret +endp + + +; Получить активную маску событий +proc dispatcher_get_active_events + mov eax, [dispatcher_active_events] + ret +endp + +; Получить активную маску capabilities +proc dispatcher_get_active_caps + mov eax, [dispatcher_active_caps] + ret +endp diff --git a/drivers/vboxguest/core/dispatcher/dispatcher_macros.inc b/drivers/vboxguest/core/dispatcher/dispatcher_macros.inc new file mode 100644 index 000000000..c82cb5d08 --- /dev/null +++ b/drivers/vboxguest/core/dispatcher/dispatcher_macros.inc @@ -0,0 +1,114 @@ +; ============================================================================= +; Модуль : Dispatcher Macros +; Файл : core/dispatcher_macros.inc +; Назначение : Макросы для авторегистрации сервисов VBoxGuest +; ============================================================================= + +; Структура SERVICE_ENTRY (52 байта = 13 DWORD) +struct SERVICE_ENTRY + id dd ? ; +0: Уникальный ID (auto-increment) + name_ptr dd ? ; +4: Указатель на строку имени + event_mask dd ? ; +8: Маска событий VMMDev + caps_mask dd ? ; +12: Маска guest capabilities + enabled dd ? ; +16: 0=выключен, 1=включен + autostart dd ? ; +20: 1=запуск с драйвером, 0=через vboxctrl + fn_init dd ? ; +24: Инициализация (или 0) + fn_enable dd ? ; +28: Включение (или 0) + fn_disable dd ? ; +32: Выключение (или 0) + fn_on_event dd ? ; +36: Обработчик IRQ (или 0) + fn_on_tick dd ? ; +40: Обработчик тика (или 0) + hgcm_wakeup_event dd ? ; +44: Event handle для пробуждения HGCM-треда из IRQ + hgcm_wakeup_event_id dd ? ; +48: Event euid для пробуждения HGCM-треда из IRQ +ends + +; Алиас для кода использующего SERVICE_ENTRY_SIZE +SERVICE_ENTRY_SIZE = sizeof.SERVICE_ENTRY + +; ============================================================================= +; Инициализация списка сервисов (пустой) +; ============================================================================= +__SERVICES_LIST__ equ + +; ============================================================================= +; REGISTER_SERVICE - Регистрация сервиса +; ID назначается автоматически (1, 2, 3, ...) +; ============================================================================= +; Это позволяет корректно работать с IRP/forward +; ============================================================================= +macro REGISTER_SERVICE svc_name, event_mask, caps_mask, fn_init, fn_enable, fn_disable, fn_on_event, fn_on_tick, autostart +{ + ; Добавляем в список с | как разделителем + ; Формат: name|evmask|capmask|init|enable|disable|onevent|ontick|autostart + match any, __SERVICES_LIST__ \{ + __SERVICES_LIST__ equ __SERVICES_LIST__,svc_name|event_mask|caps_mask|fn_init|fn_enable|fn_disable|fn_on_event|fn_on_tick|autostart + \} + match , __SERVICES_LIST__ \{ + __SERVICES_LIST__ equ svc_name|event_mask|caps_mask|fn_init|fn_enable|fn_disable|fn_on_event|fn_on_tick|autostart + \} +} + +; ============================================================================= +; BUILD_SERVICE_TABLE - Генерация таблицы сервисов +; Создаёт: +; services_table - массив SERVICE_ENTRY +; services_table_end - метка конца +; services_count - количество сервисов (dd) +; SVC_ID_xxx - константы ID для каждого сервиса +; ============================================================================= +macro BUILD_SERVICE_TABLE +{ + align 4 + services_table: + + ; Проверка: есть ли сервисы? + match , __SERVICES_LIST__ \{ + display 'WARNING: No services registered!', 13, 10 + \} + + ; Генерация записей + match list, __SERVICES_LIST__ \{ + __BUILD_ENTRIES__ list + \} + + services_table_end: + services_count dd __BUILD_ID__ +} + +; ============================================================================= +; Вспомогательный макрос для построения записей +; ============================================================================= + +__BUILD_ID__ = 0 + +macro __BUILD_ENTRIES__ [entry] +{ + forward + __BUILD_ID__ = __BUILD_ID__ + 1 + ; Парсим entry с разделителем | + ; Формат: name|evmask|capmask|init|enable|disable|onevent|ontick|autostart + match _name|_evmask|_capmask|_init|_enable|_disable|_onevent|_ontick|_auto, entry \{ + dd __BUILD_ID__ ; id + dd _name ; name_ptr + dd _evmask ; event_mask + dd _capmask ; caps_mask + dd 0 ; enabled = 0 + dd _auto ; autostart + dd _init ; fn_init + dd _enable ; fn_enable + dd _disable ; fn_disable + dd _onevent ; fn_on_event + dd _ontick ; fn_on_tick + dd 0 ; hgcm_wakeup_event (заполняется сервисом в runtime) + dd 0 ; hgcm_wakeup_event_id + \} +} + +; ============================================================================= +; Макрос для получения ID по имени переменной (опционально) +; Использование: SVC_ID svc_name_var +; ============================================================================= +macro SVC_ID name +{ + ; Поиск ID в рантайме через dispatcher_find_by_name + ; Или использовать константу если известна +} diff --git a/drivers/vboxguest/core/irq.inc b/drivers/vboxguest/core/irq.inc new file mode 100644 index 000000000..759454920 --- /dev/null +++ b/drivers/vboxguest/core/irq.inc @@ -0,0 +1,109 @@ +; ============================================================================= +; Модуль : IRQ Handler +; Назначение : Обработчик прерываний VMMDev +; Файл : core/irq.inc +; ============================================================================= + +align 4 +vbox_irq_count dd 0 + +; vbox_irq_handler — Top-half обработчик IRQ +proc vbox_irq_handler stdcall + push ebx edx + + movzx edx, word [vbox_device.port] + add dx, VMMDEV_PORT_OFF_REQUEST_FAST + in eax, dx + + test eax, eax + jz .not_ours + + mov ebx, eax + + ; ACK все события сразу + out dx, eax + + DEBUGF __DEBUG_IRQ__, "[VBoxGuest] [Dispatch] [IRQ] events=0x%x, service event=0x%x\n", ebx, [dispatcher_active_events] + + ; Немедленно разбудить HGCM-треды (Linux: wake_up() из IRQ) + test ebx, VMMDEV_EVENT_HGCM + jz .no_hgcm_wake + call hgcm_irq_dispatch +.no_hgcm_wake: + + ; Вызвать fn_on_event у всех подходящих сервисов прямо сейчас + stdcall dispatcher_dispatch, ebx + + lock inc dword [vbox_irq_count] + +.acked: + pop edx ebx + mov eax, 1 + ret + +.not_ours: + pop edx ebx + xor eax, eax + ret +endp + +; hgcm_irq_dispatch — Разбудить все HGCM-треды из IRQ контекста +proc hgcm_irq_dispatch + push eax ebx ecx edx esi edi + + mov ecx, [services_count] + test ecx, ecx + jz .done + + mov edi, services_table + +.loop: + cmp dword [edi + SERVICE_ENTRY.enabled], 0 + je .next + + cmp dword [edi + SERVICE_ENTRY.hgcm_wakeup_event], 0 + je .next + + mov eax, [edi + SERVICE_ENTRY.hgcm_wakeup_event] + mov ebx, [edi + SERVICE_ENTRY.hgcm_wakeup_event_id] + xor edx, edx + xor esi, esi + invoke RaiseEvent + +.next: + add edi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + pop edi esi edx ecx ebx eax + ret +endp + +; vmmdev_irq_install — Установить обработчик IRQ +proc vmmdev_irq_install + mov eax, [vbox_device.irq] + test eax, eax + jz .no_irq + + DEBUGF 2, "[VBoxGuest] [IRQ] Attaching to IRQ %d\n", eax + + invoke AttachIntHandler, eax, vbox_irq_handler, 0 + test eax, eax + jz .failed + + DEBUGF 2, "[VBoxGuest] [IRQ] Handler attached successfully\n" + xor eax, eax + ret + +.no_irq: + DEBUGF 2, "[VBoxGuest] [IRQ] No IRQ line\n" + mov eax, VERR_GENERAL_FAILURE + ret + +.failed: + DEBUGF 2, "[VBoxGuest] [IRQ] Attach failed\n" + mov eax, VERR_INTERNAL_ERROR + ret +endp + diff --git a/drivers/vboxguest/core/mmio.inc b/drivers/vboxguest/core/mmio.inc new file mode 100644 index 000000000..96031f1a1 --- /dev/null +++ b/drivers/vboxguest/core/mmio.inc @@ -0,0 +1,74 @@ +; ============================================================================= +; Модуль : Memory Mapped I/O Operations +; Назначение : Работа с MMIO областью VMMDev +; ============================================================================= +proc mmio_map_vmmdev + ; Маппинг физической памяти в виртуальное адресное пространство + invoke MapIoMem, [vbox_device.mmio_phys], VMMDEV_MEMORY_SIZE, PG_NOCACHE + PG_SW + test eax, eax + jz .map_failed + + ; Сохраняем виртуальный адрес + mov [vbox_device.mmio_virt], eax + DEBUGF 2, "[VBoxGuest] [MMIO] Mapped to virtual address: 0x%x\n", eax + + ; Проверяем версию VMMDev + call mmio_check_version + test eax, eax + jnz .version_check_failed + + DEBUGF 2, "[VBoxGuest] [MMIO] Mapping completed successfully\n" + + xor eax, eax + ret + +.map_failed: + DEBUGF 2, "[VBoxGuest] [MMIO] ERROR: MapIoMem failed\n" + mov eax, VERR_NO_MEMORY + ret + +.version_check_failed: + DEBUGF 2, "[VBoxGuest] [MMIO] ERROR: VMMDev version check failed\n" + ret +endp + + +; mmio_check_version — Проверка версии VMMDev структуры в MMIO +proc mmio_check_version + mov edi, [vbox_device.mmio_virt] + + ; Отладочный вывод структуры VMMDev + DEBUGF 2, "[VBoxGuest] [MMIO] VMMDev structure at 0x%x:\n", edi + DEBUGF 2, "[VBoxGuest] [MMIO] size: 0x%x\n", [edi + VMMDEV_MEMORY.size] + DEBUGF 2, "[VBoxGuest] [MMIO] version: 0x%x\n", [edi + VMMDEV_MEMORY.version] + + ; Проверяем минимальный размер структуры + mov eax, [edi + VMMDEV_MEMORY.size] + cmp eax, sizeof.VMMDEV_MEMORY + jb .invalid_size + + ; Проверяем версию VMMDev + mov eax, [edi + VMMDEV_MEMORY.version] + DEBUGF 2, "[VBoxGuest] [MMIO] Detected VMMDev version: 0x%x (required: 0x%x)\n", \ + eax, VMMDEV_MEMORY_VERSION + + ; Проверяем совместимость версии (должна быть >= требуемой) + cmp eax, VMMDEV_MEMORY_VERSION + jae .version_ok + + ; Устаревшая версия - выводим предупреждение, но продолжаем + DEBUGF 2, "[VBoxGuest] [MMIO] WARNING: Old VMMDev version: 0x%x (expected >= 0x%x)\n", \ + eax, VMMDEV_MEMORY_VERSION + DEBUGF 2, "[VBoxGuest] [MMIO] Some features may be unavailable\n" + +.version_ok: + DEBUGF 2, "[VBoxGuest] [MMIO] VMMDev version check passed\n" + xor eax, eax + ret + +.invalid_size: + DEBUGF 2, "[VBoxGuest] [MMIO] ERROR: Invalid VMMDev structure size: %d (expected >= %d)\n", \ + eax, sizeof.VMMDEV_MEMORY + mov eax, -1 + ret +endp diff --git a/drivers/vboxguest/core/pci.inc b/drivers/vboxguest/core/pci.inc new file mode 100644 index 000000000..ed6e1b573 --- /dev/null +++ b/drivers/vboxguest/core/pci.inc @@ -0,0 +1,117 @@ +; ============================================================================= +; Модуль : PCI Device Detection +; Назначение : Обнаружение и идентификация PCI устройства VirtualBox +; Файл : core/pci.inc +; ============================================================================= + +align 4 +pci_device dd ? ; Указатель на структуру PCI устройства VBox + +; vmmdev_probe — Поиск PCI устройства VirtualBox в системе +proc vmmdev_probe + call pci_find_vmmdev + test eax, eax + jnz .fail + call pci_init_vmmdev_irq + test eax, eax + jnz .fail + call pci_init_vmmdev_bar + test eax, eax + jnz .fail + xor eax, eax + ret +.fail: + DEBUGF 2, "[VBoxGuest] [PCI] Initialization failed\n" + mov eax, VERR_GENERAL_FAILURE + ret +endp + +proc pci_find_vmmdev + ; Получаем список PCI устройств + invoke GetPCIList + mov ebx, eax ; EBX = начало списка (якорь) + +.search_loop: + mov eax, [eax + PCIDEV.fd] + cmp eax, ebx + je .not_found + + ; Сравниваем Vendor/Device ID + mov edx, [eax + PCIDEV.vendor_device_id] + cmp edx, (VBOX_DEVICE_ID shl 16) + VBOX_VENDOR_ID + jne .search_loop + +.found: + mov [pci_device], eax + + DEBUGF 2, "[VBoxGuest] [PCI] VBox device found\n" + xor eax, eax + ret + +.not_found: + DEBUGF 2, "[VBoxGuest] [PCI] VBox device NOT found\n" + mov eax, VERR_GENERAL_FAILURE + ret +endp + +; pci_init_vmmdev_irq — Чтение IRQ линии из PCI конфигурации +proc pci_init_vmmdev_irq + mov ebx, [pci_device] + DEBUGF 2, "[VBoxGuest] [PCI] pci_device=0x%x\n", [pci_device] + test ebx, ebx + jz .no_device + + ; Читаем IRQ линию из PCI конфигурации + invoke PciRead32, dword [ebx + PCIDEV.bus], dword [ebx + PCIDEV.devfn], PCI_header00.interrupt_line + movzx eax, al ; IRQ линия - младший байт + + ; Проверяем валидность IRQ (должна быть 0-15) + cmp eax, 16 + jae .invalid_irq + + ; Сохраняем IRQ в структуре устройства + mov [vbox_device.irq], eax + DEBUGF 2, "[VBoxGuest] [PCI] IRQ line: %d\n", eax + + xor eax, eax + ret + +.no_device: + DEBUGF 2, "[VBoxGuest] [PCI] ERROR: No PCI device for IRQ init\n" + mov eax, VERR_GENERAL_FAILURE + ret + +.invalid_irq: + DEBUGF 2, "[VBoxGuest] [PCI] ERROR: Invalid IRQ line: %d\n", eax + mov eax, VERR_GENERAL_FAILURE + ret + +endp + +; pci_init_vmmdev_bar — Чтение BAR0 (I/O) и BAR1 (MMIO) из PCI конфигурации +proc pci_init_vmmdev_bar + mov ebx, [pci_device] + test ebx, ebx + jz .no_device + + ; Читаем BAR0 (I/O port) + invoke PciRead32, dword [ebx + PCIDEV.bus], dword [ebx + PCIDEV.devfn], PCI_header00.base_addr_0 + and eax, not 0xF + mov [vbox_device.port], ax + DEBUGF 2, "[VBoxGuest] [PCI] BAR0 (I/O Port): 0x%x\n", eax + + ; Читаем BAR1 (MMIO) + invoke PciRead32, dword [ebx + PCIDEV.bus], dword [ebx + PCIDEV.devfn], PCI_header00.base_addr_1 + and eax, not 0xF + mov [vbox_device.mmio_phys], eax + DEBUGF 2, "[VBoxGuest] [PCI] BAR1 (MMIO): phys=0x%x\n", eax + + xor eax, eax + ret + +.no_device: + DEBUGF 2, "[VBoxGuest] [PCI] ERROR: No PCI device for BAR init\n" + mov eax, VERR_GENERAL_FAILURE + ret + +endp \ No newline at end of file diff --git a/drivers/vboxguest/core/ports.inc b/drivers/vboxguest/core/ports.inc new file mode 100644 index 000000000..797da343c --- /dev/null +++ b/drivers/vboxguest/core/ports.inc @@ -0,0 +1,54 @@ +; ============================================================================= +; Модуль : VMMDev I/O Ports +; Назначение : Операции через I/O порты VMMDev (fast request/events) +; Файл : core/ports.inc +; ============================================================================= + +; ----------------------------------------------------------------------------- +; vmmdev_send_request — отправка физического адреса VMMDev-запроса в I/O порт +; +; Вход : phys_addr — физ. адрес VMMDev-пакета +; Выход: — +; ----------------------------------------------------------------------------- +proc vmmdev_send_request uses edx, phys_addr:dword + mov eax, [phys_addr] + + mov dx, [vbox_device.port] + out dx, eax + + ; Спин-ожидание (~1ms): даёт хосту время на обработку запроса + mov ecx, 1000000 +.wait: + xor eax, eax + loop .wait + + ret +endp + +; ----------------------------------------------------------------------------- +; ports_init — проверка, что порт задан +; +; Выход: eax = 0 успех / VERR_INVALID_PARAMETER +; ----------------------------------------------------------------------------- +proc ports_init + movzx eax, word [vbox_device.port] + test eax, eax + jz .bad + xor eax, eax + ret +.bad: + mov eax, VERR_INVALID_PARAMETER + ret +endp + +; ports_read_fast_events — прочитать pending mask через FAST порт +; +; Выход: eax = маска событий +proc ports_read_fast_events uses edx + mov dx, [vbox_device.port] + add dx, VMMDEV_PORT_OFF_REQUEST_FAST + in eax, dx + ret +endp + + diff --git a/drivers/vboxguest/core/state.inc b/drivers/vboxguest/core/state.inc new file mode 100644 index 000000000..5af38cdd8 --- /dev/null +++ b/drivers/vboxguest/core/state.inc @@ -0,0 +1,70 @@ +; ============================================================================= +; Модуль : Driver Internal Structures +; Назначение : Глобальная структура состояния драйвера VBoxGuest +; Файл : core/state.inc +; ============================================================================= +struct VBOX_DEVICE + port dw ? + pad1 dw ? + mmio_virt dd ? + mmio_phys dd ? + + irq dd ? + last_events dd ? ; последняя маска событий, прочитанная в IRQ + event_filter dd ? + caps dd ? + + flags dd ? + ; бит0: device_present + ; бит1: mmio_mapped + ; бит2: irq_attached + ; бит3: timer_running + + + ; HGCM + hgcm_timeout dd ? + + ; HGCM packets + hgcm_connect_virt dd ? + hgcm_connect_phys dd ? + hgcm_disconnect_virt dd ? + hgcm_disconnect_phys dd ? + hgcm_call_virt dd ? + hgcm_call_phys dd ? + + ; Pre-allocated packets + display_virt dd ? + display_phys dd ? + events_virt dd ? + events_phys dd ? + filter_virt dd ? + filter_phys dd ? + caps_virt dd ? + caps_phys dd ? + guestinfo_virt dd ? + guestinfo_phys dd ? + guestinfo2_virt dd ? + guestinfo2_phys dd ? + + mouse_virt dd ? + mouse_phys dd ? + + hypervisor_info_virt dd ? + hypervisor_info_phys dd ? + dnd_call_virt dd ? + dnd_call_phys dd ? + host_version_virt dd ? + host_version_phys dd ? + + heartbeat_config_virt dd ? + heartbeat_config_phys dd ? + heartbeat_virt dd ? + heartbeat_phys dd ? + + ; Pagelist HGCM call buffer (динамически выделяется) + hgcm_call_pl_virt dd ? + hgcm_call_pl_phys dd ? + + ; Единый блок памяти для VMMDev пакетов + vmmdev_packets_page dd ? +ends \ No newline at end of file diff --git a/drivers/vboxguest/core/timer.inc b/drivers/vboxguest/core/timer.inc new file mode 100644 index 000000000..f149116e7 --- /dev/null +++ b/drivers/vboxguest/core/timer.inc @@ -0,0 +1,57 @@ +; ============================================================================= +; Модуль : Timer +; Назначение : Периодический вызов fn_on_tick сервисов +; Файл : core/timer.inc +; ============================================================================= + +align 4 +vbox_timer_handle dd 0 + +TIMER_DELAY_START equ 10 ; 100ms до первого вызова +TIMER_INTERVAL equ 10 ; 100ms между вызовами + +; timer_init — Запуск таймера +proc timer_init + DEBUGF 2, "[VBoxGuest] [Timer] Initializing...\n" + + mov eax, [vbox_timer_handle] + test eax, eax + jnz .ok + + invoke TimerHS, TIMER_DELAY_START, TIMER_INTERVAL, timer_cb, 0 + test eax, eax + jz .err + + mov [vbox_timer_handle], eax + DEBUGF 2, "[VBoxGuest] [Timer] Started, handle=0x%x, interval=%dms\n", \ + eax, TIMER_INTERVAL * 10 + +.ok: + xor eax, eax + ret + +.err: + DEBUGF 2, "[VBoxGuest] [Timer] ERROR: Failed to start\n" + mov eax, VERR_INTERNAL_ERROR + ret +endp + +; timer_stop — Остановка таймера +proc timer_stop + mov eax, [vbox_timer_handle] + test eax, eax + jz .done + + invoke CancelTimerHS, eax + mov dword [vbox_timer_handle], 0 + DEBUGF 2, "[VBoxGuest] [Timer] Stopped\n" + +.done: + ret +endp + +; Callback таймера — вызывает fn_on_tick у всех включенных сервисов +proc timer_cb stdcall, userdata:dword + stdcall dispatcher_tick_all + ret +endp diff --git a/drivers/vboxguest/data/clipboard/constants.inc b/drivers/vboxguest/data/clipboard/constants.inc new file mode 100644 index 000000000..dbd749e6d --- /dev/null +++ b/drivers/vboxguest/data/clipboard/constants.inc @@ -0,0 +1,80 @@ +; ============================================================================= +; Clipboard Constants — из VBox 7.2.6 исходников +; +; Источники: +; include/VBox/HostServices/VBoxClipboardSvc.h +; include/VBox/GuestHost/SharedClipboard.h +; ============================================================================= + +; ============================================================================= +; Guest function numbers (VBOX_SHCL_GUEST_FN_*) +; Гость вызывает эти функции через HGCM +; ============================================================================= +VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT equ 1 ; Ждать сообщение от хоста (blocking, deprecated но работает) +VBOX_SHCL_GUEST_FN_REPORT_FORMATS equ 2 ; Сообщить хосту о доступных форматах +VBOX_SHCL_GUEST_FN_DATA_READ equ 3 ; Прочитать данные с хоста +VBOX_SHCL_GUEST_FN_DATA_WRITE equ 4 ; Записать данные на хост +VBOX_SHCL_GUEST_FN_CONNECT equ 5 ; (deprecated в 7.x) +VBOX_SHCL_GUEST_FN_REPORT_FEATURES equ 6 ; Сообщить о поддерживаемых фичах +VBOX_SHCL_GUEST_FN_QUERY_FEATURES equ 7 ; Запросить фичи хоста +VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT equ 8 ; Peek без блокировки +VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT equ 9 ; Peek с блокировкой +VBOX_SHCL_GUEST_FN_MSG_GET equ 10 ; Получить сообщение (новый протокол) +VBOX_SHCL_GUEST_FN_MSG_CANCEL equ 26 ; Отменить ожидание + +; Количества параметров +VBOX_SHCL_CPARMS_MSG_OLD_GET_WAIT equ 2 +VBOX_SHCL_CPARMS_REPORT_FORMATS equ 1 +VBOX_SHCL_CPARMS_DATA_READ equ 3 +VBOX_SHCL_CPARMS_DATA_WRITE_OLD equ 2 ; Без CONTEXT_ID (старый протокол) +VBOX_SHCL_CPARMS_DATA_WRITE equ 3 ; С CONTEXT_ID (новый протокол) + +; ============================================================================= +; Host message types (VBOX_SHCL_HOST_MSG_*) +; Приходят в parm[0] от MSG_OLD_GET_WAIT +; +; КРИТИЧНО! В старом коде были НЕПРАВИЛЬНЫЕ значения: +; было: FORMATS_REPORT=1, READ_DATA=2, WRITE_DATA=3 +; надо: QUIT=1, READ_DATA=2, FORMATS_REPORT=3 +; ============================================================================= +VBOX_SHCL_HOST_MSG_QUIT equ 1 ; Хост закрывает клипборд +VBOX_SHCL_HOST_MSG_READ_DATA equ 2 ; Хост хочет прочитать данные гостя +VBOX_SHCL_HOST_MSG_FORMATS_REPORT equ 3 ; Хост сообщает о новых форматах +VBOX_SHCL_HOST_MSG_CANCELED equ 4 ; Отмена + +; ============================================================================= +; Format flags (VBOX_SHCL_FMT_*) — БИТОВЫЕ МАСКИ +; +; КРИТИЧНО! В старом коде были НЕПРАВИЛЬНЫЕ значения: +; было: FMT_TEXT=1, FMT_UNICODETEXT=13 +; надо: FMT_UNICODETEXT=1 (bit 0), FMT_BITMAP=2 (bit 1), FMT_HTML=4 (bit 2) +; FMT_TEXT не существует в VBox! Только UNICODETEXT (UTF-16LE) +; ============================================================================= +VBOX_SHCL_FMT_NONE equ 0x0000 +VBOX_SHCL_FMT_UNICODETEXT equ 0x0001 ; bit 0 — UTF-16LE текст +VBOX_SHCL_FMT_BITMAP equ 0x0002 ; bit 1 — DIB bitmap +VBOX_SHCL_FMT_HTML equ 0x0004 ; bit 2 — HTML +VBOX_SHCL_FMT_URI_LIST equ 0x0008 ; bit 3 — URI list (drag-n-drop) +VBOX_SHCL_FMT_VALID_MASK equ 0x000F + +; Буфер +VBOX_SHCL_MAX_CHUNK_SIZE equ 0x10000 ; 64KB + +; Событие VMMDev для clipboard +CLIPBOARD_EVENT_MASK equ VMMDEV_EVENT_HGCM ; bit 1 = 0x02 +CLIPBOARD_CAPS_MASK equ 0 ; VMMDEV_GUEST_SUPPORTS_SHCL ; bit 7 = 0x80 + +; ============================================================================= +; Состояния listener'а +; ============================================================================= +CLIP_LISTEN_IDLE equ 0 ; Не слушаем +CLIP_LISTEN_SUBMITTED equ 1 ; Запрос отправлен в VMMDev, ждём ответ + +; Максимум ошибок подряд до отключения +CLIP_MAX_ERRORS equ 5 + +; Флаги KolibriOS события (для CreateEvent) +CLIP_MANUAL_DESTROY equ 0x80000000 ; Событие не уничтожается после WaitEvent + +; VBOXGUEST_GUEST_CAPS_OR_MASK equ ( VBOXGUEST_GUEST_CAPS_OR_MASK or CLIPBOARD_CAPS_MASK ) +; VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or CLIPBOARD_EVENT_MASK ) \ No newline at end of file diff --git a/drivers/vboxguest/data/clipboard/structs.inc b/drivers/vboxguest/data/clipboard/structs.inc new file mode 100644 index 000000000..c043443c2 --- /dev/null +++ b/drivers/vboxguest/data/clipboard/structs.inc @@ -0,0 +1,73 @@ +; ============================================================================= +; Clipboard (HGCM service: Shared Clipboard / SHCL) +; ============================================================================= +struct SHCL_MSG_GET + header HGCM_CALL + msg_type HGCM_PARM + formats HGCM_PARM +ends + +struct SHCL_FORMATS_REPORT + header HGCM_CALL + formats HGCM_PARM +ends + +struct SHCL_DATA_READ + header HGCM_CALL + format HGCM_PARM + buffer HGCM_PARM + size HGCM_PARM +ends + +struct SHCL_DATA_WRITE + header HGCM_CALL + format HGCM_PARM + buffer HGCM_PARM +ends + + + +; ============================================================================= +; Clipboard Structures +; +; ПРИМЕЧАНИЕ: Старые структуры SHCL_MSG_GET, SHCL_FORMATS_REPORT, +; SHCL_DATA_READ, SHCL_DATA_WRITE УДАЛЕНЫ — они не нужны. +; +; Новый код использует плоский массив HGCM_PARM + hgcm_call32_pagelist, +; как в guest_props. Структуры с встроенным HGCM_CALL заголовком +; были нужны только для ручного формирования пакетов через +; hgcm_send_request, что больше не используется. +; ============================================================================= + +; struct CLIPBOARD_STATE +; client_id dd ? ; HGCM client ID +; connected dd ? ; 0 = not connected, 1 = connected +; formats_host dd ? ; Форматы доступные на хосте (битовая маска VBOX_SHCL_FMT_*) +; formats_guest dd ? ; Форматы доступные у гостя +; listen_state dd ? +; error_count dd ? +; ends + +struct CLIPBOARD_STATE + client_id dd ? ; HGCM client ID + connected dd ? ; 0/1 + listen_state dd ? ; IDLE/SUBMITTED + error_count dd ? ; Счетчик ошибок + formats_host dd ? ; Форматы от хоста + formats_guest dd ? ; Наши форматы + has_data dd ? ; Есть данные для чтения + data_size dd ? ; Размер данных + + ; Динамически выделяемые буферы + data_buf_ptr dd ? ; -> clip_data_buf (KernelAlloc 64K) + listen_pkt_virt dd ? ; -> clip_listen_pkt (KernelAlloc 4096, DMA) + listen_pkt_phys dd ? ; физ. адрес listen_pkt + parms_ptr dd ? ; -> clip_parms (KernelAlloc) + debug_buf_ptr dd ? ; -> clip_debug_buf (KernelAlloc) + + ; Поток слушателя (thread-based listener) + hgcm_event dd ? ; event handle (из CreateEvent) + hgcm_event_id dd ? ; event euid (из CreateEvent) + thread_id dd ? ; thread ID (из CreateThread) + thread_stop dd ? ; 1 = попросить поток завершиться +ends \ No newline at end of file diff --git a/drivers/vboxguest/data/core/constants.inc b/drivers/vboxguest/data/core/constants.inc new file mode 100644 index 000000000..9613721e8 --- /dev/null +++ b/drivers/vboxguest/data/core/constants.inc @@ -0,0 +1,135 @@ +; ============================================================================= +; Модуль : VBoxGuest Core Constants +; Файл : data/core/constants.inc +; Назначение : VMMDev константы и определения (PCI, версия, маски событий) +; ============================================================================= + +; PCI +VBOX_VENDOR_ID equ 0x80EE +VBOX_DEVICE_ID equ 0xCAFE + +; VMMDev протокол v1_04 +VMMDEV_VERSION_MAJOR equ 1 +VMMDEV_VERSION_MINOR equ 4 +VMMDEV_VERSION equ (VMMDEV_VERSION_MAJOR shl 16) or VMMDEV_VERSION_MINOR + +VMMDEV_REQUEST_HEADER_VERSION equ 0x00010001 +VMMDEV_HF_FAST_IRQ_ACK equ 0x00000001 +VMMDEV_MEMORY_VERSION equ 1 +VMMDEV_MEMORY_SIZE equ 0x00400000 + +VMMDEV_PORT_OFF_REQUEST equ 0 +VMMDEV_PORT_OFF_REQUEST_FAST equ 8 + +VMMDEV_MAX_HGCM_PARMS equ 32 +VMMDEV_MAX_HGCM_DATA_SIZE equ 128 ; MB + +; VMMDev Request Types +VMMDEV_REQ_GET_HOST_VERSION equ 4 +VMMDEV_REQ_ACKNOWLEDGE_EVENTS equ 41 +VMMDEV_REQ_CTL_GUEST_FILTER_MASK equ 42 +VMMDEV_REQ_REPORT_GUEST_INFO equ 50 +VMMDEV_REQ_GET_DISPLAY_CHANGE_2 equ 54 +VMMDEV_REPORT_GUEST_CAPS equ 55 +VMMDEV_SET_GUEST_CAPS equ 56 +VMMDEV_REQ_REPORT_GUEST_INFO2 equ 58 +VMMDEV_HGCM_CONNECT equ 60 +VMMDEV_HGCM_DISCONNECT equ 61 +VMMDEV_HGCM_CALL32 equ 62 +VMMDEV_HGCM_CALL64 equ 63 + +VBOXGSTINFO2_F_REQUESTOR_INFO equ 0x00000001 + +; Guest capabilities +VMMDEV_GUEST_SUPPORTS_SEAMLESS equ 0x00000001 +VMMDEV_GUEST_SUPPORTS_GHW_MAPPING equ 0x00000002 +VMMDEV_GUEST_SUPPORTS_GRAPHICS equ 0x00000004 +VMMDEV_GUEST_SUPPORTS_VRDP equ 0x00000010 +VMMDEV_GUEST_SUPPORTS_HGCM equ 0x00000020 +VMMDEV_GUEST_SUPPORTS_ACPI equ 0x00000040 +VMMDEV_GUEST_SUPPORTS_SHCL equ 0x00000080 +VMMDEV_GUEST_SUPPORTS_VRDP_RESIZE equ 0x00000100 +VMMDEV_GUEST_SUPPORTS_DRAG_AND_DROP equ 0x00000200 +VMMDEV_GUEST_SUPPORTS_CR3_MONITORING equ 0x00000400 +VMMDEV_GUEST_SUPPORTS_TIMER_NS equ 0x00000800 +VMMDEV_GUEST_SUPPORTS_GUEST_HEARTBEAT equ 0x00001000 +VMMDEV_GUEST_SUPPORTS_HOST_DISPLAY_TOPOLOGY equ 0x00002000 +VMMDEV_GUEST_SUPPORTS_REQUESTOR equ 0x00004000 +VMMDEV_GUEST_SUPPORTS_VBVA equ 0x00008000 +VMMDEV_GUEST_SUPPORTS_SET_GUEST_CAPABILITIES equ 0x00010000 +VMMDEV_GUEST_SUPPORTS_MOUSE equ 0x00020000 +VMMDEV_GUEST_SUPPORTS_SHARED_FOLDERS equ 0x00040000 +VMMDEV_GUEST_SUPPORTS_VIDEO_ACCEL equ 0x00080000 +VMMDEV_GUEST_SUPPORTS_AUDIO equ 0x00100000 +VMMDEV_GUEST_SUPPORTS_TSC_EMULATION equ 0x00200000 + +; VMMDev события (биты) +VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED equ 0x00000001 ; bit 0 +VMMDEV_EVENT_HGCM equ 0x00000002 ; bit 1 +VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST equ 0x00000004 ; bit 2 +VMMDEV_EVENT_JUDGE_CREDENTIALS equ 0x00000008 ; bit 3 +VMMDEV_EVENT_RESTORED equ 0x00000010 ; bit 4 +VMMDEV_EVENT_SEAMLESS_MODE_CHANGE equ 0x00000020 ; bit 5 +VMMDEV_EVENT_BALLOON_CHANGE_REQUEST equ 0x00000040 ; bit 6 +VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE equ 0x00000080 ; bit 7 +VMMDEV_EVENT_VRDP equ 0x00000100 ; bit 8 +VMMDEV_EVENT_MOUSE_POSITION_CHANGED equ 0x00000200 ; bit 9 +VMMDEV_EVENT_CPU_HOTPLUG equ 0x00000400 ; bit 10 +VMMDEV_EVENT_VALID_EVENT_MASK equ 0x000007FF ; bits 0-10 +VMMDEV_EVENT_GUEST_HEARTBEAT equ 0x00040000 +VMMDEV_EVENT_GUEST_HEARTBEAT_TIMEOUT equ 0x00080000 +VMMDEV_EVENT_HGCM_ASYNC_CALL equ 0x00100000 + +; Накопительные маски событий и capabilities (сервисы добавляют через OR) +VBOXGUEST_EVENTS_OR_MASK equ 0 +VBOXGUEST_EVENTS_NOT_MASK equ 0 +VBOXGUEST_GUEST_CAPS_OR_MASK equ 0 +VBOXGUEST_GUEST_CAPS_NOT_MASK equ 0 + +; Маска «шумных» событий — НЕ логировать +DISPATCHER_NOISY_EVENTS equ (VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED or VMMDEV_EVENT_MOUSE_POSITION_CHANGED) + +; OS Type +VBOXOSTYPE_KOLIBRIOS equ 0x00090000 + +; HGCM размеры пакетов +HGCM_CALL_HEADER_SIZE equ 32 +HGCM_CALL_BASE_SIZE equ 44 +HGCM_PARAM_SIZE equ 12 + +; Host Features +VMMDEV_HVF_MMIO equ 0x00000002 + +; Memory Balloon +VMMDEV_MEMORY_BALLOON_CHUNK_SIZE equ 0x00100000 +VMMDEV_MEMORY_BALLOON_CHUNK_PAGES equ 0x100 + +; Guest Facility Types +VBoxGuestFacilityType_VBoxGuestDriver equ 0 +VBoxGuestFacilityType_VBoxService equ 1 +VBoxGuestFacilityType_VBoxTrayClient equ 2 +VBoxGuestFacilityType_Seamless equ 3 +VBoxGuestFacilityType_Graphics equ 4 + +; Guest Facility Status +VBoxGuestFacilityStatus_Inactive equ 0 +VBoxGuestFacilityStatus_Active equ 1 + +; Requestor flags +VMMDEV_REQUESTOR_USR_NOT_GIVEN equ 0x00000000 +VMMDEV_REQUESTOR_USR_DRV equ 0x00000001 +VMMDEV_REQUESTOR_USR_DRV_OTHER equ 0x00000002 +VMMDEV_REQUESTOR_USR_ROOT equ 0x00000003 +VMMDEV_REQUESTOR_USR_USER equ 0x00000006 +VMMDEV_REQUESTOR_KERNEL equ 0x00000000 +VMMDEV_REQUESTOR_USERMODE equ 0x00000008 +VMMDEV_REQUESTOR_CON_DONT_KNOW equ 0x00000000 +VMMDEV_REQUESTOR_CON_NO equ 0x00000010 +VMMDEV_REQUESTOR_CON_YES equ 0x00000020 +VMMDEV_REQUESTOR_GRP_VBOX equ 0x00000080 +VMMDEV_REQUESTOR_TRUST_NOT_GIVEN equ 0x00000000 +VMMDEV_REQUESTOR_VBOXGUEST equ (VMMDEV_REQUESTOR_USR_DRV or VMMDEV_REQUESTOR_GRP_VBOX) + +; MMIO смещения +VMMDEV_MEMORY_HAVE_EVENTS_V1_04 equ 0x14 +VMMDEV_MEMORY_HAVE_EVENTS_V1_03 equ 0x0C diff --git a/drivers/vboxguest/data/core/structs.inc b/drivers/vboxguest/data/core/structs.inc new file mode 100644 index 000000000..e5cfd6734 --- /dev/null +++ b/drivers/vboxguest/data/core/structs.inc @@ -0,0 +1,297 @@ +; ============================================================================= +; VBoxGuest : Data / VMMDev структуры +; Назначение: ABI структуры протокола VMMDev (точное соответствие заголовкам VBox) +; Файл : data/core/structs.inc +; ============================================================================= + +; ============================================================================= +; Базовый заголовок запросов VMMDev (VMMDevRequestHeader) +; ============================================================================= +struct VMMDEV_HEADER + size dd ? ; 0 - размер пакета + version dd ? ; 4 - версия, = VMMDEV_REQUEST_HEADER_VERSION + request_type dd ? ; 8 - тип запроса + rc dd ? ; 12 - код возврата + reserved1 dd ? ; 16 - ВАЖНО! Это поле ДОЛЖНО быть, значение 0 + f_requestor dd ? ; 20 - флаги requestor +ends ; Размер = 24 БАЙТА + +; ============================================================================= +; Структура VMMDevMemory v1_04 +; ============================================================================= +struct VMMDEV_MEMORY + size dd ? ; 0x00 размер области (в байтах) + version dd ? ; 0x04 версия структуры (u32Version) + have_events dd ? +; vb_va_memory VB_VA_MEMORY +ends + +; VBVA memory layout => typedef struct VBVAMEMORY +struct VB_VA_MEMORY +; fu32_mode_flags dd ? +; off32_data dd ? +; off32_free dd ? +; au8_ring_buffer dd ? ; VMMDEV_VBVA_RING_BUFFER_SIZE +; VMMDEVVBVARECORD aRecords[VMMDEV_VBVA_MAX_RECORDS]; +; index_record_first dd ? +; index_record_free dd ? +; fu32_supported_orders dd? + ; reserved1 dd ? ; 0x0C: Зарезервировано + ; host_features dd ? ; 0x10: Возможности хоста + ; guest_features dd ? ; 0x14: Возможности гостя + ; mouse_features dd ? ; 0x18: Возможности мыши + ; mouse_pos_x dd ? ; 0x1C: Позиция мыши X + ; mouse_pos_y dd ? ; 0x20: Позиция мыши Y +ends +; ============================================================================= +; Запрос: Version Request +; ============================================================================= +struct VMMDEV_GET_HOST_VERSION + header VMMDEV_HEADER + major dw ? + minor dw ? + build dd ? + revision dd ? + features dd ? +ends + +; ============================================================================= +; Запрос: ReportGuestInfo (VMMDevReq_ReportGuestInfo = 50) +; ============================================================================= +struct VMMDEV_REPORT_GUEST_INFO + header VMMDEV_HEADER + interface_version dd ? ; VMMDEV_VERSION (0x00010004) + os_type dd ? ; VBOXOSTYPE_* +ends + +; ============================================================================= +; Запрос: ReportGuestInfo2 (VMMDevReq_ReportGuestInfo2 = 58) +; Используется в vgdrvReportGuestInfo +; ============================================================================= +struct VMMDEV_GUEST_INFO2 + additions_major dw ? ; VBOX_VERSION_MAJOR + additions_minor dw ? ; VBOX_VERSION_MINOR + additions_build dd ? ; VBOX_VERSION_BUILD + additions_revision dd ? ; VBOX_SVN_REV + additions_features dd ? ; VBOXGSTINFO2_F_REQUESTOR_INFO + szName rb 128 ; VBOX_VERSION_STRING +ends + +struct VMMDEV_REPORT_GUEST_INFO2 + header VMMDEV_HEADER + guest_info VMMDEV_GUEST_INFO2 +ends + +; ============================================================================= +; Запрос: GetHypervisorInfo (VMMDevReq_GetHypervisorInfo = 2) +; ============================================================================= +struct VMMDEV_REQ_HYPERVISOR_INFO + header VMMDEV_HEADER + hypervisorStart dd ? ; GCPhys32 + hypervisorSize dd ? +ends + +; ============================================================================= +; Запрос: SetHypervisorInfo (VMMDevReq_SetHypervisorInfo = 3) +; ============================================================================= +struct VMMDEV_SET_HYPERVISOR_INFO + header VMMDEV_HEADER + hypervisorStart dd ? ; GCPhys32 + hypervisorSize dd ? +ends + +; ============================================================================= +; Запрос: AcknowledgeEvents (VMMDevReq_AcknowledgeEvents = 41) +; В коде: VMMDevEvents +; ============================================================================= +struct VMMDEV_EVENTS + header VMMDEV_HEADER + events dd ? ; IN/OUT: маска событий +ends + +; Алиас для совместимости +struct VMMDEV_ACKNOWLEDGE_EVENTS + header VMMDEV_HEADER + events dd ? +ends + +; ============================================================================= +; Запрос: CtlGuestFilterMask (VMMDevReq_CtlGuestFilterMask = 42) +; guestEventFilter = (guestEventFilter | u32OrMask) & ~u32NotMask +; ============================================================================= +struct VMMDEV_CTL_GUEST_FILTER_MASK + header VMMDEV_HEADER + or_mask dd ? ; маска OR + not_mask dd ? ; маска NOT +ends + +; ============================================================================= +; Запрос: SetGuestCapabilities (VMMDevReq_SetGuestCapabilities = 55) +; guestCaps = (guestCaps | u32OrMask) & ~u32NotMask +; VMMDevReqGuestCapabilities2 = VMMDevReq_SetGuestCapabilities +; ============================================================================= +struct VMMDEV_SET_GUEST_CAPABILITIES2 + header VMMDEV_HEADER + or_mask dd ? ; маска OR + not_mask dd ? ; маска NOT +ends + +; Алиас для совместимости +struct VMMDEV_GUEST_CAPS2 + header VMMDEV_HEADER + or_mask dd ? + not_mask dd ? +ends + +; ============================================================================= +; Запрос: GetDisplayChangeRequest2 (VMMDevReq_GetDisplayChangeRequest2 = 54) +; ============================================================================= +struct VMMDEV_GET_DISPLAY_CHANGE_REQUEST2 + header VMMDEV_HEADER + x_res dd ? + y_res dd ? + bpp dd ? + event_ack dd ? ; BOOL: 0/1 (false/true) + display dd ? ; индекс дисплея (0 для основного) +ends + +; ============================================================================= +; Запрос: ReportGuestStatus (VMMDevReq_ReportGuestStatus = 61) +; ============================================================================= +struct VBOXGUEST_STATUS + facility dd ? ; VBoxGuestFacilityType_* + status dd ? ; VBoxGuestFacilityStatus_* + flags dd ? +ends + +struct VMMDEV_REPORT_GUEST_STATUS + header VMMDEV_HEADER + guestStatus VBOXGUEST_STATUS +ends + +; ============================================================================= +; Memory Ballooning Structures +; ============================================================================= + +; Запрос: GetMemBalloonChangeRequest +struct VMMDEV_GET_MEM_BALLOON_CHANGE_REQUEST + header VMMDEV_HEADER + eventAck dd ? + cBalloonChunks dd ? + cPhysMemChunks dd ? +ends + +; Запрос: ChangeMemBalloon +struct VMMDEV_CHANGE_MEM_BALLOON + header VMMDEV_HEADER + cPages dd ? + fInflate dd ? ; true = inflate, false = deflate + aPhysPage rd VMMDEV_MEMORY_BALLOON_CHUNK_PAGES +ends + + + +; ============================================================================= +; Вспомогательные структуры +; ============================================================================= + +; SHFLSTRING - строка с длиной (UTF-8 или UTF-16) +struct SHFLSTRING + size dw ? ; Размер буфера в байтах + length dw ? ; Длина строки в байтах (без null terminator) + string rb 0 ; Начало строки (переменный размер) +ends + +; SHFLFSOBJINFO - информация о файле/директории (92 байта) +; Источник: include/iprt/types.h — RTFSOBJINFO +struct SHFLFSOBJINFO + size_low dd ? ; +0: cbObject low (int64_t) + size_high dd ? ; +4: cbObject high + allocated_low dd ? ; +8: cbAllocated low (int64_t) + allocated_high dd ? ; +12: cbAllocated high + access_time_low dd ? ; +16: AccessTime low (RTTIMESPEC, nanoseconds) + access_time_high dd ? ; +20: AccessTime high + modification_time_low dd ? ; +24: ModificationTime low + modification_time_high dd ? ; +28: ModificationTime high + change_time_low dd ? ; +32: ChangeTime low + change_time_high dd ? ; +36: ChangeTime high + birth_time_low dd ? ; +40: BirthTime low + birth_time_high dd ? ; +44: BirthTime high + fMode dd ? ; +48: RTFMODE (тип+права: 0x4000=dir, 0x8000=file) + attr_reserved rb 40 ; +52: остаток RTFSOBJATTR (enmAdditional + union) +ends ; = 48 + 4 + 40 = 92 байт + +; SHFLMAPPING - информация о mapping (для QUERY_MAPPINGS) +struct SHFLMAPPING + flags dd ? ; Флаги + root dd ? ; Root handle + ; После этого идет SHFLSTRING с именем +ends + +; ============================================================================= +; Internal Driver Structures +; ============================================================================= + +struct VBOXGUEST_WAIT + Event dd ? ; RTSEMEVENTMULTI handle + ListNode_next dd ? ; RTListNode::pNext + ListNode_prev dd ? ; RTListNode::pPrev + fReqEvents dd ? ; запрашиваемые события + fResEvents dd ? ; полученные события + pSession dd ? ; сессия + pHGCMReq dd ? ; HGCM запрос (если есть) + fPendingWakeUp dd ? ; флаг отложенного пробуждения + fFreeMe dd ? ; флаг для освобождения +ends + +struct VBOXGUEST_SESSION + Process dd ? ; RTPROCESS + R0Process dd ? ; RTR0PROCESS + pDevExt dd ? ; устройство + fRequestor dd ? ; VMMDEV_REQUESTOR_* + fUserSession dd ? ; bool + SessionSpinlock dd ? ; спинлок для синхронизации сессии + ListNode_next dd ? ; RTListNode::pNext + ListNode_prev dd ? ; RTListNode::pPrev + fEventFilter dd ? ; маска фильтра событий + fMouseStatus dd ? ; статус мыши + fCapabilities dd ? ; возможности + fAcquiredGuestCaps dd ? ; приобретенные возможности + aHGCMClientIds rd 32 ; HGCM client IDs + fPendingCancelWaitEvents dd ? ; bool + u32MousePosChangedSeq dd ? ; последовательность изменения позиции мыши +ends + +; ============================================================================= +; Memory Ballooning Internal Structures +; ============================================================================= + +struct VBOXGUEST_MEMBALLOON + hMtx dd ? ; мьютекс + cChunks dd ? ; текущее количество чанков + cMaxChunks dd ? ; максимальное количество чанков + fUseKernelAPI dd ? ; использовать ли API ядра + paMemObj dd ? ; указатель на массив объектов памяти + pOwner dd ? ; владелец (сессия) +ends + +; ============================================================================= +; Bit Usage Tracker Structure +; ============================================================================= +struct VBOXGUEST_BITUSAGE_TRACKER + acPerBitUsage rd 32 ; счетчики использования битов (0..31) + fMask dd ? ; итоговая маска +ends + +struct DISPLAY_STATE + width dd ? + height dd ? + bpp dd ? + lfb dd ? + pitch dd ? + refresh_rate dd ? + active dd ? + pending_change dd ? + x dd ? + y dd ? +ends \ No newline at end of file diff --git a/drivers/vboxguest/data/data.inc b/drivers/vboxguest/data/data.inc new file mode 100644 index 000000000..cbb1dc9f8 --- /dev/null +++ b/drivers/vboxguest/data/data.inc @@ -0,0 +1,26 @@ +; ============================================================================= +; VBoxGuest : Data / VMMDev константы структуры +; Назначение: Агрегатор все констант и структур +; Файл : data/data.inc +; ============================================================================= +include 'core\constants.inc' +include 'core\structs.inc' + +include 'hgcm\constants.inc' +include 'hgcm\structs.inc' +include 'heartbeat\constants.inc' +include 'heartbeat\structs.inc' +include 'mouse\constants.inc' +include 'mouse\structs.inc' +include 'display\constants.inc' +include 'display\structs.inc' +include 'timesync\constants.inc' +include 'timesync\structs.inc' +include 'guest_props\constants.inc' +include 'guest_props\structs.inc' +include 'shared_folders\constants.inc' +include 'shared_folders\structs.inc' +include 'clipboard\constants.inc' +include 'clipboard\structs.inc' +include 'seamless\constants.inc' +include 'seamless\structs.inc' diff --git a/drivers/vboxguest/data/display/constants.inc b/drivers/vboxguest/data/display/constants.inc new file mode 100644 index 000000000..a15ce1fb1 --- /dev/null +++ b/drivers/vboxguest/data/display/constants.inc @@ -0,0 +1,33 @@ +; ============================================================================= +; Модуль : Display Service Constants +; Файл : data/display/constants.inc +; Назначение : Параметры автоизменения разрешения экрана VMMDev Display Change +; ============================================================================= + +; Display resolution limits +DISP_W_MIN equ 640 +DISP_H_MIN equ 480 +DISP_W_MAX equ 3840 +DISP_H_MAX equ 2160 + +; BGA (Bochs Graphics Adapter) register ports +VBE_DISPI_IOPORT_INDEX equ 0x01CE +VBE_DISPI_IOPORT_DATA equ 0x01CF + +; BGA register indices +VBE_DISPI_INDEX_XRES equ 0x01 +VBE_DISPI_INDEX_YRES equ 0x02 +VBE_DISPI_INDEX_BPP equ 0x03 +VBE_DISPI_INDEX_ENABLE equ 0x04 + +; BGA enable flags +VBE_DISPI_DISABLED equ 0x00 +VBE_DISPI_ENABLED equ 0x01 +VBE_DISPI_LFB_ENABLED equ 0x40 + +; Event mask for display service +DISPLAY_EVENT_MASK equ VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST + +; Accumulate global masks +VBOXGUEST_GUEST_CAPS_OR_MASK equ ( VBOXGUEST_GUEST_CAPS_OR_MASK or VMMDEV_GUEST_SUPPORTS_GRAPHICS ) +VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or DISPLAY_EVENT_MASK ) diff --git a/drivers/vboxguest/data/display/structs.inc b/drivers/vboxguest/data/display/structs.inc new file mode 100644 index 000000000..2f840d5f1 --- /dev/null +++ b/drivers/vboxguest/data/display/structs.inc @@ -0,0 +1,17 @@ +; ============================================================================= +; Display Service Structures +; ============================================================================= +; VMMDEV_GET_DISPLAY_CHANGE_REQUEST2 and DISPLAY_STATE are defined in +; data/core/structs.inc + + +struct DISPLAY + x dd ? + y dd ? + width dd ? + height dd ? + bits_per_pixel dd ? + vrefresh dd ? + pitch dd ? + lfb dd ? +ends diff --git a/drivers/vboxguest/data/guest_props/constants.inc b/drivers/vboxguest/data/guest_props/constants.inc new file mode 100644 index 000000000..bec3b30de --- /dev/null +++ b/drivers/vboxguest/data/guest_props/constants.inc @@ -0,0 +1,39 @@ +; ============================================================================= +; Модуль : Guest Properties Constants +; Файл : data/guest_props/constants.inc +; Назначение : HGCM сервис "VBoxGuestPropSvc" функции и константы +; ============================================================================= + +; HGCM function numbers (guest side) +GUEST_PROP_FN_GET_PROP equ 1 ; Получить свойство +GUEST_PROP_FN_SET_PROP equ 2 ; Установить свойство (с флагами) +GUEST_PROP_FN_DEL_PROP equ 4 ; ?? 3 ; Удалить свойство +GUEST_PROP_FN_SET_PROP_VALUE equ 3 ; ?? 4 ; Установить значение (без флагов) +GUEST_PROP_FN_ENUM_PROPS equ 5 ; Перечислить свойства +GUEST_PROP_FN_GET_NOTIFICATION equ 6 ; Ожидать уведомления об изменении + +; GUEST_PROP_FN_HOST_SET_PROP_VALUE equ 4 ; недоступно гостю + +; Максимальные размеры +GUEST_PROP_MAX_NAME_LEN equ 256 +GUEST_PROP_MAX_VALUE_LEN equ 1024 +GUEST_PROP_MAX_FLAGS_LEN equ 128 +GUEST_PROP_ENUM_BUF_SIZE equ 4096 + +; Количество HGCM параметров для разных операций +GUEST_PROP_GET_PARM_COUNT equ 4 ; name, value, timestamp, flags +GUEST_PROP_SET_PARM_COUNT equ 3 ; name, value, flags +GUEST_PROP_SET_VALUE_PARM_COUNT equ 2 ; name, value +GUEST_PROP_DEL_PARM_COUNT equ 1 ; name +GUEST_PROP_ENUM_PARM_COUNT equ 3 ; patterns, buffer, size + +; Стандартные свойства для установки при инициализации +; /VirtualBox/GuestInfo/OS/Product = "KolibriOS" +; /VirtualBox/GuestInfo/OS/Release = "1.0" +; /VirtualBox/GuestAdd/VersionExt = "1.0.0" +; /VirtualBox/GuestAdd/Revision = "1" + +; Маска событий: Guest Properties не используют VMMDev события напрямую +GUEST_PROP_EVENT_MASK equ 0 +; Capabilities: не требует отдельных guest caps +GUEST_PROP_CAPS_MASK equ 0 diff --git a/drivers/vboxguest/data/guest_props/structs.inc b/drivers/vboxguest/data/guest_props/structs.inc new file mode 100644 index 000000000..790a27d23 --- /dev/null +++ b/drivers/vboxguest/data/guest_props/structs.inc @@ -0,0 +1,16 @@ +; ============================================================================= +; Guest Properties Structures +; ============================================================================= + +struct GUEST_PROP_STATE + client_id dd ? ; HGCM client ID + connected dd ? ; 0/1 + + ; Динамически выделяемые буферы + enum_buf_ptr dd ? ; gp_enum_buf (KernelAlloc 4096) + name_buf_ptr dd ? ; gp_name_buf (часть small_bufs) + value_buf_ptr dd ? ; gp_value_buf + flags_buf_ptr dd ? ; gp_flags_buf + parms_ptr dd ? ; gp_parms + small_bufs_ptr dd ? ; единый блок (KernelAlloc 4096) +ends diff --git a/drivers/vboxguest/data/heartbeat/constants.inc b/drivers/vboxguest/data/heartbeat/constants.inc new file mode 100644 index 000000000..b2cb9651f --- /dev/null +++ b/drivers/vboxguest/data/heartbeat/constants.inc @@ -0,0 +1,11 @@ +; ============================================================================= +; Модуль : Heartbeat Service Constants +; Файл : data/heartbeat/constants.inc +; Назначение : VMMDev heartbeat мониторинг гостя (запросы и события) +; ============================================================================= + +VMMDEV_REQ_GUEST_HEARTBEAT equ 217 ; VMMDevReq_GuestHeartbeat, Отправка heartbeat +VMMDEV_REQ_HEARTBEAT_CONFIGURE equ 218 ; VMMDevReq_HeartbeatConfigure, Настройка heartbeat + +VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or VMMDEV_EVENT_GUEST_HEARTBEAT or VMMDEV_EVENT_GUEST_HEARTBEAT_TIMEOUT) +VBOXGUEST_GUEST_CAPS_OR_MASK equ ( VBOXGUEST_GUEST_CAPS_OR_MASK or VMMDEV_GUEST_SUPPORTS_GUEST_HEARTBEAT ) diff --git a/drivers/vboxguest/data/heartbeat/structs.inc b/drivers/vboxguest/data/heartbeat/structs.inc new file mode 100644 index 000000000..26479ef83 --- /dev/null +++ b/drivers/vboxguest/data/heartbeat/structs.inc @@ -0,0 +1,20 @@ +; ============================================================================= +; Запрос: HeartbeatConfigure (VMMDevReq_HeartbeatConfigure = 60) +; Структура VMMDevReqHeartbeat +; cNsInterval - /* OUT: интервал в наносекундах */ +; f_enabled - флаг включения +; ============================================================================= +struct VMMDEV_HEARTBEAT_CONFIGURE + header VMMDEV_HEADER + c_ns_interval dd ? ; Интервал в наносекундах (младшая часть) + c_ns_interval_high dd ? ; Интервал в наносекундах (старшая часть) + f_enabled dd ? ; bool(byte) + выравнивание, 1 = включить, 0 = выключить +ends ; 24 + 12 байт +; AssertCompileSize(VMMDevReqHeartbeat, 24+12); + +; ============================================================================= +; Запрос: GuestHeartbeat (VMMDevReq_GuestHeartbeat = 61) +; ============================================================================= +struct VMMDEV_GUEST_HEARTBEAT + header VMMDEV_HEADER +ends ; 24 bytes diff --git a/drivers/vboxguest/data/hgcm/constants.inc b/drivers/vboxguest/data/hgcm/constants.inc new file mode 100644 index 000000000..b115d79af --- /dev/null +++ b/drivers/vboxguest/data/hgcm/constants.inc @@ -0,0 +1,107 @@ +; ============================================================================= +; Модуль : HGCM Protocol Constants +; Файл : data/hgcm/constants.inc +; Назначение : HGCM (Host Guest Communication Manager) протокол константы и параметры +; ============================================================================= + + +; ============================================================================= +; HGCM Protocol Constants +; ============================================================================= +VBOX_HGCM_REQ_DONE equ 0x00000001 +VBOX_HGCM_REQ_CANCELLED equ 0x00000002 + +HGCM_SERVICE_NAME_MAX equ 128 +HGCM_TIMEOUT_ITERS equ 500000 ; Количество итераций внешнего цикла ожидания +HGCM_ASYNC_DELAY_ITERS equ 1000 ; Количество pause инструкций во вложенном цикле + +HGCM_LOC_TYPE_PREDEFINED equ 2 + +; ============================================================================= +; HGCM Parameter Types (VMMDevHGCMParmType) +; Источник: include/VBox/VMMDev.h из исходного кода VirtualBox +; ============================================================================= +VMMDEV_HGCM_PARM_TYPE_INVALID equ 0 +VMMDEV_HGCM_PARM_TYPE_32BIT equ 1 +HGCM_PARM_TYPE_32BIT equ VMMDEV_HGCM_PARM_TYPE_32BIT +VMMDEV_HGCM_PARM_TYPE_64BIT equ 2 +; VMMDEV_HGCM_PARM_TYPE_PHYSADDR equ 3 ; устарел +VMMDEV_HGCM_PARM_TYPE_LINADDR equ 4 ; In and Out +HGCM_PARM_TYPE_LINADDR equ VMMDEV_HGCM_PARM_TYPE_LINADDR +VMMDEV_HGCM_PARM_TYPE_LINADDR_IN equ 5 ; Host <- Guest +HGCM_PARM_TYPE_LINADDR_IN equ VMMDEV_HGCM_PARM_TYPE_LINADDR_IN +VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT equ 6 ; Host -> Guest +HGCM_PARM_TYPE_LINADDR_OUT equ VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT +VMMDEV_HGCM_PARM_TYPE_LINADDR_LOCKED equ 7 ; Для VBoxGuest, а не хоста +VMMDEV_HGCM_PARM_TYPE_LINADDR_LOCKED_IN equ 8 ; Для VBoxGuest, а не хоста +VMMDEV_HGCM_PARM_TYPE_LINADDR_LOCKED_OUT equ 9 ; Для VBoxGuest, а не хоста +VMMDEV_HGCM_PARM_TYPE_PAGELIST equ 10 +VMMDEV_HGCM_PARM_TYPE_EMBEDDED equ 11 +VMMDEV_HGCM_PARM_TYPE_CONTIGUOUS_PAGELIST equ 12 +VMMDEV_HGCM_PARM_TYPE_NO_BOUNCE_PAGELIST equ 13 +VMMDEV_HGCM_PARM_TYPE_SIZE_HACK equ 0x7fffffff + +; ============================================================================= +; Флаги для EMBEDDED параметров (Embedded.fFlags) +; ============================================================================= +VMMDEV_HGCM_EMBEDDED_FLAG_IN equ 1 ; Данные от гостя к хосту +VMMDEV_HGCM_EMBEDDED_FLAG_OUT equ 2 ; Данные от хоста к гостю +VMMDEV_HGCM_EMBEDDED_FLAG_BOTH equ 3 ; (IN | OUT) + +; ============================================================================= +; Дополнительные константы для проверки возможностей хоста (Host Features) +; ============================================================================= +VMMDEV_HVF_HGCM_EMBEDDED_BUFFERS equ 0x00000002 ; Битовый флаг 1 + +; ============================================================================= +; (Опционально) Для обратной совместимости с вашим текущим кодом +; ============================================================================= +; Короткие алиасы для использования в коде: +HGCM_PARM_TYPE_64BIT equ VMMDEV_HGCM_PARM_TYPE_64BIT +HGCM_PARM_TYPE_LINADDR_INOUT equ VMMDEV_HGCM_PARM_TYPE_LINADDR ; = 4 (bidirectional) +HGCM_PARM_TYPE_EMBEDDED equ VMMDEV_HGCM_PARM_TYPE_EMBEDDED +HGCM_EMBED_FLAG_IN equ VMMDEV_HGCM_EMBEDDED_FLAG_IN +HGCM_EMBED_FLAG_OUT equ VMMDEV_HGCM_EMBEDDED_FLAG_OUT +HGCM_EMBED_FLAG_BOTH equ VMMDEV_HGCM_EMBEDDED_FLAG_BOTH + +; Максимальный размер данных для embedded +HGCM_MAX_EMBEDDED_DATA equ 4096 + +; Максимальный хвост PageList (для pagelist call buffer) +HGCM_MAX_PAGELIST_TAIL equ 8192 + +; ============================================================================= +; Константы PageList +; ============================================================================= +HGCM_PARM_TYPE_PAGELIST equ 10 + +; Флаги направления PageList +HGCM_PL_FLAG_IN equ 1 ; guest -> host +HGCM_PL_FLAG_OUT equ 2 ; host -> guest +HGCM_PL_FLAG_BOTH equ 3 + +; Размеры страниц +PAGE_SIZE equ 4096 +PAGE_OFFSET_MASK equ 0x0FFF +PAGE_BASE_MASK equ 0xFFFFF000 + +; Смещения внутри HGCMPageListInfo +HGCM_PLI_FLAGS equ 0 ; dd flags +HGCM_PLI_OFF_FIRST_PAGE equ 4 ; dw offFirstPage +HGCM_PLI_CPAGES equ 6 ; dw cPages +HGCM_PLI_APAGES equ 8 ; начало массива dq[] aPages + +; ============================================================================= +; Макрос FILL_HGCM_HEADER - Заполнить стандартный заголовок HGCM пакета +; Параметры: +; reg - регистр с указателем на структуру HGCM_HEADER +; size - размер пакета в байтах +; ============================================================================= +macro FILL_HGCM_HEADER reg, size { + mov dword [reg + HGCM_HEADER.header.size], size + mov dword [reg + HGCM_HEADER.header.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [reg + HGCM_HEADER.header.request_type], VMMDEV_REQ_HGCM_CALL32 + mov dword [reg + HGCM_HEADER.header.rc], VERR_GENERAL_FAILURE + mov dword [reg + HGCM_HEADER.header.reserved1], 0 + mov dword [reg + HGCM_HEADER.header.requestor], VMMDEV_REQUESTOR_KERNEL +} diff --git a/drivers/vboxguest/data/hgcm/structs.inc b/drivers/vboxguest/data/hgcm/structs.inc new file mode 100644 index 000000000..496211c95 --- /dev/null +++ b/drivers/vboxguest/data/hgcm/structs.inc @@ -0,0 +1,70 @@ +; ============================================================================= +; VBoxGuest Driver for KolibriOS - Structures HGCM +; ============================================================================= + +; ============================================================================= +; HGCM Structures +; ============================================================================= + +struct HGCM_HEADER + header VMMDEV_HEADER + flags dd ? + result dd ? +ends ; 32 байта + +struct HGCM_CONNECT + header HGCM_HEADER + location_type dd ? + service_name rb HGCM_SERVICE_NAME_MAX ; имя сервиса + client_id dd ? +ends + +struct HGCM_DISCONNECT + header HGCM_HEADER + client_id dd ? +ends + +struct HGCM_CALL + header HGCM_HEADER ; 32 + client_id dd ? ; +4 = 36 // ID клиента + function dd ? ; +4 = 40 // Номер функции (1=MSG_OLD_GET_WAIT и т.д.) + param_count dd ? ; +4 = 44 // Количество параметров + ; params[0] начинаются с offset 44 + ; Далее идут параметры: struct vmmdev_hgcm_function_parameter params[parm_count]; +ends + +struct HGCM_PARM + type dd ? ; тип параметра (VMMDevHGCMParmType) + u rb 8 ; union { uint32_t value32; RTGCPTR pointer; } +ends + +HGCM_PARM.u.value32 equ HGCM_PARM.u +HGCM_PARM.u.value_or_size equ HGCM_PARM.u + +HGCM_PARM.u.value64_lo equ HGCM_PARM.u +HGCM_PARM.u.value64_hi equ HGCM_PARM.u+4 + +HGCM_PARM.u.LinAddr.size equ HGCM_PARM.u +HGCM_PARM.u.LinAddr.offset equ HGCM_PARM.u+4 + +HGCM_PARM.value_or_size equ HGCM_PARM.u +HGCM_PARM.offset_or_addr equ HGCM_PARM.u+4 + + + +; sizeof(vmmdev_request_header) = 24 +; sizeof(vmmdev_hgcmreq_header) = 32 // header(24) + flags(4) + result(4) +; sizeof(vmmdev_hgcm_call) = 44 // header(32) + client_id(4) + function(4) + parm_count(4) +; sizeof(vmmdev_hgcm_function_parameter) = 12 +; sizeof(vmmdev_hgcm_pagelist) = 16 + (cPages * 8) + +; struct vmmdev_hgcm_pagelist { +; u32 flags; // Direction flags (1,2,3) +; u16 offFirstPage; // Смещение в первой странице +; u16 cPages; // Количество страниц +; u64 pages[1]; // Массив физических адресов страниц (RTGCPHYS64) +; }; + +; Размер буфера для PageList HGCM вызовов +; = sizeof.HGCM_CALL(44) + 32*sizeof.HGCM_PARM(12) + 8192 = 8620 → 3 страницы +HGCM_PL_BUF_SIZE equ sizeof.HGCM_CALL + (VMMDEV_MAX_HGCM_PARMS * sizeof.HGCM_PARM) + HGCM_MAX_PAGELIST_TAIL \ No newline at end of file diff --git a/drivers/vboxguest/data/mouse/constants.inc b/drivers/vboxguest/data/mouse/constants.inc new file mode 100644 index 000000000..585e01935 --- /dev/null +++ b/drivers/vboxguest/data/mouse/constants.inc @@ -0,0 +1,40 @@ +; ============================================================================= +; Модуль : Mouse Service Constants +; Файл : data/mouse/constants.inc +; Назначение : VMMDev запросы мыши (абсолютные координаты, кнопки, скролл) +; ============================================================================= +VMMDEV_REQ_GET_MOUSE_STATUS equ 1 +VMMDEV_REQ_SET_MOUSE_STATUS equ 2 +VMMDEV_REQ_SET_POINTER_SHAPE equ 3 +VMMDEV_REQ_GET_MOUSE_STATUS_EX equ 223 ; Extended: + buttons, scroll + +; VMMDEV_REQ_GET_POINTER_SHAPE equ 58 + +; Новые (v1_04): +VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE equ 0x00000001 +VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR equ 0x00000002 +VMMDEV_MOUSE_GUEST_HAS_ABSOLUTE equ 0x00000004 +VMMDEV_MOUSE_NEW_PROTOCOL equ 0x00000010 +VMMDEV_MOUSE_NOTIFY_GUEST equ 0x00000080 +VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE equ 0x00000100 +VMMDEV_MOUSE_HOST_HAS_ABSOLUTE equ 0x00000200 +VMMDEV_MOUSE_HOST_NEW_PROTOCOL equ 0x00000400 + +; Full state protocol (VBox 6.1+): кнопки + скролл в одном запросе +VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL equ 0x00000080 +VMMDEV_MOUSE_HOST_SUPPORTS_FULL_STATE_PROTOCOL equ 0x00000100 + +; Mouse button masks +VMMDEV_MOUSE_BUTTON_LEFT equ 0x01 +VMMDEV_MOUSE_BUTTON_RIGHT equ 0x02 +VMMDEV_MOUSE_BUTTON_MIDDLE equ 0x04 +VMMDEV_MOUSE_BUTTON_X1 equ 0x08 +VMMDEV_MOUSE_BUTTON_X2 equ 0x10 + +VBOX_MOUSE_GUEST_FEATURES equ VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE +VBOX_MOUSE_GUEST_FEATURES_EXT equ (VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE or VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL) + +MOUSE_EVENT_MASK equ ( VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED or VMMDEV_EVENT_MOUSE_POSITION_CHANGED) + +VBOXGUEST_GUEST_CAPS_OR_MASK equ ( VBOXGUEST_GUEST_CAPS_OR_MASK or VMMDEV_GUEST_SUPPORTS_MOUSE ) +VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or MOUSE_EVENT_MASK ) \ No newline at end of file diff --git a/drivers/vboxguest/data/mouse/structs.inc b/drivers/vboxguest/data/mouse/structs.inc new file mode 100644 index 000000000..ff28878e6 --- /dev/null +++ b/drivers/vboxguest/data/mouse/structs.inc @@ -0,0 +1,43 @@ +; ============================================================================= +; Запрос: GetMouseStatus (VMMDevReq_GetMouseStatus = 1) +; Запрос: SetMouseStatus (VMMDevReq_SetMouseStatus = 2) +; ============================================================================= +struct VMMDEV_REQ_MOUSE_STATUS + header VMMDEV_HEADER + mouse_features dd ? ; VMMDEV_MOUSE_* features + pointer_x_pos dd ? ; X position + pointer_y_pos dd ? ; Y position +ends + +; ============================================================================= +; Mouse pointer shape (для изменения курсора) +; ============================================================================= +struct VMMDEV_REQ_POINTER_SHAPE + header VMMDEV_HEADER + flags dd ? ; VMMDEV_POINTER_* flags + x_hot dd ? ; X hotspot + y_hot dd ? ; Y hotspot + width dd ? ; Width in pixels + height dd ? ; Height in pixels + ; За которыми следуют данные маски +ends +; ============================================================================= +; Extended mouse status (VMMDevReq_GetMouseStatusEx = 223) +; Включает кнопки и скролл. Требует VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL. +; ============================================================================= +struct VMMDEV_REQ_MOUSE_STATUS_EX + core VMMDEV_REQ_MOUSE_STATUS + scroll_dz dd ? ; vertical scroll delta + scroll_dw dd ? ; horizontal scroll delta + buttons dd ? ; VMMDEV_MOUSE_BUTTON_* mask +ends + +;struct VMMDEV_REQ_MOUSE_POINTER +; header VMMDEV_HEADER +; f_flags dd ? +; x_hot dd ? +; y_hot dd ? +; width dd ? +; height dd ? +; data rb 0 ; AND-mask + XOR-mask (variable) +;ends \ No newline at end of file diff --git a/drivers/vboxguest/data/seamless/constants.inc b/drivers/vboxguest/data/seamless/constants.inc new file mode 100644 index 000000000..7e2add079 --- /dev/null +++ b/drivers/vboxguest/data/seamless/constants.inc @@ -0,0 +1,26 @@ +; ============================================================================= +; Seamless Constants — VBox 7.2.6 +; Источник: include/VBox/VMMDevCoreTypes.h, include/VBox/VMMDev.h +; ============================================================================= + +; VMMDevReq_GetSeamlessChangeRequest +VMMDEV_REQ_GET_SEAMLESS_CHANGE equ 73 + +; VMMDevSeamlessMode enum +VMMDEV_SEAMLESS_DISABLED equ 0 ; Обычный режим, весь десктоп +VMMDEV_SEAMLESS_VISIBLE_REGION equ 1 ; Только top-level окна +VMMDEV_SEAMLESS_HOST_WINDOW equ 2 ; Каждое окно гостя = окно хоста + +; VMMDevReq_VideoSetVisibleRegion +VMMDEV_REQ_VIDEO_SET_VISIBLE_REGION equ 72 + +; Event: хост просит переключить seamless mode +; VMMDEV_EVENT_SEAMLESS_MODE_CHANGE = 0x00000020 (bit 5) — уже в core/constants.inc + +SEAMLESS_EVENT_MASK equ VMMDEV_EVENT_SEAMLESS_MODE_CHANGE + +; Caps: гость поддерживает seamless +; VMMDEV_GUEST_SUPPORTS_SEAMLESS = 0x00000001 — уже в core/constants.inc + +; Накопительная маска +VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or SEAMLESS_EVENT_MASK ) diff --git a/drivers/vboxguest/data/seamless/structs.inc b/drivers/vboxguest/data/seamless/structs.inc new file mode 100644 index 000000000..8935184c1 --- /dev/null +++ b/drivers/vboxguest/data/seamless/structs.inc @@ -0,0 +1,29 @@ +; ============================================================================= +; Seamless Structures — VBox 7.2.6 +; Источник: include/VBox/VMMDev.h +; ============================================================================= + +; VMMDevSeamlessChangeRequest (VMMDevReq_GetSeamlessChangeRequest = 73) +; size = 24 (header) + 8 = 32 +struct VMMDEV_SEAMLESS_CHANGE_REQUEST + header VMMDEV_HEADER + mode dd ? ; OUT: VMMDevSeamlessMode (0=disabled, 1=visible, 2=host_window) + eventAck dd ? ; IN: VMMDEV_EVENT_SEAMLESS_MODE_CHANGE для ACK +ends + +; RTRECT — прямоугольник (для VideoSetVisibleRegion) +struct RTRECT + xLeft dd ? + yTop dd ? + xRight dd ? + yBottom dd ? +ends + +; VMMDevVideoSetVisibleRegion (VMMDevReq_VideoSetVisibleRegion = 72) +; Переменная длина: header(24) + cRect(4) + Rect[cRect] (16 каждый) +; Для KolibriOS — один прямоугольник (весь экран) +struct VMMDEV_VIDEO_SET_VISIBLE_REGION + header VMMDEV_HEADER + cRect dd ? ; Количество прямоугольников + rect0 RTRECT ; Первый (и единственный) прямоугольник +ends diff --git a/drivers/vboxguest/data/shared_folders/constants.inc b/drivers/vboxguest/data/shared_folders/constants.inc new file mode 100644 index 000000000..2ee6e7219 --- /dev/null +++ b/drivers/vboxguest/data/shared_folders/constants.inc @@ -0,0 +1,113 @@ + ; ============================================================================= + ; SharedFolder Constants (VirtualBox HGCM) + ; ============================================================================= + + ; ========================================= + ; Функции службы Shared Folders (SHFL_FN_*) + ; ========================================= + SHFL_FN_QUERY_MAPPINGS equ 1 ; Запросить список подключенных папок + SHFL_FN_QUERY_MAP_NAME equ 2 ; Запросить имя подключения по индексу + SHFL_FN_CREATE equ 3 ; Создать файл/каталог + SHFL_FN_CLOSE equ 4 ; Закрыть дескриптор + SHFL_FN_READ equ 5 ; Чтение из файла +SHFL_FN_WRITE equ 6 ; Запись в файл +SHFL_FN_LOCK equ 7 ; Блокировка файла +SHFL_FN_LIST equ 8 ; Получить содержимое каталога +SHFL_FN_INFORMATION equ 9 ; Получить/установить информацию о файле +SHFL_FN_UNUSED_10 equ 10 ; (Зарезервировано) +SHFL_FN_REMOVE equ 11 ; Удалить файл/каталог +SHFL_FN_MAP_FOLDER_OLD equ 12 ; Устаревшее подключение папки (не использовать) +SHFL_FN_UNMAP_FOLDER equ 13 ; Отключить папку +SHFL_FN_RENAME equ 14 ; Переименовать/переместить +SHFL_FN_FLUSH equ 15 ; Сбросить кэш файла на диск +SHFL_FN_SET_UTF8 equ 16 ; Включить режим UTF-8 (0 параметров) +SHFL_FN_MAP_FOLDER equ 17 ; Подключить папку +SHFL_FN_READLINK equ 18 ; Прочитать символьную ссылку +SHFL_FN_SYMLINK equ 19 ; Создать символьную ссылку +SHFL_FN_SET_SYMLINKS equ 20 ; Разрешить/запретить симлинки (0 параметров) +SHFL_FN_SET_FILE_SIZE equ 24 ; Установить размер файла (усечь/расширить) + +; Флаги для SHFL_FN_REMOVE +SHFL_REMOVE_FILE equ 0x01 ; Удалить файл +SHFL_REMOVE_DIR equ 0x02 ; Удалить каталог +SHFL_REMOVE_SYMLINK equ 0x04 ; Удалить символьную ссылку + +; Флаги для SHFL_FN_RENAME +SHFL_RENAME_FILE equ 0x01 ; Переименовать файл +SHFL_RENAME_DIR equ 0x02 ; Переименовать каталог +SHFL_RENAME_REPLACE_IF_EXISTS equ 0x04 ; Заменить если существует + +; Флаги для операции LIST (используются в SHFL_LIST_PARMS.Flags) +; ============================================================== +SHFL_LIST_NONE equ 0x00000000 ; Базовый листинг +SHFL_LIST_RETURN_ONE equ 0x00000001 ; Вернуть только первую запись +SHFL_LIST_RESTART equ 0x00000002 ; Начать перебор заново +SHFL_LIST_SIZE_RETURNED equ 0x00000004 ; В SizeReturned реальный размер данных + +; Лимиты +; ====== +SHFL_MAX_MAPPINGS equ 10 ; Максимальное число подключенных папок +SHFL_MAX_NAME_LEN equ 256 ; Макс. длина имени в символах +SHFL_MAX_PATH_LEN equ 4096 ; Макс. длина пути в байтах (включая UTF-8) + +; Флаги создания/открытия файлов - SHFL_CF_* +; ====================================================================== +; SHFL_CF_* — CreateFlags для SHFL_FN_CREATE (из shflsvc.h) +; ====================================================================== +SHFL_CF_NONE equ 0x00000000 + +; Бит 0: Lookup only +SHFL_CF_LOOKUP equ 0x00000001 + +; Бит 1: Open target directory +SHFL_CF_OPEN_TARGET_DIRECTORY equ 0x00000002 + +; Бит 2: Object is a directory +SHFL_CF_DIRECTORY equ 0x00000004 + +; Биты 4..7: Action if file EXISTS +SHFL_CF_ACT_MASK_IF_EXISTS equ 0x000000F0 +SHFL_CF_ACT_OPEN_IF_EXISTS equ 0x00000000 ; Open existing +SHFL_CF_ACT_FAIL_IF_EXISTS equ 0x00000010 ; Fail if exists +SHFL_CF_ACT_REPLACE_IF_EXISTS equ 0x00000020 ; Replace (truncate) +SHFL_CF_ACT_OVERWRITE_IF_EXISTS equ 0x00000030 ; Overwrite + +; Биты 8..11: Action if file is NEW +SHFL_CF_ACT_MASK_IF_NEW equ 0x00000F00 +SHFL_CF_ACT_CREATE_IF_NEW equ 0x00000000 ; Create new file +SHFL_CF_ACT_FAIL_IF_NEW equ 0x00000100 ; Fail if doesn't exist + +; Биты 12..13: Access mode (read/write) +SHFL_CF_ACCESS_MASK_RW equ 0x00003000 +SHFL_CF_ACCESS_NONE equ 0x00000000 +SHFL_CF_ACCESS_READ equ 0x00001000 +SHFL_CF_ACCESS_WRITE equ 0x00002000 +SHFL_CF_ACCESS_READWRITE equ 0x00003000 + +; Биты 14..15: Deny mode (sharing) +SHFL_CF_ACCESS_MASK_DENY equ 0x0000C000 +SHFL_CF_ACCESS_DENYNONE equ 0x00000000 +SHFL_CF_ACCESS_DENYREAD equ 0x00004000 +SHFL_CF_ACCESS_DENYWRITE equ 0x00008000 +SHFL_CF_ACCESS_DENYALL equ 0x0000C000 + +; Биты 16..17: Attribute access +SHFL_CF_ACCESS_MASK_ATTR equ 0x00030000 +SHFL_CF_ACCESS_ATTR_NONE equ 0x00000000 +SHFL_CF_ACCESS_ATTR_READ equ 0x00010000 +SHFL_CF_ACCESS_ATTR_WRITE equ 0x00020000 +SHFL_CF_ACCESS_ATTR_READWRITE equ 0x00030000 + +; Бит 18: Append mode +SHFL_CF_ACCESS_APPEND equ 0x00040000 + +; ====================================================================== +; SHFLCREATERESULT — результат CREATE +; ====================================================================== +SHFL_NO_RESULT equ 0 +SHFL_PATH_NOT_FOUND equ 1 +SHFL_FILE_NOT_FOUND equ 2 +SHFL_FILE_EXISTS equ 3 +SHFL_FILE_CREATED equ 4 +SHFL_FILE_REPLACED equ 5 + diff --git a/drivers/vboxguest/data/shared_folders/structs.inc b/drivers/vboxguest/data/shared_folders/structs.inc new file mode 100644 index 000000000..0ea798ba6 --- /dev/null +++ b/drivers/vboxguest/data/shared_folders/structs.inc @@ -0,0 +1,304 @@ +; ============================================================================= +; VirtualBox SharedFolder Structures +; ============================================================================= + +; ----------------------------------------------------------------------------- +; Глобальное состояние SharedFolder драйвера +; ----------------------------------------------------------------------------- +struct SF_STATE + connected dd ? ; 1 = HGCM подключен к сервису + client_id dd ? ; HGCM client ID + + ; Динамически выделяемые init-буферы + packet_ptr dd ? ; sf_packet (KernelAlloc 4096) + mappings_ptr dd ? ; sf_mappings (часть small_bufs) + namebuf_ptr dd ? ; sf_namebuf + cp866_buf_ptr dd ? ; sf_cp866_buf + listbuf_ptr dd ? ; sf_listbuf (KernelAlloc 4096) + createparms_ptr dd ? ; sf_createparms + dir_path_ptr dd ? ; sf_dir_path + small_bufs_ptr dd ? ; единый блок мелких буферов (KernelAlloc 4096) + + ; Динамически выделяемые FS-буферы + fs_packet_ptr dd ? ; vboxsf_fs_packet (KernelAlloc 4096) + fs_listbuf_ptr dd ? ; vboxsf_fs_listbuf (KernelAlloc 4096) + fs_iobuf_ptr dd ? ; vboxsf_fs_iobuf (KernelAlloc 65536) + fs_cparms_ptr dd ? ; vboxsf_fs_cparms (KernelAlloc 4096) + fs_pathbuf_ptr dd ? ; vboxsf_fs_pathbuf + fs_pathbuf2_ptr dd ? ; vboxsf_fs_pathbuf2 + fs_tmpname_ptr dd ? ; vboxsf_fs_tmpname + fs_small_bufs_ptr dd ? ; единый блок мелких FS-буферов (KernelAlloc 4096) +ends + +; ----------------------------------------------------------------------------- +; Информация об одной shared folder (выделяется через KernelAlloc) +; ----------------------------------------------------------------------------- +struct SF_FOLDER + root_handle dd ? ; SHFL root handle (от MAP_FOLDER) + active dd ? ; 1 = folder активен и подключен + name rb 256 ; Имя mapping (UTF-8, null-terminated) +ends + +; ----------------------------------------------------------------------------- +; Данные для виртуального диска /vbox/ +; ----------------------------------------------------------------------------- +struct VBOXSF_DISK + folders rd SHFL_MAX_FOLDERS ; Указатели на SF_FOLDER + count dd ? ; Количество папок + disk_handle dd ? ; Handle от DiskAdd +ends + +; ============================================================================= +; Структура DISKMEDIAINFO для querymedia callback +; ============================================================================= +struct DISKMEDIAINFO + flags dd ? ; флаги медиа + sectorsize dd ? ; размер сектора + capacity dq ? ; емкость в секторах (64-bit) +ends + +; ============================================================================= +; Смещения внутренних структур ядра KolibriOS +; (нужны для create_partition callback) +; ============================================================================= +KPARTITION_OFS_DISK = 16 ; PARTITION.Disk -> DISK* +KPARTITION_OFS_FSUSERFUNCTIONS = 20 ; PARTITION.FSUserFunctions -> UserFuncs* +KDISK_OFS_USERDATA = 16 ; DISK.UserData -> void* + +; ============================================================================= +; BDFE (Block Data File Entry) — формат записи в директории KolibriOS +; ============================================================================= +BDFE_SIZE = 304 ; Размер одной BDFE записи (CP866) +BDFE_HEADER_SIZE = 32 ; Размер заголовка ответа ReadFolder + +; Структура заголовка ReadFolder ответа +struct BDFE_HEADER + version dd ? ; Версия формата (1) + entries_placed dd ? ; Количество возвращенных записей + total_entries dd ? ; Общее количество записей + reserved rd 5 ; Зарезервировано (20 байт) +ends + +; Смещения полей BDFE +BDFE_OFS_ATTR = 0 ; dword: атрибуты +BDFE_OFS_ENCODING = 4 ; byte: кодировка (0=CP866, 1=UTF-16, 2=UTF-8) +BDFE_OFS_CTIME = 8 ; dword: время создания +BDFE_OFS_CDATE = 12 ; dword: дата создания +BDFE_OFS_ATIME = 16 ; dword: время доступа +BDFE_OFS_ADATE = 20 ; dword: дата доступа +BDFE_OFS_MTIME = 24 ; dword: время модификации +BDFE_OFS_MDATE = 28 ; dword: дата модификации +BDFE_OFS_SIZE_LO = 32 ; dword: размер файла (младшие 32 бита) +BDFE_OFS_SIZE_HI = 36 ; dword: размер файла (старшие 32 бита) +BDFE_OFS_NAME = 40 ; 264 байта: имя файла (null-terminated) + +; Атрибуты файлов +FA_READONLY = 0x01 +FA_HIDDEN = 0x02 +FA_SYSTEM = 0x04 +FA_LABEL = 0x08 +FA_FOLDER = 0x10 +FA_ARCHIVED = 0x20 + +; Коды ошибок KolibriOS (syscall 70) +ERROR_SUCCESS = 0 +ERROR_DISK_FULL = 1 +ERROR_UNSUPPORTED = 2 +ERROR_UNKNOWN_FS = 3 +ERROR_FILE_NOT_FOUND = 5 +ERROR_END_OF_FILE = 6 +ERROR_MEMORY = 7 +ERROR_ACCESS_DENIED = 10 +ERROR_DEVICE = 11 + +; ============================================================================= +; Смещения внутри SHFLFSOBJINFO (92 байта) +; Используются для конвертации в BDFE при ReadFolder/GetFileInfo +; ============================================================================= +SHFLOBJINFO_OFS_SIZE_LO = 0 ; int64 cbObject (low) +SHFLOBJINFO_OFS_SIZE_HI = 4 ; int64 cbObject (high) +SHFLOBJINFO_OFS_ALLOC_LO = 8 ; int64 cbAllocated (low) +SHFLOBJINFO_OFS_ALLOC_HI = 12 ; int64 cbAllocated (high) +SHFLOBJINFO_OFS_BTIME_LO = 16 ; int64 BirthTime (low, nanosec) +SHFLOBJINFO_OFS_BTIME_HI = 20 ; int64 BirthTime (high) +SHFLOBJINFO_OFS_CTIME_LO = 24 ; int64 ChangeTime (low) +SHFLOBJINFO_OFS_CTIME_HI = 28 ; int64 ChangeTime (high) +SHFLOBJINFO_OFS_MTIME_LO = 32 ; int64 ModificationTime (low) +SHFLOBJINFO_OFS_MTIME_HI = 36 ; int64 ModificationTime (high) +SHFLOBJINFO_OFS_ATIME_LO = 40 ; int64 AccessTime (low) +SHFLOBJINFO_OFS_ATIME_HI = 44 ; int64 AccessTime (high) +SHFLOBJINFO_OFS_FMODE = 48 ; uint32 Attr (RTFMODE) + +; Биты fMode (RTFMODE) +S_IFDIR = 0x4000 +S_IFREG = 0x8000 + +; ============================================================================= +; HGCM пакеты для SharedFolder операций +; ============================================================================= + +; ----------------------------------------------------------------------------- +; SHFL_FN_QUERY_MAPPINGS - Получить список доступных mappings +; ----------------------------------------------------------------------------- +struct SHFL_QUERY_MAPPINGS + header HGCM_CALL + flags HGCM_PARM ; IN: флаги (обычно 0) + count HGCM_PARM ; OUT: количество mappings + buffer HGCM_PARM ; OUT: буфер для данных (SHFLMAPPING[]) +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_QUERY_MAP_NAME - Получить имя mapping по индексу +; ----------------------------------------------------------------------------- +struct SHFL_QUERY_MAP_NAME + header HGCM_CALL + index HGCM_PARM ; IN: индекс mapping (0..count-1) + name HGCM_PARM ; OUT: имя mapping (SHFLSTRING) +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_MAP_FOLDER - Подключить mapping +; ----------------------------------------------------------------------------- +struct SHFL_MAP_FOLDER + header HGCM_CALL + path HGCM_PARM ; IN + root HGCM_PARM ; OUT + delimiter HGCM_PARM ; IN + flags HGCM_PARM ; IN +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_UNMAP_FOLDER - Отключить mapping +; ----------------------------------------------------------------------------- +struct SHFL_UNMAP_FOLDER + header HGCM_CALL + root HGCM_PARM ; IN: root handle +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_CREATE - Открыть/создать файл +; ----------------------------------------------------------------------------- +struct SHFL_CREATE + header HGCM_CALL + root HGCM_PARM ; IN: root handle + path HGCM_PARM ; IN: путь к файлу (SHFLSTRING) + parms HGCM_PARM ; IN/OUT: параметры (SHFLCREATEPARMS) +ends + +; Параметры создания файла (108 байт) +struct SHFLCREATEPARMS + handle_lo dd ? ; +0: OUT: SHFLHANDLE low (uint64_t) + handle_hi dd ? ; +4: OUT: SHFLHANDLE high + result dd ? ; +8: OUT: SHFLCREATERESULT + flags dd ? ; +12: IN: SHFL_CF_* CreateFlags + info rb 92 ; +16: IN/OUT: SHFLFSOBJINFO +ends + +; ----------------------------------------------------------------------------- +; SHFLDIRINFO - одна запись в результате SHFL_FN_LIST (переменный размер) +; Фиксированная часть = 126 байт: +; SHFLFSOBJINFO (92) + cucShortName (2) + uszShortName (28) + SHFLSTRING hdr (4) +; За ней идут байты имени файла (переменная длина). +; ----------------------------------------------------------------------------- +SHFLDIRINFO_FIXED_SIZE = 126 + +SHFLDIRINFO_OFS_INFO = 0 ; SHFLFSOBJINFO (92 bytes) +SHFLDIRINFO_OFS_SIZE_LO = 0 ; cbObject low +SHFLDIRINFO_OFS_SIZE_HI = 4 ; cbObject high +SHFLDIRINFO_OFS_FMODE = 48 ; fMode (RTFMODE) +SHFLDIRINFO_OFS_CUC_SHORT_NAME = 92 ; cucShortName (uint16) +SHFLDIRINFO_OFS_USZ_SHORT_NAME = 94 ; uszShortName (14 x uint16 = 28 bytes) +SHFLDIRINFO_OFS_NAME_SIZE = 122 ; SHFLSTRING.u16Size +SHFLDIRINFO_OFS_NAME_LENGTH = 124 ; SHFLSTRING.u16Length +SHFLDIRINFO_OFS_NAME_STRING = 126 ; SHFLSTRING.string (данные имени) + +; ----------------------------------------------------------------------------- +; SHFL_FN_CLOSE - Закрыть файл +; ----------------------------------------------------------------------------- +struct SHFL_CLOSE + header HGCM_CALL + root HGCM_PARM ; IN: root handle + handle HGCM_PARM ; IN: file handle +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_READ - Читать из файла +; ----------------------------------------------------------------------------- +struct SHFL_READ + header HGCM_CALL + root HGCM_PARM ; IN: root handle + handle HGCM_PARM ; IN: file handle + offset_low HGCM_PARM ; IN: offset low (64-bit) + offset_high HGCM_PARM ; IN: offset high (64-bit) + size HGCM_PARM ; IN/OUT: размер для чтения + buffer HGCM_PARM ; OUT: буфер для данных +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_WRITE - Писать в файл +; ----------------------------------------------------------------------------- +struct SHFL_WRITE + header HGCM_CALL + root HGCM_PARM ; IN: root handle + handle HGCM_PARM ; IN: file handle + offset_low HGCM_PARM ; IN: offset low (64-bit) + offset_high HGCM_PARM ; IN: offset high (64-bit) + size HGCM_PARM ; IN/OUT: размер для записи + buffer HGCM_PARM ; IN: буфер с данными +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_LIST - Получить список файлов в директории +; ----------------------------------------------------------------------------- +struct SHFL_LIST + header HGCM_CALL + root HGCM_PARM ; parm[0] IN: root handle (32bit) + handle HGCM_PARM ; parm[1] IN: dir handle (64bit!) + flags HGCM_PARM ; parm[2] IN: SHFL_LIST_* (32bit) + cb HGCM_PARM ; parm[3] IN/OUT: размер буфера (32bit) + path HGCM_PARM ; parm[4] IN: search pattern (опционально) + buffer HGCM_PARM ; parm[5] OUT: SHFLDIRINFO[] + resume_pt HGCM_PARM ; parm[6] IN/OUT: точка продолжения (32bit) + file_count HGCM_PARM ; parm[7] OUT: количество файлов (32bit) +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_INFORMATION - Получить информацию о файле +; ----------------------------------------------------------------------------- +struct SHFL_INFORMATION + header HGCM_CALL + root HGCM_PARM ; IN: root handle + handle HGCM_PARM ; IN: file handle + flags HGCM_PARM ; IN: флаги + info HGCM_PARM ; OUT: информация (SHFLFSOBJINFO) +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_REMOVE - Удалить файл +; ----------------------------------------------------------------------------- +struct SHFL_REMOVE + header HGCM_CALL + root HGCM_PARM ; IN: root handle + path HGCM_PARM ; IN: путь к файлу (SHFLSTRING) + flags HGCM_PARM ; IN: флаги +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_RENAME - Переименовать файл +; ----------------------------------------------------------------------------- +struct SHFL_RENAME + header HGCM_CALL + root HGCM_PARM ; IN: root handle + src HGCM_PARM ; IN: исходный путь (SHFLSTRING) + dst HGCM_PARM ; IN: новый путь (SHFLSTRING) + flags HGCM_PARM ; IN: флаги +ends + +; ----------------------------------------------------------------------------- +; SHFL_FN_FLUSH - Flush буферов файла +; ----------------------------------------------------------------------------- +struct SHFL_FLUSH + header HGCM_CALL + root HGCM_PARM ; IN: root handle + handle HGCM_PARM ; IN: file handle +ends diff --git a/drivers/vboxguest/data/timesync/constants.inc b/drivers/vboxguest/data/timesync/constants.inc new file mode 100644 index 000000000..b6dbc6a40 --- /dev/null +++ b/drivers/vboxguest/data/timesync/constants.inc @@ -0,0 +1,16 @@ +; ============================================================================= +; Time Sync Constants +; ============================================================================= +VMMDEV_REQ_GET_HOST_TIME equ 10 ; VMMDevReq_GetHostTime + +; Интервал синхронизации: 6000 тиков * 10ms = 60 сек +TIMESYNC_INTERVAL_TICKS equ 6000 + +; Максимальное допустимое расхождение (100ms в наносекундах / 100) +TIMESYNC_MAX_DRIFT_NS100 equ 1000000 + +; Флаг: нет событий VMMDev, чисто по таймеру +TIMESYNC_EVENT_MASK equ 0 + +; Накопительные маски (timesync не требует событий и caps) +VBOXGUEST_EVENTS_OR_MASK equ ( VBOXGUEST_EVENTS_OR_MASK or TIMESYNC_EVENT_MASK ) diff --git a/drivers/vboxguest/data/timesync/structs.inc b/drivers/vboxguest/data/timesync/structs.inc new file mode 100644 index 000000000..52b51f5d7 --- /dev/null +++ b/drivers/vboxguest/data/timesync/structs.inc @@ -0,0 +1,12 @@ +; ============================================================================= +; Time Sync Structures +; ============================================================================= + +; VMMDevReq_GetHostTime (request type 10) +; Возвращает время хоста в формате 100-наносекундных интервалов с 01.01.1601 +; (Windows FILETIME формат, он же RTTIMESPEC в VBox) +struct VMMDEV_GET_HOST_TIME + header VMMDEV_HEADER + time_low dd ? ; Младшие 32 бита (100ns units since 1601) + time_high dd ? ; Старшие 32 бита +ends diff --git a/drivers/vboxguest/hgcm/async.inc b/drivers/vboxguest/hgcm/async.inc new file mode 100644 index 000000000..c536b2a38 --- /dev/null +++ b/drivers/vboxguest/hgcm/async.inc @@ -0,0 +1,63 @@ +; ============================================================================= +; Модуль : HGCM Async +; Назначение : Ожидание завершения async HGCM запросов +; Файл : hgcm/async.inc +; Версия : 1.0 +; Дата : 2025.01.15 +; ============================================================================= + +; hgcm_wait_async +proc hgcm_wait_async uses ebx ecx edx esi, request_ptr:dword + mov esi, [request_ptr] + test esi, esi + jz .bad + + mov ecx, HGCM_TIMEOUT_ITERS + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Waiting for async completion (timeout=%d iterations)\n", HGCM_TIMEOUT_ITERS + +.wait_loop: + mfence + mov eax, [esi + HGCM_HEADER.flags] + + test eax, VBOX_HGCM_REQ_DONE + jnz .completed + + test eax, VBOX_HGCM_REQ_CANCELLED + jnz .cancelled + + dec ecx + jz .timeout + + ; короткая задержка + push ecx + mov ecx, 1000 +.delay: + pause + loop .delay + pop ecx + + jmp .wait_loop + +.completed: + mfence + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Async request completed\n" + xor eax, eax + ret + +.cancelled: + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Async request cancelled\n" + mov eax, VERR_GENERAL_FAILURE + ret + +.timeout: + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] *** ASYNC TIMEOUT ***\n" + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Current flags: 0x%x\n", [esi + HGCM_HEADER.flags] + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Request RC: 0x%x\n", [esi + HGCM_HEADER.header.rc] + mov eax, VERR_TIMEOUT + ret + +.bad: + mov eax, VERR_INVALID_POINTER + ret +endp diff --git a/drivers/vboxguest/hgcm/call.inc b/drivers/vboxguest/hgcm/call.inc new file mode 100644 index 000000000..52aeec66c --- /dev/null +++ b/drivers/vboxguest/hgcm/call.inc @@ -0,0 +1,395 @@ +; ============================================================================= +; Модуль : HGCM Call32 с поддержкой PageList параметров +; Назначение : HGCM вызовы (CALL32) поверх hgcm_send_request +; Автоматическая конвертация LINADDR -> PAGE_LIST (type=10) +; Файл : hgcm/call.inc +; ============================================================================= + +; ----------------------------------------------------------------------------- +; hgcm_call32_pagelist — вызов HGCM_CALL32 с auto-pagelist +; +; Вход : client_id, function, parms_ptr, parm_count +; Выход: eax = 0 успех / VERR_* +; +; LINADDR_IN/OUT/INOUT автоматически конвертируются в PAGE_LIST (type=10). +; 32BIT/64BIT передаются как есть. +; После вызова: данные OUT/INOUT уже в памяти гостя (хост писал напрямую +; в физ. страницы). Обновляется поле size в оригинальных параметрах. +; ----------------------------------------------------------------------------- +proc hgcm_call32_pagelist uses ebx ecx edx esi edi, client_id:dword, function:dword, parms:dword, parm_count:dword +locals + packet_base dd ? + data_tail dd ? ; текущее смещение хвоста (для PageListInfo) + param_idx dd ? + ; Временные переменные для конвертации одного параметра + conv_cbdata dd ? ; размер буфера + conv_vaddr dd ? ; виртуальный адрес буфера + conv_flags dd ? ; флаги направления + conv_off_first dd ? ; смещение в первой странице + conv_cpages dd ? ; количество страниц + conv_page_virt dd ? ; текущий virt addr страницы (для цикла) +endl + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] CALL32: client=0x%x fn=%d cnt=%d\n", \ + [client_id], [function], [parm_count] + + ; --- Проверка: буфер не занят (защита от реентрантности) --- +; lock bts dword [hgcm_pl_busy], 0 +; jc .busy + + ; --- Проверки --- + mov eax, [parm_count] + cmp eax, VMMDEV_MAX_HGCM_PARMS + jbe .count_ok + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] ERROR: parm_count %d > max %d\n", eax, VMMDEV_MAX_HGCM_PARMS +; mov dword [hgcm_pl_busy], 0 + mov eax, VERR_INVALID_PARAMETER + ret +.count_ok: + + mov edi, [vbox_device.hgcm_call_pl_virt] + test edi, edi + jz .bad + mov [packet_base], edi + + ; ================================================================= + ; 1. Заголовок HGCM_CALL + ; ================================================================= + mov eax, [parm_count] + imul eax, sizeof.HGCM_PARM + add eax, sizeof.HGCM_CALL + + hgcm_prepare_header edi, eax, VMMDEV_HGCM_CALL32 + + mov eax, [client_id] + mov [edi + HGCM_CALL.client_id], eax + mov eax, [function] + mov [edi + HGCM_CALL.function], eax + mov eax, [parm_count] + mov [edi + HGCM_CALL.param_count], eax + + ; ================================================================= + ; 2. Копируем параметры из parms[] в пакет + ; ================================================================= + mov ecx, [parm_count] + test ecx, ecx + jz .send_no_params + + push ecx + mov esi, [parms] + lea edi, [edi + sizeof.HGCM_CALL] + imul ecx, sizeof.HGCM_PARM + rep movsb + pop ecx + + ; ================================================================= + ; 3. data_tail = начало области для PageListInfo + ; ================================================================= + mov eax, [parm_count] + imul eax, sizeof.HGCM_PARM + add eax, sizeof.HGCM_CALL + mov [data_tail], eax + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] PageList tail starts at offset %d\n", eax + + ; ================================================================= + ; 4. Конвертация LINADDR -> PAGE_LIST + ; ================================================================= + mov dword [param_idx], 0 + +.convert_loop: + mov ecx, [param_idx] + cmp ecx, [parm_count] + jae .convert_done + + ; esi -> текущий параметр в пакете + mov esi, [packet_base] + add esi, sizeof.HGCM_CALL + mov eax, ecx + imul eax, sizeof.HGCM_PARM + add esi, eax + + mov eax, [esi + HGCM_PARM.type] + + cmp eax, HGCM_PARM_TYPE_LINADDR_IN + je .conv_in + cmp eax, HGCM_PARM_TYPE_LINADDR_OUT + je .conv_out + cmp eax, HGCM_PARM_TYPE_LINADDR_INOUT + je .conv_inout + cmp eax, HGCM_PARM_TYPE_LINADDR + je .conv_inout + + ; Не LINADDR — пропускаем + jmp .next_param + +.conv_in: + mov dword [conv_flags], HGCM_PL_FLAG_IN + jmp .do_convert +.conv_out: + mov dword [conv_flags], HGCM_PL_FLAG_OUT + jmp .do_convert +.conv_inout: + mov dword [conv_flags], HGCM_PL_FLAG_BOTH + +; ---- Конвертация одного параметра в PAGE_LIST ---- +; esi = указатель на HGCM_PARM в пакете (сохраняется) +.do_convert: + ; Сохраняем cbData и vaddr в локальные переменные + mov eax, dword [esi + HGCM_PARM.u.LinAddr.size] + mov [conv_cbdata], eax + mov eax, dword [esi + HGCM_PARM.u.LinAddr.offset] + mov [conv_vaddr], eax + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] parm[%d]: LINADDR->PL vaddr=0x%x cb=%d fl=%d\n", \ + [param_idx], [conv_vaddr], [conv_cbdata], [conv_flags] + + ; --- cbData == 0 -> пустой PageList --- + cmp dword [conv_cbdata], 0 + je .empty_pagelist + + ; --- offFirstPage = vaddr & 0xFFF --- + mov eax, [conv_vaddr] + and eax, PAGE_OFFSET_MASK + mov [conv_off_first], eax + + ; --- cPages = (offFirstPage + cbData + 4095) >> 12 --- + mov eax, [conv_off_first] + add eax, [conv_cbdata] + add eax, PAGE_SIZE - 1 + shr eax, 12 + mov [conv_cpages], eax + + ; --- Проверка: хватает ли места в хвосте? --- + ; Нужно: HGCM_PLI_APAGES (=8) + conv_cpages * 8 + mov eax, [conv_cpages] + shl eax, 3 ; * 8 (размер одного RTGCPHYS64) + add eax, HGCM_PLI_APAGES ; + 8 (заголовок PageListInfo) + add eax, [data_tail] + cmp eax, HGCM_PL_BUF_SIZE + ja .buffer_overflow + + ; --- Заполняем заголовок HGCMPageListInfo --- + ; edi -> PageListInfo в хвосте пакета + mov edi, [packet_base] + add edi, [data_tail] + + ; flags (direction) + mov eax, [conv_flags] + mov dword [edi + HGCM_PLI_FLAGS], eax + + ; offFirstPage (word) — смещение данных внутри первой страницы + mov eax, [conv_off_first] + mov word [edi + HGCM_PLI_OFF_FIRST_PAGE], ax + + ; cPages (word) + mov eax, [conv_cpages] + mov word [edi + HGCM_PLI_CPAGES], ax + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] PLI at +%d: fl=%d offFirst=%d cPages=%d\n", \ + [data_tail], [conv_flags], [conv_off_first], [conv_cpages] + + ; --- Заполняем массив aPages[] физическими адресами --- + ; Подготовка: стартовый виртуальный адрес страницы + mov eax, [conv_vaddr] + and eax, PAGE_BASE_MASK + mov [conv_page_virt], eax ; virt base первой страницы + + xor ecx, ecx ; ecx = индекс страницы + +.page_loop: + cmp ecx, [conv_cpages] + jae .pages_done + + ; Получаем физический адрес текущей страницы + ; GetPhysAddr: eax(вход) = virt addr, eax(выход) = phys addr + ; Сохраняем всё, что может быть затёрто invoke + push ecx esi edi + mov eax, [conv_page_virt] + invoke GetPhysAddr + mov ebx, eax ; ebx = phys addr + pop edi esi ecx + + ; Записываем aPages[ecx] как RTGCPHYS64 (8 байт, little-endian) + mov dword [edi + HGCM_PLI_APAGES + ecx*8], ebx ; low 32 bits + mov dword [edi + HGCM_PLI_APAGES + ecx*8 + 4], 0 ; high 32 bits = 0 + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] aPages[%d]: virt=0x%x phys=0x%x\n", \ + ecx, [conv_page_virt], ebx + + ; Следующая страница + add dword [conv_page_virt], PAGE_SIZE + inc ecx + jmp .page_loop + +.pages_done: + ; --- Перезаписываем параметр в пакете как PAGE_LIST --- + ; type = 10 (PAGE_LIST) + mov dword [esi + HGCM_PARM.type], HGCM_PARM_TYPE_PAGELIST + + ; u[0..3] = cbData + mov eax, [conv_cbdata] + mov dword [esi + HGCM_PARM.u], eax + + ; u[4..7] = смещение к PageListInfo от начала пакета + mov eax, [data_tail] + mov dword [esi + HGCM_PARM.u + 4], eax + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] => type=10 cb=%d offPLI=%d\n", \ + [conv_cbdata], [data_tail] + + ; Сдвигаем data_tail: += HGCM_PLI_APAGES + cPages * 8 + mov eax, [conv_cpages] + shl eax, 3 ; * 8 + add eax, HGCM_PLI_APAGES ; + 8 (заголовок) + add [data_tail], eax + + jmp .next_param + +; --- Пустой PageList (cbData == 0) --- +; ВАЖНО: VMMDev проверяет cPages > 0 даже при cbData=0! +; Ставим cPages=1 с фиктивной страницей, чтобы пройти валидацию. +; cbData=0 гарантирует что данные не передаются. +.empty_pagelist: + ; Проверка места: 16 байт (8 заголовок + 8 одна фиктивная страница) + mov eax, [data_tail] + add eax, HGCM_PLI_APAGES + 8 ; 8 + 8 = 16 + cmp eax, HGCM_PL_BUF_SIZE + ja .buffer_overflow + + ; Заполняем PageListInfo с 1 фиктивной страницей + mov edi, [packet_base] + add edi, [data_tail] + mov eax, [conv_flags] + mov dword [edi + HGCM_PLI_FLAGS], eax + mov word [edi + HGCM_PLI_OFF_FIRST_PAGE], 0 + mov word [edi + HGCM_PLI_CPAGES], 1 ; cPages=1 (не 0!) + mov dword [edi + HGCM_PLI_APAGES], 0 ; dummy phys lo + mov dword [edi + HGCM_PLI_APAGES + 4], 0 ; dummy phys hi + + ; Записываем параметр + mov dword [esi + HGCM_PARM.type], HGCM_PARM_TYPE_PAGELIST + mov dword [esi + HGCM_PARM.u], 0 ; cbData = 0 + mov eax, [data_tail] + mov dword [esi + HGCM_PARM.u + 4], eax ; offset к PLI + + ; data_tail += 16 (8 заголовок + 8 одна страница) + add dword [data_tail], HGCM_PLI_APAGES + 8 + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] parm[%d]: empty pagelist (cPages=1 dummy)\n", [param_idx] + jmp .next_param + +.buffer_overflow: + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] ERROR: PageList overflow at param %d\n", [param_idx] +; mov dword [hgcm_pl_busy], 0 + mov eax, VERR_TOO_MUCH_DATA + ret + +.next_param: + inc dword [param_idx] + jmp .convert_loop + +; ================================================================= +; 5. Обновляем размер пакета и отправляем +; ================================================================= +.convert_done: + mov edi, [packet_base] + mov eax, [data_tail] + mov [edi + VMMDEV_HEADER.size], eax + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] Final packet size: %d bytes\n", eax + + ; Отправляем + stdcall hgcm_send_request, [packet_base] + + ; Debug post-send + push eax + mov esi, [packet_base] + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] POST: rc=0x%x result=0x%x\n", \ + [esi + HGCM_HEADER.header.rc], [esi + HGCM_HEADER.result] + pop eax + + test eax, eax + jnz .send_failed + + ; ================================================================= + ; 6. Copyback — только размеры и скалярные параметры + ; Данные OUT/INOUT уже на месте в памяти гостя. + ; ================================================================= + mov dword [param_idx], 0 + +.cb_loop: + mov ecx, [param_idx] + cmp ecx, [parm_count] + jae .cb_done + + ; esi -> параметр в пакете + mov esi, [packet_base] + add esi, sizeof.HGCM_CALL + mov eax, ecx + imul eax, sizeof.HGCM_PARM + add esi, eax + + ; edi -> оригинальный параметр в parms[] + mov edi, [parms] + add edi, eax + + mov eax, [esi + HGCM_PARM.type] + + cmp eax, HGCM_PARM_TYPE_PAGELIST + je .cb_pagelist + cmp eax, HGCM_PARM_TYPE_32BIT + je .cb_32bit + cmp eax, HGCM_PARM_TYPE_64BIT + je .cb_64bit + jmp .cb_next + +.cb_32bit: + ; 32-bit значение — копируем обратно + mov eax, dword [esi + HGCM_PARM.u] + mov dword [edi + HGCM_PARM.u], eax + jmp .cb_next + +.cb_64bit: + ; 64-bit значение — копируем обратно + mov eax, dword [esi + HGCM_PARM.u] + mov dword [edi + HGCM_PARM.u], eax + mov eax, dword [esi + HGCM_PARM.u + 4] + mov dword [edi + HGCM_PARM.u + 4], eax + jmp .cb_next + +.cb_pagelist: + ; Для PAGE_LIST: хост мог обновить cbData (u[0..3]). + ; Копируем обновлённый размер в оригинальный параметр (LinAddr.size). + ; Адрес буфера (LinAddr.offset) не меняется. + mov eax, dword [esi + HGCM_PARM.u] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] cb[%d]: size=%d\n", ecx, eax + jmp .cb_next + +.cb_next: + inc dword [param_idx] + jmp .cb_loop + +.cb_done: + xor eax, eax +.send_failed: +; mov dword [hgcm_pl_busy], 0 + ret + +.bad: + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] [PageList] ERROR: hgcm_call_pl_virt is NULL!\n" +; mov dword [hgcm_pl_busy], 0 + mov eax, VERR_INVALID_POINTER + ret + +.busy: + DEBUGF 2, "[VBoxGuest] [HGCM] [PageList] BUSY — reentrancy blocked\n" + mov eax, VERR_TRY_AGAIN + ret + +.send_no_params: + ; Без параметров — размер и отправка как в .convert_done + jmp .convert_done + +endp diff --git a/drivers/vboxguest/hgcm/core.inc b/drivers/vboxguest/hgcm/core.inc new file mode 100644 index 000000000..bac974fb6 --- /dev/null +++ b/drivers/vboxguest/hgcm/core.inc @@ -0,0 +1,5 @@ +; HGCM подсистема +include 'hgcm/async.inc' +include 'hgcm/params.inc' +include 'hgcm/hgcm.inc' +include 'hgcm/call.inc' \ No newline at end of file diff --git a/drivers/vboxguest/hgcm/hgcm.inc b/drivers/vboxguest/hgcm/hgcm.inc new file mode 100644 index 000000000..c2ac5a244 --- /dev/null +++ b/drivers/vboxguest/hgcm/hgcm.inc @@ -0,0 +1,276 @@ +; ============================================================================= +; Модуль : HGCM Core +; Назначение : HGCM: init packets + connect/disconnect + общий send_request +; Файл : hgcm/hgcm.inc +; ============================================================================= +; ----------------------------------------------------------------------------- +; Макрос: hgcm_prepare_header +; Назначение: заполнить VMMDevRequestHeader внутри HGCM request +; ----------------------------------------------------------------------------- +macro hgcm_prepare_header req_ptr, req_size, req_type +{ + mov dword [req_ptr + HGCM_HEADER.header.size], req_size + mov dword [req_ptr + HGCM_HEADER.header.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [req_ptr + HGCM_HEADER.header.request_type], req_type + mov dword [req_ptr + HGCM_HEADER.header.rc], VERR_GENERAL_FAILURE ; 0 ;VERR_NOT_READY + mov dword [req_ptr + HGCM_HEADER.header.reserved1], 0 + mov dword [req_ptr + HGCM_HEADER.header.f_requestor], VMMDEV_REQUESTOR_VBOXGUEST + + mov dword [req_ptr + HGCM_HEADER.flags], 0 + mov dword [req_ptr + HGCM_HEADER.result], 0 +} + +; ----------------------------------------------------------------------------- +; hgcm_init_packets — выделить и привязать буферы HGCM (KernelAlloc) +; +; Выделяем: +; 1 страница (4096) для connect + disconnect (помещаются вместе) +; 1 страница (4096) для hgcm_call_s (embedded) +; N страниц для hgcm_call_pl_s (pagelist) +; ----------------------------------------------------------------------------- +proc hgcm_init_packets uses ebx + + ; --- Страница для connect + disconnect --- + invoke KernelAlloc, PAGE_SIZE + test eax, eax + jz .alloc_fail + + ; connect = начало страницы + mov [vbox_device.hgcm_connect_virt], eax + invoke GetPhysAddr + mov [vbox_device.hgcm_connect_phys], eax + + ; disconnect = connect_virt + sizeof.HGCM_CONNECT (выровнено на 4) + mov eax, [vbox_device.hgcm_connect_virt] + add eax, ((sizeof.HGCM_CONNECT + 3) and (not 3)) + mov [vbox_device.hgcm_disconnect_virt], eax + invoke GetPhysAddr + mov [vbox_device.hgcm_disconnect_phys], eax + + ; --- Страница для embedded call --- + invoke KernelAlloc, PAGE_SIZE + test eax, eax + jz .alloc_fail_free1 + + mov [vbox_device.hgcm_call_virt], eax + invoke GetPhysAddr + mov [vbox_device.hgcm_call_phys], eax + + ; --- Буфер для pagelist call + invoke KernelAlloc, HGCM_PL_BUF_SIZE + test eax, eax + jz .alloc_fail_free2 + + mov [vbox_device.hgcm_call_pl_virt], eax + invoke GetPhysAddr + mov [vbox_device.hgcm_call_pl_phys], eax + + DEBUGF 2, "[VBoxGuest] [HGCM] Packets allocated: connect=0x%x call=0x%x pl=0x%x\n", \ + [vbox_device.hgcm_connect_virt], [vbox_device.hgcm_call_virt], [vbox_device.hgcm_call_pl_virt] + + xor eax, eax + ret + +.alloc_fail_free2: + invoke KernelFree, [vbox_device.hgcm_call_virt] +.alloc_fail_free1: + invoke KernelFree, [vbox_device.hgcm_connect_virt] +.alloc_fail: + DEBUGF 2, "[VBoxGuest] [HGCM] ERROR: KernelAlloc failed for HGCM packets\n" + mov eax, VERR_NO_MEMORY + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_free_packets — освободить буферы HGCM +; ----------------------------------------------------------------------------- +proc hgcm_free_packets + ; Pagelist call buffer + cmp dword [vbox_device.hgcm_call_pl_virt], 0 + je @f + invoke KernelFree, [vbox_device.hgcm_call_pl_virt] + mov dword [vbox_device.hgcm_call_pl_virt], 0 +@@: + ; Embedded call buffer + cmp dword [vbox_device.hgcm_call_virt], 0 + je @f + invoke KernelFree, [vbox_device.hgcm_call_virt] + mov dword [vbox_device.hgcm_call_virt], 0 +@@: + ; Connect+disconnect page + cmp dword [vbox_device.hgcm_connect_virt], 0 + je @f + invoke KernelFree, [vbox_device.hgcm_connect_virt] + mov dword [vbox_device.hgcm_connect_virt], 0 + mov dword [vbox_device.hgcm_disconnect_virt], 0 +@@: + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_init инициализация HGCM подсистемы +; ----------------------------------------------------------------------------- +proc hgcm_init + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Initializing HGCM subsystem\n" + + ; Инициализируем пакеты + call hgcm_init_packets + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Initialization complete\n" + ret +endp + +; hgcm_send_request — отправить HGCM request через VMMDev + обработать ASYNC +proc hgcm_send_request uses ebx ecx edx esi edi, request_ptr:dword + mov esi, [request_ptr] + test esi, esi + jz .bad_ptr + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Sending request type=0x%x\n", [esi + HGCM_HEADER.header.request_type] + + ; phys адрес запроса + mov eax, esi + invoke GetPhysAddr + test eax, eax + jz .phys_failed + mov edx, eax + + ; отправка VMMDev (submit-only, хост обновляет пакет через MMIO) + stdcall vmmdev_send_request, edx + + ; транспортный rc + mov eax, [esi + HGCM_HEADER.header.rc] + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Transport RC: 0x%x\n", eax + + cmp eax, VINF_HGCM_ASYNC_EXECUTE + je .async + + ; если rc < 0 — ошибка транспорта + test eax, eax + js .transport_error + + ; sync ok -> проверяем service result + jmp .check_service + +.async: + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Request is async, waiting...\n" + stdcall hgcm_wait_async, esi + test eax, eax + jnz .async_failed + + ; после async: транспортный rc + mov eax, [esi + HGCM_HEADER.header.rc] + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Post-async transport RC: 0x%x\n", eax + test eax, eax + js .transport_error_after_async + +.check_service: + ; результат сервиса + mov eax, [esi + HGCM_HEADER.result] + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Service result: 0x%x\n", eax + test eax, eax + js .service_error + + xor eax, eax + ret + +.bad_ptr: + mov eax, VERR_INVALID_POINTER + ret + +.phys_failed: + mov eax, VERR_INVALID_POINTER + ret + +.transport_error: + ret + +.transport_error_after_async: + ret + +.service_error: + ret + +.async_failed: + ret +endp + +; hgcm_connect — подключение к HGCM сервису +proc hgcm_connect uses ebx ecx edx esi edi, service_name:dword + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Connecting to service: %s\n", [service_name] + + mov edi, [vbox_device.hgcm_connect_virt] + test edi, edi + jz .failed + + ; header + hgcm_prepare_header edi, sizeof.HGCM_CONNECT, VMMDEV_HGCM_CONNECT + + ; location + mov dword [edi + HGCM_CONNECT.location_type], HGCM_LOC_TYPE_PREDEFINED + + ; очистить service_name буфер + push edi + lea edi, [edi + HGCM_CONNECT.service_name] + xor eax, eax + mov ecx, HGCM_SERVICE_NAME_MAX / 4 + rep stosd + pop edi + + ; копировать ASCII имя сервиса + mov esi, [service_name] + push edi + lea edi, [edi + HGCM_CONNECT.service_name] + mov ecx, HGCM_SERVICE_NAME_MAX - 1 +.copy_name: + lodsb + test al, al + jz .name_done + stosb + loop .copy_name +.name_done: + xor al, al + stosb + pop edi + + ; client_id = 0 + mov dword [edi + HGCM_CONNECT.client_id], 0 + + ; отправить + stdcall hgcm_send_request, [vbox_device.hgcm_connect_virt] + test eax, eax + jnz .failed + + mov eax, [edi + HGCM_CONNECT.client_id] + test eax, eax + jz .failed + + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Connected, client_id=0x%x\n", eax + ret + +.failed: + DEBUGF 2, "[VBoxGuest] [HGCM] Connect failed\n" + xor eax, eax + ret +endp + +; hgcm_disconnect — отключение клиента HGCM +proc hgcm_disconnect uses edi, client_id:dword + DEBUGF __DEBUG_HGCM__, "[VBoxGuest] [HGCM] Disconnecting client: 0x%x\n", [client_id] + + mov edi, [vbox_device.hgcm_disconnect_virt] + test edi, edi + jz .bad + + hgcm_prepare_header edi, sizeof.HGCM_DISCONNECT, VMMDEV_HGCM_DISCONNECT + + mov eax, [client_id] + mov [edi + HGCM_DISCONNECT.client_id], eax + + stdcall hgcm_send_request, [vbox_device.hgcm_disconnect_virt] + ret + +.bad: + mov eax, VERR_INVALID_POINTER + ret +endp diff --git a/drivers/vboxguest/hgcm/params.inc b/drivers/vboxguest/hgcm/params.inc new file mode 100644 index 000000000..bf881bd5d --- /dev/null +++ b/drivers/vboxguest/hgcm/params.inc @@ -0,0 +1,78 @@ +; ============================================================================= +; Модуль : HGCM Params +; Назначение : Формирование параметров HGCM_PARM +; Файл : hgcm/params.inc +; Версия : 1.1 +; Дата : 2025.01.15 +; ============================================================================= + +; hgcm_param_32 — заполнить 32-битный параметр +; +; Вход : p — указатель на HGCM_PARM, value — значение +proc hgcm_param_32 uses edi, p:dword, value:dword + mov edi, [p] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov eax, [value] + mov dword [edi + HGCM_PARM.u.value32], eax + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_param_64 — заполнить 64-битный параметр +; +; Вход : p — указатель на HGCM_PARM, lo/hi — младшая/старшая часть +; ----------------------------------------------------------------------------- +proc hgcm_param_64 uses edi, p:dword, lo:dword, hi:dword + mov edi, [p] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_64BIT + mov eax, [lo] + mov dword [edi + HGCM_PARM.u.value64_lo], eax + mov eax, [hi] + mov dword [edi + HGCM_PARM.u.value64_hi], eax + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_param_linaddr_in — линейный буфер IN (гость → хост) +; +; Вход : p — указатель на HGCM_PARM, addr — адрес буфера, size — размер +; ----------------------------------------------------------------------------- +proc hgcm_param_linaddr_in uses edi, p:dword, addr:dword, size:dword + mov edi, [p] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_LINADDR_IN + mov eax, [size] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + mov eax, [addr] + mov dword [edi + HGCM_PARM.u.LinAddr.offset], eax + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_param_linaddr_out — линейный буфер OUT (хост → гость) +; +; Вход : p — указатель на HGCM_PARM, addr — адрес буфера, size — размер +; ----------------------------------------------------------------------------- +proc hgcm_param_linaddr_out uses edi, p:dword, addr:dword, size:dword + mov edi, [p] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_LINADDR_OUT + mov eax, [size] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + mov eax, [addr] + mov dword [edi + HGCM_PARM.u.LinAddr.offset], eax + ret +endp + +; ----------------------------------------------------------------------------- +; hgcm_param_linaddr_inout — линейный буфер INOUT (двунаправленный) +; +; Вход : p — указатель на HGCM_PARM, addr — адрес буфера, size — размер +; ----------------------------------------------------------------------------- +proc hgcm_param_linaddr_inout uses edi, p:dword, addr:dword, size:dword + mov edi, [p] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_LINADDR_INOUT + mov eax, [size] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + mov eax, [addr] + mov dword [edi + HGCM_PARM.u.LinAddr.offset], eax + ret +endp diff --git a/drivers/vboxguest/services/clipboard/clipboard.inc b/drivers/vboxguest/services/clipboard/clipboard.inc new file mode 100644 index 000000000..b08d99cda --- /dev/null +++ b/drivers/vboxguest/services/clipboard/clipboard.inc @@ -0,0 +1,220 @@ +; ============================================================================= +; Модуль : VBox Shared Clipboard Service +; Файл : services/clipboard/clipboard.inc +; +; Архитектура : Async state machine + IOCTL ABI +; clipboard_init — HGCM connect + аллокация listener пакета +; clipboard_enable — отправка первого MSG_OLD_GET_WAIT (async) +; clipboard_on_tick — проверка завершения async запроса + resubmit +; +; Данные хоста и гостя передаются через IOCTL (приложение): +; CLIP_STATUS (10) — статус (formats, host_new) +; CLIP_READ (11) — чтение данных хоста (HGCM DATA_READ) +; CLIP_WRITE (12) — запись данных гостя (буферизация + HGCM REPORT_FORMATS) +; +; Источник протокола: VBox 7.2.6 +; include/VBox/HostServices/VBoxClipboardSvc.h +; ============================================================================= + +svc_clipboard_name db "CLIPBOARD", 0 + +align 4 +sz_clipboard_hgcm_service db 'VBoxSharedClipboard', 0 + +align 4 +clip_state CLIPBOARD_STATE + +; clipboard_init — Подключиться к HGCM + выделить listener пакет +proc clipboard_init uses ebx + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Initializing...\n" + + mov eax, [vbox_device.hgcm_connect_virt] + test eax, eax + jz .hgcm_not_ready + + cmp dword [clip_state.connected], 1 + je .already_connected + + ; --- Подключиться к HGCM --- + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Connecting to '%s'...\n", sz_clipboard_hgcm_service + stdcall hgcm_connect, sz_clipboard_hgcm_service + test eax, eax + jz .connect_failed + + mov [clip_state.client_id], eax + mov dword [clip_state.connected], 1 + mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE + mov dword [clip_state.error_count], 0 + mov dword [clip_state.formats_host], 0 + mov dword [clip_state.formats_guest], 0 + mov dword [clip_state.has_data], 0 + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Connected, client_id=%d\n", eax + + ; --- Выделить listener пакет (или переиспользовать существующий) --- + ; При fire-and-forget disconnect пакет не освобождается (хост может + ; ещё DMA-писать DONE flag), поэтому переиспользуем при re-enable. + cmp dword [clip_state.listen_pkt_virt], 0 + jne .pkt_ready + + invoke KernelAlloc, 4096 + test eax, eax + jz .alloc_failed + + mov [clip_state.listen_pkt_virt], eax + invoke GetPhysAddr + mov [clip_state.listen_pkt_phys], eax +.pkt_ready: + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener pkt: virt=0x%x, phys=0x%x\n", \ + [clip_state.listen_pkt_virt], [clip_state.listen_pkt_phys] + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Initialized OK\n" + xor eax, eax + ret + +.alloc_failed: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] KernelAlloc failed for listener pkt\n" + stdcall hgcm_disconnect, [clip_state.client_id] + mov dword [clip_state.connected], 0 + mov eax, VERR_NO_MEMORY + ret + +.connect_failed: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] HGCM connect failed\n" + mov eax, VERR_HGCM_SERVICE_NOT_FOUND + ret +.hgcm_not_ready: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] HGCM not ready\n" + mov eax, VERR_NOT_READY + ret +.already_connected: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Already connected\n" + xor eax, eax + ret +endp + +; clipboard_enable — Отправить первый MSG_OLD_GET_WAIT +proc clipboard_enable + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enabling (async listener)...\n" + + cmp dword [clip_state.connected], 0 + jne .connected + + ; После disable HGCM отключен — переподключиться + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Not connected, re-initializing...\n" + call clipboard_init + test eax, eax + jnz .init_failed + +.connected: + ; Отправить первый listener запрос + call clipboard_listener_submit + test eax, eax + jnz .submit_failed + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enabled OK\n" + xor eax, eax + ret + +.init_failed: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enable: re-init failed (0x%x)\n", eax + ret + +.submit_failed: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enable: first submit failed (0x%x)\n", eax + xor eax, eax + ret +endp + +; clipboard_on_event — Вызывается из IRQ (dispatcher_dispatch) +; ПРИМЕЧАНИЕ: IRQ обработка полностью делегирована on_tick, +; поэтому здесь просто возвращаем (событие уже обработано в tick) +proc clipboard_on_event + xor eax, eax + ret +endp + +; clipboard_on_tick — Вызывается из таймера каждые 100ms +proc clipboard_on_tick + cmp dword [clip_state.connected], 0 + je .done + + cmp dword [clip_state.error_count], CLIP_MAX_ERRORS + jae .done + + mov eax, [clip_state.listen_state] + + cmp eax, CLIP_LISTEN_SUBMITTED + je .check + + ; IDLE — отправить новый запрос + call clipboard_listener_submit + jmp .done + +.check: + call clipboard_listener_check + +.done: + ret +endp + +; clipboard_disable — Остановить listener, disconnect HGCM +proc clipboard_disable uses edi + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Disabling...\n" + + cmp dword [clip_state.connected], 0 + je .done + + ; Если listener pending — disconnect fire-and-forget (без busy-wait). + ; При shutdown VBox не может быстро обработать disconnect из-за pending + ; MSG_OLD_GET_WAIT, и hgcm_send_request зависает в hgcm_wait_async + ; на ~30-60 сек (HGCM_TIMEOUT_MS = 500000 итераций busy-loop). + cmp dword [clip_state.listen_state], CLIP_LISTEN_SUBMITTED + je .fast_disconnect + + ; Нет pending запроса — обычный disconnect (sync, быстрый) + stdcall hgcm_disconnect, [clip_state.client_id] + jmp .cleanup + +.fast_disconnect: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener pending — fire-and-forget disconnect\n" + + mov edi, [vbox_device.hgcm_disconnect_virt] + test edi, edi + jz .cleanup + + hgcm_prepare_header edi, sizeof.HGCM_DISCONNECT, VMMDEV_HGCM_DISCONNECT + mov eax, [clip_state.client_id] + mov [edi + HGCM_DISCONNECT.client_id], eax + + ; Отправить через VMMDev напрямую (non-blocking out dx, eax) + ; НЕ вызываем hgcm_send_request чтобы избежать hgcm_wait_async + stdcall vmmdev_send_request, [vbox_device.hgcm_disconnect_phys] + +.cleanup: + ; НЕ освобождаем listener пакет — хост может ещё DMA-писать DONE flag + ; в эту страницу. Пакет будет переиспользован при следующем enable. + + ; Освободить буфер guest→host данных + cmp dword [clip_guest_buf_ptr], 0 + je .no_guest_buf + invoke KernelFree, [clip_guest_buf_ptr] + mov dword [clip_guest_buf_ptr], 0 + mov dword [clip_guest_buf_size], 0 +.no_guest_buf: + + mov dword [clip_state.connected], 0 + mov dword [clip_state.client_id], 0 + mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE + mov dword [clip_host_new], 0 + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Disabled\n" +.done: + xor eax, eax + ret +endp + +include 'clipboard_listener.inc' + +REGISTER_SERVICE svc_clipboard_name, CLIPBOARD_EVENT_MASK, CLIPBOARD_CAPS_MASK, \ + clipboard_init, clipboard_enable, clipboard_disable, clipboard_on_event, clipboard_on_tick, AUTOSTART_CLIPBOARD diff --git a/drivers/vboxguest/services/clipboard/clipboard_listener.inc b/drivers/vboxguest/services/clipboard/clipboard_listener.inc new file mode 100644 index 000000000..d112f8570 --- /dev/null +++ b/drivers/vboxguest/services/clipboard/clipboard_listener.inc @@ -0,0 +1,614 @@ +; ============================================================================= +; Clipboard Listener + IOCTL handlers +; +; Файл: services/clipboard/clipboard_listener.inc +; +; Listener: async MSG_OLD_GET_WAIT для получения сообщений от хоста +; IOCTL: clip_ioctl_status, clip_ioctl_read, clip_ioctl_write +; ============================================================================= + +; Размер пакета: HGCM_CALL(44) + 2 * HGCM_PARM(12) = 68 +CLIP_LISTEN_PKT_SIZE = sizeof.HGCM_CALL + (VBOX_SHCL_CPARMS_MSG_OLD_GET_WAIT * sizeof.HGCM_PARM) + +; Смещения параметров от начала пакета +CLIP_LISTEN_PARM0 = sizeof.HGCM_CALL ; offset 44: msg_type +CLIP_LISTEN_PARM1 = sizeof.HGCM_CALL + sizeof.HGCM_PARM ; offset 56: formats + +; Начальный размер буфера для HGCM DATA_READ (retry с большим если не хватит) +CLIP_INITIAL_BUF_SIZE = 65536 ; 64KB + +; Максимум символов для debug вывода +CLIP_DEBUG_MAX_CHARS = 200 + +; Статический буфер для cp866-вывода (UTF-16LE → cp866) +align 4 +clip_ascii_buf rb CLIP_DEBUG_MAX_CHARS + 4 + +; Host clipboard state (set by async listener, read by IOCTL) +align 4 +clip_host_new dd 0 ; 1 = host clipboard changed since last CLIP_READ + +; Guest → host buffered data (set by CLIP_WRITE, sent on host READ_DATA) +align 4 +clip_guest_buf_ptr dd 0 ; KernelAlloc'd buffer, or 0 +clip_guest_buf_size dd 0 ; data size in bytes +clip_guest_buf_fmt dd 0 ; VBOX_SHCL_FMT_* + +; clipboard_listener_submit — Отправить MSG_OLD_GET_WAIT (async) +proc clipboard_listener_submit uses ebx edx edi + mov edi, [clip_state.listen_pkt_virt] + test edi, edi + jz .bad_ptr + + ; --- Заполнить HGCM_CALL заголовок --- + hgcm_prepare_header edi, CLIP_LISTEN_PKT_SIZE, VMMDEV_HGCM_CALL32 + + mov eax, [clip_state.client_id] + mov [edi + HGCM_CALL.client_id], eax + mov dword [edi + HGCM_CALL.function], VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT + mov dword [edi + HGCM_CALL.param_count], VBOX_SHCL_CPARMS_MSG_OLD_GET_WAIT + + ; --- parm[0]: msg_type (32BIT, OUT) --- + mov dword [edi + CLIP_LISTEN_PARM0 + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov dword [edi + CLIP_LISTEN_PARM0 + HGCM_PARM.u.value32], 0 + mov dword [edi + CLIP_LISTEN_PARM0 + HGCM_PARM.u.value32 + 4], 0 + + ; --- parm[1]: formats (32BIT, OUT) --- + mov dword [edi + CLIP_LISTEN_PARM1 + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov dword [edi + CLIP_LISTEN_PARM1 + HGCM_PARM.u.value32], 0 + mov dword [edi + CLIP_LISTEN_PARM1 + HGCM_PARM.u.value32 + 4], 0 + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Submitting MSG_OLD_GET_WAIT (async)...\n" + + stdcall vmmdev_send_request, [clip_state.listen_pkt_phys] + + mov eax, [edi + HGCM_HEADER.header.rc] + + cmp eax, VINF_HGCM_ASYNC_EXECUTE + je .async_ok + + test eax, eax + js .error + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] MSG_OLD_GET_WAIT completed sync (rc=0x%x)\n", eax + mov dword [clip_state.listen_state], CLIP_LISTEN_SUBMITTED + xor eax, eax + ret + +.async_ok: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] MSG_OLD_GET_WAIT submitted (async)\n" + mov dword [clip_state.listen_state], CLIP_LISTEN_SUBMITTED + xor eax, eax + ret + +.error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] MSG_OLD_GET_WAIT submit failed: rc=0x%x\n", eax + mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE + ret + +.bad_ptr: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener packet not allocated!\n" + mov eax, VERR_INVALID_POINTER + ret +endp + +; clipboard_listener_check — Проверить завершение async запроса +proc clipboard_listener_check uses ebx edx edi + mov edi, [clip_state.listen_pkt_virt] + + mfence + + mov eax, [edi + HGCM_HEADER.flags] + test eax, VBOX_HGCM_REQ_DONE + jz .not_done + + mfence + + mov eax, [edi + HGCM_HEADER.header.rc] + test eax, eax + js .transport_error + + mov eax, [edi + HGCM_HEADER.result] + test eax, eax + js .service_error + + mov eax, dword [edi + CLIP_LISTEN_PARM0 + HGCM_PARM.u.value32] + mov edx, dword [edi + CLIP_LISTEN_PARM1 + HGCM_PARM.u.value32] + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Got message: type=%d, formats=0x%x\n", eax, edx + + push eax edx + stdcall clipboard_listener_dispatch, eax, edx + pop edx eax + + mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE + mov dword [clip_state.error_count], 0 + mov eax, 1 + ret + +.transport_error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener transport error: rc=0x%x\n", eax + jmp .handle_error + +.service_error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener service error: result=0x%x\n", eax + +.handle_error: + mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE + inc dword [clip_state.error_count] + mov eax, 1 + ret + +.not_done: + xor eax, eax + ret +endp + +; clipboard_listener_dispatch — Обработать сообщение хоста +proc clipboard_listener_dispatch stdcall, msg_type:dword, formats:dword + + mov eax, [msg_type] + + cmp eax, VBOX_SHCL_HOST_MSG_FORMATS_REPORT + je .formats_report + + cmp eax, VBOX_SHCL_HOST_MSG_READ_DATA + je .read_data + + cmp eax, VBOX_SHCL_HOST_MSG_QUIT + je .quit + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Unknown msg_type=%d\n", eax + ret + +.formats_report: + ; Хост сообщает что у него новые данные в клипборде + mov eax, [formats] + mov [clip_state.formats_host], eax + mov dword [clip_host_new], 1 + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Host formats: 0x%x (flagged for app)\n", eax + ret + +.read_data: + ; Хост хочет прочитать данные гостя — отправляем буферизованные + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Host requests READ_DATA (formats=0x%x, buf=%d bytes)\n", \ + [formats], [clip_guest_buf_size] + + cmp dword [clip_guest_buf_ptr], 0 + je .no_guest_data + + stdcall clipboard_respond_read_data + ret + +.no_guest_data: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] READ_DATA: no guest data buffered!\n" + ret + +.quit: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Host sent QUIT\n" + mov dword [clip_state.connected], 0 + ret +endp + +; clipboard_respond_read_data — Отправить буферизованные данные хосту +proc clipboard_respond_read_data stdcall uses ebx ecx edx esi edi +locals + parms rb sizeof.HGCM_PARM * 2 +endl + + ; parm[0]: format (32BIT, IN) + lea edi, [parms] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov eax, [clip_guest_buf_fmt] + mov dword [edi + HGCM_PARM.u.value32], eax + mov dword [edi + HGCM_PARM.u.value32 + 4], 0 + + ; parm[1]: data (LINADDR_IN) + add edi, sizeof.HGCM_PARM + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_LINADDR_IN + mov eax, [clip_guest_buf_size] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + mov eax, [clip_guest_buf_ptr] + mov dword [edi + HGCM_PARM.u.LinAddr.offset], eax + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Sending %d bytes to host (fmt=0x%x)\n", \ + [clip_guest_buf_size], [clip_guest_buf_fmt] + + lea eax, [parms] + stdcall hgcm_call32_pagelist, [clip_state.client_id], \ + VBOX_SHCL_GUEST_FN_DATA_WRITE, eax, VBOX_SHCL_CPARMS_DATA_WRITE_OLD + + test eax, eax + jnz .error + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Guest data sent to host OK\n" + ret + +.error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] DATA_WRITE FAILED: 0x%x\n", eax + ret +endp + +; clipboard_report_formats — Сообщить хосту о форматах гостевого clipboard +proc clipboard_report_formats stdcall uses ebx ecx edx esi edi, formats:dword +locals + parms rb sizeof.HGCM_PARM +endl + lea edi, [parms] + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov eax, [formats] + mov dword [edi + HGCM_PARM.u.value32], eax + mov dword [edi + HGCM_PARM.u.value32 + 4], 0 + + lea eax, [parms] + stdcall hgcm_call32_pagelist, [clip_state.client_id], \ + VBOX_SHCL_GUEST_FN_REPORT_FORMATS, eax, VBOX_SHCL_CPARMS_REPORT_FORMATS + + test eax, eax + jnz .error + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Reported formats 0x%x to host\n", [formats] + ret +.error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Report formats FAILED: 0x%x\n", eax + ret +endp + +; clip_ioctl_status — IOCTL 10: Статус clipboard +proc clip_ioctl_status stdcall uses ebx, ioctl_ptr:dword + mov ebx, [ioctl_ptr] + cmp [ebx + IOCTL.out_size], 12 + jb .fail + + mov eax, [ebx + IOCTL.output] + + mov ecx, [clip_state.connected] + mov [eax], ecx + + mov ecx, [clip_state.formats_host] + mov [eax + 4], ecx + + mov ecx, [clip_host_new] + mov [eax + 8], ecx + + xor eax, eax + ret + +.fail: + or eax, -1 + ret +endp + +; clip_ioctl_read — IOCTL 11: Прочитать clipboard хоста +proc clip_ioctl_read stdcall uses ebx ecx edx esi edi, ioctl_ptr:dword +locals + parms rb sizeof.HGCM_PARM * 3 + kern_buf dd ? + kern_size dd ? + req_format dd ? +endl + mov ebx, [ioctl_ptr] + + ; Validate input/output + cmp [ebx + IOCTL.inp_size], 4 + jb .fail + cmp [ebx + IOCTL.out_size], 4 + jb .fail + cmp dword [clip_state.connected], 0 + je .fail + + ; Сбросить host_new СРАЗУ — чтобы при ошибке HGCM приложение + ; не повторяло CLIP_READ бесконечно (retry storm). + ; Новое изменение хоста снова выставит host_new=1 через listener. + mov dword [clip_host_new], 0 + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_READ: out_size=%d\n", [ebx + IOCTL.out_size] + + ; Get requested format + mov eax, [ebx + IOCTL.input] + mov eax, [eax] + mov [req_format], eax + + ; Calculate kernel buffer size: max(out_size - 4, CLIP_INITIAL_BUF_SIZE) + mov ecx, [ebx + IOCTL.out_size] + sub ecx, 4 + cmp ecx, CLIP_INITIAL_BUF_SIZE + jae .size_ok + mov ecx, CLIP_INITIAL_BUF_SIZE +.size_ok: + ; Round up to page + add ecx, (PAGE_SIZE - 1) + and ecx, PAGE_BASE_MASK + mov [kern_size], ecx + + ; Allocate kernel buffer + invoke KernelAlloc, ecx + test eax, eax + jz .no_mem + mov [kern_buf], eax + +.do_read: + ; --- Заполнить HGCM DATA_READ параметры --- + lea edi, [parms] + + ; parm[0]: format (32BIT, IN) + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov eax, [req_format] + mov dword [edi + HGCM_PARM.u.value32], eax + mov dword [edi + HGCM_PARM.u.value32 + 4], 0 + + ; parm[1]: buffer (LINADDR_OUT) + add edi, sizeof.HGCM_PARM + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_LINADDR_OUT + mov eax, [kern_size] + mov dword [edi + HGCM_PARM.u.LinAddr.size], eax + mov eax, [kern_buf] + mov dword [edi + HGCM_PARM.u.LinAddr.offset], eax + + ; parm[2]: size (32BIT, OUT) + add edi, sizeof.HGCM_PARM + mov dword [edi + HGCM_PARM.type], HGCM_PARM_TYPE_32BIT + mov dword [edi + HGCM_PARM.u.value32], 0 + mov dword [edi + HGCM_PARM.u.value32 + 4], 0 + + ; --- Вызвать HGCM DATA_READ --- + lea eax, [parms] + stdcall hgcm_call32_pagelist, [clip_state.client_id], \ + VBOX_SHCL_GUEST_FN_DATA_READ, eax, VBOX_SHCL_CPARMS_DATA_READ + + ; Прочитать actual_size из parm[2] + lea edi, [parms] + mov ecx, dword [edi + 2 * sizeof.HGCM_PARM + HGCM_PARM.u.value32] + + ; Проверить результат HGCM + test eax, eax + jnz .check_overflow + + ; Успех — данные прочитаны + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_READ: OK, %d bytes (fmt=0x%x)\n", ecx, [req_format] + + ; Записать actual_size в output[0] + mov ebx, [ioctl_ptr] + mov edi, [ebx + IOCTL.output] + mov [edi], ecx + + ; Хватает ли места в output для данных? + mov eax, [ebx + IOCTL.out_size] + sub eax, 4 + cmp eax, ecx + jb .app_overflow + + ; Скопировать данные из kern_buf в output + 4 + push ecx + mov esi, [kern_buf] + add edi, 4 + cld + rep movsb + pop ecx + + ; Debug: вывести текст если это UNICODETEXT + cmp dword [req_format], VBOX_SHCL_FMT_UNICODETEXT + jne .read_ok + cmp ecx, 4 + jb .read_ok + stdcall clipboard_utf16_to_debug, [kern_buf], ecx + +.read_ok: + invoke KernelFree, [kern_buf] + xor eax, eax ; success + ret + +.app_overflow: + ; Буфер приложения мал — actual_size записан, данных нет + invoke KernelFree, [kern_buf] + mov eax, 1 ; buffer overflow + ret + +.check_overflow: + ; HGCM вернул ошибку — если actual_size > kern_size, это overflow + ; Попробовать с большим буфером + cmp ecx, [kern_size] + jbe .hgcm_error ; не overflow, реальная ошибка + + ; actual_size > kern_size — нужен буфер побольше + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_READ: overflow, need %d bytes\n", ecx + + invoke KernelFree, [kern_buf] + + ; Выделить буфер нужного размера (round up to page) + add ecx, (PAGE_SIZE - 1) + and ecx, PAGE_BASE_MASK + mov [kern_size], ecx + invoke KernelAlloc, ecx + test eax, eax + jz .no_mem + mov [kern_buf], eax + + jmp .do_read ; retry + +.hgcm_error: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_READ: HGCM error 0x%x\n", eax + ; Записать actual_size если возможно + mov ebx, [ioctl_ptr] + cmp [ebx + IOCTL.out_size], 4 + jb .hgcm_err_ret + mov edi, [ebx + IOCTL.output] + mov [edi], ecx +.hgcm_err_ret: + invoke KernelFree, [kern_buf] + or eax, -1 + ret + +.no_mem: + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_READ: KernelAlloc failed\n" + or eax, -1 + ret + +.fail: + or eax, -1 + ret +endp + +; clip_ioctl_write — IOCTL 12: Записать данные в clipboard хоста +proc clip_ioctl_write stdcall uses ebx ecx edx esi edi, ioctl_ptr:dword + mov ebx, [ioctl_ptr] + + cmp [ebx + IOCTL.inp_size], 8 ; минимум: dd format + хотя бы 4 байта данных + jb .fail + cmp dword [clip_state.connected], 0 + je .fail + + ; Прочитать format и data size + mov esi, [ebx + IOCTL.input] + mov ecx, [esi] ; format + mov edx, [ebx + IOCTL.inp_size] + sub edx, 4 ; data_size = inp_size - 4 + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_WRITE: %d bytes, fmt=0x%x\n", edx, ecx + + ; Освободить предыдущий буфер + cmp dword [clip_guest_buf_ptr], 0 + je .no_old + push ecx edx esi + invoke KernelFree, [clip_guest_buf_ptr] + mov dword [clip_guest_buf_ptr], 0 + pop esi edx ecx +.no_old: + + ; Выделить новый буфер (round up to page, min 4096) + push ecx edx esi + mov eax, edx + add eax, (PAGE_SIZE - 1) + and eax, PAGE_BASE_MASK + cmp eax, PAGE_SIZE + jae .alloc_go + mov eax, PAGE_SIZE +.alloc_go: + invoke KernelAlloc, eax + test eax, eax + jz .no_mem + mov [clip_guest_buf_ptr], eax + pop esi edx ecx + + mov [clip_guest_buf_size], edx + mov [clip_guest_buf_fmt], ecx + + ; Скопировать данные из input + 4 в ядерный буфер + add esi, 4 ; skip format field + mov edi, [clip_guest_buf_ptr] + mov ecx, edx + cld + rep movsb + + ; Сообщить хосту что у гостя есть данные + stdcall clipboard_report_formats, [clip_guest_buf_fmt] + + xor eax, eax + ret + +.no_mem: + pop esi edx ecx + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] CLIP_WRITE: KernelAlloc failed\n" + or eax, -1 + ret + +.fail: + or eax, -1 + ret +endp + +; clipboard_utf16_to_debug — Конвертировать UTF-16LE -> cp866 и вывести +proc clipboard_utf16_to_debug stdcall uses ebx ecx edx esi edi, buf_ptr:dword, buf_size:dword + + mov esi, [buf_ptr] + mov edi, clip_ascii_buf + mov ecx, [buf_size] + shr ecx, 1 ; количество UTF-16 символов (size / 2) + + ; Ограничить до CLIP_DEBUG_MAX_CHARS - 1 + cmp ecx, CLIP_DEBUG_MAX_CHARS - 1 + jbe .len_ok + mov ecx, CLIP_DEBUG_MAX_CHARS - 1 +.len_ok: + + test ecx, ecx + jz .done_empty + +.loop: + lodsw ; ax = UTF-16LE char (si += 2) + + ; Null terminator? + test ax, ax + jz .done + + ; --- ASCII range (< 0x80) --- + cmp ax, 0x80 + jb .ascii + + ; --- Cyrillic: U+0410..U+044F → cp866 --- + cmp ax, 0x0410 + jb .check_yo + cmp ax, 0x0440 + jb .cyr_80 ; U+0410..U+043F → cp866 0x80..0xAF + cmp ax, 0x0450 + jb .cyr_e0 ; U+0440..U+044F → cp866 0xE0..0xEF + jmp .check_yo + +.cyr_80: + sub ax, 0x0410 + add al, 0x80 + jmp .store + +.cyr_e0: + sub ax, 0x0440 + add al, 0xE0 + jmp .store + +.check_yo: + cmp ax, 0x0401 ; Ё → cp866 0xF0 + je .yo_upper + cmp ax, 0x0451 ; ё → cp866 0xF1 + je .yo_lower + + ; Unknown non-ASCII → '?' + mov al, '?' + jmp .store + +.yo_upper: + mov al, 0xF0 + jmp .store + +.yo_lower: + mov al, 0xF1 + jmp .store + +.ascii: + ; Контрольные символы (< 0x20) кроме \n, \r, \t → пропускаем + cmp al, 0x20 + jae .store + cmp al, 0x0A + je .store + cmp al, 0x0D + je .store + cmp al, 0x09 + je .store + jmp .skip + +.store: + stosb +.skip: + dec ecx + jnz .loop + +.done: + xor al, al + stosb + + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Text: '%s'\n", clip_ascii_buf + ret + +.done_empty: + mov byte [clip_ascii_buf], 0 + DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Text: (empty)\n" + ret +endp diff --git a/drivers/vboxguest/services/display/display.inc b/drivers/vboxguest/services/display/display.inc new file mode 100644 index 000000000..294c8b1b1 --- /dev/null +++ b/drivers/vboxguest/services/display/display.inc @@ -0,0 +1,206 @@ +; ============================================================================= +; Модуль : VMMDev Display Service (Auto-resize) +; Назначение : Автоматическое изменение разрешения экрана при resize окна VBox +; Файл : services/display/display.inc +; +; Логика задержки (паттерн heartbeat): +; Event → запомнить время, поставить флаг pending +; Tick → если pending и прошло 2 секунды → display_change +; Каждое новое событие сбрасывает таймер (debounce) +; ============================================================================= + +svc_display_name db "DISPLAY", 0 + +; --- Display Data --- +align 4 +vbox_display_enabled dd 0 +kos_display_ptr dd 0 + +; Отложенное обновление (debounce) +vbox_display_pending dd 0 ; 1 = есть отложенный запрос +vbox_display_event_time dd 0 ; timer_ticks момента последнего события +DISPLAY_DELAY_TICKS equ 50 ; 500ms (50 * 10ms) + +; Статический буфер для запроса GetDisplayChangeRequest2 +align 4 +vmmdev_display_change_s VMMDEV_GET_DISPLAY_CHANGE_REQUEST2 \ + , \ + 0, 0, 0, 1, 0 ; x_res, y_res, bpp, event_ack=1, display=0 + +; --- BGA Macros --- + +macro bga_write index_val, data_val { + mov eax, index_val + mov dx, VBE_DISPI_IOPORT_INDEX + out dx, ax + mov eax, data_val + mov dx, VBE_DISPI_IOPORT_DATA + out dx, ax +} + +macro bga_set_mode w, h, bpp { + bga_write VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED + bga_write VBE_DISPI_INDEX_XRES, w + bga_write VBE_DISPI_INDEX_YRES, h + bga_write VBE_DISPI_INDEX_BPP, bpp + bga_write VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED or VBE_DISPI_LFB_ENABLED +} + +; vmmdev_display_init — Инициализация подсистемы дисплея +proc vmmdev_display_init + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Initializing...\n" + + ; Получить указатель на DISPLAY структуру ядра KolibriOS + invoke GetDisplay + mov [kos_display_ptr], eax + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] KOS display struct: 0x%x\n", eax + + ; Получить физический адрес буфера запроса + mov eax, vmmdev_display_change_s + mov [vbox_device.display_virt], eax + invoke GetPhysAddr + mov [vbox_device.display_phys], eax + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Buffer: virt=0x%x, phys=0x%x\n", \ + vmmdev_display_change_s, eax + + mov dword [vbox_display_enabled], 1 + + ; Сразу обработать текущее разрешение (без задержки) + call display_change + + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Initialized OK\n" + xor eax, eax + ret +endp + +; display_handle_event — Обработка события (из IRQ) +proc display_handle_event + test eax, VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST + jz .done + cmp dword [vbox_display_enabled], 0 + je .done + + ; Запомнить время события (сбросить таймер при повторных) + invoke GetTimerTicks + mov [vbox_display_event_time], eax + + ; Поставить флаг + mov dword [vbox_display_pending], 1 + + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Event queued, will apply in 2s\n" + +.done: + ret +endp + +; display_tick — Вызывается периодически из timer tick +proc display_tick + cmp dword [vbox_display_pending], 0 + je .done + + invoke GetTimerTicks + sub eax, [vbox_display_event_time] + cmp eax, DISPLAY_DELAY_TICKS + jb .done + + ; Сбросить флаг ДО обработки (новое событие во время display_change + ; не потеряется — оно поставит pending заново) + mov dword [vbox_display_pending], 0 + + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Delay elapsed, applying...\n" + call display_change + +.done: + ret +endp + +; display_change — Запросить и применить новое разрешение +proc display_change uses ebx ecx edx esi edi + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] display_change\n" + + mov ebx, [vbox_device.display_virt] + test ebx, ebx + jz .skip + + mov dword [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.header.rc], 0 + mov dword [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.event_ack], 1 + mov dword [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.display], 0 + + mov eax, [vbox_device.display_phys] + stdcall vmmdev_send_request, eax + + mov ebx, [vbox_device.display_virt] + mov edi, [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.x_res] + mov esi, [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.y_res] + mov ecx, [ebx + VMMDEV_GET_DISPLAY_CHANGE_REQUEST2.bpp] + + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Requested: %dx%d %dbpp\n", edi, esi, ecx + + ; Валидация + cmp edi, DISP_W_MIN + jb .skip + cmp esi, DISP_H_MIN + jb .skip + cmp edi, DISP_W_MAX + ja .skip + cmp esi, DISP_H_MAX + ja .skip + + cmp ecx, 32 + je .valid + cmp ecx, 24 + je .valid + cmp ecx, 16 + je .valid + cmp ecx, 8 + jne .skip + +.valid: + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Setting %dx%d %dbpp\n", edi, esi, ecx + + ; BGA под cli (как в v5) + cli + bga_set_mode edi, esi, ecx + sti + + ; Обновить DISPLAY структуру ядра + mov eax, [kos_display_ptr] + mov [eax + DISPLAY.width], edi + mov [eax + DISPLAY.height], esi + mov [eax + DISPLAY.bits_per_pixel], ecx + + ; pitch = (bpp / 8) * width + push ebx + mov ebx, ecx + shr ecx, 3 + imul ecx, edi + mov [eax + DISPLAY.pitch], ecx + pop ebx + + ; SetScreen(width-1, height-1) + mov eax, edi + mov edx, esi + dec eax + dec edx + invoke SetScreen + + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Resolution set successfully\n" + +.skip: + ret +endp + +; display_disable — Отключить подсистему дисплея +proc display_disable + DEBUGF __DEBUG_DISPLAY__, "[VBoxGuest] [Display] Disabling...\n" + mov dword [vbox_display_enabled], 0 + mov dword [vbox_display_pending], 0 + xor eax, eax + ret +endp + +REGISTER_SERVICE svc_display_name, DISPLAY_EVENT_MASK, VMMDEV_GUEST_SUPPORTS_GRAPHICS, \ + vmmdev_display_init, 0, display_disable, display_handle_event, display_tick, AUTOSTART_DISPLAY diff --git a/drivers/vboxguest/services/guest_props/guest_props.inc b/drivers/vboxguest/services/guest_props/guest_props.inc new file mode 100644 index 000000000..299d29775 --- /dev/null +++ b/drivers/vboxguest/services/guest_props/guest_props.inc @@ -0,0 +1,263 @@ +; ============================================================================= +; Модуль : VBox Guest Properties Service +; Назначение : Обмен ключ-значение свойствами между хостом и гостем +; Файл : services/guest_props/guest_props.inc +; Протокол : HGCM (VBoxGuestPropSvc) +; +; Функционал: +; - Подключение к HGCM сервису VBoxGuestPropSvc +; - Установка начальных свойств гостевой ОС +; - GET_PROP / SET_PROP_VALUE / DEL_PROP / ENUM_PROPS +; +; Стандартные свойства (устанавливаются при инициализации): +; /VirtualBox/GuestInfo/OS/Product = "KolibriOS" +; /VirtualBox/GuestInfo/OS/Release = "1.0" +; /VirtualBox/GuestAdd/VersionExt = "1.0.0" +; /VirtualBox/GuestAdd/Revision = "1" +; +; Аналог в Linux: VBoxService --property +; ============================================================================= + +svc_guestprop_name db "GUEST_PROPS", 0 + +; HGCM service name on host +align 4 +sz_guestprop_hgcm_service db 'VBoxGuestPropSvc', 0 + +; --- Guest Properties Data --- +align 4 +gp_state GUEST_PROP_STATE + +; Буферы выделяются динамически в guestprop_init, указатели в gp_state + +; --- Строки стандартных свойств --- +align 4 +gp_key_os_product db '/VirtualBox/GuestInfo/OS/Product', 0 +gp_val_os_product db 'KolibriOS', 0 + +gp_key_os_release db '/VirtualBox/GuestInfo/OS/Release', 0 +gp_val_os_release db '1.0', 0 + +gp_key_version db '/VirtualBox/GuestAdd/VersionExt', 0 +gp_val_version db '1.0.0', 0 + +gp_key_revision db '/VirtualBox/GuestAdd/Revision', 0 +gp_val_revision db '1', 0 + +; guestprop_init — Инициализация сервиса Guest Properties +proc guestprop_init uses ebx ecx edx esi edi + DEBUGF 2, "[VBoxGuest] [GuestProp] Initializing...\n" + + ; Проверка что HGCM готов + mov eax, [vbox_device.hgcm_connect_virt] + test eax, eax + jz .hgcm_not_ready + + ; Уже подключены? + cmp dword [gp_state.connected], 1 + je .already_connected + + ; --- Выделить буферы --- + invoke KernelAlloc, 4096 ; gp_enum_buf (page-aligned для HGCM) + test eax, eax + jz .alloc_fail + mov [gp_state.enum_buf_ptr], eax + + invoke KernelAlloc, 4096 ; small_bufs: parms+name+value+flags + test eax, eax + jz .alloc_fail_free1 + mov [gp_state.small_bufs_ptr], eax + + ; Вычислить указатели внутри small_bufs + lea ecx, [eax + 0] + mov [gp_state.parms_ptr], ecx ; +0: 48 байт (sizeof.HGCM_PARM * 4) + lea ecx, [eax + 48] + mov [gp_state.name_buf_ptr], ecx ; +48: 256 байт + lea ecx, [eax + 304] + mov [gp_state.value_buf_ptr], ecx ; +304: 1024 байт + lea ecx, [eax + 1328] + mov [gp_state.flags_buf_ptr], ecx ; +1328: 128 байт + + DEBUGF 2, "[VBoxGuest] [GuestProp] Buffers allocated\n" + + ; Подключиться к HGCM сервису + DEBUGF 2, "[VBoxGuest] [GuestProp] Connecting to '%s'...\n", sz_guestprop_hgcm_service + stdcall hgcm_connect, sz_guestprop_hgcm_service + test eax, eax + jz .connect_failed + + mov [gp_state.client_id], eax + mov dword [gp_state.connected], 1 + DEBUGF 2, "[VBoxGuest] [GuestProp] Connected, client_id=%d\n", eax + + ; Установить начальные свойства + call guestprop_set_initial_props + + DEBUGF 2, "[VBoxGuest] [GuestProp] Initialized OK\n" + xor eax, eax + ret + +.connect_failed: + DEBUGF 2, "[VBoxGuest] [GuestProp] HGCM connect failed\n" + call guestprop_free_bufs + mov eax, VERR_HGCM_SERVICE_NOT_FOUND + ret + +.alloc_fail_free1: + invoke KernelFree, [gp_state.enum_buf_ptr] + mov dword [gp_state.enum_buf_ptr], 0 +.alloc_fail: + DEBUGF 2, "[VBoxGuest] [GuestProp] KernelAlloc failed\n" + mov eax, VERR_NO_MEMORY + ret + +.hgcm_not_ready: + DEBUGF 2, "[VBoxGuest] [GuestProp] HGCM not ready\n" + mov eax, VERR_NOT_READY + ret + +.already_connected: + DEBUGF 2, "[VBoxGuest] [GuestProp] Already connected\n" + xor eax, eax + ret +endp + +; guestprop_set_initial_props — Установить стандартные свойства ОС +proc guestprop_set_initial_props uses eax + DEBUGF 2, "[VBoxGuest] [GuestProp] Setting initial properties...\n" + + stdcall guestprop_set, gp_key_os_product, gp_val_os_product + stdcall guestprop_set, gp_key_os_release, gp_val_os_release + stdcall guestprop_set, gp_key_version, gp_val_version + stdcall guestprop_set, gp_key_revision, gp_val_revision + + DEBUGF 2, "[VBoxGuest] [GuestProp] Initial properties set\n" + ret +endp + +; guestprop_set — Установить свойство (SET_PROP_VALUE, fn=4) +proc guestprop_set stdcall uses ebx ecx edx esi edi, name_ptr:dword, value_ptr:dword + cmp dword [gp_state.connected], 0 + je .not_connected + + DEBUGF 2, "[VBoxGuest] [GuestProp] SET '%s' = '%s'\n", [name_ptr], [value_ptr] + + mov edi, [gp_state.parms_ptr] + + ; parm[0] = name (linaddr IN) + stdcall hgcm_strlen, [name_ptr] + inc eax ; +null + stdcall hgcm_param_linaddr_in, edi, [name_ptr], eax + add edi, sizeof.HGCM_PARM + + ; parm[1] = value (linaddr IN) + stdcall hgcm_strlen, [value_ptr] + inc eax ; +null + stdcall hgcm_param_linaddr_in, edi, [value_ptr], eax + + ; Вызвать HGCM + stdcall hgcm_call32_pagelist, [gp_state.client_id], GUEST_PROP_FN_SET_PROP_VALUE, [gp_state.parms_ptr], GUEST_PROP_SET_VALUE_PARM_COUNT + test eax, eax + jnz .failed + + DEBUGF 2, "[VBoxGuest] [GuestProp] SET OK\n" + xor eax, eax + ret + +.not_connected: + mov eax, VERR_NOT_READY + ret +.failed: + DEBUGF 2, "[VBoxGuest] [GuestProp] SET failed: 0x%x\n", eax + ret + +; strlen(esi) → eax +endp + +; guestprop_set_with_flags — Установить свойство с флагами (SET_PROP, fn=2) +proc guestprop_set_with_flags stdcall uses ebx ecx edx esi edi, name_ptr:dword, value_ptr:dword, flags_ptr:dword + cmp dword [gp_state.connected], 0 + je .not_connected + + mov edi, [gp_state.parms_ptr] + + ; parm[0] = name + stdcall hgcm_strlen, [name_ptr] + inc eax + stdcall hgcm_param_linaddr_in, edi, [name_ptr], eax + add edi, sizeof.HGCM_PARM + + ; parm[1] = value + stdcall hgcm_strlen, [value_ptr] + inc eax + stdcall hgcm_param_linaddr_in, edi, [value_ptr], eax + add edi, sizeof.HGCM_PARM + + ; parm[2] = flags + stdcall hgcm_strlen, [flags_ptr] + inc eax + stdcall hgcm_param_linaddr_in, edi, [flags_ptr], eax + + stdcall hgcm_call32_pagelist, [gp_state.client_id], GUEST_PROP_FN_SET_PROP, [gp_state.parms_ptr], GUEST_PROP_SET_PARM_COUNT + ret + +.not_connected: + mov eax, VERR_NOT_READY + ret + +endp + +proc hgcm_strlen uses esi, str_ptr:dword + mov esi, [str_ptr] + xor eax, eax +@@: cmp byte [esi], 0 + je @f + inc esi + inc eax + jmp @b +@@: ret +endp + +; guestprop_disable — Отключение сервиса +proc guestprop_disable uses edi + DEBUGF 2, "[VBoxGuest] [GuestProp] Disabling...\n" + + cmp dword [gp_state.connected], 0 + je .done + + stdcall hgcm_disconnect, [gp_state.client_id] + + mov dword [gp_state.connected], 0 + mov dword [gp_state.client_id], 0 + + ; Освободить буферы + call guestprop_free_bufs + + DEBUGF 2, "[VBoxGuest] [GuestProp] Disabled\n" +.done: + xor eax, eax + ret +endp + +; guestprop_free_bufs — Освободить буферы Guest Properties +proc guestprop_free_bufs + cmp dword [gp_state.enum_buf_ptr], 0 + je @f + invoke KernelFree, [gp_state.enum_buf_ptr] + mov dword [gp_state.enum_buf_ptr], 0 +@@: + cmp dword [gp_state.small_bufs_ptr], 0 + je @f + invoke KernelFree, [gp_state.small_bufs_ptr] + mov dword [gp_state.small_bufs_ptr], 0 + ; Обнулить указатели-потомки + mov dword [gp_state.parms_ptr], 0 + mov dword [gp_state.name_buf_ptr], 0 + mov dword [gp_state.value_buf_ptr], 0 + mov dword [gp_state.flags_buf_ptr], 0 +@@: + ret +endp + +REGISTER_SERVICE svc_guestprop_name, GUEST_PROP_EVENT_MASK, GUEST_PROP_CAPS_MASK, \ + guestprop_init, 0, guestprop_disable, 0, 0, AUTOSTART_GUEST_PROPS diff --git a/drivers/vboxguest/services/heartbeat/heartbeat.inc b/drivers/vboxguest/services/heartbeat/heartbeat.inc new file mode 100644 index 000000000..d1070e23f --- /dev/null +++ b/drivers/vboxguest/services/heartbeat/heartbeat.inc @@ -0,0 +1,243 @@ +; ============================================================================= +; Модуль : VMMDev Heartbeat Service +; Назначение : Периодическая отправка heartbeat сигналов хосту для мониторинга +; Файл : vmmdev/heartbeat.inc +; Протокол : Прямые VMMDev запросы (БЕЗ HGCM) +; ============================================================================= + +svc_heartbeat_name db "HEARTBEAT", 0 + +; --- Heartbeat Data --- +align 4 + +vbox_hb_last_send_time dd 0 ; timer_ticks последней отправки +vbox_hb_tick_interval dd 100 +vbox_hb_enabled dd 0 + +; Статические буферы для запросов +align 4 +vmmdev_heartbeat_configure_s VMMDEV_HEARTBEAT_CONFIGURE \ + , \ + 0, 0, 0; интервал и enabled заполняются при вызове + +align 4 +vmmdev_heartbeat_s VMMDEV_GUEST_HEARTBEAT \ + + +; vmmdev_heartbeat_init — Инициализация heartbeat подсистемы +proc vmmdev_heartbeat_init + DEBUGF 2, "[VBoxGuest] [Heartbeat] Initializing...\n" + + ; Получить физические адреса буферов + mov eax, vmmdev_heartbeat_configure_s + mov [vbox_device.heartbeat_config_virt], eax + invoke GetPhysAddr + mov [vbox_device.heartbeat_config_phys], eax + DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Config packet: virt=0x%x, phys=0x%x\n", \ + vmmdev_heartbeat_configure_s, eax + + mov eax, vmmdev_heartbeat_s + mov [vbox_device.heartbeat_virt], eax + invoke GetPhysAddr + mov [vbox_device.heartbeat_phys], eax + DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Send packet : virt=0x%x, phys=0x%x\n", \ + vmmdev_heartbeat_s, eax + + ; Запросить конфигурацию у хоста + call vmmdev_heartbeat_configure + test eax, eax + jnz .failed + + DEBUGF 2, "[VBoxGuest] [Heartbeat] Initialized successfully\n" + xor eax, eax + ret + +.failed: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Initialization failed: rc=0x%x\n", eax + ret +endp + +; vmmdev_heartbeat_configure — Запросить настройки heartbeat у хоста и включить мониторинг +proc vmmdev_heartbeat_configure uses ebx edi + DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Configuring...\n" + + mov edi, [vbox_device.heartbeat_config_virt] + test edi, edi + jz .bad_ptr + + ; Заполнить запрос (header уже инициализирован статически) + ; Обнулить rc перед отправкой + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.header.rc], 0 + + ; Запросить у хоста интервал (0 = использовать дефолтный) + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval], 0 + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval_high], 0 + + ; Включить heartbeat + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.f_enabled], 1 + + ; Отправить запрос + mov ebx, [vbox_device.heartbeat_config_phys] + stdcall vmmdev_send_request, ebx + + ; Проверить результат + mov eax, [edi + VMMDEV_HEARTBEAT_CONFIGURE.header.rc] + test eax, eax + js .error + + ; Получить интервал от хоста (если вернул) + mov eax, [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval] + mov edx, [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval_high] + + ; Конвертировать наносекунды в тики + ; Предположим timer tick = 10ms = 10,000,000 ns + ; Делим ns на 10,000,000 чтобы получить тики + test eax, eax + jz .use_default + test edx, edx + jnz .use_default ; Если интервал > 4.2 секунды, используем дефолт + + ; eax = наносекунды, конвертируем в тики (10ms) + ; ticks = ns / 10,000,000 + ; правильное деление: 10,000,000 / 10,000 = 1000 (тики в 10ms) + mov ebx, 10000 + xor edx, edx + div ebx ; eax = eax / 10000 (микросекунды) + mov ebx, 1000 + xor edx, edx + div ebx ; eax = eax / 1000 (тики 10ms) + test eax, eax + jz .use_default + + mov [vbox_hb_tick_interval], eax + DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Interval set to %d ticks\n", eax + jmp .enabled + +.use_default: + DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Using default interval: %d ticks\n", \ + [vbox_hb_tick_interval] + +.enabled: + ; Включить heartbeat + mov dword [vbox_hb_enabled], 1 + DEBUGF 2, "[VBoxGuest] [Heartbeat] Enabled\n" + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Configure failed: rc=0x%x\n", eax + ret + +.bad_ptr: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Configure: bad pointer\n" + mov eax, VERR_INVALID_POINTER + ret +endp + +; vmmdev_heartbeat_send — Отправить heartbeat хосту +proc vmmdev_heartbeat_send uses ebx edi + mov edi, [vbox_device.heartbeat_virt] + mov ebx, [vbox_device.heartbeat_phys] + test edi, edi + jz .bad_ptr + test ebx, ebx + jz .bad_ptr + + ; Обнулить rc перед отправкой + mov dword [edi + VMMDEV_GUEST_HEARTBEAT.header.rc], 0 + + ; DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Sending...\n" + + ; Отправить запрос + stdcall vmmdev_send_request, ebx + + ; Проверить результат + mov eax, [edi + VMMDEV_GUEST_HEARTBEAT.header.rc] + test eax, eax + js .error + + ; DEBUGF __DEBUG_HB__, "[VBoxGuest] [Heartbeat] Sent successfully\n" + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Send failed: rc=0x%x\n", eax + ret + +.bad_ptr: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Send: bad pointer virt=0x%x phys=0x%x\n", edi, ebx + mov eax, VERR_INVALID_POINTER + ret +endp + +proc vmmdev_heartbeat_tick + cmp dword [vbox_hb_enabled], 0 + je .done + + invoke GetTimerTicks ; eax = timer_ticks + sub eax, [vbox_hb_last_send_time] + cmp eax, [vbox_hb_tick_interval] + jb .done + + invoke GetTimerTicks ; eax = текущее время для обновления last_send_time + mov [vbox_hb_last_send_time], eax + + call vmmdev_heartbeat_send + test eax, eax + jz .done + + DEBUGF 2, "[VBoxGuest] [Heartbeat] Tick: send error 0x%x\n", eax + +.done: + ret +endp + +; vmmdev_heartbeat_disable — Отключить heartbeat (при выгрузке драйвера) +proc vmmdev_heartbeat_disable uses ebx edi + + DEBUGF 2, "[VBoxGuest] [Heartbeat] Disabling...\n" + + ; Выключить локальный флаг + mov dword [vbox_hb_enabled], 0 + + mov edi, [vbox_device.heartbeat_config_virt] + test edi, edi + jz .bad_ptr + + ; Обнулить rc + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.header.rc], 0 + + ; Отключить на хосте + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.f_enabled], 0 + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval], 0 + mov dword [edi + VMMDEV_HEARTBEAT_CONFIGURE.c_ns_interval_high], 0 + + ; Отправить запрос + mov ebx, [vbox_device.heartbeat_config_phys] + stdcall vmmdev_send_request, ebx + + mov eax, [edi + VMMDEV_HEARTBEAT_CONFIGURE.header.rc] + test eax, eax + js .error + + DEBUGF 2, "[VBoxGuest] [Heartbeat] Disabled\n" + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Heartbeat] Disable failed: rc=0x%x\n", eax + ret + +.bad_ptr: + mov eax, VERR_INVALID_POINTER + ret +endp + +REGISTER_SERVICE svc_heartbeat_name, 0, 0, \ + vmmdev_heartbeat_init, 0, vmmdev_heartbeat_disable, 0, vmmdev_heartbeat_tick, AUTOSTART_HEARTBEAT diff --git a/drivers/vboxguest/services/mouse/mouse.inc b/drivers/vboxguest/services/mouse/mouse.inc new file mode 100644 index 000000000..32fed84ab --- /dev/null +++ b/drivers/vboxguest/services/mouse/mouse.inc @@ -0,0 +1,382 @@ +; ============================================================================= +; Модуль : VMMDev Mouse Service (Absolute Pointer) +; Назначение : Поддержка абсолютных координат мыши VirtualBox +; Файл : services/mouse/mouse.inc +; +; Использует расширенный протокол GetMouseStatusEx (type=223) если хост +; поддерживает FULL_STATE_PROTOCOL — кнопки и скролл приходят от VMMDev. +; Если хост не поддерживает — fallback на стандартный протокол + PS/2. +; ============================================================================= + +svc_mouse_name db "MOUSE", 0 + +; --- Mouse Data --- +align 4 +vbox_mouse_enabled dd 0 +vbox_mouse_x dd 0 +vbox_mouse_y dd 0 +vbox_mouse_buttons dd 0 +vbox_mouse_dz dd 0 ; vertical scroll delta +vbox_mouse_dw dd 0 ; horizontal scroll delta +vbox_mouse_host_features dd 0 +vbox_mouse_use_ext dd 0 ; 1 = extended protocol (buttons+scroll from VMMDev) + +; Статический буфер для запроса (48 байт: 36 standard + 12 extended fields) +align 4 +vmmdev_mouse_status_s VMMDEV_REQ_MOUSE_STATUS \ + , \ + VBOX_MOUSE_GUEST_FEATURES, 0, 0 +; Extended fields (immediately after VMMDEV_REQ_MOUSE_STATUS in memory) +; VMMDev заполнит их при GET_MOUSE_STATUS_EX (type=223) +vmmdev_mouse_ext_dz dd 0 ; offset +36: vertical scroll +vmmdev_mouse_ext_dw dd 0 ; offset +40: horizontal scroll +vmmdev_mouse_ext_buttons dd 0 ; offset +44: button state + +SIZEOF_MOUSE_STATUS_EX = sizeof.VMMDEV_REQ_MOUSE_STATUS + 12 +MOUSE_EXT_OFS_DZ = sizeof.VMMDEV_REQ_MOUSE_STATUS +MOUSE_EXT_OFS_DW = sizeof.VMMDEV_REQ_MOUSE_STATUS + 4 +MOUSE_EXT_OFS_BUTTONS = sizeof.VMMDEV_REQ_MOUSE_STATUS + 8 + +; vmmdev_mouse_init — Инициализация подсистемы мыши +proc vmmdev_mouse_init + ; Guard against double initialization (fn_init = fn_enable in REGISTER_SERVICE) + cmp dword [vbox_mouse_enabled], 1 + je .already_initialized + + DEBUGF 2, "[VBoxGuest] [Mouse] Initializing...\n" + + ; Получить физический адрес буфера + mov eax, vmmdev_mouse_status_s + mov [vbox_device.mouse_virt], eax + invoke GetPhysAddr + mov [vbox_device.mouse_phys], eax + + DEBUGF 2, "[VBoxGuest] [Mouse] Buffer: virt=0x%x, phys=0x%x, ext_size=%d\n", \ + vmmdev_mouse_status_s, eax, SIZEOF_MOUSE_STATUS_EX + + ; Запросить текущий статус (стандартный GET, т.к. use_ext=0) + call mouse_query_status + test eax, eax + jnz .failed + + DEBUGF 2, "[VBoxGuest] [Mouse] Host features: 0x%x\n", [vbox_mouse_host_features] + DEBUGF 2, "[VBoxGuest] [Mouse] Host position: X=%d, Y=%d\n", [vbox_mouse_x], [vbox_mouse_y] + + ; Проверить поддержку расширенного протокола + test dword [vbox_mouse_host_features], VMMDEV_MOUSE_HOST_SUPPORTS_FULL_STATE_PROTOCOL + jz .no_ext + + ; === Расширенный протокол === + mov dword [vbox_mouse_use_ext], 1 + DEBUGF 2, "[VBoxGuest] [Mouse] Host supports FULL_STATE_PROTOCOL — using extended mode\n" + + call mouse_send_status + test eax, eax + jnz .failed + + ; Отключить PS/2 IRQ12 — VMMDev сам сообщает кнопки+скролл. + ; Если PS/2 остаётся активным, SetMouseData(buttons=0) от VMMDev + ; сбрасывает нажатые кнопки из PS/2 на каждом движении (glitch при drag). + stdcall i8042_mouse_control, 0 + DEBUGF 2, "[VBoxGuest] [Mouse] PS/2 IRQ12 disabled (buttons from VMMDev)\n" + + jmp .ok + +.no_ext: + ; === Стандартный протокол (fallback) === + mov dword [vbox_mouse_use_ext], 0 + DEBUGF 2, "[VBoxGuest] [Mouse] WARNING: Host does NOT support FULL_STATE_PROTOCOL\n" + DEBUGF 2, "[VBoxGuest] [Mouse] Fallback: standard protocol, PS/2 stays enabled for buttons\n" + + call mouse_send_status + test eax, eax + jnz .failed + +.ok: + DEBUGF 2, "[VBoxGuest] [Mouse] Initialized OK (ext=%d)\n", [vbox_mouse_use_ext] + xor eax, eax + ret + +.already_initialized: + DEBUGF 2, "[VBoxGuest] [Mouse] Already initialized (skip reinit)\n" + xor eax, eax + ret + +.failed: + DEBUGF 2, "[VBoxGuest] [Mouse] Init failed: 0x%x\n", eax + ret +endp + +; mouse_query_status — Запросить статус у хоста +proc mouse_query_status + push ebx edi + + mov edi, [vbox_device.mouse_virt] + test edi, edi + jz .bad_ptr + + ; Очистить rc + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.rc], 0 + + ; Выбрать тип запроса + cmp dword [vbox_mouse_use_ext], 0 + je .setup_standard + + ; === Extended GET (type=223, size=48) === + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.size], SIZEOF_MOUSE_STATUS_EX + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.request_type], VMMDEV_REQ_GET_MOUSE_STATUS_EX + jmp .send + +.setup_standard: + ; === Standard GET (type=1, size=36) === + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.size], sizeof.VMMDEV_REQ_MOUSE_STATUS + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.request_type], VMMDEV_REQ_GET_MOUSE_STATUS + +.send: + mov ebx, [vbox_device.mouse_phys] + stdcall vmmdev_send_request, ebx + + mov eax, [edi + VMMDEV_REQ_MOUSE_STATUS.header.rc] + test eax, eax + js .error + + ; === Сохранить общие данные === + mov eax, [edi + VMMDEV_REQ_MOUSE_STATUS.mouse_features] + mov [vbox_mouse_host_features], eax + + mov eax, [edi + VMMDEV_REQ_MOUSE_STATUS.pointer_x_pos] + mov [vbox_mouse_x], eax + + mov eax, [edi + VMMDEV_REQ_MOUSE_STATUS.pointer_y_pos] + mov [vbox_mouse_y], eax + + ; === Если extended — сохранить кнопки и скролл === + cmp dword [vbox_mouse_use_ext], 0 + je .no_ext + + mov eax, [edi + MOUSE_EXT_OFS_BUTTONS] + and eax, 0x1F ; биты 0-4: left, right, middle, X1, X2 + mov [vbox_mouse_buttons], eax + + mov eax, [edi + MOUSE_EXT_OFS_DZ] + mov [vbox_mouse_dz], eax + + mov eax, [edi + MOUSE_EXT_OFS_DW] + mov [vbox_mouse_dw], eax + +.no_ext: + DEBUGF __DEBUG_MOUSE__, "[VBoxGuest] [Mouse] VMMDev: feat=0x%x, X=%d, Y=%d, btn=0x%x, dz=%d\n", \ + [vbox_mouse_host_features], [vbox_mouse_x], [vbox_mouse_y], \ + [vbox_mouse_buttons], [vbox_mouse_dz] + + ; Восстановить header для SET запросов + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.size], sizeof.VMMDEV_REQ_MOUSE_STATUS + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.request_type], VMMDEV_REQ_SET_MOUSE_STATUS + + xor eax, eax + pop edi ebx + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Mouse] Query failed: rc=0x%x\n", eax + ; Восстановить header даже при ошибке + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.size], sizeof.VMMDEV_REQ_MOUSE_STATUS + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.request_type], VMMDEV_REQ_SET_MOUSE_STATUS + pop edi ebx + ret + +.bad_ptr: + mov eax, VERR_INVALID_POINTER + pop edi ebx + ret +endp + +; mouse_send_status — Отправить guest capabilities хосту +proc mouse_send_status + push ebx edi + + mov edi, [vbox_device.mouse_virt] + mov ebx, [vbox_device.mouse_phys] + test edi, edi + jz .bad_ptr + test ebx, ebx + jz .bad_ptr + + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.rc], 0 + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.pointer_x_pos], 0 + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.pointer_y_pos], 0 + + ; Выбрать guest features + cmp dword [vbox_mouse_use_ext], 0 + je .std_feats + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.mouse_features], VBOX_MOUSE_GUEST_FEATURES_EXT + DEBUGF 2, "[VBoxGuest] [Mouse] Sending guest features: 0x%x (extended)\n", VBOX_MOUSE_GUEST_FEATURES_EXT + jmp .send +.std_feats: + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.mouse_features], VBOX_MOUSE_GUEST_FEATURES + DEBUGF 2, "[VBoxGuest] [Mouse] Sending guest features: 0x%x (standard)\n", VBOX_MOUSE_GUEST_FEATURES +.send: + stdcall vmmdev_send_request, ebx + + mov eax, [edi + VMMDEV_REQ_MOUSE_STATUS.header.rc] + test eax, eax + js .error + + mov dword [vbox_mouse_enabled], 1 + xor eax, eax + pop edi ebx + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Mouse] Send status failed: 0x%x\n", eax + pop edi ebx + ret + +.bad_ptr: + mov eax, VERR_INVALID_POINTER + pop edi ebx + ret +endp + +; mouse_handle_event — Обработка событий мыши (вызывается из IRQ) +proc mouse_handle_event + push eax + + DEBUGF __DEBUG_MOUSE__, "[VBoxGuest] [Mouse] Event: mask=0x%x\n", eax + + ; Любое из двух событий — нужен свежий статус + call mouse_query_status + test eax, eax + jnz .query_fail + + pop eax + push eax + + ; Для position changed — обновить координаты в системе + test eax, VMMDEV_EVENT_MOUSE_POSITION_CHANGED + jz .done + + call mouse_update_system_position + jmp .done + +.query_fail: + DEBUGF __DEBUG_MOUSE__, "[VBoxGuest] [Mouse] Query failed in event handler: 0x%x\n", eax + +.done: + pop eax + ret +endp + +; mouse_update_system_position — Отправить позицию + кнопки + скролл в KolibriOS +proc mouse_update_system_position + push eax ebx ecx edx + + ; VirtualBox: 0-65535, KolibriOS: 0-32767 + mov ecx, [vbox_mouse_x] + shr ecx, 1 ; X / 2 + + mov edx, [vbox_mouse_y] + shr edx, 1 ; Y / 2 + + ; Флаги: bit31=X_abs, bit30=Y_abs + кнопки (bits 0-4) + mov eax, 0xC0000000 + or eax, [vbox_mouse_buttons] + + DEBUGF __DEBUG_MOUSE__, "[VBoxGuest] [Mouse] SetMouseData: flags=0x%x, X=%d, Y=%d, btn=0x%x, dz=%d, dw=%d\n", \ + eax, ecx, edx, [vbox_mouse_buttons], [vbox_mouse_dz], [vbox_mouse_dw] + + invoke SetMouseData, eax, ecx, edx, [vbox_mouse_dz], [vbox_mouse_dw] + + ; Сбросить дельты скролла (это дельты, не состояние) + mov dword [vbox_mouse_dz], 0 + mov dword [vbox_mouse_dw], 0 + + pop edx ecx ebx eax + ret +endp + +; mouse_disable — Отключить мышь +proc mouse_disable + push ebx edi + + DEBUGF 2, "[VBoxGuest] [Mouse] Disabling...\n" + + mov dword [vbox_mouse_enabled], 0 + + mov edi, [vbox_device.mouse_virt] + test edi, edi + jz .done + + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.header.rc], 0 + mov dword [edi + VMMDEV_REQ_MOUSE_STATUS.mouse_features], 0 + + mov ebx, [vbox_device.mouse_phys] + stdcall vmmdev_send_request, ebx + + ; Восстановить PS/2 при выключении + stdcall i8042_mouse_control, 1 + +.done: + xor eax, eax + pop edi ebx + ret +endp + + +; i8042_mouse_control — Включить/выключить IRQ12 PS/2 мыши +proc i8042_mouse_control stdcall, enable:dword + call .wait_input + mov al, 0x20 + out 0x64, al + + call .wait_output + in al, 0x60 + + and al, not 0x02 + cmp [enable], 0 + je @f + or al, 0x02 +@@: + mov ah, al + + call .wait_input + mov al, 0x60 + out 0x64, al + + call .wait_input + mov al, ah + out 0x60, al + ret + +.wait_input: + push ecx + mov ecx, 0xFFFF +@@: + in al, 0x64 + test al, 0x02 + jz @f + dec ecx + jnz @b +@@: + pop ecx + ret + +.wait_output: + push ecx + mov ecx, 0xFFFF +@@: + in al, 0x64 + test al, 0x01 + jnz @f + dec ecx + jnz @b +@@: + pop ecx + ret +endp + +REGISTER_SERVICE svc_mouse_name, MOUSE_EVENT_MASK, VMMDEV_GUEST_SUPPORTS_MOUSE, \ + vmmdev_mouse_init, vmmdev_mouse_init, mouse_disable, mouse_handle_event, 0, AUTOSTART_MOUSE diff --git a/drivers/vboxguest/services/seamless/seamless.inc b/drivers/vboxguest/services/seamless/seamless.inc new file mode 100644 index 000000000..a9e17c6a1 --- /dev/null +++ b/drivers/vboxguest/services/seamless/seamless.inc @@ -0,0 +1,265 @@ +; ============================================================================= +; Модуль : VMMDev Seamless Mode Service +; Назначение : Получение запросов на seamless mode от хоста +; Файл : services/seamless/seamless.inc +; Протокол : VMMDevReq_GetSeamlessChangeRequest (73) +; +; Seamless mode = «бесшовные окна»: окна гостя рисуются как окна хоста, +; без рамки VM. Хост периодически спрашивает гостя: «какие области экрана +; заняты окнами?» (visible region). +; +; Для KolibriOS полный seamless пока невозможен (нет API получения +; регионов окон), но мы: +; 1. Сообщаем хосту VMMDEV_GUEST_SUPPORTS_SEAMLESS → кнопка +; «View → Seamless Mode» в VBox GUI станет активной +; 2. Получаем события SEAMLESS_MODE_CHANGE через IRQ +; 3. Запрашиваем текущий режим через GetSeamlessChangeRequest +; 4. При включении seamless → отправляем visible region = весь экран +; (один прямоугольник 0,0 → width,height) +; +; Архитектура (как display): +; IRQ event → seamless_handle_event → pending=1, timestamp +; Timer tick → seamless_tick → если pending и debounce прошёл → обработать +; ============================================================================= + +svc_seamless_name db "SEAMLESS", 0 + +; --- Data --- +align 4 +vbox_seamless_enabled dd 0 +vbox_seamless_current_mode dd 0 ; VMMDEV_SEAMLESS_* +vbox_seamless_pending dd 0 +vbox_seamless_event_time dd 0 + +; Debounce: 500ms (50 тиков * 10ms) +SEAMLESS_DELAY_TICKS equ 50 + +; --- Пакет GetSeamlessChangeRequest --- +align 4 +vmmdev_seamless_change_s VMMDEV_SEAMLESS_CHANGE_REQUEST \ + , \ + 0, \ + VMMDEV_EVENT_SEAMLESS_MODE_CHANGE ; eventAck + +align 4 +seamless_change_virt dd 0 +seamless_change_phys dd 0 + +; --- Пакет VideoSetVisibleRegion (1 прямоугольник) --- +align 4 +vmmdev_visible_region_s VMMDEV_VIDEO_SET_VISIBLE_REGION \ + , \ + 1, \ + <0, 0, 0, 0> ; rect0: заполняется при вызове + +align 4 +visible_region_virt dd 0 +visible_region_phys dd 0 + +; seamless_init — Инициализация +proc seamless_init uses ebx + DEBUGF 2, "[VBoxGuest] [Seamless] Initializing...\n" + + ; --- Физические адреса пакетов --- + mov eax, vmmdev_seamless_change_s + mov [seamless_change_virt], eax + invoke GetPhysAddr + test eax, eax + jz .phys_failed + mov [seamless_change_phys], eax + + mov eax, vmmdev_visible_region_s + mov [visible_region_virt], eax + invoke GetPhysAddr + test eax, eax + jz .phys_failed + mov [visible_region_phys], eax + + DEBUGF __DEBUG_SEAMLESS__, "[VBoxGuest] [Seamless] SeamlessChange pkt: virt=0x%x phys=0x%x\n", \ + [seamless_change_virt], [seamless_change_phys] + DEBUGF __DEBUG_SEAMLESS__, "[VBoxGuest] [Seamless] VisibleRegion pkt: virt=0x%x phys=0x%x\n", \ + [visible_region_virt], [visible_region_phys] + + mov dword [vbox_seamless_enabled], 1 + mov dword [vbox_seamless_current_mode], VMMDEV_SEAMLESS_DISABLED + + ; Сразу запросить текущий режим (может быть уже включен) + call seamless_query_mode + + DEBUGF 2, "[VBoxGuest] [Seamless] Initialized OK (mode=%d)\n", \ + [vbox_seamless_current_mode] + xor eax, eax + ret + +.phys_failed: + DEBUGF 2, "[VBoxGuest] [Seamless] GetPhysAddr failed\n" + mov eax, VERR_GENERAL_FAILURE + ret +endp + +; seamless_handle_event — Из IRQ контекста +proc seamless_handle_event + test eax, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE + jz .done + cmp dword [vbox_seamless_enabled], 0 + je .done + + invoke GetTimerTicks + mov [vbox_seamless_event_time], eax + mov dword [vbox_seamless_pending], 1 + + DEBUGF __DEBUG_SEAMLESS__, "[VBoxGuest] [Seamless] Event queued\n" +.done: + ret +endp + +; seamless_tick — Из таймера, debounce +proc seamless_tick + cmp dword [vbox_seamless_pending], 0 + je .done + + invoke GetTimerTicks + sub eax, [vbox_seamless_event_time] + cmp eax, SEAMLESS_DELAY_TICKS + jb .done + + mov dword [vbox_seamless_pending], 0 + + DEBUGF __DEBUG_SEAMLESS__, "[VBoxGuest] [Seamless] Processing mode change...\n" + call seamless_query_mode + +.done: + ret +endp + +; seamless_query_mode — Запросить текущий seamless mode у хоста (req 73) +proc seamless_query_mode uses ebx edi + mov edi, [seamless_change_virt] + test edi, edi + jz .bad_ptr + + ; Заполнить запрос + mov dword [edi + VMMDEV_SEAMLESS_CHANGE_REQUEST.header.rc], 0 + mov dword [edi + VMMDEV_SEAMLESS_CHANGE_REQUEST.mode], 0 + mov dword [edi + VMMDEV_SEAMLESS_CHANGE_REQUEST.eventAck], \ + VMMDEV_EVENT_SEAMLESS_MODE_CHANGE + + ; Отправить + stdcall vmmdev_send_request, [seamless_change_phys] + + ; Проверить rc + mov eax, [edi + VMMDEV_SEAMLESS_CHANGE_REQUEST.header.rc] + test eax, eax + jnz .error + + ; Прочитать режим + mov eax, [edi + VMMDEV_SEAMLESS_CHANGE_REQUEST.mode] + mov ebx, [vbox_seamless_current_mode] + mov [vbox_seamless_current_mode], eax + + DEBUGF 2, "[VBoxGuest] [Seamless] Mode: %d → %d\n", ebx, eax + + ; Обработать смену режима + cmp eax, VMMDEV_SEAMLESS_DISABLED + je .mode_disabled + + cmp eax, VMMDEV_SEAMLESS_VISIBLE_REGION + je .mode_visible_region + + cmp eax, VMMDEV_SEAMLESS_HOST_WINDOW + je .mode_host_window + + DEBUGF 2, "[VBoxGuest] [Seamless] Unknown mode: %d\n", eax + ret + +.mode_disabled: + DEBUGF 2, "[VBoxGuest] [Seamless] Seamless DISABLED\n" + ret + +.mode_visible_region: + DEBUGF 2, "[VBoxGuest] [Seamless] Seamless ENABLED (visible region)\n" + ; Отправить видимую область = весь экран + call seamless_send_fullscreen_region + ret + +.mode_host_window: + DEBUGF 2, "[VBoxGuest] [Seamless] Seamless ENABLED (host window)\n" + call seamless_send_fullscreen_region + ret + +.bad_ptr: + DEBUGF 2, "[VBoxGuest] [Seamless] Bad pointer\n" + ret + +.error: + DEBUGF 2, "[VBoxGuest] [Seamless] GetSeamlessChange failed: rc=0x%x\n", eax + ret +endp + +; seamless_send_fullscreen_region — Отправить visible region = весь экран +proc seamless_send_fullscreen_region uses ebx edi + mov edi, [visible_region_virt] + test edi, edi + jz .bad_ptr + + ; Получить текущее разрешение из KOS + invoke GetDisplay + test eax, eax + jz .no_display + + mov ebx, eax + mov ecx, [ebx + DISPLAY.width] + mov edx, [ebx + DISPLAY.height] + + DEBUGF 1, "[VBoxGuest] [Seamless] Sending region: 0,0 → %d,%d\n", ecx, edx + + ; Заполнить заголовок + mov dword [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.header.rc], 0 + + ; Заполнить прямоугольник + mov dword [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.cRect], 1 + mov dword [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.rect0.xLeft], 0 + mov dword [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.rect0.yTop], 0 + mov [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.rect0.xRight], ecx + mov [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.rect0.yBottom], edx + + ; Отправить + stdcall vmmdev_send_request, [visible_region_phys] + + mov eax, [edi + VMMDEV_VIDEO_SET_VISIBLE_REGION.header.rc] + test eax, eax + jnz .error + + DEBUGF 1, "[VBoxGuest] [Seamless] Region sent OK\n" + xor eax, eax + ret + +.bad_ptr: + DEBUGF 2, "[VBoxGuest] [Seamless] Bad pointer\n" + ret +.no_display: + DEBUGF 2, "[VBoxGuest] [Seamless] No display\n" + ret +.error: + DEBUGF 2, "[VBoxGuest] [Seamless] SetVisibleRegion failed: rc=0x%x\n", eax + ret +endp + +; seamless_disable — Выключить seamless +proc seamless_disable + DEBUGF 2, "[VBoxGuest] [Seamless] Disabling...\n" + mov dword [vbox_seamless_enabled], 0 + mov dword [vbox_seamless_pending], 0 + mov dword [vbox_seamless_current_mode], VMMDEV_SEAMLESS_DISABLED + xor eax, eax + ret +endp + +REGISTER_SERVICE svc_seamless_name, SEAMLESS_EVENT_MASK, VMMDEV_GUEST_SUPPORTS_SEAMLESS, \ + seamless_init, 0, seamless_disable, seamless_handle_event, seamless_tick, AUTOSTART_SEAMLESS diff --git a/drivers/vboxguest/services/services.inc b/drivers/vboxguest/services/services.inc new file mode 100644 index 000000000..70ebc89f7 --- /dev/null +++ b/drivers/vboxguest/services/services.inc @@ -0,0 +1,11 @@ +; ============================================================================= +; Services Aggregator +; ============================================================================= +include 'services/mouse/mouse.inc' +include 'services/heartbeat/heartbeat.inc' +include 'services/guest_props/guest_props.inc' +include 'services/display/display.inc' +include 'services/shared_folders/shared_folders.inc' +include 'services/clipboard/clipboard.inc' +include 'services/seamless/seamless.inc' +include 'services/timesync/timesync.inc' \ No newline at end of file diff --git a/drivers/vboxguest/services/shared_folders/shared_folders.inc b/drivers/vboxguest/services/shared_folders/shared_folders.inc new file mode 100644 index 000000000..a4a56408c --- /dev/null +++ b/drivers/vboxguest/services/shared_folders/shared_folders.inc @@ -0,0 +1,1119 @@ +; ============================================================================= +; Модуль : VBox SharedFolder Service +; Назначение : Подключение VBox shared folders как ФС в KolibriOS +; Файл : services/shared_folders/shared_folders.inc +; Протокол : HGCM (VBoxSharedFolders) +; +; Функционал: +; - Подключение к HGCM сервису VBoxSharedFolders +; - Включение UTF-8 режима (SHFL_FN_SET_UTF8) +; - Запрос и подключение всех shared folders +; - Регистрация диска через DiskAdd + ФС через FsAdd +; - Обслуживание ReadFolder/Read/GetFileInfo через SHFL +; ============================================================================= + +svc_shfl_name db "SHARED_FOLDERS", 0 + +align 4 +sz_shfl_hgcm_service db 'VBoxSharedFolders', 0 + +; --- Data --- +align 4 +sf_state SF_STATE + +; Данные для диска +align 4 +vboxsf_data VBOXSF_DISK + +; Буферы выделяются динамически в shfl_init, указатели хранятся в sf_state + +; Состояние +align 4 +sf_map_count dd 0 ; Количество найденных mappings +sf_list_resume dd 0 ; Resume point для LIST пагинации +sf_list_total dd 0 ; Общее количество файлов (все страницы) + +; vboxsf_add_folder — Добавить папку в массив vboxsf_data +proc vboxsf_add_folder stdcall uses ebx ecx edx esi edi, root:dword, name_ptr:dword + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] add_folder: root=%d name='%s'\n", [root], [name_ptr] + + ; Проверить лимит + cmp dword [vboxsf_data.count], 32 + jae .error + + ; Выделить память для SF_FOLDER + invoke KernelAlloc, 4096 + test eax, eax + jz .error + + ; Инициализировать структуру + mov edi, eax + push edi + + ; Очистить + push eax + xor eax, eax + mov ecx, sizeof.SF_FOLDER / 4 + rep stosd + pop eax + pop edi + + ; Сохранить указатель в массив + mov ecx, [vboxsf_data.count] + mov [vboxsf_data.folders + ecx*4], edi + + ; Установить root handle + mov edx, [root] + mov [edi + SF_FOLDER.root_handle], edx + + ; Установить active + mov dword [edi + SF_FOLDER.active], 1 + + ; Скопировать имя папки (UTF-8) + mov esi, [name_ptr] + lea edi, [edi + SF_FOLDER.name] + mov ecx, 255 + +.copy_name: + lodsb + stosb + test al, al + jz .name_done + dec ecx + jnz .copy_name + +.name_done: + mov byte [edi], 0 ; гарантировать null-terminator + + ; Увеличить счётчик папок + inc dword [vboxsf_data.count] + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] folder added, total count: %d\n", [vboxsf_data.count] + xor eax, eax + ret + +.error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] add_folder: failed\n" + mov eax, -1 + ret +endp + +; vboxsf_register_disk — Зарегистрировать виртуальный диск + ФС в системе +; Смещения в структуре DISK ядра (вычислены из kernel/trunk/blkdev/disk.inc + const.inc) +; MUTEX = LHEAD(8) + count(4) = 12 bytes +; DISKMEDIAINFO = Flags(4) + SectorSize(4) + Capacity(8) + LastSessionSector(4) = 20 bytes +; DISK layout: Next(4) + Prev(4) + Functions(4) + Name(4) + UserData(4) + +; DriverFlags(4) + RefCount(4) + MediaLock(12) + +; MediaInserted(1) + MediaUsed(1) + pad(2) + MediaRefCount(4) + +; MediaInfo(20) = 68 +KDISK_OFS_NUMPARTITIONS = 68 +KDISK_OFS_PARTITIONS = 72 + +proc vboxsf_register_disk stdcall uses ebx ecx edx esi edi + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Registering filesystem...\n" + + ; Проверить что есть хотя бы одна папка + cmp dword [vboxsf_data.count], 0 + je .no_folders + + ; --- Шаг 1: DiskAdd — создать виртуальный диск --- + ; НЕ используем FsAdd — в ядре баг: fs_add итерирует ВСЕ диски + ; включая те, у которых NumPartitions не инициализирован (CD без диска), + ; что вызывает GPF. Вместо этого вручную ставим FSUserFunctions. + invoke DiskAdd, vboxsf_disk_functions, sz_vboxsf_diskname, vboxsf_data, 0 + + DEBUGF __DEBUG_SF__, "[DiskAdd] result eax=0x%x\n", eax + test eax, eax + jz .disk_failed + + mov [vboxsf_data.disk_handle], eax + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] DiskAdd OK, handle=0x%x\n", eax + + ; --- Шаг 2: DiskMediaChanged — ядро создаст raw-раздел --- + ; vboxsf_read_sectors вернёт нули → нет MBR → disk_add_partition создаст + ; один раздел с default_fs_functions (ни одна встроенная ФС не узнает). + invoke DiskMediaChanged, [vboxsf_data.disk_handle], 1 + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] DiskMediaChanged done\n" + + ; --- Шаг 3: Вручную установить наши FSUserFunctions на раздел --- + mov eax, [vboxsf_data.disk_handle] + mov ecx, [eax + KDISK_OFS_NUMPARTITIONS] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] NumPartitions=%d\n", ecx + + test ecx, ecx + jz .no_partitions + + mov ecx, [eax + KDISK_OFS_PARTITIONS] ; ecx = Partitions array + mov ecx, [ecx] ; ecx = first PARTITION* + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Partition[0]=0x%x, old FSUserFuncs=0x%x\n", ecx, [ecx + KPARTITION_OFS_FSUSERFUNCTIONS] + + ; Заменить default_fs_functions на наши + mov dword [ecx + KPARTITION_OFS_FSUSERFUNCTIONS], vboxsf_user_functions + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Set FSUserFunctions=0x%x\n", vboxsf_user_functions + + ; Верификация: прочитать обратно и показать содержимое таблицы + mov eax, [ecx + KPARTITION_OFS_FSUSERFUNCTIONS] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Verify readback=0x%x\n", eax + mov eax, [vboxsf_user_functions + 0] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Table[0] free=0x%x\n", eax + mov eax, [vboxsf_user_functions + 4] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Table[1] count=%d\n", eax + mov eax, [vboxsf_user_functions + 8] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Table[2] subfn0_Read=0x%x\n", eax + mov eax, [vboxsf_user_functions + 12] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Table[3] subfn1_ReadFolder=0x%x\n", eax + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Disk + FS registered, accessible via /vbox/1/\n" + mov eax, [vboxsf_data.disk_handle] + ret + +.no_folders: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] No folders to register\n" + xor eax, eax + ret + +.no_partitions: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] ERROR: DiskMediaChanged created 0 partitions\n" + xor eax, eax + ret + +.disk_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] DiskAdd failed\n" + xor eax, eax + ret + +endp + +; shfl_init — Инициализация SharedFolder сервиса +proc shfl_init uses ebx ecx edx esi edi + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Initializing SharedFolders...\n" + + ; Проверка что HGCM готов + mov eax, [vbox_device.hgcm_connect_virt] + test eax, eax + jz .hgcm_not_ready + + ; Уже подключены? + cmp dword [sf_state.connected], 1 + je .already_connected + + ; --- Выделить init-буферы --- + invoke KernelAlloc, 4096 ; sf_packet + test eax, eax + jz .alloc_fail + mov [sf_state.packet_ptr], eax + + invoke KernelAlloc, 4096 ; sf_listbuf + test eax, eax + jz .alloc_fail_free1 + mov [sf_state.listbuf_ptr], eax + + invoke KernelAlloc, 4096 ; small_bufs: mappings+namebuf+cp866+dir_path+createparms + test eax, eax + jz .alloc_fail_free2 + mov [sf_state.small_bufs_ptr], eax + + ; Вычислить указатели внутри small_bufs + lea ecx, [eax + 0] + mov [sf_state.mappings_ptr], ecx ; +0: 512 байт + lea ecx, [eax + 512] + mov [sf_state.namebuf_ptr], ecx ; +512: 512 байт + lea ecx, [eax + 1024] + mov [sf_state.cp866_buf_ptr], ecx ; +1024: 512 байт + lea ecx, [eax + 1536] + mov [sf_state.dir_path_ptr], ecx ; +1536: 32 байт + lea ecx, [eax + 1568] + mov [sf_state.createparms_ptr], ecx ; +1568: 128 байт + + DEBUGF 2, "[VBoxGuest] [SF] Init buffers allocated\n" + + ; Подключение к HGCM сервису + DEBUGF 2, "[VBoxGuest] [SF] Connecting to '%s'...\n", sz_shfl_hgcm_service + stdcall hgcm_connect, sz_shfl_hgcm_service + test eax, eax + jz .connect_failed + + mov [sf_state.client_id], eax + mov dword [sf_state.connected], 1 + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Connected! client_id=%d\n", eax + + ; --- Выделить FS-буферы --- + invoke KernelAlloc, 4096 ; fs_packet + test eax, eax + jz .fs_alloc_fail + mov [sf_state.fs_packet_ptr], eax + + invoke KernelAlloc, 4096 ; fs_listbuf + test eax, eax + jz .fs_alloc_fail_f1 + mov [sf_state.fs_listbuf_ptr], eax + + invoke KernelAlloc, 65536 ; fs_iobuf (64KB bounce buffer) + test eax, eax + jz .fs_alloc_fail_f2 + mov [sf_state.fs_iobuf_ptr], eax + + invoke KernelAlloc, 4096 ; fs_cparms (page-aligned for PageList) + test eax, eax + jz .fs_alloc_fail_f3 + mov [sf_state.fs_cparms_ptr], eax + + invoke KernelAlloc, 4096 ; fs_small_bufs: pathbuf+pathbuf2 + test eax, eax + jz .fs_alloc_fail_f4 + mov [sf_state.fs_small_bufs_ptr], eax + + ; Вычислить указатели внутри fs_small_bufs + lea ecx, [eax + 0] + mov [sf_state.fs_pathbuf_ptr], ecx ; +0: 1024 байт + lea ecx, [eax + 1024] + mov [sf_state.fs_pathbuf2_ptr], ecx ; +1024: 1024 байт + + DEBUGF 2, "[VBoxGuest] [SF] FS buffers allocated\n" + + ; Перечислить папки, подключить, зарегистрировать диск+ФС + stdcall shfl_list_all + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Initialized OK\n" + xor eax, eax + ret + +.connect_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] HGCM connect failed\n" + ; Init-буферы уже выделены — освободить + call shfl_free_init_bufs + mov eax, VERR_HGCM_SERVICE_NOT_FOUND + ret + +; FS buffer allocation failure cascade +.fs_alloc_fail_f4: + invoke KernelFree, [sf_state.fs_cparms_ptr] + mov dword [sf_state.fs_cparms_ptr], 0 +.fs_alloc_fail_f3: + invoke KernelFree, [sf_state.fs_iobuf_ptr] + mov dword [sf_state.fs_iobuf_ptr], 0 +.fs_alloc_fail_f2: + invoke KernelFree, [sf_state.fs_listbuf_ptr] + mov dword [sf_state.fs_listbuf_ptr], 0 +.fs_alloc_fail_f1: + invoke KernelFree, [sf_state.fs_packet_ptr] + mov dword [sf_state.fs_packet_ptr], 0 +.fs_alloc_fail: + DEBUGF 2, "[VBoxGuest] [SF] KernelAlloc failed for FS buffers\n" + ; HGCM уже подключен — отключить + stdcall hgcm_disconnect, [sf_state.client_id] + mov dword [sf_state.connected], 0 + mov dword [sf_state.client_id], 0 + call shfl_free_init_bufs + mov eax, VERR_NO_MEMORY + ret + +; Init buffer allocation failure cascade +.alloc_fail_free2: + invoke KernelFree, [sf_state.listbuf_ptr] + mov dword [sf_state.listbuf_ptr], 0 +.alloc_fail_free1: + invoke KernelFree, [sf_state.packet_ptr] + mov dword [sf_state.packet_ptr], 0 +.alloc_fail: + DEBUGF 2, "[VBoxGuest] [SF] KernelAlloc failed for init buffers\n" + mov eax, VERR_NO_MEMORY + ret + +.hgcm_not_ready: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] HGCM not ready\n" + mov eax, VERR_NOT_READY + ret + +.already_connected: + DEBUGF 2, "[VBoxGuest] [SF] Already connected\n" + xor eax, eax + ret +endp + +; shfl_list_all — Перечислить все shared folders, подключить, зарегистрировать +proc shfl_list_all uses ebx ecx edx esi edi +locals + map_idx dd ? + map_root dd ? +endl + + cmp dword [sf_state.connected], 1 + jne .not_connected + + ; ================================================================ + ; Шаг 1: SET_UTF8 (fn=16, 0 параметров) + ; ================================================================ + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + mov dword [edi + 0], 44 ; size (header only) + mov dword [edi + 4], 0x00010001 ; version + mov dword [edi + 8], 62 ; HGCM_CALL32 + mov dword [edi + 12], VERR_GENERAL_FAILURE ; rc + mov dword [edi + 16], 0 ; reserved1 + mov dword [edi + 20], 0x00000081 ; f_requestor + mov dword [edi + 24], 0 ; fu32Flags + mov dword [edi + 28], 0 ; result + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax ; client_id + mov dword [edi + 36], 16 ; SHFL_FN_SET_UTF8 + mov dword [edi + 40], 0 ; param_count = 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[SF] SET_UTF8 result: %d\n", eax + + ; ================================================================ + ; Шаг 2: QUERY_MAPPINGS (fn=1, 3 параметра) + ; ================================================================ + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 1024/4 + rep stosd + + mov edi, [sf_state.mappings_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + + mov dword [edi + 0], 96 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], 1 ; SHFL_FN_QUERY_MAPPINGS + mov dword [edi + 40], 3 ; 3 params + + ; parm[0] @ +44: flags = SHFL_MF_UTF8 (1) + mov dword [edi + 44], 1 + mov dword [edi + 48], 1 + mov dword [edi + 52], 0 + + ; parm[1] @ +56: numberOfMappings = SHFL_MAX_FOLDERS + mov dword [edi + 56], 1 + mov dword [edi + 60], SHFL_MAX_FOLDERS + mov dword [edi + 64], 0 + + ; parm[2] @ +68: PageList OUT, cbData = SHFL_MAX_FOLDERS * 8 + mov dword [edi + 68], 10 ; type = PAGELIST + mov dword [edi + 72], SHFL_MAX_FOLDERS * 8 + mov dword [edi + 76], SHFL_MAX_FOLDERS * 8 + + ; PageListInfo @ +80 + push edi + mov eax, [sf_state.mappings_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 80], 2 + mov eax, [sf_state.mappings_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +12] + mov ebx, dword [edi +28] + DEBUGF __DEBUG_SF__, "[SF] QUERY_MAPPINGS: rc=0x%x result=%d\n", eax, ebx + + test ebx, ebx + jnz .qm_failed + + mov ecx, dword [edi +60] + DEBUGF __DEBUG_SF__, "[SF] Found %d shared folders\n", ecx + + test ecx, ecx + jz .no_folders + + cmp ecx, SHFL_MAX_FOLDERS + jbe @f + mov ecx, SHFL_MAX_FOLDERS +@@: + mov [sf_map_count], ecx + + ; ================================================================ + ; Шаг 3: Для каждого mapping — получить имя, подключить, добавить + ; ================================================================ + mov dword [map_idx], 0 + mov esi, [sf_state.mappings_ptr] + +.folder_loop: + mov eax, [map_idx] + cmp eax, [sf_map_count] + jae .all_done + + push esi + + ; Получить root index из SHFLMAPPING + mov eax, [esi + 4] + + ; --- 3a: QUERY_MAP_NAME --- + mov edi, [sf_state.namebuf_ptr] + push eax + xor eax, eax + mov ecx, 512/4 + rep stosd + pop eax + mov edi, [sf_state.namebuf_ptr] + mov word [edi + 0], 508 + mov word [edi + 2], 0 + + push eax + + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + pop eax + mov edi, [sf_state.packet_ptr] + + mov dword [edi + 0], 92 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + push eax + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + pop eax + mov dword [edi + 36], 2 + mov dword [edi + 40], 2 + + mov dword [edi + 44], 1 + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 10 + mov dword [edi + 60], 512 + mov dword [edi + 64], 68 + + push edi + mov eax, [sf_state.namebuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 68], 3 + mov eax, [sf_state.namebuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 72], ax + mov word [edi + 74], 1 + mov dword [edi + 76], ebx + mov dword [edi + 80], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + test eax, eax + jnz .name_failed + + mov edi, [sf_state.namebuf_ptr] + movzx eax, word [edi + 2] + test eax, eax + jz .name_failed + + ; Null-terminate имя + lea edi, [edi + 4] + mov byte [edi + eax], 0 + + ; Конвертировать UTF-8 -> CP866 для отладочного вывода + push eax + stdcall sf_utf8_to_cp866, edi, [sf_state.cp866_buf_ptr], eax + pop eax + + DEBUGF __DEBUG_SF__, "[SF] Folder[%d]: '%s'\n", [map_idx], [sf_state.cp866_buf_ptr] + + ; --- 3b: MAP_FOLDER --- + mov edi, [sf_state.namebuf_ptr] + movzx eax, word [edi + 2] + inc eax + mov word [edi + 0], ax + + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + + mov dword [edi + 0], 108 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], 17 + mov dword [edi + 40], 4 + + mov dword [edi + 44], 10 + push ecx + mov ecx, [sf_state.namebuf_ptr] + movzx eax, word [ecx + 0] + pop ecx + add eax, 4 + mov dword [edi + 48], eax + mov dword [edi + 52], 92 + + mov dword [edi + 56], 1 + mov dword [edi + 60], 0 + mov dword [edi + 64], 0 + + mov dword [edi + 68], 1 + mov dword [edi + 72], '/' + mov dword [edi + 76], 0 + + mov dword [edi + 80], 1 + mov dword [edi + 84], 1 + mov dword [edi + 88], 0 + + push edi + mov eax, [sf_state.namebuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 92], 1 + mov eax, [sf_state.namebuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 96], ax + mov word [edi + 98], 1 + mov dword [edi + 100], ebx + mov dword [edi + 104], 0 + + stdcall hgcm_send_request, edi + + mov ebx, dword [edi +28] + test ebx, ebx + jnz .map_failed + + ; Получить root handle + mov eax, dword [edi +60] + mov [map_root], eax + DEBUGF __DEBUG_SF__, "[SF] Mapped, root=%d\n", eax + + ; --- 3c: Добавить папку в массив для диска --- + mov edi, [sf_state.namebuf_ptr] + lea edi, [edi + 4] ; UTF-8 имя + stdcall vboxsf_add_folder, eax, edi + + ; --- 3d: Листинг корневой директории (debug) --- + stdcall shfl_list_root, [map_root] + + jmp .next_folder + +.name_failed: + DEBUGF __DEBUG_SF__, "[SF] Folder[%d]: \n", [map_idx] + jmp .next_folder + +.map_failed: + DEBUGF __DEBUG_SF__, "[SF] Folder[%d]: \n", [map_idx], ebx + +.next_folder: + pop esi + add esi, 8 + inc dword [map_idx] + jmp .folder_loop + +.all_done: + DEBUGF __DEBUG_SF__, "[SF] All %d folders enumerated\n", [sf_map_count] + + ; --- Шаг 4: Зарегистрировать диск + ФС --- + stdcall vboxsf_register_disk + test eax, eax + jz .disk_failed + + DEBUGF __DEBUG_SF__, "[SF] Virtual disk registered OK\n" + xor eax, eax + ret + +.disk_failed: + DEBUGF __DEBUG_SF__, "[SF] Failed to register virtual disk\n" + xor eax, eax + ret + +.qm_failed: + DEBUGF __DEBUG_SF__, "[SF] QUERY_MAPPINGS failed: result=%d\n", ebx + ret + +.no_folders: + DEBUGF __DEBUG_SF__, "[SF] No shared folders found\n" + xor eax, eax + ret + +.not_connected: + DEBUGF __DEBUG_SF__, "[SF] Not connected\n" + mov eax, -1 + ret +endp + +; shfl_list_root — Листинг корневой директории mapped папки (debug) +proc shfl_list_root uses ebx ecx edx esi edi, root_handle:dword +locals + dir_handle_lo dd ? + dir_handle_hi dd ? + list_bytes dd ? + list_files dd ? +endl + + ; Подготовить SHFLSTRING path = "/" + mov edi, [sf_state.dir_path_ptr] + xor eax, eax + mov ecx, 4 + rep stosd + mov edi, [sf_state.dir_path_ptr] + mov word [edi + 0], 2 + mov word [edi + 2], 1 + mov byte [edi + 4], '/' + mov byte [edi + 5], 0 + + ; Подготовить SHFLCREATEPARMS + mov edi, [sf_state.createparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + mov edi, [sf_state.createparms_ptr] + mov dword [edi + 12], 0x00001104 + + ; CREATE "/" + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], 3 + mov dword [edi + 40], 3 + + mov dword [edi + 44], 1 + mov eax, [root_handle] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 10 + mov dword [edi + 60], 6 + mov dword [edi + 64], 80 + + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + push edi + mov eax, [sf_state.dir_path_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 80], 3 + mov eax, [sf_state.dir_path_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + push edi + mov eax, [sf_state.createparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 96], 3 + mov eax, [sf_state.createparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov ebx, dword [edi +28] + test ebx, ebx + jnz .create_failed + + mov ecx, [sf_state.createparms_ptr] + mov eax, dword [ecx + 0] + mov ebx, dword [ecx + 4] + mov [dir_handle_lo], eax + mov [dir_handle_hi], ebx + + ; LIST с пагинацией + mov dword [sf_list_resume], 0 + mov dword [sf_list_total], 0 + + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + mov dword [edi + 0], 172 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], 8 + mov dword [edi + 40], 8 + + mov dword [edi + 44], 1 + mov eax, [root_handle] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 2 + mov eax, [dir_handle_lo] + mov dword [edi + 60], eax + mov eax, [dir_handle_hi] + mov dword [edi + 64], eax + + mov dword [edi + 68], 1 + mov dword [edi + 72], 0 + mov dword [edi + 76], 0 + + mov dword [edi + 80], 1 + mov dword [edi + 84], 4096 + mov dword [edi + 88], 0 + + mov dword [edi + 92], 10 + mov dword [edi + 96], 0 + mov dword [edi + 100], 140 + + mov dword [edi + 104], 10 + mov dword [edi + 108], 4096 + mov dword [edi + 112], 156 + + mov dword [edi + 116], 1 + mov dword [edi + 120], 0 + mov dword [edi + 124], 0 + + mov dword [edi + 128], 1 + mov dword [edi + 132], 0 + mov dword [edi + 136], 0 + + mov dword [edi + 140], 1 + mov word [edi + 144], 0 + mov word [edi + 146], 1 + mov dword [edi + 148], 0 + mov dword [edi + 152], 0 + + push edi + mov eax, [sf_state.listbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + + mov dword [edi + 156], 2 + mov eax, [sf_state.listbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 160], ax + mov word [edi + 162], 1 + mov dword [edi + 164], ebx + mov dword [edi + 168], 0 + +.list_page: + push edi + mov edi, [sf_state.listbuf_ptr] + xor eax, eax + mov ecx, 4096/4 + rep stosd + pop edi + + mov edi, [sf_state.packet_ptr] + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov dword [edi + 84], 4096 + mov eax, [sf_list_resume] + mov dword [edi + 120], eax + mov dword [edi + 132], 0 + + stdcall hgcm_send_request, edi + + mov ebx, dword [edi +28] + + cmp ebx, -201 + je .list_done + cmp ebx, -18 + je .list_done + + test ebx, ebx + js .list_failed + + mov eax, dword [edi +84] + mov [list_bytes], eax + mov eax, dword [edi +132] + mov [list_files], eax + + test eax, eax + jz .list_done + + mov esi, [sf_state.listbuf_ptr] + xor ebx, ebx + +.parse_entry: + cmp ebx, [list_files] + jae .page_done + + mov eax, esi + sub eax, [sf_state.listbuf_ptr] + cmp eax, [list_bytes] + jae .page_done + + mov eax, dword [esi + SHFLDIRINFO_OFS_SIZE_LO] + mov ecx, dword [esi + SHFLDIRINFO_OFS_FMODE] + movzx edx, word [esi + SHFLDIRINFO_OFS_NAME_LENGTH] + + push eax ecx esi + lea eax, [esi + SHFLDIRINFO_OFS_NAME_STRING] + stdcall sf_utf8_to_cp866, eax, [sf_state.cp866_buf_ptr], edx + pop esi ecx eax + + push esi ebx eax + mov edx, [sf_list_total] + add edx, ebx + + test ecx, 0x4000 + jnz .is_dir + + DEBUGF __DEBUG_SF__, "[SF] [%d] F '%s' size=%d\n", edx, [sf_state.cp866_buf_ptr], eax + jmp .printed +.is_dir: + DEBUGF __DEBUG_SF__, "[SF] [%d] D '%s'\n", edx, [sf_state.cp866_buf_ptr] +.printed: + pop eax ebx esi + + movzx eax, word [esi + SHFLDIRINFO_OFS_NAME_SIZE] + add eax, SHFLDIRINFO_OFS_NAME_STRING + add esi, eax + + inc ebx + jmp .parse_entry + +.page_done: + mov eax, [list_files] + add [sf_list_total], eax + + mov eax, dword [edi +120] + mov [sf_list_resume], eax + + mov eax, dword [edi +28] + test eax, eax + jg .list_page + + mov eax, [list_files] + test eax, eax + jnz .list_page + +.list_done: + DEBUGF __DEBUG_SF__, "[SF] Total: %d entries\n", [sf_list_total] + +.close_dir: + mov edi, [sf_state.packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.packet_ptr] + mov dword [edi + 0], 68 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], 4 + mov dword [edi + 40], 2 + + mov dword [edi + 44], 1 + mov eax, [root_handle] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 2 + mov eax, [dir_handle_lo] + mov dword [edi + 60], eax + mov eax, [dir_handle_hi] + mov dword [edi + 64], eax + + stdcall hgcm_send_request, edi + + xor eax, eax + ret + +.create_failed: + DEBUGF __DEBUG_SF__, "[SF] CREATE '/' failed: result=%d\n", ebx + ret + +.list_failed: + DEBUGF __DEBUG_SF__, "[SF] LIST failed: result=%d\n", ebx + jmp .close_dir +endp + +; shfl_disable — Отключить SharedFolder сервис +proc shfl_disable uses ebx ecx edx esi edi + DEBUGF 2, "[VBoxGuest] [SF] Disabling (removing disk)...\n" + + ; 1. Заблокировать FS callbacks (guard в vboxsf.inc) + mov dword [sf_state.connected], 0 + + ; 2. Удалить диск из системы + ; DiskDel вызывает disk_media_changed(0) внутри → + ; → освобождает разделы (вызывает vboxsf_fs_free) → + ; → удаляет диск из глобального списка + cmp dword [vboxsf_data.disk_handle], 0 + je .no_disk + invoke DiskDel, [vboxsf_data.disk_handle] + mov dword [vboxsf_data.disk_handle], 0 + DEBUGF 2, "[VBoxGuest] [SF] DiskDel done\n" +.no_disk: + + ; 3. Отключить HGCM + cmp dword [sf_state.client_id], 0 + je .no_hgcm + stdcall hgcm_disconnect, [sf_state.client_id] + mov dword [sf_state.client_id], 0 +.no_hgcm: + + ; 4. Освободить буферы + call shfl_free_fs_bufs + call shfl_free_init_bufs + + ; 5. Освободить SF_FOLDER структуры и сбросить счётчик + call shfl_free_folders + + DEBUGF 2, "[VBoxGuest] [SF] Disabled, disk removed\n" + xor eax, eax + ret +endp + +; shfl_free_init_bufs — Освободить init-буферы SharedFolders +proc shfl_free_init_bufs + cmp dword [sf_state.packet_ptr], 0 + je @f + invoke KernelFree, [sf_state.packet_ptr] + mov dword [sf_state.packet_ptr], 0 +@@: + cmp dword [sf_state.listbuf_ptr], 0 + je @f + invoke KernelFree, [sf_state.listbuf_ptr] + mov dword [sf_state.listbuf_ptr], 0 +@@: + cmp dword [sf_state.small_bufs_ptr], 0 + je @f + invoke KernelFree, [sf_state.small_bufs_ptr] + mov dword [sf_state.small_bufs_ptr], 0 + ; Обнулить указатели-потомки + mov dword [sf_state.mappings_ptr], 0 + mov dword [sf_state.namebuf_ptr], 0 + mov dword [sf_state.cp866_buf_ptr], 0 + mov dword [sf_state.dir_path_ptr], 0 + mov dword [sf_state.createparms_ptr], 0 +@@: + ret +endp + +; shfl_free_fs_bufs — Освободить FS-буферы SharedFolders +proc shfl_free_fs_bufs + cmp dword [sf_state.fs_packet_ptr], 0 + je @f + invoke KernelFree, [sf_state.fs_packet_ptr] + mov dword [sf_state.fs_packet_ptr], 0 +@@: + cmp dword [sf_state.fs_listbuf_ptr], 0 + je @f + invoke KernelFree, [sf_state.fs_listbuf_ptr] + mov dword [sf_state.fs_listbuf_ptr], 0 +@@: + cmp dword [sf_state.fs_iobuf_ptr], 0 + je @f + invoke KernelFree, [sf_state.fs_iobuf_ptr] + mov dword [sf_state.fs_iobuf_ptr], 0 +@@: + cmp dword [sf_state.fs_cparms_ptr], 0 + je @f + invoke KernelFree, [sf_state.fs_cparms_ptr] + mov dword [sf_state.fs_cparms_ptr], 0 +@@: + cmp dword [sf_state.fs_small_bufs_ptr], 0 + je @f + invoke KernelFree, [sf_state.fs_small_bufs_ptr] + mov dword [sf_state.fs_small_bufs_ptr], 0 + ; Обнулить указатели-потомки + mov dword [sf_state.fs_pathbuf_ptr], 0 + mov dword [sf_state.fs_pathbuf2_ptr], 0 +@@: + ret +endp + +; shfl_free_folders — Освободить SF_FOLDER структуры и сбросить счётчик +proc shfl_free_folders uses ebx esi + mov ebx, [vboxsf_data.count] + test ebx, ebx + jz .done + xor esi, esi +.loop: + cmp dword [vboxsf_data.folders + esi*4], 0 + je .next + invoke KernelFree, [vboxsf_data.folders + esi*4] + mov dword [vboxsf_data.folders + esi*4], 0 +.next: + inc esi + dec ebx + jnz .loop +.done: + mov dword [vboxsf_data.count], 0 + ret +endp + +include 'vboxsf.inc' + +REGISTER_SERVICE svc_shfl_name, 0, VMMDEV_GUEST_SUPPORTS_SHARED_FOLDERS, \ + shfl_init, shfl_init, shfl_disable, 0, 0, AUTOSTART_SHARED_FOLDERS diff --git a/drivers/vboxguest/services/shared_folders/vboxsf.inc b/drivers/vboxguest/services/shared_folders/vboxsf.inc new file mode 100644 index 000000000..d810c486a --- /dev/null +++ b/drivers/vboxguest/services/shared_folders/vboxsf.inc @@ -0,0 +1,3153 @@ +; ============================================================================= +; Модуль : VBox SharedFolder FS Callbacks +; Назначение : DiskAdd DISKFUNC + FsAdd UserFuncs для ФС VBox Shared Folders +; Файл : services/shared_folders/vboxsf.inc +; +; Архитектура: +; FsAdd регистрирует тип ФС "vboxsf" с create_partition callback. +; DiskAdd создаёт виртуальный диск "vbox". +; Ядро вызывает create_partition — мы ставим наши UserFuncs. +; ReadFolder обслуживает листинг через SHFL HGCM. +; +; Путь доступа: /vbox/1/FolderName/path/to/file +; FolderName = имя shared folder из VirtualBox +; path/to/file = путь внутри shared folder +; ============================================================================= + +; --- Строки --- +sz_vboxsf_diskname db 'vbox', 0 +sz_vboxsf_fsname db 'vboxsf', 0 + +; Таблица DISKFUNC для DiskAdd +align 4 +vboxsf_disk_functions: + dd vboxsf_disk_functions_end - vboxsf_disk_functions + dd vboxsf_disk_close ; close + dd 0 ; closemedia + dd vboxsf_querymedia ; querymedia + dd vboxsf_read_sectors ; read + dd vboxsf_write_sectors ; write + dd 0 ; flush + dd vboxsf_adjust_cache ; adjust_cache_size +vboxsf_disk_functions_end: + +; Таблица UserFuncs для FsAdd (ФС callbacks, dispatch по номеру subfn) +align 4 +vboxsf_user_functions: + dd vboxsf_fs_free + dd (vboxsf_user_functions_end - vboxsf_user_functions - 8) / 4 + dd vboxsf_fs_Read ; subfn 0 + dd vboxsf_fs_ReadFolder ; subfn 1 + dd vboxsf_fs_CreateFile ; subfn 2 + dd vboxsf_fs_Write ; subfn 3 + dd vboxsf_fs_SetFileEnd ; subfn 4 + dd vboxsf_fs_GetFileInfo ; subfn 5 + dd 0 ; subfn 6: SetFileInfo + dd 0 ; subfn 7: reserved + dd vboxsf_fs_Delete ; subfn 8 + dd vboxsf_fs_CreateFolder ; subfn 9 + dd vboxsf_fs_Rename ; subfn 10 +vboxsf_user_functions_end: + +; FS буферы выделяются динамически в shfl_init, указатели в sf_state.fs_*_ptr + +; Спинлок для сериализации FS-операций (HGCM буферы общие) +align 4 +vboxsf_fs_lock dd 0 + +; Таблица дней в месяце (для конвертации timestamp) +vboxsf_month_days db 31,28,31,30,31,30,31,31,30,31,30,31 + +; vboxsf_lock_fs / vboxsf_unlock_fs — простой спинлок +vboxsf_lock_fs: + lock bts dword [vboxsf_fs_lock], 0 + jc vboxsf_lock_fs + ret + +vboxsf_unlock_fs: + mov dword [vboxsf_fs_lock], 0 + ret + +; --- DISKFUNC CALLBACKS --- + +; vboxsf_querymedia — информация о виртуальном диске +proc vboxsf_querymedia stdcall, userdata:dword, info:dword + mov ecx, [info] + mov dword [ecx + DISKMEDIAINFO.flags], 0 + mov dword [ecx + DISKMEDIAINFO.sectorsize], 512 + ; Виртуальная ёмкость — 1 сектор (нам не нужны реальные секторы) + mov dword [ecx + DISKMEDIAINFO.capacity], 1 + mov dword [ecx + DISKMEDIAINFO.capacity + 4], 0 + xor eax, eax + ret +endp + +; vboxsf_read_sectors — чтение секторов (возвращает нули, чтобы ядро создало raw-раздел) +proc vboxsf_read_sectors stdcall uses edi, userdata:dword, buffer:dword, \ + startsector_lo:dword, startsector_hi:dword, numsectors:dword + ; Заполнить буфер нулями — ядро не найдёт MBR и создаст raw-раздел + ; ВАЖНО: numsectors — это int* (указатель), не значение! + mov edi, [buffer] + mov ecx, [numsectors] ; ecx = указатель на количество секторов + mov ecx, [ecx] ; ecx = само количество секторов + shl ecx, 7 ; * 128 = 512/4 dwords per sector + xor eax, eax + rep stosd + xor eax, eax + ret +endp + +; vboxsf_write_sectors — запись секторов (не поддерживается) +proc vboxsf_write_sectors stdcall, userdata:dword, buffer:dword, \ + startsector_lo:dword, startsector_hi:dword, numsectors:dword + mov eax, -1 ; error: read-only + ret +endp + +; vboxsf_disk_close — закрыть диск +proc vboxsf_disk_close stdcall, userdata:dword + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] disk_close\n" + xor eax, eax + ret +endp + +; vboxsf_adjust_cache — настройка кэша (не используем) +proc vboxsf_adjust_cache stdcall, userdata:dword, suggested:dword + xor eax, eax + ret +endp + +; --- FsAdd CREATE_PARTITION CALLBACK --- + +; vboxsf_create_partition — проверить, наш ли это диск, и установить UserFuncs +vboxsf_create_partition: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition CALLED: ebp=0x%x ebx=0x%x\n", ebp, ebx + + ; Проверить ebp != 0 + test ebp, ebp + jz .bad_ebp + + ; Проверить: PARTITION.Disk -> DISK.UserData == vboxsf_data? + mov eax, [ebp + KPARTITION_OFS_DISK] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: PARTITION.Disk=0x%x\n", eax + + test eax, eax + jz .bad_disk + + mov eax, [eax + KDISK_OFS_USERDATA] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: UserData=0x%x (want 0x%x)\n", eax, vboxsf_data + + cmp eax, vboxsf_data + jne .not_ours + + ; Это наш виртуальный диск — установить FS callbacks + mov dword [ebp + KPARTITION_OFS_FSUSERFUNCTIONS], vboxsf_user_functions + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: OUR disk! partition=0x%x\n", ebp + mov eax, ebp + ret + +.not_ours: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: not ours, skip\n" + xor eax, eax + ret + +.bad_ebp: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: ERROR ebp=0!\n" + xor eax, eax + ret + +.bad_disk: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] create_partition: ERROR Disk ptr=0!\n" + xor eax, eax + ret + +; --- FS USERFUNCS CALLBACKS --- + +; vboxsf_fs_free — cleanup callback (UserFuncs[0]) +vboxsf_fs_free: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] fs_free called\n" + ret + +; Guard: если HGCM отключен — вернуть ERROR_DEVICE (safety net) +; Вызывается ДО push регистров, поэтому просто ret. +vboxsf_not_connected: + DEBUGF 2, "[VBoxGuest] [SF] FS callback: HGCM not connected!\n" + mov eax, ERROR_DEVICE + xor ebx, ebx + ret + +; vboxsf_fs_ReadFolder — листинг директории (subfn 1) +vboxsf_fs_ReadFolder: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] ReadFolder: path='%s'\n", esi + + ; ebx = params ptr (сохраняем в edx для безопасности) + mov edx, ebx + + ; Определить тип пути + cmp byte [esi], 0 + je .rf_root + cmp byte [esi], '/' + jne .rf_subdir + cmp byte [esi + 1], 0 + je .rf_root + + ; Пропустить начальный '/' + inc esi + +.rf_subdir: + ; --- Листинг поддиректории через SHFL --- + ; Найти имя shared folder (первый компонент пути) + mov edi, esi + xor ecx, ecx +.rf_find_slash: + cmp byte [edi + ecx], '/' + je .rf_has_subpath + cmp byte [edi + ecx], 0 + je .rf_folder_root + inc ecx + cmp ecx, 256 + jb .rf_find_slash + jmp .rf_error_notfound + +.rf_folder_root: + ; Путь = "FolderName" (корень shared folder) + stdcall vboxsf_find_folder_by_name, esi, ecx + test eax, eax + jz .rf_error_notfound + + ; eax = SF_FOLDER*, листим "/" + push dword [edx + 16] ; buffer + push dword [edx + 12] ; count + push dword [edx + 4] ; start + push 0 ; subpath = NULL (корень) + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_list + jmp .rf_done + +.rf_has_subpath: + ; Путь = "FolderName/sub/path" + push ecx ; save name_len + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .rf_error_notfound + + lea esi, [esi + ecx + 1] ; subpath after '/' + push dword [edx + 16] ; buffer + push dword [edx + 12] ; count + push dword [edx + 4] ; start + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_list + jmp .rf_done + +.rf_root: + ; --- Листинг корня: список shared folders --- + stdcall vboxsf_list_root_folders, [edx + 4], [edx + 12], [edx + 16] + ; eax = error, ebx = count + jmp .rf_done + +.rf_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.rf_done: + ; eax = error code, ebx = count (установлены вызванными функциями) + pop esi edi ebp + ret + +; vboxsf_list_root_folders — вывести список shared folders как директории +proc vboxsf_list_root_folders stdcall uses ecx edx esi edi, \ + start:dword, count:dword, buffer:dword +locals + written dd ? +endl + mov dword [written], 0 + + mov edi, [buffer] + + ; Заголовок (32 байта) + mov dword [edi + BDFE_HEADER.version], 1 + mov dword [edi + BDFE_HEADER.entries_placed], 0 ; обновим позже + mov eax, [vboxsf_data.count] + mov dword [edi + BDFE_HEADER.total_entries], eax + ; Reserved 20 bytes + xor eax, eax + mov dword [edi + BDFE_HEADER.reserved + 0], eax + mov dword [edi + BDFE_HEADER.reserved + 4], eax + mov dword [edi + BDFE_HEADER.reserved + 8], eax + mov dword [edi + BDFE_HEADER.reserved + 12], eax + mov dword [edi + BDFE_HEADER.reserved + 16], eax + add edi, BDFE_HEADER_SIZE + + mov ecx, [count] + test ecx, ecx + jz .done + + mov esi, [start] + +.next: + cmp esi, [vboxsf_data.count] + jae .done + test ecx, ecx + jz .done + + ; Получить SF_FOLDER* + mov eax, [vboxsf_data.folders + esi*4] + test eax, eax + jz .skip + + ; --- Заполнить BDFE запись (304 байта) --- + ; Attributes = FA_FOLDER (0x10) + mov dword [edi + BDFE_OFS_ATTR], FA_FOLDER + ; Encoding = CP866 (0) + mov dword [edi + BDFE_OFS_ENCODING], 0 + ; Timestamps = 0 (виртуальные папки, дата неизвестна) + push edi + add edi, BDFE_OFS_CTIME + xor eax, eax + stosd + stosd + stosd + stosd + stosd + stosd + stosd + stosd + pop edi + + ; Имя: конвертировать UTF-8 -> CP866 + push ecx esi + mov esi, [vboxsf_data.folders + esi*4] + lea esi, [esi + SF_FOLDER.name] + ; Определить длину UTF-8 имени + xor ecx, ecx +.namelen: + cmp byte [esi + ecx], 0 + je .namelen_done + inc ecx + cmp ecx, 255 + jb .namelen +.namelen_done: + lea eax, [edi + BDFE_OFS_NAME] + stdcall sf_utf8_to_cp866, esi, eax, ecx + pop esi ecx + + add edi, BDFE_SIZE + inc dword [written] + dec ecx + +.skip: + inc esi + jmp .next + +.done: + ; Обновить количество записей в заголовке + mov eax, [buffer] + mov edx, [written] + mov [eax + BDFE_HEADER.entries_placed], edx + + mov ebx, edx ; return count in ebx + xor eax, eax ; ERROR_SUCCESS + ret +endp + +; vboxsf_do_list — листинг директории через SHFL HGCM +proc vboxsf_do_list stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword, start:dword, count:dword, buffer:dword +locals + dir_handle_lo dd ? + dir_handle_hi dd ? + list_bytes dd ? + list_files dd ? + cur_index dd ? + written dd ? + resume_pt dd ? +endl + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: root=%d subpath=0x%x start=%d count=%d\n", \ + [root], [subpath], [start], [count] + + call vboxsf_lock_fs + + mov dword [cur_index], 0 + mov dword [written], 0 + mov dword [resume_pt], 0 + + ; --- Заполнить заголовок буфера --- + mov edi, [buffer] + mov dword [edi + BDFE_HEADER.version], 1 + mov dword [edi + BDFE_HEADER.entries_placed], 0 + mov dword [edi + BDFE_HEADER.total_entries], 0 ; обновим позже + xor eax, eax + mov dword [edi + BDFE_HEADER.reserved + 0], eax + mov dword [edi + BDFE_HEADER.reserved + 4], eax + mov dword [edi + BDFE_HEADER.reserved + 8], eax + mov dword [edi + BDFE_HEADER.reserved + 12], eax + mov dword [edi + BDFE_HEADER.reserved + 16], eax + + ; --- Построить SHFLSTRING для пути --- + cmp dword [subpath], 0 + je .build_root_path + + ; Путь = "/" + subpath + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 ; u16Size (заполним) + mov word [edi + 2], 0 ; u16Length (заполним) + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + ; Копировать subpath (ASCII часть CP866 == UTF-8 для ASCII) + mov esi, [subpath] + xor ecx, ecx +.copy_subpath: + lodsb + cmp al, 0 + je .subpath_done + stosb + inc ecx + cmp ecx, 1000 + jb .copy_subpath +.subpath_done: + mov byte [edi], 0 + inc ecx ; +1 for '/' + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx ; u16Length + inc ecx ; +1 for null + mov word [edx + 0], cx ; u16Size + jmp .open_dir + +.build_root_path: + ; Путь = "/" + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 0], 2 ; u16Size + mov word [edx + 2], 1 ; u16Length + mov byte [edx + 4], '/' + mov byte [edx + 5], 0 + +.open_dir: + ; --- SHFL_FN_CREATE — открыть директорию --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + ; SHFL_CF_DIRECTORY | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], 0x00001104 + + ; Собрать HGCM пакет CREATE + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + ; parm[0]: root + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 ; + SHFLSTRING header + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + ; parm[2]: createparms (PageList) + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + ; PLI for path @ +80 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + ; PLI for cparms @ +96 + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: CREATE result=%d\n", eax + test eax, eax + jnz .create_failed + + ; Сохранить dir handle + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 0] + mov [dir_handle_lo], eax + mov eax, dword [ecx + 4] + mov [dir_handle_hi], eax + mov ebx, dword [ecx + 8] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: handle=%d:%d create_result=%d\n", [dir_handle_hi], [dir_handle_lo], ebx + + ; --- SHFL_FN_LIST — построить базовый пакет --- + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 172 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_LIST + mov dword [edi + 40], 8 + + ; parm[0]: root + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: handle (64bit) + mov dword [edi + 56], 2 + mov eax, [dir_handle_lo] + mov dword [edi + 60], eax + mov eax, [dir_handle_hi] + mov dword [edi + 64], eax + + ; parm[2]: flags = 0 + mov dword [edi + 68], 1 + mov dword [edi + 72], 0 + mov dword [edi + 76], 0 + + ; parm[3]: cb = 4096 + mov dword [edi + 80], 1 + mov dword [edi + 84], 4096 + mov dword [edi + 88], 0 + + ; parm[4]: filter (empty) + mov dword [edi + 92], 10 + mov dword [edi + 96], 0 + mov dword [edi + 100], 140 + + ; parm[5]: buffer OUT + mov dword [edi + 104], 10 + mov dword [edi + 108], 4096 + mov dword [edi + 112], 156 + + ; parm[6]: resumePoint + mov dword [edi + 116], 1 + mov dword [edi + 120], 0 + mov dword [edi + 124], 0 + + ; parm[7]: cFiles + mov dword [edi + 128], 1 + mov dword [edi + 132], 0 + mov dword [edi + 136], 0 + + ; PLI for filter @ +140 + mov dword [edi + 140], 1 + mov word [edi + 144], 0 + mov word [edi + 146], 1 + mov dword [edi + 148], 0 + mov dword [edi + 152], 0 + + ; PLI for buffer @ +156 + push edi + mov eax, [sf_state.fs_listbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 156], 2 + mov eax, [sf_state.fs_listbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 160], ax + mov word [edi + 162], 1 + mov dword [edi + 164], ebx + mov dword [edi + 168], 0 + + ; --- Цикл пагинации LIST --- +.list_page: + ; Очистить listbuf + push edi + mov edi, [sf_state.fs_listbuf_ptr] + xor eax, eax + mov ecx, 4096/4 + rep stosd + pop edi + + ; Обновить пакет + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov dword [edi + 84], 4096 + mov eax, [resume_pt] + mov dword [edi + 120], eax + mov dword [edi + 132], 0 + + stdcall hgcm_send_request, edi + + ; edi = fs_packet_ptr (preserved by stdcall) + mov eax, dword [edi + 28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: LIST rc=0x%x result=%d cb=%d files=%d\n", \ + [edi + 12], eax, [edi + 84], [edi + 132] + + ; VERR_NO_MORE_FILES = -18 или -201 (VBox 7.x вариант) + cmp eax, -18 + je .list_end + cmp eax, -201 + je .list_end + + test eax, eax + js .list_error + + ; Прочитать результаты (edi всё ещё = packet ptr) + mov eax, dword [edi + 84] + mov [list_bytes], eax + mov eax, dword [edi + 132] + mov [list_files], eax + + test eax, eax + jz .list_end + + ; --- Разбор SHFLDIRINFO записей, конвертация в BDFE --- + mov esi, [sf_state.fs_listbuf_ptr] + xor ecx, ecx ; entry index in page + +.parse_entry: + cmp ecx, [list_files] + jae .page_done + + ; Проверка границ + mov eax, esi + sub eax, [sf_state.fs_listbuf_ptr] + cmp eax, [list_bytes] + jae .page_done + + ; Проверяем: cur_index >= start && written < count? + mov eax, [cur_index] + cmp eax, [start] + jb .skip_entry + + mov eax, [written] + cmp eax, [count] + jae .skip_entry ; достаточно записей, но продолжаем считать total + + ; --- Конвертировать SHFLDIRINFO в BDFE --- + ; Вычислить адрес BDFE в выходном буфере + mov edi, [buffer] + add edi, BDFE_HEADER_SIZE + mov eax, [written] + imul eax, BDFE_SIZE + add edi, eax + + ; Attributes + mov eax, dword [esi + SHFLDIRINFO_OFS_FMODE] + test eax, S_IFDIR + jnz .entry_is_dir + ; Обычный файл + mov dword [edi + BDFE_OFS_ATTR], FA_ARCHIVED + jmp .entry_attr_done +.entry_is_dir: + mov dword [edi + BDFE_OFS_ATTR], FA_FOLDER +.entry_attr_done: + + ; Encoding = CP866 + mov dword [edi + BDFE_OFS_ENCODING], 0 + + ; Timestamps: BirthTime -> ctime, ModificationTime -> mtime, AccessTime -> atime + push ecx ; save entry loop counter + push esi ; save SHFLDIRINFO ptr + lea eax, [esi + SHFLOBJINFO_OFS_BTIME_LO] + lea ecx, [edi + BDFE_OFS_CTIME] + lea edx, [edi + BDFE_OFS_CDATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + + mov esi, [esp] ; restore esi without pop + lea eax, [esi + SHFLOBJINFO_OFS_ATIME_LO] + lea ecx, [edi + BDFE_OFS_ATIME] + lea edx, [edi + BDFE_OFS_ADATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + + pop esi + lea eax, [esi + SHFLOBJINFO_OFS_MTIME_LO] + lea ecx, [edi + BDFE_OFS_MTIME] + lea edx, [edi + BDFE_OFS_MDATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + pop ecx ; restore entry loop counter + + ; File size + mov eax, dword [esi + SHFLDIRINFO_OFS_SIZE_LO] + mov [edi + BDFE_OFS_SIZE_LO], eax + mov eax, dword [esi + SHFLDIRINFO_OFS_SIZE_HI] + mov [edi + BDFE_OFS_SIZE_HI], eax + + ; Name: UTF-8 -> CP866 + push ecx esi + movzx ecx, word [esi + SHFLDIRINFO_OFS_NAME_LENGTH] + lea esi, [esi + SHFLDIRINFO_OFS_NAME_STRING] + lea eax, [edi + BDFE_OFS_NAME] + stdcall sf_utf8_to_cp866, esi, eax, ecx + pop esi ecx + + inc dword [written] + +.skip_entry: + inc dword [cur_index] + + ; Перейти к следующей записи + movzx eax, word [esi + SHFLDIRINFO_OFS_NAME_SIZE] + add eax, SHFLDIRINFO_OFS_NAME_STRING + add esi, eax + + inc ecx + jmp .parse_entry + +.page_done: + ; Reload packet ptr (edi was used for BDFE output in parse loop) + mov edi, [sf_state.fs_packet_ptr] + + ; Обновить resume point + mov eax, dword [edi + 120] + mov [resume_pt], eax + + ; Всегда продолжать пагинацию для подсчёта total_entries + ; (файловый менеджер делает probe с count=0 чтобы узнать total) + mov eax, dword [edi + 28] + test eax, eax + jg .list_page ; VINF_BUFFER_OVERFLOW -> ещё есть + + mov eax, [list_files] + test eax, eax + jnz .list_page ; файлы есть -> продолжаем + +.list_end: + ; Обновить заголовок + mov edi, [buffer] + mov eax, [written] + mov [edi + BDFE_HEADER.entries_placed], eax + mov eax, [cur_index] + mov [edi + BDFE_HEADER.total_entries], eax + + ; Закрыть директорию + stdcall vboxsf_close_handle, [root], [dir_handle_lo], [dir_handle_hi] + + call vboxsf_unlock_fs + + mov ebx, [written] + xor eax, eax + ret + +.create_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: CREATE failed, result=%d\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.list_error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] do_list: LIST failed, result=%d\n", eax + stdcall vboxsf_close_handle, [root], [dir_handle_lo], [dir_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_DEVICE + xor ebx, ebx + ret +endp + +; vboxsf_fs_Read — чтение файла (subfn 0) +vboxsf_fs_Read: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: path='%s'\n", esi + + mov edx, ebx + + cmp byte [esi], '/' + jne .rd_parse + inc esi +.rd_parse: + cmp byte [esi], 0 + je .rd_error_notfound + + mov edi, esi + xor ecx, ecx +.rd_find_slash: + cmp byte [edi + ecx], '/' + je .rd_has_subpath + cmp byte [edi + ecx], 0 + je .rd_folder_only + inc ecx + cmp ecx, 256 + jb .rd_find_slash + jmp .rd_error_notfound + +.rd_folder_only: + ; Can't read a folder as a file + mov eax, ERROR_ACCESS_DENIED + xor ebx, ebx + jmp .rd_done + +.rd_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .rd_error_notfound + + lea esi, [esi + ecx + 1] + push dword [edx + 16] ; user_buffer + push dword [edx + 12] ; req_size + push dword [edx + 8] ; offset_hi + push dword [edx + 4] ; offset_lo + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_read + jmp .rd_done + +.rd_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.rd_done: + pop esi edi ebp + ret + +; vboxsf_fs_GetFileInfo — получить информацию о файле (subfn 5) +vboxsf_fs_GetFileInfo: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] GetFileInfo: path='%s'\n", esi + + mov edx, ebx + + cmp byte [esi], '/' + jne .gfi_parse + inc esi +.gfi_parse: + cmp byte [esi], 0 + je .gfi_volume + + mov edi, esi + xor ecx, ecx +.gfi_find_slash: + cmp byte [edi + ecx], '/' + je .gfi_has_subpath + cmp byte [edi + ecx], 0 + je .gfi_folder_only + inc ecx + cmp ecx, 256 + jb .gfi_find_slash + jmp .gfi_error_notfound + +.gfi_folder_only: + ; GetFileInfo on folder root -> LOOKUP "/" + stdcall vboxsf_find_folder_by_name, esi, ecx + test eax, eax + jz .gfi_error_notfound + push dword [edx + 16] + push 0 + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_getfileinfo + jmp .gfi_done + +.gfi_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .gfi_error_notfound + lea esi, [esi + ecx + 1] + push dword [edx + 16] + push esi + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_getfileinfo + jmp .gfi_done + +.gfi_volume: + ; Пустой путь = корень раздела, файла нет + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + jmp .gfi_done + +.gfi_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.gfi_done: + pop esi edi ebp + ret + +; vboxsf_fs_CreateFile — создание/перезапись файла (subfn 2) +vboxsf_fs_CreateFile: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFile: path='%s'\n", esi + + mov edx, ebx + + cmp byte [esi], '/' + jne .cf_parse + inc esi +.cf_parse: + cmp byte [esi], 0 + je .cf_error_notfound + + mov edi, esi + xor ecx, ecx +.cf_find_slash: + cmp byte [edi + ecx], '/' + je .cf_has_subpath + cmp byte [edi + ecx], 0 + je .cf_error_notfound ; Нельзя создать файл на уровне корня + inc ecx + cmp ecx, 256 + jb .cf_find_slash + jmp .cf_error_notfound + +.cf_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .cf_error_notfound + + lea esi, [esi + ecx + 1] + ; CreateFile: CREATE_IF_NEW | REPLACE_IF_EXISTS | ACCESS_READWRITE + push dword [edx + 16] ; user_buffer + push dword [edx + 12] ; req_size + push 0 ; offset_hi = 0 + push 0 ; offset_lo = 0 + push SHFL_CF_ACT_REPLACE_IF_EXISTS or SHFL_CF_ACCESS_READWRITE + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_write_common + jmp .cf_done + +.cf_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.cf_done: + pop esi edi ebp + ret + +; vboxsf_fs_Write — запись в файл (subfn 3) +vboxsf_fs_Write: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: path='%s'\n", esi + + mov edx, ebx + + cmp byte [esi], '/' + jne .wr_parse + inc esi +.wr_parse: + cmp byte [esi], 0 + je .wr_error_notfound + + mov edi, esi + xor ecx, ecx +.wr_find_slash: + cmp byte [edi + ecx], '/' + je .wr_has_subpath + cmp byte [edi + ecx], 0 + je .wr_folder_only + inc ecx + cmp ecx, 256 + jb .wr_find_slash + jmp .wr_error_notfound + +.wr_folder_only: + ; Нельзя писать в папку + mov eax, ERROR_ACCESS_DENIED + xor ebx, ebx + jmp .wr_done + +.wr_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .wr_error_notfound + + lea esi, [esi + ecx + 1] + ; Write: OPEN_IF_EXISTS | FAIL_IF_NEW | ACCESS_READWRITE + push dword [edx + 16] ; user_buffer + push dword [edx + 12] ; req_size + push dword [edx + 8] ; offset_hi + push dword [edx + 4] ; offset_lo + push SHFL_CF_ACT_FAIL_IF_NEW or SHFL_CF_ACCESS_READWRITE + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_write_common + jmp .wr_done + +.wr_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.wr_done: + pop esi edi ebp + ret + +; vboxsf_fs_CreateFolder — создание папки (subfn 9) +vboxsf_fs_CreateFolder: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: path='%s'\n", esi + + cmp byte [esi], '/' + jne .mkdir_parse + inc esi +.mkdir_parse: + cmp byte [esi], 0 + je .mkdir_error_notfound + + mov edi, esi + xor ecx, ecx +.mkdir_find_slash: + cmp byte [edi + ecx], '/' + je .mkdir_has_subpath + cmp byte [edi + ecx], 0 + je .mkdir_error_notfound ; Нельзя создать папку на уровне корня + inc ecx + cmp ecx, 256 + jb .mkdir_find_slash + jmp .mkdir_error_notfound + +.mkdir_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .mkdir_error_notfound + + lea esi, [esi + ecx + 1] + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_createfolder + jmp .mkdir_done + +.mkdir_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.mkdir_done: + pop esi edi ebp + ret + +; vboxsf_fs_Delete — удаление файла/каталога (subfn 8) +vboxsf_fs_Delete: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Delete: path='%s'\n", esi + + cmp byte [esi], '/' + jne .del_parse + inc esi +.del_parse: + cmp byte [esi], 0 + je .del_error_notfound + + mov edi, esi + xor ecx, ecx +.del_find_slash: + cmp byte [edi + ecx], '/' + je .del_has_subpath + cmp byte [edi + ecx], 0 + je .del_error_notfound ; Нельзя удалить shared folder + inc ecx + cmp ecx, 256 + jb .del_find_slash + jmp .del_error_notfound + +.del_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .del_error_notfound + + lea esi, [esi + ecx + 1] + push esi + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_delete + jmp .del_done + +.del_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.del_done: + pop esi edi ebp + ret + +; vboxsf_fs_Rename — переименование файла/каталога (subfn 10) +vboxsf_fs_Rename: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: src='%s'\n", esi + + mov edx, ebx + + ; Skip leading '/' in source + cmp byte [esi], '/' + jne .ren_parse_src + inc esi +.ren_parse_src: + cmp byte [esi], 0 + je .ren_error_notfound + + ; Find folder name in source + mov edi, esi + xor ecx, ecx +.ren_find_slash_src: + cmp byte [edi + ecx], '/' + je .ren_has_src_subpath + cmp byte [edi + ecx], 0 + je .ren_error_notfound + inc ecx + cmp ecx, 256 + jb .ren_find_slash_src + jmp .ren_error_notfound + +.ren_has_src_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .ren_error_notfound + + ; eax = SF_FOLDER*, save it and src subpath + push eax ; [esp+4] = SF_FOLDER* + lea esi, [esi + ecx + 1] ; src subpath after "FolderName/" + push esi ; [esp] = src_subpath + + ; --- Parse destination path from [edx+16] --- + ; Destination may be: + ; a) "FolderName/newpath" (kernel-stripped) + ; b) "/vbox/1/FolderName/newpath" (full absolute path) + ; c) "newname" (just a name, no slashes) + ; Strategy: skip components until we find the shared folder name, + ; then use the rest as dst subpath. If no folder name found, use as-is. + mov esi, [edx + 16] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: dst='%s'\n", esi + + ; Skip leading '/' + cmp byte [esi], '/' + jne .ren_dst_scan + inc esi + +.ren_dst_scan: + ; Find next '/' or end of string + mov edi, esi + xor ecx, ecx +.ren_dst_find_slash: + cmp byte [edi + ecx], '/' + je .ren_dst_try_folder + cmp byte [edi + ecx], 0 + je .ren_dst_no_slash + inc ecx + cmp ecx, 512 + jb .ren_dst_find_slash + jmp .ren_dst_use_asis ; overflow — use whatever we have + +.ren_dst_try_folder: + ; Component before '/' has length ecx. Check if it's a shared folder name. + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jnz .ren_dst_found_folder + + ; Not a folder name — skip this component ("vbox/", "1/", etc.) and try next + lea esi, [esi + ecx + 1] + jmp .ren_dst_scan + +.ren_dst_found_folder: + ; Found the folder name. Subpath is everything after "FolderName/" + lea edi, [esi + ecx + 1] ; edi = dst subpath + jmp .ren_dst_ready + +.ren_dst_no_slash: + ; No more slashes — treat entire remaining string as subpath + ; (handles case where dest is just "newname") + mov edi, esi + jmp .ren_dst_ready + +.ren_dst_use_asis: + mov edi, esi + +.ren_dst_ready: + ; edi = dst subpath + pop esi ; src_subpath + pop eax ; SF_FOLDER* + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: src_sub='%s' dst_sub='%s'\n", esi, edi + + push edi ; dst_subpath + push esi ; src_subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_rename + jmp .ren_done + +.ren_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.ren_done: + pop esi edi ebp + ret + +; vboxsf_fs_SetFileEnd — установка размера файла (subfn 4) +vboxsf_fs_SetFileEnd: + cmp dword [sf_state.connected], 0 + je vboxsf_not_connected + push ebp edi esi + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: path='%s'\n", esi + + mov edx, ebx + + cmp byte [esi], '/' + jne .sfe_parse + inc esi +.sfe_parse: + cmp byte [esi], 0 + je .sfe_error_notfound + + mov edi, esi + xor ecx, ecx +.sfe_find_slash: + cmp byte [edi + ecx], '/' + je .sfe_has_subpath + cmp byte [edi + ecx], 0 + je .sfe_error_notfound + inc ecx + cmp ecx, 256 + jb .sfe_find_slash + jmp .sfe_error_notfound + +.sfe_has_subpath: + push ecx + stdcall vboxsf_find_folder_by_name, esi, ecx + pop ecx + test eax, eax + jz .sfe_error_notfound + + lea esi, [esi + ecx + 1] + push dword [edx + 8] ; size_hi + push dword [edx + 4] ; size_lo + push esi ; subpath + push dword [eax + SF_FOLDER.root_handle] + call vboxsf_do_setfileend + jmp .sfe_done + +.sfe_error_notfound: + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + +.sfe_done: + pop esi edi ebp + ret + +; vboxsf_do_getfileinfo — SHFL_FN_CREATE с SHFL_CF_LOOKUP для получения атрибутов +proc vboxsf_do_getfileinfo stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword, buffer:dword + + call vboxsf_lock_fs + + ; --- Build SHFLSTRING for path --- + cmp dword [subpath], 0 + je .gfi_build_root + + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.gfi_copy_sub: + lodsb + cmp al, 0 + je .gfi_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .gfi_copy_sub +.gfi_sub_done: + mov byte [edi], 0 + inc ecx ; +1 for '/' + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx + inc ecx ; +1 for null + mov word [edx + 0], cx + jmp .gfi_do_lookup + +.gfi_build_root: + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 0], 2 + mov word [edx + 2], 1 + mov byte [edx + 4], '/' + mov byte [edx + 5], 0 + +.gfi_do_lookup: + ; --- Clear createparms --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + + ; SHFL_CF_LOOKUP = 0x00000001 + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], 0x00000001 + + ; --- Build HGCM CREATE packet (3 params, 2 PLIs) --- + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + ; parm[2]: createparms (PageList) + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + ; PLI for path @ +80 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + ; PLI for cparms @ +96 + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] GetFileInfo: LOOKUP result=%d\n", eax + test eax, eax + jnz .gfi_lookup_failed + + ; Check create result (SHFL_FILE_EXISTS = 3) + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 8] + cmp eax, 3 + jne .gfi_not_found + + ; --- Fill 40-byte BDFE --- + mov edi, [buffer] + + ; Attributes (from SHFLFSOBJINFO.fMode at cparms+16+48) + mov eax, dword [ecx + 16 + SHFLOBJINFO_OFS_FMODE] + test eax, S_IFDIR + jnz .gfi_is_dir + mov dword [edi + BDFE_OFS_ATTR], FA_ARCHIVED + jmp .gfi_attr_done +.gfi_is_dir: + mov dword [edi + BDFE_OFS_ATTR], FA_FOLDER +.gfi_attr_done: + + ; Encoding = 0 (CP866) + mov dword [edi + BDFE_OFS_ENCODING], 0 + + ; Timestamps: BirthTime -> ctime, ModificationTime -> mtime, AccessTime -> atime + ; SHFLFSOBJINFO is at cparms + 16 + mov ecx, [sf_state.fs_cparms_ptr] + lea eax, [ecx + 16 + SHFLOBJINFO_OFS_BTIME_LO] + lea ecx, [edi + BDFE_OFS_CTIME] + lea edx, [edi + BDFE_OFS_CDATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + + mov ecx, [sf_state.fs_cparms_ptr] + lea eax, [ecx + 16 + SHFLOBJINFO_OFS_ATIME_LO] + lea ecx, [edi + BDFE_OFS_ATIME] + lea edx, [edi + BDFE_OFS_ADATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + + mov ecx, [sf_state.fs_cparms_ptr] + lea eax, [ecx + 16 + SHFLOBJINFO_OFS_MTIME_LO] + lea ecx, [edi + BDFE_OFS_MTIME] + lea edx, [edi + BDFE_OFS_MDATE] + stdcall vboxsf_ns_to_bdfe, eax, ecx, edx + + ; File size (from SHFLFSOBJINFO at cparms+16) + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 16 + SHFLOBJINFO_OFS_SIZE_LO] + mov [edi + BDFE_OFS_SIZE_LO], eax + mov eax, dword [ecx + 16 + SHFLOBJINFO_OFS_SIZE_HI] + mov [edi + BDFE_OFS_SIZE_HI], eax + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] GetFileInfo: OK attr=0x%x size=%d\n", \ + [edi + BDFE_OFS_ATTR], [edi + BDFE_OFS_SIZE_LO] + + call vboxsf_unlock_fs + xor eax, eax + xor ebx, ebx + ret + +.gfi_not_found: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] GetFileInfo: not found (create_result=%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.gfi_lookup_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] GetFileInfo: LOOKUP failed (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret +endp + +; vboxsf_do_read — чтение файла через SHFL_FN_CREATE + SHFL_FN_READ + CLOSE +proc vboxsf_do_read stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword, offset_lo:dword, offset_hi:dword, \ + req_size:dword, user_buffer:dword +locals + file_handle_lo dd ? + file_handle_hi dd ? + total_read dd ? + cur_offset_lo dd ? + cur_offset_hi dd ? + cur_buffer dd ? + remaining dd ? + chunk_size dd ? + num_pages dd ? +endl + + call vboxsf_lock_fs + + mov dword [total_read], 0 + mov eax, [offset_lo] + mov [cur_offset_lo], eax + mov eax, [offset_hi] + mov [cur_offset_hi], eax + mov eax, [user_buffer] + mov [cur_buffer], eax + mov eax, [req_size] + mov [remaining], eax + + ; --- Build SHFLSTRING for path --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.rd_copy_sub: + lodsb + cmp al, 0 + je .rd_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .rd_copy_sub +.rd_sub_done: + mov byte [edi], 0 + inc ecx + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx + inc ecx + mov word [edx + 0], cx + + ; --- SHFL_FN_CREATE — open file for reading --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + + ; SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], 0x00001100 + + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: CREATE result=%d\n", eax + test eax, eax + jnz .rd_open_failed + + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 0] + mov [file_handle_lo], eax + mov eax, dword [ecx + 4] + mov [file_handle_hi], eax + + ; Check create result (SHFL_FILE_EXISTS = 3) + mov eax, dword [ecx + 8] + cmp eax, 3 + jne .rd_open_not_found + + ; --- Read loop: 64KB chunks via bounce buffer (vboxsf_fs_iobuf) --- +.rd_read_loop: + cmp dword [remaining], 0 + je .rd_read_done + + ; chunk = min(65536, remaining) + mov ecx, [remaining] + cmp ecx, 65536 + jbe @f + mov ecx, 65536 +@@: + mov [chunk_size], ecx + + ; num_pages = ceil(chunk_size / 4096) — iobuf is page-aligned, so no offset + mov eax, ecx + add eax, 0xFFF + shr eax, 12 + mov [num_pages], eax + + ; Clear packet area + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + push ecx + mov ecx, 512/4 + rep stosd + pop ecx + + ; Build SHFL_FN_READ packet (5 params, multi-page PLI on iobuf) + mov edi, [sf_state.fs_packet_ptr] + ; packet_size = 112 + num_pages * 8 + mov eax, [num_pages] + shl eax, 3 + add eax, 112 + mov dword [edi + 0], eax ; packet size (dynamic) + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_READ + mov dword [edi + 40], 5 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: handle (64bit) + mov dword [edi + 56], 2 + mov eax, [file_handle_lo] + mov dword [edi + 60], eax + mov eax, [file_handle_hi] + mov dword [edi + 64], eax + + ; parm[2]: offset (64bit) + mov dword [edi + 68], 2 + mov eax, [cur_offset_lo] + mov dword [edi + 72], eax + mov eax, [cur_offset_hi] + mov dword [edi + 76], eax + + ; parm[3]: cb (32bit) = chunk size + mov dword [edi + 80], 1 + mov eax, [chunk_size] + mov dword [edi + 84], eax + mov dword [edi + 88], 0 + + ; parm[4]: buffer (PageList OUT, into iobuf) + mov dword [edi + 92], 10 + mov eax, [chunk_size] + mov dword [edi + 96], eax + mov dword [edi + 100], 104 ; offset to PLI within packet + + ; PLI header @ +104: iobuf is page-aligned → offFirstPage = 0 + mov dword [edi + 104], 2 ; flags = OUT (host writes to guest) + mov word [edi + 108], 0 ; offFirstPage = 0 + mov eax, [num_pages] + mov word [edi + 110], ax ; cPages + + ; Build PLI page entries from vboxsf_fs_iobuf (kernel memory, always committed) + mov ecx, [num_pages] + mov esi, [sf_state.fs_iobuf_ptr] + lea ebx, [edi + 112] + +.rd_build_pli: + push ebx ecx esi + mov eax, esi + invoke GetPhysAddr + pop esi ecx ebx + + mov [ebx], eax ; phys_addr (kernel — always valid) + mov dword [ebx + 4], 0 ; pad (upper 32 bits) + add ebx, 8 + add esi, 0x1000 + dec ecx + jnz .rd_build_pli + + ; Send HGCM request + stdcall hgcm_send_request, edi + + ; Check transport error (timeout, bad pointer, etc.) + test eax, eax + jnz .rd_transport_error + + ; Check service result + mov eax, dword [edi +28] + test eax, eax + jnz .rd_read_error + + ; Actual bytes read (parm[3].value updated by host) + mov ecx, dword [edi +84] + test ecx, ecx + jz .rd_read_done + + ; Copy iobuf → user_buffer (ecx = bytes_read) + push ecx + mov esi, [sf_state.fs_iobuf_ptr] + mov edi, [cur_buffer] + push ecx + shr ecx, 2 + rep movsd ; copy dwords + pop ecx + and ecx, 3 + rep movsb ; copy remaining bytes + pop ecx + + ; Update counters + add [total_read], ecx + add [cur_buffer], ecx + add [cur_offset_lo], ecx + adc dword [cur_offset_hi], 0 + sub [remaining], ecx + + ; Short read = EOF, don't do another round-trip + cmp ecx, [chunk_size] + jb .rd_read_done + + jmp .rd_read_loop + +.rd_read_done: + ; Close file + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + + call vboxsf_unlock_fs + + mov ebx, [total_read] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: done, %d bytes read\n", ebx + + ; Return ERROR_END_OF_FILE if we read less than requested + mov ecx, [req_size] + cmp ebx, ecx + jae .rd_return_ok + ; Partial or zero read + mov eax, ERROR_END_OF_FILE + ret +.rd_return_ok: + xor eax, eax + ret + +.rd_open_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: CREATE failed (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.rd_open_not_found: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: file not found (create_result=%d)\n", eax + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.rd_transport_error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: HGCM transport error (eax=0x%x rc=0x%x)\n", eax, [edi +12] + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_DEVICE + mov ebx, [total_read] + ret + +.rd_read_error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: SHFL_READ failed (result=%d rc=0x%x)\n", eax, [edi +12] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Read: offset=%d:%d chunk=%d remaining=%d\n", \ + [cur_offset_hi], [cur_offset_lo], [chunk_size], [remaining] + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_DEVICE + mov ebx, [total_read] + ret +endp + +; vboxsf_do_write_common — запись в файл через SHFL_FN_CREATE + SHFL_FN_WRITE +proc vboxsf_do_write_common stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword, create_flags:dword, \ + offset_lo:dword, offset_hi:dword, req_size:dword, user_buffer:dword +locals + file_handle_lo dd ? + file_handle_hi dd ? + total_written dd ? + cur_offset_lo dd ? + cur_offset_hi dd ? + cur_buffer dd ? + remaining dd ? + chunk_size dd ? + num_pages dd ? +endl + + call vboxsf_lock_fs + + mov dword [total_written], 0 + mov eax, [offset_lo] + mov [cur_offset_lo], eax + mov eax, [offset_hi] + mov [cur_offset_hi], eax + mov eax, [user_buffer] + mov [cur_buffer], eax + mov eax, [req_size] + mov [remaining], eax + + ; --- Построить SHFLSTRING для пути --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.wr_copy_sub: + lodsb + cmp al, 0 + je .wr_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .wr_copy_sub +.wr_sub_done: + mov byte [edi], 0 + inc ecx ; +1 for '/' + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx ; u16Length + inc ecx ; +1 for null + mov word [edx + 0], cx ; u16Size + + ; --- SHFL_FN_CREATE — открыть/создать файл --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + + mov eax, [create_flags] + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], eax + + ; Собрать HGCM CREATE пакет + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + ; parm[2]: createparms (PageList) + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + ; PLI for path @ +80 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + ; PLI for cparms @ +96 + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: CREATE result=%d\n", eax + test eax, eax + jnz .wr_open_failed + + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 0] + mov [file_handle_lo], eax + mov eax, dword [ecx + 4] + mov [file_handle_hi], eax + + ; Проверить результат CREATE + mov eax, dword [ecx + 8] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: handle=%d:%d create_result=%d\n", \ + [file_handle_hi], [file_handle_lo], eax + cmp eax, SHFL_FILE_EXISTS + je .wr_file_ok + cmp eax, SHFL_FILE_CREATED + je .wr_file_ok + cmp eax, SHFL_FILE_REPLACED + je .wr_file_ok + jmp .wr_open_not_found + +.wr_file_ok: + ; Если нечего писать — просто закрываем (создание пустого файла) + cmp dword [remaining], 0 + je .wr_write_done + + ; --- Цикл записи: 64KB через bounce buffer (vboxsf_fs_iobuf) --- +.wr_write_loop: + cmp dword [remaining], 0 + je .wr_write_done + + ; chunk = min(65536, remaining) + mov ecx, [remaining] + cmp ecx, 65536 + jbe @f + mov ecx, 65536 +@@: + mov [chunk_size], ecx + + ; Copy user_buffer → iobuf (ecx = chunk_size) + push ecx + mov esi, [cur_buffer] + mov edi, [sf_state.fs_iobuf_ptr] + push ecx + shr ecx, 2 + rep movsd ; copy dwords + pop ecx + and ecx, 3 + rep movsb ; copy remaining bytes + pop ecx + + ; num_pages = ceil(chunk_size / 4096) — iobuf is page-aligned + mov eax, ecx + add eax, 0xFFF + shr eax, 12 + mov [num_pages], eax + + ; Очистить пакет + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + push ecx + mov ecx, 512/4 + rep stosd + pop ecx + + ; Собрать SHFL_FN_WRITE пакет (5 параметров, multi-page PLI на iobuf) + mov edi, [sf_state.fs_packet_ptr] + ; packet_size = 112 + num_pages * 8 + mov eax, [num_pages] + shl eax, 3 + add eax, 112 + mov dword [edi + 0], eax ; размер пакета (динамический) + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_WRITE + mov dword [edi + 40], 5 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: handle (64bit) + mov dword [edi + 56], 2 + mov eax, [file_handle_lo] + mov dword [edi + 60], eax + mov eax, [file_handle_hi] + mov dword [edi + 64], eax + + ; parm[2]: offset (64bit) + mov dword [edi + 68], 2 + mov eax, [cur_offset_lo] + mov dword [edi + 72], eax + mov eax, [cur_offset_hi] + mov dword [edi + 76], eax + + ; parm[3]: cb (32bit) = chunk size + mov dword [edi + 80], 1 + mov eax, [chunk_size] + mov dword [edi + 84], eax + mov dword [edi + 88], 0 + + ; parm[4]: buffer (PageList IN, из iobuf) + mov dword [edi + 92], 10 + mov eax, [chunk_size] + mov dword [edi + 96], eax + mov dword [edi + 100], 104 ; смещение до PLI в пакете + + ; PLI header @ +104: iobuf page-aligned → offFirstPage = 0 + mov dword [edi + 104], 1 ; flags = IN (хост читает из гостя) + mov word [edi + 108], 0 ; offFirstPage = 0 + mov eax, [num_pages] + mov word [edi + 110], ax ; cPages + + ; Построить PLI записи из vboxsf_fs_iobuf (kernel memory, всегда committed) + mov ecx, [num_pages] + mov esi, [sf_state.fs_iobuf_ptr] + lea ebx, [edi + 112] + +.wr_build_pli: + push ebx ecx esi + mov eax, esi + invoke GetPhysAddr + pop esi ecx ebx + + mov [ebx], eax ; phys_addr (kernel — всегда валиден) + mov dword [ebx + 4], 0 ; pad (upper 32 bits) + add ebx, 8 + add esi, 0x1000 + dec ecx + jnz .wr_build_pli + + ; Отправить HGCM запрос + stdcall hgcm_send_request, edi + + ; Проверить транспортную ошибку + test eax, eax + jnz .wr_transport_error + + ; Проверить результат сервиса + mov eax, dword [edi +28] + test eax, eax + jnz .wr_write_error + + ; Реально записанные байты (parm[3] обновляется хостом) + mov ecx, dword [edi +84] + test ecx, ecx + jz .wr_write_done + + ; Обновить счётчики + add [total_written], ecx + add [cur_buffer], ecx + add [cur_offset_lo], ecx + adc dword [cur_offset_hi], 0 + sub [remaining], ecx + + ; Short write -> стоп + cmp ecx, [chunk_size] + jb .wr_write_done + + jmp .wr_write_loop + +.wr_write_done: + ; Закрыть файл + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + + call vboxsf_unlock_fs + + mov ebx, [total_written] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: done, %d bytes written\n", ebx + xor eax, eax + ret + +.wr_open_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: CREATE failed (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.wr_open_not_found: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: file not found/not created (create_result=%d)\n", eax + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.wr_transport_error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: HGCM transport error (eax=0x%x rc=0x%x)\n", eax, [edi +12] + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_DEVICE + mov ebx, [total_written] + ret + +.wr_write_error: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: SHFL_WRITE failed (result=%d rc=0x%x)\n", eax, [edi +12] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Write: offset=%d:%d chunk=%d remaining=%d\n", \ + [cur_offset_hi], [cur_offset_lo], [chunk_size], [remaining] + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_DEVICE + mov ebx, [total_written] + ret +endp + +; vboxsf_do_createfolder — создание папки через SHFL_FN_CREATE + SHFL_CF_DIRECTORY +proc vboxsf_do_createfolder stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword + + call vboxsf_lock_fs + + ; --- Построить SHFLSTRING для пути --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.mf_copy_sub: + lodsb + cmp al, 0 + je .mf_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .mf_copy_sub +.mf_sub_done: + mov byte [edi], 0 + inc ecx ; +1 for '/' + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx ; u16Length + inc ecx ; +1 for null + mov word [edx + 0], cx ; u16Size + + ; --- SHFL_FN_CREATE с DIRECTORY --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + + ; SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ (open if exists, create if new) + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], SHFL_CF_DIRECTORY or SHFL_CF_ACCESS_READ + + ; Собрать HGCM CREATE пакет + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + ; parm[2]: createparms (PageList) + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + ; PLI for path @ +80 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + ; PLI for cparms @ +96 + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: CREATE result=%d\n", eax + test eax, eax + jnz .mf_failed + + ; Проверить результат + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 8] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: create_result=%d\n", eax + cmp eax, SHFL_FILE_CREATED + je .mf_ok + cmp eax, SHFL_FILE_EXISTS + je .mf_ok + jmp .mf_failed_result + +.mf_ok: + ; Закрыть handle (папка создана/открыта) + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 0] + mov ebx, dword [ecx + 4] + stdcall vboxsf_close_handle, [root], eax, ebx + + call vboxsf_unlock_fs + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: OK\n" + xor eax, eax + xor ebx, ebx + ret + +.mf_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: CREATE failed (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.mf_failed_result: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] CreateFolder: unexpected result (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_ACCESS_DENIED + xor ebx, ebx + ret +endp + +; vboxsf_do_delete — удаление файла/каталога через SHFL_FN_REMOVE +proc vboxsf_do_delete stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword + + call vboxsf_lock_fs + + ; --- Построить SHFLSTRING для пути --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.del_copy_sub: + lodsb + cmp al, 0 + je .del_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .del_copy_sub +.del_sub_done: + mov byte [edi], 0 + inc ecx ; +1 for '/' + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx ; u16Length + inc ecx ; +1 for null + mov word [edx + 0], cx ; u16Size + + ; --- Собрать SHFL_FN_REMOVE пакет (3 параметра, 1 PLI) --- + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 96 ; 44 + 3*12 + 16 PLI + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_REMOVE + mov dword [edi + 40], 3 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 ; offset to PLI + + ; parm[2]: flags (32bit) = SHFL_REMOVE_FILE + mov dword [edi + 68], 1 + mov dword [edi + 72], SHFL_REMOVE_FILE + mov dword [edi + 76], 0 + + ; PLI for path @ +80 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 1 ; flags = IN (path to host) + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + ; Попытка 1: удалить как файл + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Delete: REMOVE_FILE result=%d\n", eax + test eax, eax + jz .del_ok + + ; Попытка 2: удалить как каталог + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov dword [edi + 72], SHFL_REMOVE_DIR + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Delete: REMOVE_DIR result=%d\n", eax + test eax, eax + jnz .del_failed + +.del_ok: + call vboxsf_unlock_fs + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Delete: OK\n" + xor eax, eax + xor ebx, ebx + ret + +.del_failed: + call vboxsf_unlock_fs + mov eax, ERROR_ACCESS_DENIED + xor ebx, ebx + ret +endp + +; vboxsf_do_rename — переименование через SHFL_FN_RENAME +proc vboxsf_do_rename stdcall uses ecx edx esi edi, \ + root:dword, src_subpath:dword, dst_subpath:dword + + call vboxsf_lock_fs + + ; --- Построить SHFLSTRING для src в pathbuf --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [src_subpath] + xor ecx, ecx +.rn_copy_src: + lodsb + cmp al, 0 + je .rn_src_done + stosb + inc ecx + cmp ecx, 1000 + jb .rn_copy_src +.rn_src_done: + mov byte [edi], 0 + inc ecx + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx + inc ecx + mov word [edx + 0], cx + + ; --- Построить SHFLSTRING для dst в pathbuf2 --- + mov edi, [sf_state.fs_pathbuf2_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [dst_subpath] + xor ecx, ecx +.rn_copy_dst: + lodsb + cmp al, 0 + je .rn_dst_done + stosb + inc ecx + cmp ecx, 1000 + jb .rn_copy_dst +.rn_dst_done: + mov byte [edi], 0 + inc ecx + mov edx, [sf_state.fs_pathbuf2_ptr] + mov word [edx + 2], cx + inc ecx + mov word [edx + 0], cx + + ; --- Собрать SHFL_FN_RENAME пакет (4 параметра, 2 PLI) --- + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 124 ; 44 + 4*12 + 2*16 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_RENAME + mov dword [edi + 40], 4 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: src path (PageList) + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 92 ; offset to PLI_src + + ; parm[2]: dst path (PageList) + mov dword [edi + 68], 10 + mov edx, [sf_state.fs_pathbuf2_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 72], eax + mov dword [edi + 76], 108 ; offset to PLI_dst + + ; parm[3]: flags (32bit) + mov dword [edi + 80], 1 + mov dword [edi + 84], SHFL_RENAME_FILE or SHFL_RENAME_REPLACE_IF_EXISTS + mov dword [edi + 88], 0 + + ; PLI for src path @ +92 + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 92], 1 ; flags = IN + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 96], ax + mov word [edi + 98], 1 + mov dword [edi + 100], ebx + mov dword [edi + 104], 0 + + ; PLI for dst path @ +108 + push edi + mov eax, [sf_state.fs_pathbuf2_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 108], 1 ; flags = IN + mov eax, [sf_state.fs_pathbuf2_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 112], ax + mov word [edi + 114], 1 + mov dword [edi + 116], ebx + mov dword [edi + 120], 0 + + ; Попытка 1: переименовать как файл + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: result=%d (as file)\n", eax + test eax, eax + jz .rn_ok + + ; Попытка 2: переименовать как каталог + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov dword [edi + 84], SHFL_RENAME_DIR or SHFL_RENAME_REPLACE_IF_EXISTS + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: result=%d (as dir)\n", eax + test eax, eax + jnz .rn_failed + +.rn_ok: + call vboxsf_unlock_fs + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] Rename: OK\n" + xor eax, eax + xor ebx, ebx + ret + +.rn_failed: + call vboxsf_unlock_fs + mov eax, ERROR_ACCESS_DENIED + xor ebx, ebx + ret +endp + +; vboxsf_do_setfileend — установка размера файла через SHFL_FN_SET_FILE_SIZE +proc vboxsf_do_setfileend stdcall uses ecx edx esi edi, \ + root:dword, subpath:dword, size_lo:dword, size_hi:dword +locals + file_handle_lo dd ? + file_handle_hi dd ? +endl + + call vboxsf_lock_fs + + ; --- Построить SHFLSTRING для пути --- + mov edi, [sf_state.fs_pathbuf_ptr] + mov word [edi + 0], 0 + mov word [edi + 2], 0 + lea edi, [edi + 4] + mov byte [edi], '/' + inc edi + + mov esi, [subpath] + xor ecx, ecx +.sfe_copy_sub: + lodsb + cmp al, 0 + je .sfe_sub_done + stosb + inc ecx + cmp ecx, 1000 + jb .sfe_copy_sub +.sfe_sub_done: + mov byte [edi], 0 + inc ecx + mov edx, [sf_state.fs_pathbuf_ptr] + mov word [edx + 2], cx + inc ecx + mov word [edx + 0], cx + + ; --- SHFL_FN_CREATE — открыть файл для записи --- + mov edi, [sf_state.fs_cparms_ptr] + xor eax, eax + mov ecx, 128/4 + rep stosd + + ; SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READWRITE + mov ecx, [sf_state.fs_cparms_ptr] + mov dword [ecx + 12], 0x00003100 + + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 512/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 112 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CREATE + mov dword [edi + 40], 3 + + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + mov dword [edi + 56], 10 + mov edx, [sf_state.fs_pathbuf_ptr] + movzx eax, word [edx + 0] + add eax, 4 + mov dword [edi + 60], eax + mov dword [edi + 64], 80 + + mov dword [edi + 68], 10 + mov dword [edi + 72], 108 + mov dword [edi + 76], 96 + + push edi + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 80], 3 + mov eax, [sf_state.fs_pathbuf_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 84], ax + mov word [edi + 86], 1 + mov dword [edi + 88], ebx + mov dword [edi + 92], 0 + + push edi + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_BASE_MASK + invoke GetPhysAddr + mov ebx, eax + pop edi + mov dword [edi + 96], 3 + mov eax, [sf_state.fs_cparms_ptr] + and eax, PAGE_OFFSET_MASK + mov word [edi + 100], ax + mov word [edi + 102], 1 + mov dword [edi + 104], ebx + mov dword [edi + 108], 0 + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: CREATE result=%d\n", eax + test eax, eax + jnz .sfe_open_failed + + mov ecx, [sf_state.fs_cparms_ptr] + mov eax, dword [ecx + 0] + mov [file_handle_lo], eax + mov eax, dword [ecx + 4] + mov [file_handle_hi], eax + + mov eax, dword [ecx + 8] + cmp eax, SHFL_FILE_EXISTS + jne .sfe_not_found + + ; --- SHFL_FN_SET_FILE_SIZE (3 параметра, без PLI) --- + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 80 ; 44 + 3*12 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_SET_FILE_SIZE + mov dword [edi + 40], 3 + + ; parm[0]: root (32bit) + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: handle (64bit) + mov dword [edi + 56], 2 + mov eax, [file_handle_lo] + mov dword [edi + 60], eax + mov eax, [file_handle_hi] + mov dword [edi + 64], eax + + ; parm[2]: newSize (64bit) + mov dword [edi + 68], 2 + mov eax, [size_lo] + mov dword [edi + 72], eax + mov eax, [size_hi] + mov dword [edi + 76], eax + + stdcall hgcm_send_request, edi + + mov eax, dword [edi +28] + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: SET_FILE_SIZE result=%d\n", eax + push eax ; сохранить результат + + ; Закрыть файл + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + + call vboxsf_unlock_fs + + ; Если SET_FILE_SIZE вернул ошибку — сообщаем + pop eax + test eax, eax + jnz .sfe_resize_failed + + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: OK size=%d:%d\n", [size_hi], [size_lo] + xor eax, eax + xor ebx, ebx + ret + +.sfe_resize_failed: + mov eax, ERROR_DEVICE + xor ebx, ebx + ret + +.sfe_open_failed: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: CREATE failed (%d)\n", eax + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret + +.sfe_not_found: + DEBUGF __DEBUG_SF__, "[VBoxGuest] [SF] SetFileEnd: file not found (result=%d)\n", eax + stdcall vboxsf_close_handle, [root], [file_handle_lo], [file_handle_hi] + call vboxsf_unlock_fs + mov eax, ERROR_FILE_NOT_FOUND + xor ebx, ebx + ret +endp + +; --- HELPER FUNCTIONS --- + +; vboxsf_find_folder_by_name — найти SF_FOLDER по имени (CP866/ASCII) +proc vboxsf_find_folder_by_name stdcall uses ebx ecx edx esi edi, \ + name_ptr:dword, name_len:dword + mov ecx, [vboxsf_data.count] + test ecx, ecx + jz .not_found + + xor ebx, ebx + +.scan: + mov eax, [vboxsf_data.folders + ebx*4] + test eax, eax + jz .next + + ; Сравнить имя + lea edi, [eax + SF_FOLDER.name] + mov esi, [name_ptr] + mov edx, [name_len] + push ecx eax + +.cmp_loop: + test edx, edx + jz .cmp_end_check + mov cl, [esi] + cmp cl, [edi] + jne .cmp_fail + inc esi + inc edi + dec edx + jmp .cmp_loop + +.cmp_end_check: + ; Проверить что строка в SF_FOLDER тоже закончилась + cmp byte [edi], 0 + jne .cmp_fail + pop eax ecx + ret ; eax = SF_FOLDER* + +.cmp_fail: + pop eax ecx + +.next: + inc ebx + dec ecx + jnz .scan + +.not_found: + xor eax, eax + ret +endp + +; vboxsf_close_handle — закрыть SHFL handle +proc vboxsf_close_handle stdcall uses ebx ecx edx esi edi, \ + root:dword, handle_lo:dword, handle_hi:dword + + mov edi, [sf_state.fs_packet_ptr] + xor eax, eax + mov ecx, 256/4 + rep stosd + + mov edi, [sf_state.fs_packet_ptr] + mov dword [edi + 0], 68 + mov dword [edi + 4], 0x00010001 + mov dword [edi + 8], 62 + mov dword [edi + 12], 0xFFFFFFFF + mov dword [edi + 16], 0 + mov dword [edi + 20], 0x00000081 + mov dword [edi + 24], 0 + mov dword [edi + 28], 0 + mov eax, [sf_state.client_id] + mov dword [edi + 32], eax + mov dword [edi + 36], SHFL_FN_CLOSE + mov dword [edi + 40], 2 + + ; parm[0]: root + mov dword [edi + 44], 1 + mov eax, [root] + mov dword [edi + 48], eax + mov dword [edi + 52], 0 + + ; parm[1]: handle (64bit) + mov dword [edi + 56], 2 + mov eax, [handle_lo] + mov dword [edi + 60], eax + mov eax, [handle_hi] + mov dword [edi + 64], eax + + stdcall hgcm_send_request, edi + + xor eax, eax + ret +endp + +; vboxsf_is_leap_year — проверка високосного года +vboxsf_is_leap_year: + push ecx edx + mov ecx, eax ; ecx = year + + ; year % 4 + test ecx, 3 + jnz .nly_365 + + ; year % 100 + mov eax, ecx + xor edx, edx + push ecx + mov ecx, 100 + div ecx + pop ecx + test edx, edx + jnz .nly_366 ; %4==0 && %100!=0 -> leap + + ; year % 400 + mov eax, ecx + xor edx, edx + push ecx + mov ecx, 400 + div ecx + pop ecx + test edx, edx + jnz .nly_365 ; %100==0 && %400!=0 -> not leap + +.nly_366: + mov eax, 366 + pop edx ecx + ret +.nly_365: + mov eax, 365 + pop edx ecx + ret + +; vboxsf_ns_to_bdfe — конвертация VBox наносекунд в KolibriOS BDFE time+date +proc vboxsf_ns_to_bdfe stdcall uses ebx ecx edx esi edi, \ + src:dword, dst_time:dword, dst_date:dword +locals + days dd ? + year dd ? +endl + + ; Прочитать 64-bit наносекунды + mov esi, [src] + mov eax, [esi] ; ns_lo + mov edx, [esi + 4] ; ns_hi + + ; Проверка на ноль или отрицательное + test edx, edx + js .ns_zero + mov ecx, eax + or ecx, edx + jz .ns_zero + + ; Делим ns / 10^9 = unix_seconds + mov ecx, 1000000000 + div ecx + ; eax = unix_seconds + + ; Делим на 86400: days и остаток (секунды в дне) + xor edx, edx + mov ecx, 86400 + div ecx + mov [days], eax ; total days since epoch + ; edx = seconds within day + + ; Извлечь часы, минуты, секунды из edx + mov eax, edx + xor edx, edx + mov ecx, 3600 + div ecx + mov edi, eax ; edi = hours + mov eax, edx + xor edx, edx + mov ecx, 60 + div ecx + ; eax = minutes, edx = seconds + + ; Собрать time: sec | (min << 8) | (hr << 16) + mov ecx, eax ; ecx = minutes + shl ecx, 8 + or edx, ecx ; edx = sec | (min << 8) + mov ecx, edi ; ecx = hours + shl ecx, 16 + or edx, ecx ; edx = sec | (min << 8) | (hr << 16) + mov ecx, [dst_time] + mov [ecx], edx + + ; --- Конвертация days -> year/month/day --- + mov dword [year], 1970 + +.ns_year_loop: + mov eax, [year] + call vboxsf_is_leap_year ; eax = 365 or 366 + cmp [days], eax + jb .ns_year_done + sub [days], eax + inc dword [year] + jmp .ns_year_loop + +.ns_year_done: + ; days = day of year (0-based) + mov ebx, 1 ; ebx = month (1-based) + +.ns_month_loop: + ; Дней в текущем месяце + movzx ecx, byte [vboxsf_month_days + ebx - 1] + + ; Февраль в високосный год + cmp ebx, 2 + jne @f + mov eax, [year] + call vboxsf_is_leap_year + cmp eax, 366 + jne @f + inc ecx ; 29 дней +@@: + cmp [days], ecx + jb .ns_month_done + sub [days], ecx + inc ebx + cmp ebx, 13 + jb .ns_month_loop + +.ns_month_done: + ; day = days + 1 (1-based) + mov eax, [days] + inc eax ; eax = day + + ; Собрать date: day | (month << 8) | (year << 16) + mov ecx, ebx + shl ecx, 8 + or eax, ecx + mov ecx, [year] + shl ecx, 16 + or eax, ecx + mov ecx, [dst_date] + mov [ecx], eax + + ret + +.ns_zero: + mov ecx, [dst_time] + mov dword [ecx], 0 + mov ecx, [dst_date] + mov dword [ecx], 0 + ret +endp + +; get_page_list — Получить список физических страниц для буфера +proc get_page_list stdcall uses ebx ecx edx esi edi, vaddr:dword, size:dword, pages:dword + mov esi, [vaddr] + mov edi, [pages] + xor ebx, ebx ; счетчик страниц + + ; Вычислить первую страницу + mov edx, esi + and edx, PAGE_BASE_MASK ; начало первой страницы (virt addr) + mov ecx, [size] + +.next_page: + ; Получить физический адрес страницы + push ecx + mov eax, edx ; eax = виртуальный адрес страницы + invoke GetPhysAddr + pop ecx + test eax, eax + jz .error + + ; Сохранить физический адрес (RTGCPHYS64) + stosd ; младшие 32 бита + xor eax, eax + stosd ; старшие 32 бита = 0 + inc ebx + + ; Перейти к следующей странице + add edx, 0x1000 + sub ecx, 0x1000 + ja .next_page + + mov eax, ebx + ret + +.error: + xor eax, eax + ret +endp \ No newline at end of file diff --git a/drivers/vboxguest/services/timesync/timesync.inc b/drivers/vboxguest/services/timesync/timesync.inc new file mode 100644 index 000000000..4c7f9dd8a --- /dev/null +++ b/drivers/vboxguest/services/timesync/timesync.inc @@ -0,0 +1,444 @@ +; ============================================================================= +; Модуль : VMMDev Time Sync Service +; Назначение : Синхронизация часов гостевой ОС с хостом VirtualBox +; Файл : services/timesync/timesync.inc +; +; VBox возвращает 64-bit миллисекунды с Unix epoch (1970-01-01). +; Драйвер пишет время напрямую в CMOS RTC (порты 0x70/0x71). +; int 0x40 нельзя из kernel-mode: обработчик syscall предполагает ring 3 +; (переключение стеков user→kernel), из ring 0 это разрушает стек. +; ============================================================================= + +svc_timesync_name db "TIMESYNC", 0 + +; --- Data --- +align 4 +vbox_timesync_enabled dd 0 +vbox_timesync_last_tick dd 0 + +align 4 +vmmdev_get_host_time_s VMMDEV_GET_HOST_TIME \ + , \ + 0, 0 + +align 4 +timesync_time_virt dd 0 +timesync_time_phys dd 0 + +; CMOS port constants +CMOS_ADDR equ 0x70 +CMOS_DATA equ 0x71 + +; timesync_init — Инициализация +proc timesync_init + DEBUGF 2, "[VBoxGuest] [TimeSync] Initializing...\n" + + mov eax, vmmdev_get_host_time_s + mov [timesync_time_virt], eax + invoke GetPhysAddr + mov [timesync_time_phys], eax + + DEBUGF 2, "[VBoxGuest] [TimeSync] Buffer: virt=0x%x, phys=0x%x\n", \ + [timesync_time_virt], [timesync_time_phys] + + mov dword [vbox_timesync_enabled], 1 + call timesync_do_sync + + DEBUGF 2, "[VBoxGuest] [TimeSync] Initialized OK\n" + xor eax, eax + ret +endp + +; timesync_tick — Проверка интервала и синхронизация +proc timesync_tick + cmp dword [vbox_timesync_enabled], 0 + je .done + invoke GetTimerTicks + sub eax, [vbox_timesync_last_tick] + cmp eax, TIMESYNC_INTERVAL_TICKS + jb .done + invoke GetTimerTicks + mov [vbox_timesync_last_tick], eax + call timesync_do_sync +.done: + ret +endp + +; timesync_do_sync — Получить время хоста и записать в CMOS RTC +proc timesync_do_sync uses ebx ecx edx esi edi + DEBUGF 2, "[VBoxGuest] [TimeSync] Syncing...\n" + + mov edi, [timesync_time_virt] + test edi, edi + jz .bad_ptr + + mov dword [edi + VMMDEV_GET_HOST_TIME.header.rc], 0 + mov dword [edi + VMMDEV_GET_HOST_TIME.time_low], 0 + mov dword [edi + VMMDEV_GET_HOST_TIME.time_high], 0 + + stdcall vmmdev_send_request, [timesync_time_phys] + + mov eax, [edi + VMMDEV_GET_HOST_TIME.header.rc] + test eax, eax + js .error + + ; 64-bit миллисекунды с epoch + mov eax, [edi + VMMDEV_GET_HOST_TIME.time_low] + mov edx, [edi + VMMDEV_GET_HOST_TIME.time_high] + + DEBUGF 2, "[VBoxGuest] [TimeSync] Host ms: hi=0x%x lo=0x%x\n", edx, eax + + mov ecx, eax + or ecx, edx + jz .bad_time + + ; edx:eax / 1000 → eax = unix seconds + mov ecx, 1000 + div ecx + + DEBUGF 2, "[VBoxGuest] [TimeSync] Unix timestamp: %d\n", eax + call timesync_set_cmos + DEBUGF 2, "[VBoxGuest] [TimeSync] Sync complete\n" + xor eax, eax + ret + +.bad_ptr: + DEBUGF 2, "[VBoxGuest] [TimeSync] Bad pointer\n" + mov eax, VERR_INVALID_POINTER + ret +.error: + DEBUGF 2, "[VBoxGuest] [TimeSync] GetHostTime failed: rc=0x%x\n", eax + ret +.bad_time: + DEBUGF 2, "[VBoxGuest] [TimeSync] Zero time returned\n" + mov eax, VERR_GENERAL_FAILURE + ret +endp + +; timesync_set_cmos — Вычисляет дату/время и записывает в CMOS RTC +proc timesync_set_cmos uses ebx ecx edx esi edi + locals + ts_sec dd ? + ts_min dd ? + ts_hour dd ? + ts_day dd ? + ts_month dd ? + ts_year dd ? + ts_dow dd ? + endl + + ; ── Разложить на дни и время дня ── + xor edx, edx + mov ecx, 86400 + div ecx + ; eax = total_days, edx = time_of_day_seconds + mov esi, eax ; total_days + + ; Время суток + mov eax, edx + xor edx, edx + mov ecx, 3600 + div ecx + mov [ts_hour], eax + + mov eax, edx + xor edx, edx + mov ecx, 60 + div ecx + mov [ts_min], eax + mov [ts_sec], edx + + ; ── День недели: (days + 3) % 7 + 1 ── + lea eax, [esi + 3] + xor edx, edx + mov ecx, 7 + div ecx + inc edx + mov [ts_dow], edx + + ; ── civil_from_days (Howard Hinnant) ── + ; eax = days → дата + mov eax, esi + call .civil_from_days + mov [ts_day], eax + mov [ts_month], ebx + mov [ts_year], ecx + + DEBUGF 2, "[VBoxGuest] [TimeSync] Date: %d-%d-%d %d:%d:%d DoW=%d\n", \ + [ts_year], [ts_month], [ts_day], [ts_hour], [ts_min], [ts_sec], [ts_dow] + + ; ── Ждать окончания обновления RTC ── + call .cmos_wait + + ; ── Запретить обновления (SET bit в Status B) ── + mov al, 0x0B + out CMOS_ADDR, al + in al, CMOS_DATA + or al, 0x80 + push eax + mov al, 0x0B + out CMOS_ADDR, al + pop eax + out CMOS_DATA, al + + ; ── Записать все поля (BCD) ── + mov eax, [ts_sec] + call .to_bcd + mov ah, al + mov al, 0x00 ; seconds register + call .cmos_wr + + mov eax, [ts_min] + call .to_bcd + mov ah, al + mov al, 0x02 ; minutes register + call .cmos_wr + + mov eax, [ts_hour] + call .to_bcd + mov ah, al + mov al, 0x04 ; hours register + call .cmos_wr + + mov eax, [ts_dow] + mov ah, al + mov al, 0x06 ; weekday register + call .cmos_wr + + mov eax, [ts_day] + call .to_bcd + mov ah, al + mov al, 0x07 ; day register + call .cmos_wr + + mov eax, [ts_month] + call .to_bcd + mov ah, al + mov al, 0x08 ; month register + call .cmos_wr + + ; YY = year mod 100 + mov eax, [ts_year] + xor edx, edx + mov ecx, 100 + div ecx + push eax ; century + mov eax, edx + call .to_bcd + mov ah, al + mov al, 0x09 ; year register + call .cmos_wr + + ; Century + pop eax + call .to_bcd + mov ah, al + mov al, 0x32 ; century register + call .cmos_wr + + ; ── Разрешить обновления ── + mov al, 0x0B + out CMOS_ADDR, al + in al, CMOS_DATA + and al, 0x7F + push eax + mov al, 0x0B + out CMOS_ADDR, al + pop eax + out CMOS_DATA, al + + ret + +; ── Helpers ── + +.cmos_wr: + ; al = register, ah = value + out CMOS_ADDR, al + mov al, ah + out CMOS_DATA, al + ret + +.cmos_wait: + push eax ecx + mov ecx, 10000 +@@: + mov al, 0x0A + out CMOS_ADDR, al + in al, CMOS_DATA + test al, 0x80 + jz @f + pause + loop @b +@@: + pop ecx eax + ret + +.to_bcd: + ; eax (0-99) → eax BCD + push ecx edx + and eax, 0xFF + xor edx, edx + mov ecx, 10 + div ecx + shl eax, 4 + or eax, edx + pop edx ecx + ret + +; .civil_from_days — Howard Hinnant algorithm +.civil_from_days: + ; z = days + 719468 + add eax, 719468 + + ; era = z / 146097 + xor edx, edx + mov ecx, 146097 + div ecx + mov esi, eax ; era + ; doe = z - era * 146097 (но z уже потерян → используем edx = remainder) + mov ecx, edx ; ecx = doe + + ; yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365 + mov eax, ecx + xor edx, edx + mov edi, 1460 + div edi + mov edi, eax ; doe/1460 + + mov eax, ecx + xor edx, edx + push ecx + mov ecx, 36524 + div ecx + pop ecx + sub edi, eax ; doe/1460 - doe/36524 (note: we subtract then add later) + ; Actually: val = doe - doe/1460 + doe/36524 - doe/146096 + ; Let me redo properly: + + ; A = doe/1460 + push ecx + mov eax, ecx + xor edx, edx + mov ecx, 1460 + div ecx + pop ecx + push eax ; [esp] = A + + ; B = doe/36524 + push ecx + mov eax, ecx + xor edx, edx + mov ecx, 36524 + div ecx + pop ecx + push eax ; [esp]=B, [esp+4]=A + + ; C = doe/146096 + push ecx + mov eax, ecx + xor edx, edx + mov ecx, 146096 + div ecx + pop ecx + ; eax = C + + pop ebx ; B + pop edi ; A + + ; val = doe - A + B - C + mov edx, ecx ; doe + sub edx, edi + add edx, ebx + sub edx, eax + + ; yoe = val / 365 + mov eax, edx + xor edx, edx + push ecx + mov ecx, 365 + div ecx + pop ecx + ; eax = yoe, ecx still = doe + mov edi, eax ; edi = yoe + + ; year = yoe + era * 400 + mov eax, esi ; era + imul eax, 400 + add eax, edi + push eax ; [esp] = year + + ; doy = doe - (365*yoe + yoe/4 - yoe/100) + mov eax, edi + imul eax, 365 + mov esi, eax ; 365*yoe + + mov eax, edi + shr eax, 2 + add esi, eax ; + yoe/4 + + mov eax, edi + xor edx, edx + push ecx + mov ecx, 100 + div ecx + pop ecx + sub esi, eax ; - yoe/100 + + sub ecx, esi ; ecx = doy + + ; mp = (5*doy + 2) / 153 + imul eax, ecx, 5 + add eax, 2 + xor edx, edx + push ecx + mov ecx, 153 + div ecx + pop ecx + mov edi, eax ; mp + + ; day = doy - (153*mp + 2)/5 + 1 + imul eax, edi, 153 + add eax, 2 + xor edx, edx + push ecx + mov ecx, 5 + div ecx + pop ecx + sub ecx, eax + inc ecx ; ecx = day + + ; month = mp < 10 ? mp + 3 : mp - 9 + cmp edi, 10 + jae .mp_ge10 + add edi, 3 + jmp .month_done2 +.mp_ge10: + sub edi, 9 +.month_done2: + ; edi = month, ecx = day + + ; if month <= 2 → year++ + pop eax ; year + cmp edi, 2 + ja .year_ok2 + inc eax +.year_ok2: + ; Формируем результат: eax=day, ebx=month, ecx=year + mov ebx, edi ; month + xchg eax, ecx ; eax=day, ecx=year + ret +endp + +; timesync_disable — Выключить timesync +proc timesync_disable + DEBUGF 2, "[VBoxGuest] [TimeSync] Disabling...\n" + mov dword [vbox_timesync_enabled], 0 + xor eax, eax + ret +endp + +REGISTER_SERVICE svc_timesync_name, TIMESYNC_EVENT_MASK, 0, \ + timesync_init, 0, timesync_disable, 0, timesync_tick, AUTOSTART_TIMESYNC diff --git a/drivers/vboxguest/sys/init.inc b/drivers/vboxguest/sys/init.inc new file mode 100644 index 000000000..3e87e38b8 --- /dev/null +++ b/drivers/vboxguest/sys/init.inc @@ -0,0 +1,70 @@ +; ============================================================================= +; Модуль : Инициализация драйвера VBoxGuest +; Назначение : Инициализация всех подсистем драйвера, подключение к VMMDev, +; настройка сервисов, установка обработчиков +; Файл : sys/init.inc +; ============================================================================= + +; Полная инициализация драйвера VBoxGuest +proc sys_init + + call vmmdev_probe + test eax, eax + jnz .fail + + call mmio_map_vmmdev + test eax, eax + jnz .fail + + call ports_init ; чекалка + test eax, eax + jnz .fail + + call vmmdev_init_packets + test eax, eax + jnz .fail + + call vmmdev_check_version ; получить версию хоста (для guest_info2) + + call vmmdev_init_protocol ; guest_info + caps + event_filter + test eax, eax + jnz .fail + + call hgcm_init ; подготовка пакетов/таймаутов + test eax, eax + jnz .fail + + call timer_init + test eax, eax + jnz .fail + + call vmmdev_irq_install + test eax, eax + jnz .fail + + call dispatcher_init_all + call dispatcher_enable_autostart + + call dispatcher_get_active_events + call vmmdev_update_event_filter + test eax, eax + jnz .fail_event_filter + + call dispatcher_get_active_caps + call vmmdev_update_capabilities + test eax, eax + jnz .fail_caps + + xor eax, eax + ret + +.fail_event_filter: + DEBUGF 2, "[VBoxGuest] [Init] Event filter setup failed: 0x%x\n", eax + ret + +.fail_caps: + DEBUGF 2, "[VBoxGuest] [Init] Guest capabilities setup failed: 0x%x\n", eax + ret +.fail: + ret +endp \ No newline at end of file diff --git a/drivers/vboxguest/sys/ioctl.inc b/drivers/vboxguest/sys/ioctl.inc new file mode 100644 index 000000000..d9b9af006 --- /dev/null +++ b/drivers/vboxguest/sys/ioctl.inc @@ -0,0 +1,183 @@ +; ============================================================================= +; Модуль : IOCTL интерфейс драйвера VBoxGuest +; Назначение : Обработка команд от приложений (service_proc, ioctl_get_services) +; Файл : sys/ioctl.inc +; ============================================================================= + +; IOCTL ABI v1 +API_VERSION equ 1 + +; General +VBOX_IOCTL_GET_VERSION equ 0 +VBOX_IOCTL_GET_SERVICES equ 1 +VBOX_IOCTL_SVC_ENABLE equ 2 +VBOX_IOCTL_SVC_DISABLE equ 3 + +; Clipboard +VBOX_IOCTL_CLIP_STATUS equ 10 +VBOX_IOCTL_CLIP_READ equ 11 +VBOX_IOCTL_CLIP_WRITE equ 12 + +; SERVICE_INFO size for GET_SERVICES (dd id + rb name[16] + dd enabled = 24) +SVC_INFO_SIZE equ 24 +SVC_INFO_NAME_LEN equ 16 + +; ============================================================================= +; Service Procedure — IOCTL dispatch +; ============================================================================= +proc service_proc stdcall, ioctl:dword + mov ebx, [ioctl] + mov eax, [ebx + IOCTL.io_code] + + cmp eax, VBOX_IOCTL_GET_VERSION + je .get_version + cmp eax, VBOX_IOCTL_GET_SERVICES + je .get_services + cmp eax, VBOX_IOCTL_SVC_ENABLE + je .svc_enable + cmp eax, VBOX_IOCTL_SVC_DISABLE + je .svc_disable + cmp eax, VBOX_IOCTL_CLIP_STATUS + je .clip_status + cmp eax, VBOX_IOCTL_CLIP_READ + je .clip_read + cmp eax, VBOX_IOCTL_CLIP_WRITE + je .clip_write + + jmp .fail + + ; --- GET_VERSION (0) --- +.get_version: + cmp [ebx + IOCTL.out_size], 4 + jb .fail + mov eax, [ebx + IOCTL.output] + mov dword [eax], API_VERSION + xor eax, eax + ret + + ; --- GET_SERVICES (1) --- +.get_services: + stdcall ioctl_get_services, ebx + ret + + ; --- SVC_ENABLE (2) --- +.svc_enable: + cmp [ebx + IOCTL.inp_size], 4 + jb .fail + mov eax, [ebx + IOCTL.input] + mov eax, [eax] + stdcall dispatcher_enable_by_id, eax + push eax + call dispatcher_get_active_events + call vmmdev_update_event_filter + call dispatcher_get_active_caps + call vmmdev_update_capabilities + pop eax + ret + + ; --- SVC_DISABLE (3) --- +.svc_disable: + cmp [ebx + IOCTL.inp_size], 4 + jb .fail + mov eax, [ebx + IOCTL.input] + mov eax, [eax] + stdcall dispatcher_disable_by_id, eax + push eax + call dispatcher_get_active_events + call vmmdev_update_event_filter + call dispatcher_get_active_caps + call vmmdev_update_capabilities + pop eax + ret + + ; --- CLIP_STATUS (10) --- +.clip_status: + stdcall clip_ioctl_status, ebx + ret + + ; --- CLIP_READ (11) --- +.clip_read: + stdcall clip_ioctl_read, ebx + ret + + ; --- CLIP_WRITE (12) --- +.clip_write: + stdcall clip_ioctl_write, ebx + ret + +.fail: + or eax, -1 + ret +endp + +; ============================================================================= +; ioctl_get_services — Заполнить список сервисов +; ============================================================================= +proc ioctl_get_services stdcall uses ebx ecx edx esi edi, ioctl_ptr:dword + mov ebx, [ioctl_ptr] + cmp [ebx + IOCTL.out_size], 4 + jb .fail + + mov edi, [ebx + IOCTL.output] + mov ecx, [services_count] + mov [edi], ecx ; dd count + add edi, 4 + + test ecx, ecx + jz .done + + ; Доступное место = out_size - 4 + mov edx, [ebx + IOCTL.out_size] + sub edx, 4 + + mov esi, services_table + +.loop: + cmp edx, SVC_INFO_SIZE + jb .done + + ; dd id + mov eax, [esi + SERVICE_ENTRY.id] + mov [edi], eax + add edi, 4 + + ; rb name[16] — null-padded + push ecx esi edi + mov ecx, SVC_INFO_NAME_LEN + ; Заполнить нулями + push edi + xor al, al + rep stosb + pop edi + ; Скопировать имя + mov esi, [esi + SERVICE_ENTRY.name_ptr] + mov ecx, SVC_INFO_NAME_LEN - 1 +.copy_name: + lodsb + test al, al + jz .name_done + stosb + dec ecx + jnz .copy_name +.name_done: + pop edi esi ecx + add edi, SVC_INFO_NAME_LEN + + ; dd enabled + mov eax, [esi + SERVICE_ENTRY.enabled] + mov [edi], eax + add edi, 4 + + sub edx, SVC_INFO_SIZE + add esi, sizeof.SERVICE_ENTRY + dec ecx + jnz .loop + +.done: + xor eax, eax + ret + +.fail: + or eax, -1 + ret +endp diff --git a/drivers/vboxguest/sys/shutdown.inc b/drivers/vboxguest/sys/shutdown.inc new file mode 100644 index 000000000..455946278 --- /dev/null +++ b/drivers/vboxguest/sys/shutdown.inc @@ -0,0 +1,47 @@ +; ============================================================================= +; Модуль : Корректное завершение работы драйвера VBoxGuest +; Назначение : Деинициализация всех подсистем при выгрузке драйвера (DRV_EXIT) +; Файл : sys/shutdown.inc +; Порядок : По образцу Linux vbg_core_exit для избежания зависаний +; ============================================================================= + +; Полная деинициализация драйвера VBoxGuest (вызывается при DRV_EXIT) +proc sys_shutdown + + DEBUGF 2, "[VBoxGuest] Shutdown started\n" + + ; 1. Остановить все сервисы (отправить уведомления хосту) + call dispatcher_disable_all + DEBUGF 2, "[VBoxGuest] All services disabled\n" + + ; 2. Остановить таймер (ПЕРЕД IRQ detach, иначе tick вызовется во время cleanup) + call timer_stop + DEBUGF 2, "[VBoxGuest] Timer stopped\n" + + ; 3. Убрать IRQ handler ОБЯЗАТЕЛЬНО до освобождения буферов + ; ISR не должна писать в освобождаемую память +; call vbox_irq_detach +; DEBUGF 2, "[VBoxGuest] IRQ handler detached\n" + + ; 4. Сбросить маски события и возможностей на хосте (маска = 0) + xor eax, eax + mov [dispatcher_active_events], eax + call vmmdev_update_event_filter + DEBUGF 2, "[VBoxGuest] Event filter cleared\n" + + mov [dispatcher_active_caps], eax + call vmmdev_update_capabilities + DEBUGF 2, "[VBoxGuest] Capabilities cleared\n" + + ; 5. Освободить пакеты (ПОСЛЕ IRQ detach — ISR больше не пишет в них) + call hgcm_free_packets + DEBUGF 2, "[VBoxGuest] HGCM packets freed\n" + + call vmmdev_free_packets + DEBUGF 2, "[VBoxGuest] VMMDev packets freed\n" + + DEBUGF 2, "[VBoxGuest] Shutdown complete\n" + + xor eax, eax + ret +endp diff --git a/drivers/vboxguest/vboxguest.asm b/drivers/vboxguest/vboxguest.asm new file mode 100644 index 000000000..a4ab64e62 --- /dev/null +++ b/drivers/vboxguest/vboxguest.asm @@ -0,0 +1,92 @@ +; ============================================================================= +; Программа : VirtualBox Guest Driver для KolibriOS +; Модуль : Main driver entry +; Назначение : Full-featured driver +; Файл : vboxguest.asm +; Автор : lex_coder(lex_coder@mail.ru), Алексей Михайлов +; При моральной поддержке сообщества KolibriOS +; +; cp /usbhd0/1/vboxguest/vboxguest /sys/Drivers/vboxguest.sys +; loaddrv vboxguest +; +; ============================================================================= +format PE DLL native 0.05 +entry START + +section '.flat' code readable writable executable + +include '/usbhd0/1/ksrc_n/DRIVERS/proc32.inc' +include '/usbhd0/1/ksrc_n/DRIVERS/struct.inc' +include '/usbhd0/1/ksrc_n/DRIVERS/macros.inc' +include '/usbhd0/1/ksrc_n/DRIVERS/peimport.inc' +include '/usbhd0/1/ksrc_n/DRIVERS/fdo.inc' +include '/usbhd0/1/ksrc_n/DRIVERS/pci.inc' + +include 'config.inc' +include 'common/common.inc' +include 'data/data.inc' +include 'core/core.inc' +include 'vmmdev/vmmdev.inc' +include 'hgcm/core.inc' +include 'sys/init.inc' +include 'sys/shutdown.inc' +include 'sys/ioctl.inc' +include 'services/services.inc' + +BUILD_SERVICE_TABLE + +; ============================================================================= +; Driver Entry Point +; ============================================================================= +proc START c, state:dword, cmdline:dword + push ebx esi edi + cmp [state], DRV_ENTRY + je .entry + cmp [state], DRV_EXIT + je .exit + + pop edi esi ebx + xor eax, eax + ret + +.entry: + DEBUGF 2, "[VBoxGuest] VBoxGuest Driver v2.0\n" + + call sys_init ; + test eax, eax + jnz .fail + + ; Регистрация сервиса (IOCTL интерфейс для приложений) + invoke RegService, service_name, service_proc + + DEBUGF 2, "[VBoxGuest] IOCTL service '%s' registered (API v%d)\n", service_name, API_VERSION + DEBUGF 2, "[VBoxGuest] Initialization complete!\n" + pop edi esi ebx + ret + +.fail: + DEBUGF 2, "[VBoxGuest] Initialization FAILED\n" + +.exit: + DEBUGF 2, "[VBoxGuest] DRV_EXIT: Shutting down driver\n" + call sys_shutdown + pop edi esi ebx + xor eax, eax + ret +endp + + +; ============================================================================= +; Data Section +; ============================================================================= +align 4 +service_name db 'VBOXGUEST', 0 + +align 4 +vbox_device VBOX_DEVICE + +include_debug_strings + +align 4 +data fixups +end data diff --git a/drivers/vboxguest/vmmdev/capabilities.inc b/drivers/vboxguest/vmmdev/capabilities.inc new file mode 100644 index 000000000..91672e05e --- /dev/null +++ b/drivers/vboxguest/vmmdev/capabilities.inc @@ -0,0 +1,27 @@ +; ============================================================================= +; Модуль : VMMDev Guest Capabilities +; Назначение : Настройка возможностей гостя (REQ 56) +; Файл : vmmdev/capabilities.inc +; ============================================================================= + +; vmmdev_update_capabilities — Обновить caps по runtime-маске из dispatcher +proc vmmdev_update_capabilities + mov edi, [vbox_device.caps_virt] + test edi, edi + jz .bad + + ; Обновить or_mask в пакете + mov [edi + VMMDEV_SET_GUEST_CAPABILITIES2.or_mask], eax + mov dword [edi + VMMDEV_SET_GUEST_CAPABILITIES2.header.rc], 0 + + DEBUGF 2, "[VBoxGuest] [Caps] Updating capabilities: or_mask=0x%x\n", eax + + mov ebx, [vbox_device.caps_phys] + stdcall vmmdev_send_request, ebx + + mov eax, [edi + VMMDEV_SET_GUEST_CAPABILITIES2.header.rc] + ret +.bad: + mov eax, VERR_INVALID_POINTER + ret +endp \ No newline at end of file diff --git a/drivers/vboxguest/vmmdev/core.inc b/drivers/vboxguest/vmmdev/core.inc new file mode 100644 index 000000000..21a0b5f89 --- /dev/null +++ b/drivers/vboxguest/vmmdev/core.inc @@ -0,0 +1,25 @@ +; ============================================================================= +; Модуль : VMMDev Core +; Назначение : Базовые операции VMMDev: send_request, init протокола +; Файл : vmmdev/core.inc +; ============================================================================= + +; Базовая настройка протокола VMMDev +proc vmmdev_init_protocol + + ; GuestInfo + call guest_info_report + test eax, eax + jnz .fail + + ; GuestInfo2 (non-fatal — не все версии VBox поддерживают) + call guest_info_2_report + test eax, eax + jz @f + DEBUGF 2, "[VBoxGuest] [VMMDev] GuestInfo2 failed (rc=0x%x), continuing...\n", eax +@@: + + xor eax, eax +.fail: + ret +endp diff --git a/drivers/vboxguest/vmmdev/event_filter.inc b/drivers/vboxguest/vmmdev/event_filter.inc new file mode 100644 index 000000000..32c0a29bd --- /dev/null +++ b/drivers/vboxguest/vmmdev/event_filter.inc @@ -0,0 +1,28 @@ +; ============================================================================= +; Модуль : VMMDev Event Filter +; Назначение : Фильтр событий VMMDev (REQ 42) +; Файл : vmmdev/event_filter.inc +; ============================================================================= + +; vmmdev_update_event_filter — Обновить фильтр по маске из dispatcher +proc vmmdev_update_event_filter + mov edi, [vbox_device.filter_virt] + test edi, edi + jz .bad + + ; Обновить or_mask в пакете + mov [edi + VMMDEV_CTL_GUEST_FILTER_MASK.or_mask], eax + mov dword [edi + VMMDEV_CTL_GUEST_FILTER_MASK.header.rc], 0 + + DEBUGF 2, "[VBoxGuest] [Filter] Updating event filter: or_mask=0x%x\n", eax + + mov ebx, [vbox_device.filter_phys] + stdcall vmmdev_send_request, ebx + + mov eax, [edi + VMMDEV_CTL_GUEST_FILTER_MASK.header.rc] + ret + +.bad: + mov eax, VERR_INVALID_POINTER + ret +endp \ No newline at end of file diff --git a/drivers/vboxguest/vmmdev/guest_info.inc b/drivers/vboxguest/vmmdev/guest_info.inc new file mode 100644 index 000000000..43611fe40 --- /dev/null +++ b/drivers/vboxguest/vmmdev/guest_info.inc @@ -0,0 +1,77 @@ +; ============================================================================= +; Модуль : VMMDev Guest Info +; Назначение : ReportGuestInfo / ReportGuestInfo2 +; Файл : vmmdev/guest_info.inc +; ============================================================================= + +; Отправка ReportGuestInfo (REQ 50) +proc guest_info_report uses ebx esi + mov esi, [vbox_device.guestinfo_virt] + mov ebx, [vbox_device.guestinfo_phys] + test esi, esi + jz .bad + test ebx, ebx + jz .bad + + stdcall vmmdev_send_request, ebx + mov eax, [esi + VMMDEV_HEADER.rc] + test eax, eax + jnz .error + + DEBUGF 2, "[VBoxGuest] [VMMDev] Guest info sent (interface 0x%x)\n", VMMDEV_VERSION + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [VMMDev] send_guest_info failed, rc=0x%x\n", eax + mov eax, VERR_GENERAL_FAILURE + ret + +.bad: + mov eax, VERR_INVALID_PARAMETER + ret +endp + + +; Отправка ReportGuestInfo2 (REQ 58), версия заполняется из host_version +proc guest_info_2_report uses ebx esi edi + mov esi, [vbox_device.guestinfo2_virt] + mov ebx, [vbox_device.guestinfo2_phys] + test esi, esi + jz .bad + test ebx, ebx + jz .bad + + ; Заполнить версию GA из полученной версии хоста + mov edi, [vbox_device.host_version_virt] + test edi, edi + jz .send ; нет данных — отправляем как есть (нули) + + movzx eax, word [edi + VMMDEV_GET_HOST_VERSION.major] + mov word [esi + VMMDEV_REPORT_GUEST_INFO2.guest_info.additions_major], ax + movzx eax, word [edi + VMMDEV_GET_HOST_VERSION.minor] + mov word [esi + VMMDEV_REPORT_GUEST_INFO2.guest_info.additions_minor], ax + mov eax, [edi + VMMDEV_GET_HOST_VERSION.build] + mov dword [esi + VMMDEV_REPORT_GUEST_INFO2.guest_info.additions_build], eax + mov eax, [edi + VMMDEV_GET_HOST_VERSION.revision] + mov dword [esi + VMMDEV_REPORT_GUEST_INFO2.guest_info.additions_revision], eax + +.send: + stdcall vmmdev_send_request, ebx + mov eax, [esi + VMMDEV_HEADER.rc] + test eax, eax + jnz .error + + DEBUGF 2, "[VBoxGuest] [VMMDev] Guest info 2 sent (interface 0x%x)\n", VMMDEV_VERSION + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [VMMDev] send_guest_info_2 failed, rc=0x%x\n", eax + mov eax, VERR_GENERAL_FAILURE + ret + +.bad: + mov eax, VERR_INVALID_PARAMETER + ret +endp diff --git a/drivers/vboxguest/vmmdev/hypervisor.inc b/drivers/vboxguest/vmmdev/hypervisor.inc new file mode 100644 index 000000000..c45145fe0 --- /dev/null +++ b/drivers/vboxguest/vmmdev/hypervisor.inc @@ -0,0 +1,51 @@ +; ============================================================================= +; Модуль : VMMDev Host/Hypervisor Info +; Назначение : Получение информации о хосте (REQ 4: GetHostVersion) +; Файл : vmmdev/hypervisor.inc +; ============================================================================= + +; Отправить REQ 4 GetHostVersion, eax = header.rc +proc vmmdev_get_host_version uses ebx edi + mov edi, [vbox_device.host_version_virt] + mov ebx, [vbox_device.host_version_phys] + test edi, edi + jz .bad + test ebx, ebx + jz .bad + + stdcall vmmdev_send_request, ebx + mov eax, [edi + VMMDEV_HEADER.rc] + ret + +.bad: + mov eax, VERR_INVALID_POINTER + ret +endp + + +proc vmmdev_check_version uses ebx + DEBUGF 2, "[VBoxGuest] [VMMDev] Checking host version (GetHostVersion)...\n" + + stdcall vmmdev_get_host_version + + DEBUGF 2, "[VBoxGuest] [VMMDev] GetHostVersion rc=0x%x\n", eax + test eax, eax + jnz .error + + mov eax, [vbox_device.host_version_virt] + movzx ebx, [eax + VMMDEV_GET_HOST_VERSION.major] + movzx ecx, [eax + VMMDEV_GET_HOST_VERSION.minor] + DEBUGF 2, "[VBoxGuest] [VMMDev] Host version: %d.%d.%d r%d features=0x%x\n", \ + ebx, ecx, \ + [eax + VMMDEV_GET_HOST_VERSION.build], \ + [eax + VMMDEV_GET_HOST_VERSION.revision], \ + [eax + VMMDEV_GET_HOST_VERSION.features] + + xor eax, eax + ret + +.error: + DEBUGF 2, "[VBoxGuest] [VMMDev] ERROR: GetHostVersion failed, rc=0x%x\n", eax + mov eax, VERR_GENERAL_FAILURE + ret +endp \ No newline at end of file diff --git a/drivers/vboxguest/vmmdev/packets.inc b/drivers/vboxguest/vmmdev/packets.inc new file mode 100644 index 000000000..2daa46a62 --- /dev/null +++ b/drivers/vboxguest/vmmdev/packets.inc @@ -0,0 +1,169 @@ +; ============================================================================= +; Модуль : VMMDev Packets +; Назначение : Выделение и инициализация пакетов VMMDev (одна страница 4KB) +; Файл : vmmdev/packets.inc +; ============================================================================= + +VMMDEV_PKT_OFS_HOST_VERSION = 0 ; sizeof.VMMDEV_GET_HOST_VERSION +VMMDEV_PKT_OFS_GUEST_INFO = 64 ; sizeof.VMMDEV_REPORT_GUEST_INFO +VMMDEV_PKT_OFS_GUEST_INFO2 = 128 ; sizeof.VMMDEV_REPORT_GUEST_INFO2 (~168) +VMMDEV_PKT_OFS_CAPS = 320 ; sizeof.VMMDEV_SET_GUEST_CAPABILITIES2 +VMMDEV_PKT_OFS_FILTER = 384 ; sizeof.VMMDEV_CTL_GUEST_FILTER_MASK +VMMDEV_PKT_OFS_ACK_EVENTS = 448 ; sizeof.VMMDEV_ACKNOWLEDGE_EVENTS + +; Строка версии (read-only, для копирования в пакет) +vmmdev_version_string db '1.0.0_KolibriOS', 0 +VMMDEV_VERSION_STRING_LEN = $ - vmmdev_version_string + +; ============================================================================= +; vmmdev_init_packets — Выделить страницу и инициализировать все VMMDev пакеты +; ============================================================================= +proc vmmdev_init_packets uses ebx ecx edx esi edi + DEBUGF 2, "[VBoxGuest] [VMMDev] Init packets (dynamic)\n" + + ; --- Выделить страницу --- + invoke KernelAlloc, 4096 + test eax, eax + jz .alloc_fail + + mov [vbox_device.vmmdev_packets_page], eax + + ; Обнулить страницу + mov edi, eax + xor eax, eax + mov ecx, 4096 / 4 + rep stosd + + mov ebx, [vbox_device.vmmdev_packets_page] + + ; ================================================================= + ; 1) HOST_VERSION (offset 0) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_HOST_VERSION] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_GET_HOST_VERSION + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_REQ_GET_HOST_VERSION + + mov [vbox_device.host_version_virt], edi + mov eax, edi + invoke GetPhysAddr + mov [vbox_device.host_version_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] HostVersion packet: virt=0x%x, phys=0x%x\n", \ + [vbox_device.host_version_virt], eax + + ; ================================================================= + ; 2) REPORT_GUEST_INFO (offset 64) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_GUEST_INFO] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_REPORT_GUEST_INFO + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_REQ_REPORT_GUEST_INFO + mov dword [edi + VMMDEV_REPORT_GUEST_INFO.interface_version], VMMDEV_VERSION + mov dword [edi + VMMDEV_REPORT_GUEST_INFO.os_type], VBOXOSTYPE_KOLIBRIOS + + mov [vbox_device.guestinfo_virt], edi + mov eax, edi + invoke GetPhysAddr + mov [vbox_device.guestinfo_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] GuestInfo packet : virt=0x%x, phys=0x%x\n", \ + [vbox_device.guestinfo_virt], eax + + ; ================================================================= + ; 3) REPORT_GUEST_INFO2 (offset 128) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_GUEST_INFO2] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_REPORT_GUEST_INFO2 + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_REQ_REPORT_GUEST_INFO2 + ; rc, reserved1, f_requestor уже 0 (обнулено) + ; payload: версия заполняется из host_version в guest_info_2_report() + mov dword [edi + VMMDEV_REPORT_GUEST_INFO2.guest_info + VMMDEV_GUEST_INFO2.additions_features], VBOXGSTINFO2_F_REQUESTOR_INFO + ; szName[128] — копируем строку версии + push edi ecx + lea edi, [edi + VMMDEV_REPORT_GUEST_INFO2.guest_info + VMMDEV_GUEST_INFO2.szName] + mov esi, vmmdev_version_string + mov ecx, VMMDEV_VERSION_STRING_LEN + rep movsb + pop ecx edi + + lea eax, [ebx + VMMDEV_PKT_OFS_GUEST_INFO2] + mov [vbox_device.guestinfo2_virt], eax + invoke GetPhysAddr + mov [vbox_device.guestinfo2_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] GuestInfo2 packet : virt=0x%x, phys=0x%x\n", \ + [vbox_device.guestinfo2_virt], eax + + ; ================================================================= + ; 4) SET_GUEST_CAPABILITIES2 (offset 320) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_CAPS] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_SET_GUEST_CAPABILITIES2 + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_SET_GUEST_CAPS + mov dword [edi + VMMDEV_SET_GUEST_CAPABILITIES2.or_mask], VBOXGUEST_GUEST_CAPS_OR_MASK + mov dword [edi + VMMDEV_SET_GUEST_CAPABILITIES2.not_mask], VBOXGUEST_GUEST_CAPS_NOT_MASK + + mov [vbox_device.caps_virt], edi + mov eax, edi + invoke GetPhysAddr + mov [vbox_device.caps_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] Caps packet : virt=0x%x, phys=0x%x\n", \ + [vbox_device.caps_virt], eax + + ; ================================================================= + ; 5) CTL_GUEST_FILTER_MASK (offset 384) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_FILTER] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_CTL_GUEST_FILTER_MASK + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_REQ_CTL_GUEST_FILTER_MASK + mov dword [edi + VMMDEV_CTL_GUEST_FILTER_MASK.or_mask], VBOXGUEST_EVENTS_OR_MASK + mov dword [edi + VMMDEV_CTL_GUEST_FILTER_MASK.not_mask], VBOXGUEST_EVENTS_NOT_MASK + + mov [vbox_device.filter_virt], edi + mov eax, edi + invoke GetPhysAddr + mov [vbox_device.filter_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] Filter packet : virt=0x%x, phys=0x%x\n", \ + [vbox_device.filter_virt], eax + + DEBUGF 2, "[VBoxGuest] [VMMDev] Filter: OR=0x%x, NOT=0x%x\n", \ + [edi + VMMDEV_CTL_GUEST_FILTER_MASK.or_mask], \ + [edi + VMMDEV_CTL_GUEST_FILTER_MASK.not_mask] + + ; ================================================================= + ; 6) ACKNOWLEDGE_EVENTS (offset 448) + ; ================================================================= + lea edi, [ebx + VMMDEV_PKT_OFS_ACK_EVENTS] + mov dword [edi + VMMDEV_HEADER.size], sizeof.VMMDEV_ACKNOWLEDGE_EVENTS + mov dword [edi + VMMDEV_HEADER.version], VMMDEV_REQUEST_HEADER_VERSION + mov dword [edi + VMMDEV_HEADER.request_type], VMMDEV_REQ_ACKNOWLEDGE_EVENTS + + mov [vbox_device.events_virt], edi + mov eax, edi + invoke GetPhysAddr + mov [vbox_device.events_phys], eax + DEBUGF 2, "[VBoxGuest] [VMMDev] ACK packet : virt=0x%x, phys=0x%x\n", \ + [vbox_device.events_virt], eax + + DEBUGF 2, "[VBoxGuest] [VMMDev] Init packets success\n" + xor eax, eax + ret + +.alloc_fail: + DEBUGF 2, "[VBoxGuest] [VMMDev] ERROR: KernelAlloc failed for VMMDev packets\n" + mov eax, VERR_NO_MEMORY + ret +endp + +; ============================================================================= +; vmmdev_free_packets — Освободить страницу VMMDev пакетов +; ============================================================================= +proc vmmdev_free_packets + cmp dword [vbox_device.vmmdev_packets_page], 0 + je @f + invoke KernelFree, [vbox_device.vmmdev_packets_page] + mov dword [vbox_device.vmmdev_packets_page], 0 +@@: + ret +endp diff --git a/drivers/vboxguest/vmmdev/vmmdev.inc b/drivers/vboxguest/vmmdev/vmmdev.inc new file mode 100644 index 000000000..323968df6 --- /dev/null +++ b/drivers/vboxguest/vmmdev/vmmdev.inc @@ -0,0 +1,12 @@ +; ============================================================================= +; Модуль : VMMDev Aggregator +; Назначение : Подключение модулей протокола VMMDev +; Файл : vmmdev/vmmdev.inc +; ============================================================================= + +include 'vmmdev/packets.inc' +include 'vmmdev/core.inc' +include 'vmmdev/guest_info.inc' +include 'vmmdev/capabilities.inc' +include 'vmmdev/event_filter.inc' +include 'vmmdev/hypervisor.inc' -- 2.49.1