; ---------------------------------------------------
; Виртуальный драйвер VXDSRV
; Version 1.1
; ---------------------------------------------------
; Copyright (C) Александр Фролов 1995
; ---------------------------------------------------
; Работает совместно с приложением dos2win.exe
; и DLL-библиотекой d2w.dll
;
; Позволяет запускать приложения Windows из виртуальной
; машины MS-DOS, из командной строки Norton Commander или
; аналогичной оболочки, работающей на виртуальной
; машине MS-DOS
;
; Выполняет перехват функции 4B00h прерывания int 21h
; и сохраняет полученные этой функцией командную строку 
; и строку параметров, а также определенные отдельно
; текущий диск и текущий каталог в области памяти, 
; зарезервированной DLL-библиотекой d2w.dll. 
; Затем драйвер вызывает функцию, определенную
; в этой библиотеке и посылающую сообщение приложению
; dos2win.exe. Приняв сообщение, приложение dos2win.exe
; запускает программу, пользуясь данными, полученными
; от виртуального драйвера
; ---------------------------------------------------

.386p
include vmm.inc

; Идентификатор драйвера VXDSRV. 
VXDSRV_Id equ 28C2h 

HiVers    equ 1    ; верхний номер версии драйвера
LoVers    equ 1    ; нижний номер версии драйвера
Vers      equ ((HiVers shl 8) or LoVers)
CFlag     equ 1

; ===================================================
; Заголовок виртуального драйвера
; ===================================================
Declare_Virtual_Device VXDSRV, HiVers, LoVers, \
  VXDSRV_Control, VXDSRV_Id, Undefined_Init_Order, \
  VXDSRV_V86API_Handler, VXDSRV_PMAPI_Handler,

; ===================================================
; Инициализация в реальном режиме
; ===================================================
VXD_REAL_INIT_SEG

RealInit proc near

; Вывод "рекламной" текстовой строки 
    mov ah, 9
    mov dx, offset VxD_Hello
    int 21h

; Признак успешной инициализации
    mov ax, Device_Load_Ok  

; Страницы физической памяти не резервируются   
    xor bx, bx
    
; Данные для каждого экземпляра виртуальной машины    
; не резервируются
    xor si, si

; Значение, передаваемое процедуре Sys_Critical_Init
    xor edx, edx
    ret
RealInit endp

VxD_Hello db '*VXDSRV* Copyright (C) Alexandr Frolov 1995'
          db 0dh, 0ah, '$'
VXD_REAL_INIT_ENDS

; ===================================================
; Системная критическая инициализация
; ===================================================
VXD_ICODE_SEG
BeginProc VXDSRV_Sys_Crit_Init
  
; Устанавливаем фильтр для прерывания int 21h  
  mov  eax, 21h
  mov  esi, offset32 V86_Int21_Handler
  VMMcall Hook_V86_Int_Chain
  
  clc     ; признак успешного завершения
  ret
EndProc   VXDSRV_Sys_Crit_Init
VXD_ICODE_ENDS

; ===================================================
; Зафиксированный сегмент кода
; ===================================================
VxD_LOCKED_CODE_SEG

; ---------------------------------------------------
; Определение управляющих процедур
; ---------------------------------------------------
BeginProc VXDSRV_Control

; Процедура системной критической инициализации  
  Control_Dispatch Sys_Critical_Init, VXDSRV_Sys_Crit_Init
  
  clc
  ret
EndProc VXDSRV_Control

VxD_LOCKED_CODE_ENDS

; ===================================================
; Сегмент данных
; ===================================================
VxD_DATA_SEG

; ---------------------------------------------------
; Таблица адресов функций API драйвера
; ---------------------------------------------------
VXDSRV_API_Call label dword
  
  dd  offset32 vxdapiGetVersion         ; AX=0
  dd  offset32 vxdapiRegisterWnd        ; AX=1
  dd  offset32 vxdapiUnregisterWnd      ; AX=2

VXDSRV_API_MaxCall EQU ($ - VXDSRV_API_Call) / 4

CallbackSel     dw 0h ; селектор функции обратного вызова
CallbackOff     dd 0h ; смещение функции обратного вызова
V86CallFlag     dd 0  ; вызов из виртуальной машины V86
CallbackBuf     dd 0  ; адрес буфера для передачи строки

flatpCmdLine  dd 0      ; адрес командной строки
flatpParmLine dd 0      ; адрес строки параметров 
hSem   dd 0             ; идентификатор семафора

nCurDisk  db 0          ; текущий диск
szCurPath db 64 dup(0)  ; текущий каталог

; Сегмент буфера для получения текущего пути
wPathSeg     dw 0

; Соответствующий этому буферу FLAT-адрес
flatpPathBuf dd 0

VxD_DATA_ENDS

; ===================================================
; Перемещаемый сегмент кода
; ===================================================
VxD_CODE_SEG

; ---------------------------------------------------
; Вход API для виртуальных машин V86
; ---------------------------------------------------
BeginProc   VXDSRV_V86API_Handler
  mov eax, 1            ; признак машины V86
  mov V86CallFlag, eax
  
; Вызываем универсальное API драйвера
  call VXDSRV_API_Handler
  ret
EndProc     VXDSRV_V86API_Handler

; ---------------------------------------------------
; Вход API для виртуальных машин защищенного режима
; ---------------------------------------------------
BeginProc   VXDSRV_PMAPI_Handler
  mov eax, 0 ; признак защищенного режима
  mov V86CallFlag, eax
  
  call VXDSRV_API_Handler
  ret
EndProc     VXDSRV_PMAPI_Handler

; ---------------------------------------------------
; Универсальное API драйвера, вызывается как для
; режима VM86, так и для защищенного режима
; ---------------------------------------------------
BeginProc   VXDSRV_API_Handler
    
; Загружаем номер функции API    
    movzx   eax, [ebp.Client_AX]

; Проверяем этот номер на допустимость
    cmp     eax, VXDSRV_API_MaxCall
    jae     short InvalidNumber

; В случае успеха сбрасываем флаг переноса и    
; вызываем функцию по таблице адресов
    and     [ebp.Client_EFlags], NOT CFlag
    call    VXDSRV_API_Call[eax * 4]
    ret
InvalidNumber:
    or      [ebp.Client_EFlags], CFlag
    ret
EndProc     VXDSRV_API_Handler

; ---------------------------------------------------
; vxdapiGetVersion, номер = 0
; Возвращает в AX номер версии:
;    AH - старший номер, AL - младший номер
; ---------------------------------------------------
BeginProc   vxdapiGetVersion
    mov     [ebp.Client_AX], Vers
    clc
    ret
EndProc     vxdapiGetVersion

; ---------------------------------------------------
; vxdapiRegisterWnd, номер = 1
; ---------------------------------------------------
BeginProc vxdapiRegisterWnd

; Можно вызывать только из защищенного режима  
  mov eax, V86CallFlag
  cmp eax, 0
  jnz short RW_CallFromRealMode
  
; Сохраняем смещение и селектор процедуры обратного
; вызова, расположенной в фиксированном сегменте
; кода DLL-библиотеки d2w.dll
  
  movzx eax, [ebp.Client_CX] ; смещение
  mov [CallbackOff], eax
  mov ax, [ebp.Client_DX]    ; селектор
  mov [CallbackSel], ax
  
; Преобразуем адрес буфера во flat-адрес 
  
  mov  ax, (Client_SI shl 8) + Client_DI
  VMMcall Map_Flat 
  mov [CallbackBuf], eax
  
; Создаем семафор с начальным значением 1  
  mov  ecx, 1
  VMMcall Create_Semaphore
  mov  hSem, eax  ; сохраняем идентификатор семафора
  
  clc
  ret

RW_CallFromRealMode:
  stc
  ret
EndProc   vxdapiRegisterWnd

; ---------------------------------------------------
; vxdapiUnregister, номер = 2
; ---------------------------------------------------
BeginProc vxdapiUnregisterWnd

; Можно вызывать только из защищенного режима  
  mov eax, V86CallFlag
  cmp eax, 0
  jnz short UW_CallFromRealMode
  
  mov eax, 0
  mov [CallbackOff],   eax
  mov [CallbackSel],   ax
  mov [CallbackBuf],   eax
  
; Уничтожаем семафор  
  mov  eax, hSem
  VMMcall Destroy_Semaphore
  
  clc
  ret

UW_CallFromRealMode:
  stc
  ret
EndProc   vxdapiUnregisterWnd

; ---------------------------------------------------
; StrCpy, копирование строки ASCIIZ
; [ecx] - исходный адрес
; [edx] - адрес буфера
; ---------------------------------------------------
BeginProc StrCpy
  push  eax
  push  ecx
  push  edx

StrCpyLoop:
  mov   al, [ecx]
  mov   [edx], al
  cmp   al, 0
  jz    short StrCpyEnd
  inc   ecx
  inc   edx
  jmp short StrCpyLoop
StrCpyEnd:
  
  pop edx
  pop ecx
  pop eax
  ret
EndProc   StrCpy

; ---------------------------------------------------
; CopyParm, копирование строки параметров
; ---------------------------------------------------
BeginProc CopyParm
  push  eax
  push  ebx
  push  ecx
  push  edx
  
; Вычисляем адрес строки параметров в буфере  
  mov   edx, CallbackBuf
  add   edx, 65 + 128

; Определяем размер строки параметров
  mov   ecx, flatpParmLine
  movzx ebx, byte ptr [ecx]
  inc   ecx
  
; Если параметров нет, закрываем строку нулем
  cmp   ebx, 0
  jz short ParmCopyEnd

; Цикл копирования строки параметров
ParmCopyLoop:
  cmp   ebx, 0
  jz    short ParmCopyEnd
  
  mov   al, [ecx]
  mov   [edx], al
  
  dec   ebx
  inc   ecx
  inc   edx
  jmp short ParmCopyLoop

ParmCopyEnd:

; Закрываем строку нулем 
  mov   al, 0
  mov   [edx], al
  
  pop edx
  pop ecx
  pop ebx
  pop eax
  ret
EndProc   CopyParm

; ---------------------------------------------------
; V86_Int21_Handler
; Фильтр для функции 4B00h прерывания INT 21h,
; вызываемого из виртуальной машины MS-DOS
; (эта функция выполняет запуск программы MS-DOS)
; ---------------------------------------------------
BeginProc V86_Int21_Handler
  
  pushad
  
; Проверяем номер функции. Нас интересует только  
; запуск программ
  mov ax, word ptr [ebp.Client_AX]
  cmp ax, 4B00h
  jnz HandlerExit
  
; Если окно приложения dos2win не зарегистрировано,
; ничего не делаем
  mov eax, CallbackBuf
  cmp eax, 0
  jz  HandlerExit

; Если запускается программа MS-DOS, ничего 
; не делаем
  call short IsWindowsApp
  jz HandlerExit

; Для исключения реентерабельных вызовов выполняем
; ожидание семафора
  mov  eax, hSem
  mov  ecx,(Block_Enable_Ints OR Block_Svc_If_Ints_Locked)
  VMMcall Wait_Semaphore
  
  
; Получаем текущий диск и каталог  
  call short GetCurDir
  
; Сохраняем номер текущего диска  
  mov   edx, CallbackBuf
  mov   al, nCurDisk
  mov   byte ptr [edx], al
  
; Определяем FLAT-адрес командной строки
  mov ax, (Client_DS shl 8) + Client_DX
  VMMcall Map_Flat
  mov flatpCmdLine, eax

; Определяем FLAT-адрес блока EPB
  mov ax, (Client_ES shl 8) + Client_BX 
  VMMcall Map_Flat
  
; Загружаем в DX:BX адрес строки параметров  
  mov bx, [eax + 2]
  mov dx, [eax + 4]

; Определяем FLAT-адрес строки параметров
  
  push dword ptr [ebp.Client_ES]
  push [ebp.Client_EBX]
  
  mov [ebp.Client_ES], dx
  mov [ebp.Client_BX], bx
  mov ax, (Client_ES shl 8) + Client_BX
  VMMcall Map_Flat
  mov flatpParmLine, eax

  pop [ebp.Client_EBX]
  pop dword ptr [ebp.Client_ES]
  
; Копируем командную строку в буфер, который
; находится в DLL-библиотеке d2w.dll
  mov   ecx, flatpCmdLine
  mov   edx, CallbackBuf
  add   edx, 65
  call  short StrCpy
 
; Выполняем копирование строки параметров
  call  short CopyParm
  
; Определяем идентификатор текущей VM
  VMMCall Get_Cur_VM_Handle
  mov edx, ebx
  
; Определяем идентификатор системной VM
  VMMcall Get_Sys_VM_Handle
  
; Планируем вызов функции обратного вызова
  mov esi, offset32 CallbackProc
  VMMcall Schedule_VM_Event
  
  popad
  
; Если запускается приложение Windows, блокируем  
; выполнение прерывания INT 21h в виртуальной
; машине MS-DOS
  clc
  
  ret
  
HandlerExit:  
  popad

; Если запускается программа MS-DOS, наш драйвер
; не мешает этому процессу
  stc
  
  ret
EndProc   V86_Int21_Handler

; ---------------------------------------------------
; CallbackProc
; Функция обратного вызова
; Вызывается в системной VM по запросу фильтра
; прерывания INT 21h, установленного нашим драйвером
; ---------------------------------------------------
BeginProc CallbackProc
  
; Сохраняем состояние системной VM
  Push_Client_State
  
; Начинаем вложенное выполнение  
  VMMcall Begin_Nest_Exec

; Записываем в стек системной VM параметр -
; версию нашего VxD-драйвера
  mov ax, Vers
  VMMcall Simulate_Push
  
; Вызов функции обратного вызова, определенной
; в DLL-библиотеке d2w.dll
  mov edx, [CallbackOff]
  mov cx, [CallbackSel]
  VMMcall Simulate_Far_Call

; Выполняем вызов и восстановление состояния 
; системной VM
  VMMcall Resume_Exec
  VMMcall End_Nest_Exec
  Pop_Client_State
  
; Сбрасываем семафор, разрешая обработку 
; следующего прерывания INT 21h
  mov  eax, hSem
  VMMcall Signal_Semaphore
  
  ret  
EndProc   CallbackProc
  
; ---------------------------------------------------
; GetCurDir
; Определение текущего диска и текущего каталога
; в виртуальной машине MS-DOS, выполняющей запуск
; программы с помощью прерывания INT 21h
; ---------------------------------------------------
BeginProc GetCurDir  
  
  pushad
  
; Сохраняем состояние VM MS-DOS
  Push_Client_State
  
  VMMcall Begin_Nest_Exec
  
; Определяем номер текущего диска в VM MS-DOS
; Используем для этого функцию 1900h прерывания
; INT 21h. Номер диска возвращается в регистре AL
  mov ax, 1900h
  mov word ptr [ebp.Client_AX], ax
  mov eax, 21h
  VMMcall Exec_Int

; Сохраняем номер текущего диска в VM MS-DOS
  mov ax, word ptr [ebp.Client_AX]
  mov nCurDisk, al
  
; Для определения текущего пути VM MS-DOS
; заказываем буфер размером 64 байта в адресном
; пространстве VM MS-DOS, пользуясь функцией 4800h
; прерывания INT 21h
  mov ax, 4800h
  mov word ptr [ebp.Client_AX], ax
  
; Размер буфера задается в параграфах (по 16 байт)  
  mov ax, 0004h   
  mov word ptr [ebp.Client_BX], ax
  mov eax, 21h
  VMMcall Exec_Int

; Сохраняем сегментную компоненту адреса  
; (смещение полученного буфера всегда равно 0)
  mov ax, word ptr [ebp.Client_AX]
  mov wPathSeg, ax

; В маловероятном случае, когда в VM MS-DOS  
; совсем нет свободной памяти (даже 64 байт),
; мы не заполняем строку параметров: 
; если памяти нет, все равно нельзя запустить
; ни программу MS-DOS, ни приложение Windows
  mov ax, wPathSeg
  cmp ax, 0
  jz DoNotGetPath
  
; Определяем FLAT-адрес полученного буфера
  mov [ebp.Client_ES], ax
  mov [ebp.Client_BX], 0
  mov ax, (Client_ES shl 8) + Client_BX
  VMMcall Map_Flat
  mov flatpPathBuf, eax

; Получаем строку текущего каталога VM MS-DOS,
; для чего вызываем функцию 4700h прерывания 
; INT 21h. Перед вызовом этой функции нужно
; предоставить в DS:SI адрес буфера размером
; 64 байта, в который и будет записана строка
  mov ax, wPathSeg
  mov word ptr [ebp.Client_DS], ax
  mov ax, 0
  mov word ptr [ebp.Client_SI], ax
  mov ax, 0
  mov word ptr [ebp.Client_DX], ax
  mov ax, 4700h
  mov word ptr [ebp.Client_AX], ax
  mov eax, 21h
  VMMcall Exec_Int

; Копируем строку текущего каталога в буфер  
; CallbackBuf со смещением 1 байт
  mov   ecx, flatpPathBuf
  mov   edx, CallbackBuf
  add   edx, 1
  call  short StrCpy

; Освобождаем буфер, полученный в адресном
; пространстве VM MS-DOS
  mov ax, wPathSeg
  mov word ptr [ebp.Client_ES], ax
  mov ax, 4900h
  mov word ptr [ebp.Client_AX], ax
  mov eax, 21h
  VMMcall Exec_Int
  
  VMMcall End_Nest_Exec
  
DoNotGetPath:
  
  Pop_Client_State
  popad
  ret
EndProc GetCurDir  

; ---------------------------------------------------
; IsWindowsApp
; Проверка файла запускаемой программы
;
; Функция возвращает флаг Z = 0, если запускается
; приложение Windows в формате NE или PE, и Z != 0, 
; если запускается программа MS-DOS
;
; Входные параметры:
; [ebp.Client_DS] - сегмент буфера, в котором находится
;   путь к запускаемой программе
; [ebp.Client_DX] - смещение буфера, в котором находится
;   путь к запускаемой программе
; ---------------------------------------------------
; Copyright (C) Сергей Ноженко, 1995
; ---------------------------------------------------
BeginProc IsWindowsApp

  Push_Client_State
  VMMcall Begin_Nest_Exec

; Индикатор, который будет равен нулю на выходе  
; из функции, если запускаемый файл не содержит
; приложение Windows
  sub esi, esi

; Открываем файл с программой  
  mov word ptr [ebp.Client_AX], 3D00h
  mov eax, 21h
  VMMcall Exec_Int

; Проверка ошибки  
  test word ptr [ebp.Client_Flags], 1
  jnz EndNest

; Сохраняем идентификатор файла  
  mov ax, word ptr [ebp.Client_AX]
  push eax

; Заказываем 4 параграфа памяти  
  mov word ptr [ebp.Client_AX], 4800h
  mov word ptr [ebp.Client_BX], 4
  mov eax, 21h
  VMMcall Exec_Int

; Проверяем, выделена ли нам память  
  mov ax, word ptr [ebp.Client_AX]
  or  ax, ax
  jz  CloseFile

; Читаем старый заголовок исполнимого файла 
; (заголовок программы MS-DOS)
; Готовим ES для вызова функции освобождения
; памяти
  mov word ptr [ebp.Client_DS], ax
  mov word ptr [ebp.Client_ES], ax
  mov word ptr [ebp.Client_AX], 3F00h
  pop eax
  mov word ptr [ebp.Client_BX], ax
  mov word ptr [ebp.Client_CX], 40h ; читаем 40h байт
  mov word ptr [ebp.Client_DX], 0
  mov eax, 21h
  VMMcall Exec_Int

; Проверка ошибки
  test word ptr [ebp.Client_Flags], 1
  jnz FreeMem

; Если размер файла меньше 40h байт, это
; не приложение Windows
  cmp word ptr [ebp.Client_AX], 40h
  jl  short FreeMem

; Получаем FLAT-адрес
  mov ax, (Client_DS shl 8) + Client_DX
  VMMcall Map_Flat

; Проверяем сигнатуру EXE-файла. Если это не
; EXE-файл, то мы имеем дело не с приложением
; Windows
  cmp word ptr [eax], "ZM"
  jne short FreeMem

; Проверяем смещение таблицы relocation table.  
; Если оно меньше чем 40h, это программа MS-DOS
  cmp word ptr [eax + 18h], 40h
  jl  short FreeMem

  mov edi, eax

; Ищем заголовок NewEXE
  mov word ptr [ebp.Client_AX], 4200h
  mov bx, word ptr [eax + 3Ch]
  mov word ptr [ebp.Client_DX], bx
  mov bx, word ptr [eax + 3Eh]
  mov word ptr [ebp.Client_CX], bx
  mov eax, 21h
  VMMcall Exec_Int

  test word ptr [ebp.Client_Flags], 1
  jnz short FreeMem

; Читаем первое слово заголовка NewEXE  
  mov word ptr [ebp.Client_AX], 3F00h
  mov word ptr [ebp.Client_CX], 2
  mov word ptr [ebp.Client_DX], 0
  mov eax, 21h
  VMMcall Exec_Int

  test word ptr [ebp.Client_Flags], 1
  jnz short FreeMem
  cmp word ptr [ebp.Client_AX], 2
  jne short FreeMem

; Проверяем сигнатуру исполнимого сегмента.
; Допустимы сигнатуры "NE" (segmented executable)
; и "PE" (portable executable)
  cmp word ptr [edi], "EN"
  je  short WinApp

  cmp word ptr [edi], "EP"
  jne short FreeMem

WinApp:
  
; Устанавливаем индикатор - запускается
; приложение Windows
  inc esi

; Освобождаем память
FreeMem:
  mov word ptr [ebp.Client_AX], 4900h
  mov eax, 21h
  VMMcall Exec_Int

; Закрываем файл
CloseFile:
  mov word ptr [ebp.Client_AX], 3E00h
  mov eax, 21h
  VMMcall Exec_Int

EndNest:
  VMMcall End_Nest_Exec
  Pop_Client_State

  or esi, esi
  ret
EndProc   IsWindowsApp

  VxD_CODE_ENDS
END

