.586
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

include MultimonDX.inc

;--------------------------------------------------------------------------------

.code

start:
    ;get information about all available monitors and save it to structures
    invoke  GetMonitorsDX
    .if eax==NULL
    ;if error then show error message and exit
        invoke  MessageBox,0,offset szNoDX,offset szAppName,MB_OK or MB_ICONERROR or MB_TASKMODAL or MB_TOPMOST
    .else
        ;get handle of current module
        invoke  GetModuleHandle,NULL
        mov     hInstance,eax
        ;make and run main dialog box
        invoke  DialogBoxParam,eax,IDD_DIALOG1,NULL,offset DlgProc,NULL
    .endif
    ;in case the DX object was created we need to release it
    invoke  ReleaseDX
    invoke  ExitProcess,NULL

;--------------------------------------------------------------------------------

;PURPOSE
;window procedure of the main dialog box

;IN
;hWin - handle to procedure's window
;uMsg - number of message being processed
;wParam,lParam - parameters of the message

;RET
;return TRUE if message was processed or FALSE otherwise

;REMARKS
;it's just a simple modal dialog window procedure

DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

    mov     eax,uMsg
    .if eax==WM_INITDIALOG
        ;save handle of window into a global var
        mov     eax,hWin
        mov     hDlg,eax
        ;fill all fields in dialog box
        invoke  FillDlg,eax
    .elseif eax==WM_COMMAND
        mov     eax,wParam
        .if eax==IDB_CLOSE + (BN_CLICKED shl 16)
        ;if close button pressed then close dialog
            invoke  EndDialog,hWin,0
        .elseif eax==IDB_REFRESH + (BN_CLICKED shl 16)
            ;release the DirectX object
            invoke  ReleaseDX
            ;obtain the DirectX object again reflecting refreshed new condition
            ;of system and monitors
            invoke  GetMonitorsDX
            .if eax!=NULL
            ;if success then show this data on the dialog box
                invoke  FillDlg,hWin
            .endif
        .elseif eax==IDB_SAVE + (BN_CLICKED shl 16)
        ;I did not implement saving of data; you can do it yourself
            nop
        .elseif eax==IDCB_ADAPTERS + (CBN_SELCHANGE shl 16)
        ;if another adapter is selected in adapters' combo box
        ;then show infor about currently selected adapter
            invoke  SendDlgItemMessage,hWin,IDCB_ADAPTERS,CB_GETCURSEL,0,0
            invoke  ShowMonInfo,hWin,eax
        .endif
    .elseif eax==WM_CLOSE
        invoke  EndDialog,hWin,0
    .else
        return FALSE
    .endif
    return TRUE

DlgProc endp

;--------------------------------------------------------------------------------

;PURPOSE
;fills all contents of the dialog (including adapter count and adapter names)
;used upon dialog start up and refreshing of monitor information

;IN
;hWin - handle to dialog window to be filled

;RET
;void

;REMARKS
;useful function for filling the contents of dialog box

.const
szAdaptName db "%s (%lu)",0
.code
FillDlg proc uses ebx esi edi hWin:DWORD
    
    ;set amount of found adapters
    invoke  SetDlgItemInt,hWin,IDS_ADAPTS,nAdapters,FALSE
    ;clear contents of combo box with names of adapters
    invoke  SendDlgItemMessage,hWin,IDCB_ADAPTERS,CB_RESETCONTENT,0,0
    zeroReg ebx
    mov     esi,offset DXMon_Info0
    assume esi:ptr DXMON_INFO
    mov     edi,offset szString
    .while ebx < nAdapters
        ;form adapter name with ordinal number
        invoke  wsprintf,edi,offset szAdaptName,addr [esi].AdapterInfo.Description,[esi].nAdapterOrdinal
        ;save the string to combo box
        invoke  SendDlgItemMessage,hWin,IDCB_ADAPTERS,CB_ADDSTRING,0,edi
        ;increment adapter ordinal
        inc     ebx
        ;point to next structure
        add     esi,sizeof DXMON_INFO
    .endw
    ;choose default adapter
    invoke  SendDlgItemMessage,hWin,IDCB_ADAPTERS,CB_SETCURSEL,0,0
    ;show info about this adapter
    invoke  ShowMonInfo,hWin,0
    ret

    assume esi:nothing
FillDlg endp

;--------------------------------------------------------------------------------

;PURPOSE
;write the given string in the given static control

;IN
;nID - ID of static control
;lpStr - ptr to string to be written

;RET
;any value returned by the previous call to SendDlgItemMessage

;REMARKS
;this is a small function that I made because many times I needed writing a string
;into static controls, and call to this funciton takes less bytes than
;calling SendDlgItemMessage directly

SetStaticText proc nID,lpStr:DWORD
    
    invoke  SendDlgItemMessage,hDlg,nID,WM_SETTEXT,0,lpStr
    ret

SetStaticText endp

;--------------------------------------------------------------------------------

;PURPOSE
;write info about given adapter into the fields of given dialog box

;IN
;hWin - handle to dialog box
;nAdapter - ordinal of adapter who's info needs to be shown

;RET
;void

;REMARKS
;it's just a useful function that fills all fields at once

.const
szShowRect  db "%lu X %lu, %lu X %lu",0
szPrim      db " (primary)",0

.code

ShowMonInfo proc uses ebx esi edi hWin,nAdapter:DWORD
    
    ;get ptr to the respective DXMON_INFO structure based on adapter ordinal
    mov     eax,nAdapter
    mov     ecx,sizeof DXMON_INFO
    mul     ecx
    mov     ebx,hWin
    lea     esi,[eax+offset DXMon_Info0]
    assume esi:ptr DXMON_INFO
    mov     edi,offset szString
    ;set name of adapter
    invoke  SetStaticText,IDS_NAME_ADAPT,addr [esi].AdapterInfo.Description
    ;internal name of adapter
    invoke  SetStaticText,IDS_INT_NAME_AD,addr [esi].MonInfo.szDevice
    ;driver file name
    invoke  SetStaticText,IDS_DRIVER_NAME,addr [esi].AdapterInfo.Driver
    ;show driver version as string
    invoke  DriverVersionString,edi,addr [esi].AdapterInfo.DriverVersion
    invoke  SetStaticText,IDS_DRIVER_VER,edi
    ;show device GUID identifier as a string
    invoke  GUID2Str,edi,addr [esi].AdapterInfo.DeviceIdentifier
    invoke  SetStaticText,IDS_DRIVER_ID,edi
    ;show normal monitor name and indicate if it is the primary monitor in system
    invoke  lstrcpy,edi,addr [esi].DispDev.DeviceString
    .if [esi].MonInfo.dwFlags & MONITORINFOF_PRIMARY
        invoke  lstrcat,edi,offset szPrim
    .endif
    invoke  SetStaticText,IDS_MON_NAME,edi
    ;show internal name of the monitor
    invoke  SetStaticText,IDS_MON_INT_NAME,addr [esi].DispDev.DeviceName
    ;show monitor rectangle coordinates of the monitor
    invoke  wsprintf,edi,offset szShowRect,[esi].MonInfo.rcMonitor.left,[esi].MonInfo.rcMonitor.top,[esi].MonInfo.rcMonitor.right,[esi].MonInfo.rcMonitor.bottom
    invoke  SetStaticText,IDS_MONRECT,edi
    ;show work rectangle coordinates of the monitor
    invoke  wsprintf,edi,offset szShowRect,[esi].MonInfo.rcWork.left,[esi].MonInfo.rcWork.top,[esi].MonInfo.rcWork.right,[esi].MonInfo.rcWork.bottom
    invoke  SetStaticText,IDS_WORKRECT,edi
    ;show current video mode as string
    invoke  DXVideoMode2Str,edi,addr [esi].CurrDispMode
    invoke  SetStaticText,IDS_CURR_VMODE,edi
    ;get all video modes and select the current one
    invoke  ShowVModes,hWin,nAdapter,addr [esi].CurrDispMode
    ;exit function
    ret

    assume esi:nothing
ShowMonInfo endp

;--------------------------------------------------------------------------------

;PURPOSE
;fills IDCB_VMODES combo box with information about all available video modes
;for the given adapter/display
;it also selects current video mode in combo box

;IN
;hWin - handle to dialog window with the control
;nAdapter - adapter ordinal
;lpCurrMode - ptr to D3DDISPLAYMODE structure that contains current mode

;RET
;TRUE if success or FALSE otherwise

ShowVModes proc uses ebx esi edi hWin,nAdapter,lpCurrMode:DWORD
    LOCAL   nResult:DWORD
    LOCAL   nCurrMode:DWORD
    LOCAL   cntModes:DWORD
    LOCAL   VMode:D3DDISPLAYMODE

    cld
    zeroReg ebx
    lea     esi,VMode
    assume esi:ptr D3DDISPLAYMODE
    mov     nCurrMode,ebx
    mov     nResult,ebx     ;nResult=FALSE
    mov     edi,lpCurrMode
    assume edi:ptr D3DDISPLAYMODE
    ;check if Direct3D object was created successfully
    mov     edx,pd3d
    .if edx!=NULL
        ;clear all contents from the combo box
        invoke  SendDlgItemMessage,hWin,IDCB_VMODES,CB_RESETCONTENT,0,0
        ;get amount of modes supported by this video adapter
        d3d8    GetAdapterModeCount,pd3d,nAdapter
        .if eax!=NULL
        ;if SUCCESS then save amount of modes and start enumerating video modes
        ;starting with NULL or DEFAULT_ADAPTER
            mov     cntModes,eax
            .while ebx < cntModes
                ;get information about one video mode
                d3d8    EnumAdapterModes,pd3d,nAdapter,ebx,esi
                .break.if eax!=D3D_OK
                ;if OK then check if this video mode is identical to current one
                mov     ecx,sizeof D3DDISPLAYMODE / sizeof DWORD
                push    esi
                push    edi
                    repe    cmpsd
                pop     edi
                pop     esi
                .if ZERO?
                ;if it's the current video mode then save its number for future reference
                    mov     nCurrMode,ebx
                .endif
;                mov     eax,[esi].Width1
;                mov     edx,[esi].Height
;                .if eax==[edi].Width1 && edx==[edi].Height
;                    mov     eax,[esi].RefreshRate
;                    mov     edx,[esi].Format
;                    .if eax==[edi].RefreshRate && edx==[edi].Format
;                        mov     nCurrMode,ebx
;                    .endif
;                .endif
                ;convert video mode into a string representation
                invoke  DXVideoMode2Str,offset szString,esi
                ;add this string to the combo box
                invoke  SendDlgItemMessage,hWin,IDCB_VMODES,CB_ADDSTRING,0,offset szString
                ;prepare number for the next video mode
                inc     ebx
            .endw
            ;everything was successfull so nResult=TRUE
            inc     nResult
        .endif
        ;select current video mode
        invoke  SendDlgItemMessage,hWin,IDCB_VMODES,CB_SETCURSEL,nCurrMode,0
    .endif
    retVar nResult

    assume esi:nothing
    assume edi:nothing
ShowVModes endp

;--------------------------------------------------------------------------------

;PURPOSE
;release DirectX object

ReleaseDX proc
    
    mov     eax,pd3d
    .if eax!=NULL
        d3d8    Release,pd3d
    .endif
    ret

ReleaseDX endp

;--------------------------------------------------------------------------------

;PURPOSE
;init directX and obtain info about all available adapters

;RET
;TRUE if success or FALSE otherwise

;REMARKS
;here we get amount of adapters, then enum adapters obtaining additional info
;about them
;also obtain current display mode and handle to monitor
;which allows us to query all the info about the monitor with Win32API

GetMonitorsDX proc uses ebx esi

    ;create DirectX object
    invoke  Direct3DCreate8,D3D_SDK_VERSION
    .if eax!=NULL
        mov     pd3d,eax
        ;get amount of adapters on PC
        mov     ebx,offset DXMon_Info0
        assume ebx:ptr DXMON_INFO
        d3d8    GetAdapterCount,pd3d
        .if eax!=0
            ;enum all adapters
            zeroReg esi
            mov     nAdapters,eax
            .while esi<nAdapters && esi<2   ;i have only 2 DXMON_INFO structures
                lea     edx,[ebx].AdapterInfo
                ;get adapter identifier
                mov     [ebx].nAdapterOrdinal,esi
                d3d8    GetAdapterIdentifier,pd3d,esi,D3DENUM_NO_WHQL_LEVEL,edx
                .break.if eax!=D3D_OK
                ;get current video mode
                lea     edx,[ebx].CurrDispMode
                d3d8    GetAdapterDisplayMode,pd3d,esi,edx
                .break.if eax!=D3D_OK
                ;get handle to monitor device
                d3d8    GetAdapterMonitor,pd3d,esi
                .break.if eax==NULL
                lea     edx,[ebx].MonInfo
                mov     [ebx].hMonitor,eax
                ;get additional info about the monitor using Win32API
                mov     dword ptr [edx],sizeof MONITORINFOEXA
                invoke  GetMonitorInfo,eax,edx
                .if eax!=0
                    invoke  GetRealMonitor,addr [ebx].MonInfo.szDevice,addr [ebx].DispDev
                .endif
                ;increment nAdapterOrdinal
                inc     esi
                ;point to the next DXMON_INFO structure
                add     ebx,sizeof DXMON_INFO
            .endw
        .endif
    .endif
    ret

    assume ebx:nothing
    assume esi:nothing
GetMonitorsDX endp

;--------------------------------------------------------------------------------

;PURPOSE
;among possible virtual monitors get only the real one  

;IN
;lpszDeviceName - ptr to internal device name

;OUT
;lpDispDev - ptr to DISPLAY_DEVICE struct which will receive info about real monitor

;RET
;it returns FALSE if failed or TRUE if succeeded

;REMARKS
;

GetRealMonitor proc uses ebx esi edi lpszDeviceName:DWORD,lpDispDev:DWORD
;    LOCAL   nthDevice:DWORD
    LOCAL   DispInfo:DISPLAY_DEVICE

    lea     esi,DispInfo
    ;init number of device and local DISPLAY_DEVICE structure
    zeroReg ebx ;and     nthDevice,0
    mov     edi,lpDispDev
    mov     (DISPLAY_DEVICE ptr [esi]).cb,sizeof DISPLAY_DEVICE
@@:
    invoke  EnumDisplayDevices,lpszDeviceName,ebx,esi,0
    ;exit loop when FALSE
    .if eax!=FALSE
        ;if StateFlags==0 then look for the next monitor
        .if DispInfo.StateFlags==0
            inc     ebx     ;nthDevice
            jmp     @B
        .endif
        ;else copy data from local DISPLAY_DEVICE structure to the one pointed to by lpDispDev.
        ;at this point ESI is the local structure and EDI is the output structure
        ;copy data by DWORDs so size of structure is divided by 4
        mov     ecx,sizeof DISPLAY_DEVICE / sizeof DWORD
        rep     movsd
    .endif
    ret

GetRealMonitor endp

;--------------------------------------------------------------------------------

;PURPOSE
;convert given GUID into a string representation

;IN
;lpGuid - ptr to GUID structure

;OUT
;lpStr - ptr to output string that will receive final string

;RET
;amount of chars written to output buffer

;REMARKS
;uses the template string to form GUID string
;should work faster than original function from C++ tutorial
;Attention: no buffer length check!

.const
szGUID      db "{%0.8X-%0.4X-%0.4X-%0.4X-%0.4X%0.8X}",0  ;"{%8X-%4X-%4X-%2X%2X-%2X%2X%2X%2X%2X%2X}",0
.code

GUID2Str proc uses ebx esi lpStr,lpGuid:DWORD
    
    mov     esi,lpGuid
    mov     eax,[esi+12]
    mov     ecx,[esi+8]
    bswap   eax
    bswap   ecx
    push    eax
    rol     ecx,16
    mov     eax,[esi+4]
    mov     edx,eax
    mov     ebx,ecx
    shr     edx,16
    and     eax,0000FFFFh
    shr     ebx,16
    and     ecx,0000FFFFh
    push    ebx
    push    ecx
    push    edx
    push    eax
    push    dword ptr [esi]
    push    offset szGUID
    push    lpStr
    call    wsprintf
    add     esp,8 * sizeof DWORD
    ;invoke  wsprintf,lpStr,offset szGUID,dw[esi],w[esi+4],w[esi+6],
    ret

GUID2Str endp

;--------------------------------------------------------------------------------

;PURPOSE
;convert given driver version 64-bit value into a normal string representation

;IN
;lpDriverVer - ptr to LARGE INTEGER value of driver version

;OUT
;lpStr - ptr to output buffer for final string with driver version

;RET
;return count of chars written to buffer

;REMARKS
;uses given below template string for conversion
;Attention: no buffer length check!

.const
szDriverVer db "%lu.%lu.%0.4lu.%0.4lu",0
.code

DriverVersionString proc uses ebx esi lpStr,lpDriverVer:DWORD
    
    mov     esi,lpDriverVer
    mov     eax,[esi+4]
    mov     edx,[esi]
    mov     ecx,eax
    mov     ebx,edx
    shr     ecx,16
    and     eax,0000FFFFh
    shr     ebx,16
    and     edx,0000FFFFh
    invoke  wsprintf,lpStr,offset szDriverVer,ecx,eax,ebx,edx
    ret

DriverVersionString endp

;--------------------------------------------------------------------------------

;PURPOSE
;convert constant of DX format into a string

;IN
;lpD3dDispMode - ptr to D3DDISPLAYMODE struct to convert to string

;OUT
;lpStr - ptr to output buffer for final string with DX format name and video mode

;RET
;count of chars written to output buffer

;REMARKS
;Attention: no buffer length check!
;the buffer should be big enough to hold the output string

.const
;strings representing most common DX formats
szDXFormat          db "%lu X %lu, %s, %s",0
szHz                db "%lu Hz",0
szDefault           db "def. rate",0
szD3DFMT_X8R8G8B8   db "D3DFMT_X8R8G8B8",0
szD3DFMT_A8R8G8B8   db "D3DFMT_A8R8G8B8",0
szD3DFMT_R8G8B8     db "D3DFMT_R8G8B8",0
szD3DFMT_R5G6B5     db "D3DFMT_R5G6B5",0
szD3DFMT_X1R5G5B5   db "D3DFMT_X1R5G5B5",0
szD3DFMT_A1R5G5B5   db "D3DFMT_A1R5G5B5",0
szD3DFMT_A4R4G4B4   db "D3DFMT_A4R4G4B4",0
szD3DFMT_R3G3B2     db "D3DFMT_R3G3B2",0
szD3DFMT_A8R3G3B2   db "D3DFMT_A8R3G3B2",0
szD3DFMT_X4R4G4B4   db "D3DFMT_X4R4G4B4",0
szD3DFMT_A2B10G10R10 db "D3DFMT_A2B10G10R10",0
szD3DFMT_G16R16     db "D3DFMT_G16R16",0
szD3DFMT_A8P8       db "D3DFMT_A8P8",0
szD3DFMT_P8         db "D3DFMT_P8",0
szD3DFMT_UNKNOWN    db "D3DFMT_UNKNOWN",0

.code

DXVideoMode2Str proc uses ebx esi lpStr,lpD3dDispMode:DWORD
    LOCAL   szHzs[16]:BYTE

    mov     esi,lpD3dDispMode
    assume esi:ptr D3DDISPLAYMODE
    ;form string of this videomode
    mov     edx,[esi].RefreshRate
    ;if Hz == 0 then it means default rate will be used
    .if edx==0
        mov     ebx,offset szDefault
    .else
        ;make string with amount of Hz
        lea     ebx,szHzs
        invoke  wsprintf,ebx,offset szHz,edx
    .endif
    mov     eax,[esi].Format
    .if eax==D3DFMT_X8R8G8B8
        mov     eax,offset szD3DFMT_X8R8G8B8
    .elseif eax==D3DFMT_A8R8G8B8
        mov     eax,offset szD3DFMT_A8R8G8B8
    .elseif eax==D3DFMT_R8G8B8
        mov     eax,offset szD3DFMT_R8G8B8
    .elseif eax==D3DFMT_R5G6B5
        mov     eax,offset szD3DFMT_R5G6B5
    .elseif eax==D3DFMT_X1R5G5B5
        mov     eax,offset szD3DFMT_X1R5G5B5
    .elseif eax==D3DFMT_A1R5G5B5
        mov     eax,offset szD3DFMT_A1R5G5B5
    .elseif eax==D3DFMT_A4R4G4B4
        mov     eax,offset szD3DFMT_A4R4G4B4
    .elseif eax==D3DFMT_R3G3B2
        mov     eax,offset szD3DFMT_R3G3B2
    .elseif eax==D3DFMT_A8R3G3B2
        mov     eax,offset szD3DFMT_A8R3G3B2
    .elseif eax==D3DFMT_X4R4G4B4
        mov     eax,offset szD3DFMT_X4R4G4B4
    .elseif eax==D3DFMT_A2B10G10R10
        mov     eax,offset szD3DFMT_A2B10G10R10
    .elseif eax==D3DFMT_G16R16
        mov     eax,offset szD3DFMT_G16R16
    .elseif eax==D3DFMT_A8P8
        mov     eax,offset szD3DFMT_A8P8
    .elseif eax==D3DFMT_P8
        mov     eax,offset szD3DFMT_P8
    .else
    ;any other format will be simply marked as Unknown because there is
    ;a little possibility that any other formats will be reported by a monitor
        mov     eax,offset szD3DFMT_UNKNOWN
    .endif
    invoke  wsprintf,lpStr,offset szDXFormat,[esi].Width1,[esi].Height,eax,ebx
    ret

    assume esi:nothing
DXVideoMode2Str endp

;--------------------------------------------------------------------------------

end start
