; ---------------------
;      WinVer.nsh
; ---------------------
;
; LogicLib extensions for handling Windows versions and service packs.
;
; IsNT checks if the installer is running on Windows NT family (NT4, 2000, XP, etc.)
;
;   ${If} ${IsNT}
;     DetailPrint "Running on NT. Installing Unicode enabled application."
;   ${Else}
;     DetailPrint "Not running on NT. Installing ANSI application."
;   ${EndIf}
;
; IsServer checks if the installer is running on a server version of Windows (NT4, 2003, 2008, etc.)
;
; AtLeastWin<version> checks if the installer is running on Windows version at least as specified.
; IsWin<version> checks if the installer is running on Windows version exactly as specified.
; AtMostWin<version> checks if the installer is running on Windows version at most as specified.
;
; <version> can be replaced with the following values:
;
;   95
;   98
;   ME
;
;   NT4
;   2000
;   XP
;   2003
;   Vista
;   2008
;   7
;   2008R2
;
; AtLeastServicePack checks if the installer is running on Windows service pack version at least as specified.
; IsServicePack checks if the installer is running on Windows service pack version exactly as specified.
; AtMostServicePack checks if the installer is running on Windows service version pack at most as specified.
;
; Usage examples:
;
;   ${If} ${IsNT}
;   DetailPrint "Running on NT family."
;   DetailPrint "Surely not running on 95, 98 or ME."
;   ${AndIf} ${AtLeastWinNT4}
;     DetailPrint "Running on NT4 or better. Could even be 2003."
;   ${EndIf}
;
;   ${If} ${AtLeastWinXP}
;     DetailPrint "Running on XP or better."
;   ${EndIf}
;
;   ${If} ${IsWin2000}
;     DetailPrint "Running on 2000."
;   ${EndIf}
;
;   ${If} ${IsWin2000}
;   ${AndIf} ${AtLeastServicePack} 3
;   ${OrIf} ${AtLeastWinXP}
;     DetailPrint "Running Win2000 SP3 or above"
;   ${EndIf}
;
;   ${If} ${AtMostWinXP}
;     DetailPrint "Running on XP or older. Surely not running on Vista. Maybe 98, or even 95."
;   ${EndIf}
;
; Warning:
;
;   Windows 95 and NT both use the same version number. To avoid getting NT4 misidentified
;   as Windows 95 and vice-versa or 98 as a version higher than NT4, always use IsNT to
;   check if running on the NT family.
;
;     ${If} ${AtLeastWin95}
;     ${And} ${AtMostWinME}
;       DetailPrint "Running 95, 98 or ME."
;       DetailPrint "Actually, maybe it's NT4?"
;       ${If} ${IsNT}
;         DetailPrint "Yes, it's NT4! oops..."
;       ${Else}
;         DetailPrint "Nope, not NT4. phew..."
;       ${EndIf}
;     ${EndIf}
;
;
; Other useful extensions are:
;
;    * IsWin2003R2
;    * IsStarterEdition
;    * OSHasMediaCenter
;    * OSHasTabletSupport
;

!verbose push
!verbose 3

!ifndef ___WINVER__NSH___
!define ___WINVER__NSH___

!include LogicLib.nsh
!include Util.nsh

# masks for our variables

!define _WINVER_VERXBIT  0x00000001
!define _WINVER_MASKVMAJ 0x7F000000
!define _WINVER_MASKVMIN 0x00FF0000

!define _WINVER_NTBIT    0x80000000
!define _WINVER_NTMASK   0x7FFFFFFF
!define _WINVER_NTSRVBIT 0x40000000
!define _WINVER_MASKVBLD 0x0000FFFF
!define _WINVER_MASKSP   0x000F0000

# possible variable values for different versions

!define WINVER_95_NT     0x04000000 ;4.00.0950
!define WINVER_95        0x04000000 ;4.00.0950
!define WINVER_98_NT     0x040a0000 ;4.10.1998
!define WINVER_98        0x040a0000 ;4.10.1998
;define WINVER_98SE      0x040a0000 ;4.10.2222
!define WINVER_ME_NT     0x045a0000 ;4.90.3000
!define WINVER_ME        0x045a0000 ;4.90.3000
;define WINVER_NT3d51               ;3.51.1057
!define WINVER_NT4_NT    0x84000000 ;4.00.1381
!define WINVER_NT4       0x04000000 ;4.00.1381
!define WINVER_2000_NT   0x85000000 ;5.00.2195
!define WINVER_2000      0x05000000 ;5.00.2195
!define WINVER_XP_NT     0x85010000 ;5.01.2600
!define WINVER_XP        0x05010000 ;5.01.2600
;define WINVER_XP64                 ;5.02.3790
!define WINVER_2003_NT   0x85020000 ;5.02.3790
!define WINVER_2003      0x05020000 ;5.02.3790
!define WINVER_VISTA_NT  0x86000000 ;6.00.6000
!define WINVER_VISTA     0x06000000 ;6.00.6000
!define WINVER_2008_NT   0x86000001 ;6.00.6001
!define WINVER_2008      0x06000001 ;6.00.6001
!define WINVER_7_NT      0x86010000 ;6.01.????
!define WINVER_7         0x06010000 ;6.01.????
!define WINVER_2008R2_NT 0x86010001 ;6.01.????
!define WINVER_2008R2    0x06010001 ;6.01.????


# use this to make all nt > 9x

!ifdef WINVER_NT4_OVER_W95
  !define __WINVERTMP ${WINVER_NT4}
  !undef WINVER_NT4
  !define /math WINVER_NT4 ${__WINVERTMP} | ${_WINVER_VERXBIT}
  !undef __WINVERTMP
!endif

# some definitions from header files

!define OSVERSIONINFOA_SIZE   148
!define OSVERSIONINFOEXA_SIZE 156
!define VER_PLATFORM_WIN32_NT 2
!define VER_NT_WORKSTATION    1

!define SM_TABLETPC    86
!define SM_MEDIACENTER 87
!define SM_STARTER     88
!define SM_SERVERR2    89

# variable declaration

!macro __WinVer_DeclareVars

  !ifndef __WINVER_VARS_DECLARED

    !define __WINVER_VARS_DECLARED

    Var /GLOBAL __WINVERV
    Var /GLOBAL __WINVERSP

  !endif

!macroend

# lazy initialization macro

!ifmacrondef __WinVer_Call_GetVersionEx

  !macro __WinVer_Call_GetVersionEx STRUCT_SIZE

    System::Call '*$0(i ${STRUCT_SIZE})'
    System::Call kernel32::GetVersionEx(ir0)i.r3

  !macroend

!endif

!macro __WinVer_InitVars
  # variables
  !insertmacro __WinVer_DeclareVars

  # only calculate version once
  StrCmp $__WINVERV "" _winver_noveryet
    Return
  _winver_noveryet:

  # push used registers on the stack
  Push $0
  Push $1 ;maj
  Push $2 ;min
  Push $3 ;bld
  Push $R0 ;temp

  # allocate memory
  System::Alloc ${OSVERSIONINFOEXA_SIZE}
  Pop $0

  # use OSVERSIONINFOEX
  !insertmacro __WinVer_Call_GetVersionEx ${OSVERSIONINFOEXA_SIZE}

  IntCmp $3 0 "" _winver_ex _winver_ex
    # OSVERSIONINFOEX not allowed (Win9x or NT4 w/SP < 6), use OSVERSIONINFO
    !insertmacro __WinVer_Call_GetVersionEx ${OSVERSIONINFOA_SIZE}
  _winver_ex:

  # get results from struct
  System::Call '*$0(i.s,i.r1,i.r2,i.r3,i.s,&t128.s,&i2.s,&i2,&i2,&i1.s,&i1)'

  # free struct
  System::Free $0

  # win9x has major and minor info in high word of dwBuildNumber - remove it
  IntOp $3 $3 & 0xFFFF

  # get dwOSVersionInfoSize
  Pop $R0

  # get dwPlatformId
  Pop $0

  # NT?
  IntCmp $0 ${VER_PLATFORM_WIN32_NT} "" _winver_notnt _winver_notnt
    IntOp $__WINVERSP $__WINVERSP | ${_WINVER_NTBIT}
    IntOp $__WINVERV  $__WINVERV  | ${_WINVER_NTBIT}
  _winver_notnt:

  # get service pack information
  IntCmp $0 ${VER_PLATFORM_WIN32_NT} _winver_nt "" _winver_nt  # win9x

    # get szCSDVersion
    Pop $0

    # copy second char
    StrCpy $0 $0 1 1

    # discard invalid wServicePackMajor and wProductType
    Pop $R0
    Pop $R0

    # switch
    StrCmp $0 'A' "" +3
      StrCpy $0 1
      Goto _winver_sp_done
    StrCmp $0 'B' "" +3
      StrCpy $0 2
      Goto _winver_sp_done
    StrCmp $0 'C' "" +3
      StrCpy $0 3
      Goto _winver_sp_done
    StrCpy $0 0
    Goto _winver_sp_done

  _winver_nt: # nt

    IntCmp $R0 ${OSVERSIONINFOEXA_SIZE} "" _winver_sp_noex _winver_sp_noex

      # discard szCSDVersion
      Pop $0

      # get wProductType
      Exch
      Pop $0

      # is server?
      IntCmp $0 ${VER_NT_WORKSTATION} _winver_noserver _winver_noserver ""
        IntOp $__WINVERSP $__WINVERSP | ${_WINVER_NTSRVBIT}
      _winver_noserver:

      # get wServicePackMajor
      Pop $0

      # done with sp
      Goto _winver_sp_done

    _winver_sp_noex: # OSVERSIONINFO, not OSVERSIONINFOEX

      ####  TODO
      ## For IsServer to support < NT4SP6, we need to check the registry
      ## here to see if we are a server and/or DC

      # get szCSDVersion
      Pop $0

      # discard invalid wServicePackMajor and wProductType
      Pop $R0
      Pop $R0

      # get service pack number from text
      StrCpy $R0 $0 13
      StrCmp $R0 "Service Pack " "" +3
        StrCpy $0 $0 "" 13 # cut "Service Pack "
        Goto +2
        StrCpy $0 0 # no service pack

!ifdef WINVER_NT4_OVER_W95
      IntOp $__WINVERV $__WINVERV | ${_WINVER_VERXBIT}
!endif

  _winver_sp_done:

  # store service pack
  IntOp $0 $0 << 16
  IntOp $__WINVERSP $__WINVERSP | $0

  ### now for the version

  # is server?
  IntOp $0 $__WINVERSP & ${_WINVER_NTSRVBIT}

  # windows xp x64?
  IntCmp $0 0 "" _winver_not_xp_x64 _winver_not_xp_x64 # not server
  IntCmp $1 5 "" _winver_not_xp_x64 _winver_not_xp_x64 # maj 5
  IntCmp $2 2 "" _winver_not_xp_x64 _winver_not_xp_x64 # min 2
    # change XP x64 from 5.2 to 5.1 so it's still XP
    StrCpy $2 1
  _winver_not_xp_x64:

  # server 2008?
  IntCmp $0 0 _winver_not_ntserver # server
  IntCmp 6 $1 "" "" _winver_not_ntserver # maj 6
    # extra bit so Server 2008 comes after Vista SP1 that has the same minor version, same for Win7 vs 2008R2
    IntOp $__WINVERV $__WINVERV | ${_WINVER_VERXBIT}
  _winver_not_ntserver:

  # pack version
  IntOp $1 $1 << 24 # VerMajor
  IntOp $__WINVERV $__WINVERV | $1
  IntOp $0 $2 << 16
  IntOp $__WINVERV $__WINVERV | $0 # VerMinor
  IntOp $__WINVERSP $__WINVERSP | $3 # VerBuild

  # restore registers
  Pop $R0
  Pop $3
  Pop $2
  Pop $1
  Pop $0

!macroend

# version comparison LogicLib macros

!macro _WinVerAtLeast _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${CallArtificialFunction} __WinVer_InitVars
  IntOp $_LOGICLIB_TEMP $__WINVERV & ${_WINVER_NTMASK}
  !insertmacro _>= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}`
!macroend
!macro _WinVerIs _a _b _t _f
  ${CallArtificialFunction} __WinVer_InitVars
  !insertmacro _= $__WINVERV `${_b}` `${_t}` `${_f}`
!macroend
!macro _WinVerAtMost _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${CallArtificialFunction} __WinVer_InitVars
  IntOp $_LOGICLIB_TEMP $__WINVERV & ${_WINVER_NTMASK}
  !insertmacro _<= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}`
!macroend

!macro __WinVer_DefineOSTest Test OS Suffix
  !define ${Test}Win${OS} `"" WinVer${Test} ${WINVER_${OS}${Suffix}}`
!macroend

!macro __WinVer_DefineOSTests Test Suffix
  !insertmacro __WinVer_DefineOSTest ${Test} 95     '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 98     '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} ME     '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} NT4    '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 2000   '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} XP     '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 2003   '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} VISTA  '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 2008   '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 7      '${Suffix}'
  !insertmacro __WinVer_DefineOSTest ${Test} 2008R2 '${Suffix}'
!macroend

!insertmacro __WinVer_DefineOSTests AtLeast ""
!insertmacro __WinVer_DefineOSTests Is _NT
!insertmacro __WinVer_DefineOSTests AtMost ""

# version feature LogicLib macros

!macro _IsNT _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${CallArtificialFunction} __WinVer_InitVars
  IntOp $_LOGICLIB_TEMP $__WINVERSP & ${_WINVER_NTBIT}
  !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}`
!macroend
!define IsNT `"" IsNT ""`

!macro _IsServerOS _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${CallArtificialFunction} __WinVer_InitVars
  IntOp $_LOGICLIB_TEMP $__WINVERSP & ${_WINVER_NTSRVBIT}
  !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}`
!macroend
!define IsServerOS `"" IsServerOS ""`

# service pack macros

!macro _WinVer_GetServicePackLevel OUTVAR
  ${CallArtificialFunction} __WinVer_InitVars
  IntOp ${OUTVAR} $__WINVERSP & ${_WINVER_MASKSP}
  IntOp ${OUTVAR} ${OUTVAR} >> 16
!macroend
!define WinVerGetServicePackLevel '!insertmacro _WinVer_GetServicePackLevel '

!macro _AtLeastServicePack _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP
  !insertmacro _>= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}`
!macroend
!define AtLeastServicePack `"" AtLeastServicePack`

!macro _AtMostServicePack _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP
  !insertmacro _<= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}`
!macroend
!define AtMostServicePack `"" AtMostServicePack`

!macro _IsServicePack _a _b _t _f
  !insertmacro _LOGICLIB_TEMP
  ${WinVerGetServicePackLevel} $_LOGICLIB_TEMP
  !insertmacro _= $_LOGICLIB_TEMP `${_b}` `${_t}` `${_f}`
!macroend
!define IsServicePack `"" IsServicePack`

# special feature LogicLib macros

!macro _WinVer_SysMetricCheck m _b _t _f
  !insertmacro _LOGICLIB_TEMP
  System::Call user32::GetSystemMetrics(i${m})i.s
  pop $_LOGICLIB_TEMP
  !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}`
!macroend

!define IsWin2003R2        `${SM_SERVERR2}    WinVer_SysMetricCheck ""`
!define IsStarterEdition   `${SM_STARTER}     WinVer_SysMetricCheck ""`
!define OSHasMediaCenter   `${SM_MEDIACENTER} WinVer_SysMetricCheck ""`
!define OSHasTabletSupport `${SM_TABLETPC}    WinVer_SysMetricCheck ""`

# version retrieval macros

!macro __WinVer_GetVer var rshift mask outvar
  ${CallArtificialFunction} __WinVer_InitVars
  !if "${mask}" != ""
    IntOp ${outvar} ${var} & ${mask}
    !if "${rshift}" != ""
      IntOp ${outvar} ${outvar} >> ${rshift}
    !endif
  !else
    IntOp ${outvar} ${var} >> ${rshift}
  !endif
!macroend

!define WinVerGetMajor '!insertmacro __WinVer_GetVer $__WINVERV  24 ${_WINVER_MASKVMAJ}'
!define WinVerGetMinor '!insertmacro __WinVer_GetVer $__WINVERV  16 ${_WINVER_MASKVMIN}'
!define WinVerGetBuild '!insertmacro __WinVer_GetVer $__WINVERSP "" ${_WINVER_MASKVBLD}'

# done

!endif # !___WINVER__NSH___

!verbose pop
