{
  Copyright 2014 Stas'M Corp.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
}

library rdpwrap;

uses
  SysUtils,
  Windows,
  TlHelp32;

{$R rdpwrap.res}

// Hook core definitions

type
  OldCode = packed record
    One: DWORD;
    two: Word;
  end;

  far_jmp = packed record
    PushOp: Byte;
    PushArg: Pointer;
    RetOp: Byte;
  end;

  mov_far_jmp = packed record
    MovOp: Byte;
    MovArg: Byte;
    PushOp: Byte;
    PushArg: Pointer;
    RetOp: Byte;
  end;

  TTHREADENTRY32 = packed record
    dwSize: DWORD;
    cntUsage: DWORD;
    th32ThreadID: DWORD;
    th32OwnerProcessID: DWORD;
    tpBasePri: LongInt;
    tpDeltaPri: LongInt;
    dwFlags: DWORD;
  end;
  IntArray = Array of Integer;
  FILE_VERSION = record
    Version: record case Boolean of
      True: (dw: DWORD);
      False: (w: record
        Minor, Major: Word;
      end;)
    end;
    Release, Build: Word;
    bDebug, bPrerelease, bPrivate, bSpecial: Boolean;
  end;

const
  THREAD_SUSPEND_RESUME = 2;
  TH32CS_SNAPTHREAD = 4;
var
  bw: DWORD;
  IsHooked: Boolean = False;
  FCount: Cardinal = 0;

// Unhooked import

function OpenThread(dwDesiredAccess: DWORD; bInheritHandle: BOOL;
  dwThreadId: DWORD): DWORD; stdcall; external kernel32;

function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): DWORD;
  stdcall; external kernel32;

function Thread32First(hSnapshot: THandle; var lpte: TTHREADENTRY32): bool;
  stdcall; external kernel32;

function Thread32Next(hSnapshot: THandle; var lpte: TTHREADENTRY32): bool;
  stdcall; external kernel32;

// Wrapped import

var
  TSMain: function(dwArgc: DWORD; lpszArgv: PWideChar): DWORD; stdcall;
  TSGlobals: function(lpGlobalData: Pointer): DWORD; stdcall;

// Hooked import and vars

var
  SLGetWindowsInformationDWORD: function(pwszValueName: PWideChar;
    pdwValue: PDWORD): HRESULT; stdcall;
  TermSrvBase: Pointer;
  FV: FILE_VERSION;

const
  CDefPolicy_Query_edx_ecx: Array[0..12] of Byte =
  ($BA,$00,$01,$00,$00,$89,$91,$20,$03,$00,$00,$5E,$90);
  CDefPolicy_Query_eax_esi: Array[0..11] of Byte =
  ($B8,$00,$01,$00,$00,$89,$86,$20,$03,$00,$00,$90);
  CDefPolicy_Query_eax_ecx: Array[0..11] of Byte =
  ($B8,$00,$01,$00,$00,$89,$81,$20,$03,$00,$00,$90);

{
termsrv.dll 6.0.6000.16386

Original
.text:6F335CD8          cmp     edx, [ecx+320h]
.text:6F335CDE          pop     esi
.text:6F335CDF          jz      loc_6F3426F1
_______________

Changed
.text:6F335CD8          mov     edx, 100h
.text:6F335CDD          mov     [ecx+320h], edx
.text:6F335CE3          pop     esi
.text:6F335CE4          nop
CDefPolicy_Query_edx_ecx

termsrv.dll 6.0.6001.18000

Original
.text:6E817FD8          cmp     edx, [ecx+320h]
.text:6E817FDE          pop     esi
.text:6E817FDF          jz      loc_6E826F16
_______________

Changed
.text:6E817FD8          mov     edx, 100h
.text:6E817FDD          mov     [ecx+320h], edx
.text:6E817FE3          pop     esi
.text:6E817FE4          nop
CDefPolicy_Query_edx_ecx

termsrv.dll 6.0.6002.18005

Original
.text:6F5979C0          cmp     edx, [ecx+320h]
.text:6F5979C6          pop     esi
.text:6F5979C7          jz      loc_6F5A6F26
_______________

Changed
.text:6F5979C0          mov     edx, 100h
.text:6F5979C5          mov     [ecx+320h], edx
.text:6F5979CB          pop     esi
.text:6F5979CC          nop
CDefPolicy_Query_edx_ecx

termsrv.dll 6.0.6002.19214

Original
.text:6F5979B8          cmp     edx, [ecx+320h]
.text:6F5979BE          pop     esi
.text:6F5979BF          jz      loc_6F5A6F3E
_______________

Changed
.text:6F5979B8          mov     edx, 100h
.text:6F5979BD          mov     [ecx+320h], edx
.text:6F5979C3          pop     esi
.text:6F5979C4          nop
CDefPolicy_Query_edx_ecx

termsrv.dll 6.0.6002.23521

Original
.text:6F5979CC          cmp     edx, [ecx+320h]
.text:6F5979D2          pop     esi
.text:6F5979D3          jz      loc_6F5A6F2E
_______________

Changed
.text:6F5979CC          mov     edx, 100h
.text:6F5979D1          mov     [ecx+320h], edx
.text:6F5979D7          pop     esi
.text:6F5979D8          nop
CDefPolicy_Query_edx_ecx

termsrv.dll 6.1.7600.16385

Original
.text:6F2F96F3          cmp     eax, [esi+320h]
.text:6F2F96F9          jz      loc_6F30E256
_______________

Changed
.text:6F2F96F3          mov     eax, 100h
.text:6F2F96F8          mov     [esi+320h], eax
.text:6F2F96FE          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.1.7601.17514

Original
.text:6F2F9D53          cmp     eax, [esi+320h]
.text:6F2F9D59          jz      loc_6F30B25E
_______________

Changed
.text:6F2F9D53          mov     eax, 100h
.text:6F2F9D58          mov     [esi+320h], eax
.text:6F2F9D5E          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.1.7601.18540

Original
.text:6F2F9D9F          cmp     eax, [esi+320h]
.text:6F2F9DA5          jz      loc_6F30B2AE
_______________

Changed
.text:6F2F9D9F          mov     eax, 100h
.text:6F2F9DA4          mov     [esi+320h], eax
.text:6F2F9DAA          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.1.7601.22750

Original
.text:6F2F9E21          cmp     eax, [esi+320h]
.text:6F2F9E27          jz      loc_6F30B6CE
_______________

Changed
.text:6F2F9E21          mov     eax, 100h
.text:6F2F9E26          mov     [esi+320h], eax
.text:6F2F9E2C          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.1.7601.18637

Original
.text:6F2F9DBB          cmp     eax, [esi+320h]
.text:6F2F9DC1          jz      loc_6F30B2A6
_______________

Changed
.text:6F2F9DBB          mov     eax, 100h
.text:6F2F9DC0          mov     [esi+320h], eax
.text:6F2F9DC6          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.1.7601.22843

Original
.text:6F2F9E25          cmp     eax, [esi+320h]
.text:6F2F9E2B          jz      loc_6F30B6D6
_______________

Changed
.text:6F2F9E25          mov     eax, 100h
.text:6F2F9E2A          mov     [esi+320h], eax
.text:6F2F9E30          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.8102.0

Original
.text:1000E47C          cmp     eax, [esi+320h]
.text:1000E482          jz      loc_1002D775
_______________

Changed
.text:1000E47C          mov     eax, 100h
.text:1000E481          mov     [esi+320h], eax
.text:1000E487          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.8250.0

Original
.text:10013520          cmp     eax, [esi+320h]
.text:10013526          jz      loc_1002DB85
_______________

Changed
.text:10013520          mov     eax, 100h
.text:10013525          mov     [esi+320h], eax
.text:1001352B          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.8400.0

Original
.text:10013E48          cmp     eax, [esi+320h]
.text:10013E4E          jz      loc_1002E079
_______________

Changed
.text:10013E48          mov     eax, 100h
.text:10013E4D          mov     [esi+320h], eax
.text:10013E53          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.9200.16384

Original
.text:10013F08          cmp     eax, [esi+320h]
.text:10013F0E          jz      loc_1002E161
_______________

Changed
.text:10013F08          mov     eax, 100h
.text:10013F0D          mov     [esi+320h], eax
.text:10013F13          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.9200.17048

Original
.text:1001F408          cmp     eax, [esi+320h]
.text:1001F40E          jz      loc_1002E201
_______________

Changed
.text:1001F408          mov     eax, 100h
.text:1001F40D          mov     [esi+320h], eax
.text:1001F413          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.2.9200.21166

Original
.text:10013F30          cmp     eax, [esi+320h]
.text:10013F36          jz      loc_1002E189
_______________

Changed
.text:10013F30          mov     eax, 100h
.text:10013F35          mov     [esi+320h], eax
.text:10013F3B          nop
CDefPolicy_Query_eax_esi

termsrv.dll 6.3.9431.0

Original
.text:1002EA25          cmp     eax, [ecx+320h]
.text:1002EA2B          jz      loc_100348C1
_______________

Changed
.text:1002EA25          mov     eax, 100h
.text:1002EA2A          mov     [ecx+320h], eax
.text:1002EA30          nop
CDefPolicy_Query_eax_ecx

termsrv.dll 6.3.9600.16384

Original
.text:10016115          cmp     eax, [ecx+320h]
.text:1001611B          jz      loc_10034DE1
_______________

Changed
.text:10016115          mov     eax, 100h
.text:1001611A          mov     [ecx+320h], eax
.text:10016120          nop
CDefPolicy_Query_eax_ecx

termsrv.dll 6.3.9600.17095

Original
.text:10037529          cmp     eax, [ecx+320h]
.text:1003752F          jz      loc_10043662
_______________

Changed
.text:10037529          mov     eax, 100h
.text:1003752E          mov     [ecx+320h], eax
.text:10037534          nop
CDefPolicy_Query_eax_ecx

termsrv.dll 6.4.9841.0

Original
.text:1003B989          cmp     eax, [ecx+320h]
.text:1003B98F          jz      loc_1005E809
_______________

Changed
.text:1003B989          mov     eax, 100h
.text:1003B98E          mov     [ecx+320h], eax
.text:1003B994          nop
CDefPolicy_Query_eax_ecx

termsrv.dll 6.4.9860.0

Original
.text:1003BEC9          cmp     eax, [ecx+320h]
.text:1003BECF          jz      loc_1005EE1A
_______________

Changed
.text:1003BEC9          mov     eax, 100h
.text:1003BECE          mov     [ecx+320h], eax
.text:1003BED4          nop
CDefPolicy_Query_eax_ecx
}

var
  Stub_SLGetWindowsInformationDWORD: far_jmp;
  Old_SLGetWindowsInformationDWORD: OldCode;

// Main code

procedure WriteLog(S: AnsiString);
const
  LogFile = '\rdpwrap.txt';
var
  F: TextFile;
begin
  if not FileExists(LogFile) then
    Exit;
  AssignFile(F, LogFile);
  Append(F);
  Write(F, S+#13#10);
  CloseFile(F);
end;

procedure StopThreads;
var
  h, CurrTh, ThrHandle, CurrPr: DWORD;
  Thread: TTHREADENTRY32;
begin
  CurrTh := GetCurrentThreadId;
  CurrPr := GetCurrentProcessId;
  h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  if h <> INVALID_HANDLE_VALUE then
  begin
    Thread.dwSize := SizeOf(TTHREADENTRY32);
    if Thread32First(h, Thread) then
      repeat
        if (Thread.th32ThreadID <> CurrTh) and
          (Thread.th32OwnerProcessID = CurrPr) then
        begin
          ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false,
            Thread.th32ThreadID);
          if ThrHandle > 0 then
          begin
            SuspendThread(ThrHandle);
            CloseHandle(ThrHandle);
          end;
        end;
      until not Thread32Next(h, Thread);
      CloseHandle(h);
  end;
end;

procedure RunThreads;
var
  h, CurrTh, ThrHandle, CurrPr: DWORD;
  Thread: TTHREADENTRY32;
begin
  CurrTh := GetCurrentThreadId;
  CurrPr := GetCurrentProcessId;
  h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  if h <> INVALID_HANDLE_VALUE then
  begin
    Thread.dwSize := SizeOf(TTHREADENTRY32);
    if Thread32First(h, Thread) then
      repeat
        if (Thread.th32ThreadID <> CurrTh) and
          (Thread.th32OwnerProcessID = CurrPr) then
        begin
          ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false,
            Thread.th32ThreadID);
          if ThrHandle > 0 then
          begin
            ResumeThread(ThrHandle);
            CloseHandle(ThrHandle);
          end;
        end;
      until not Thread32Next(h, Thread);
      CloseHandle(h);
  end;
end;

function GetModuleAddress(ModuleName: String; ProcessId: DWORD; var BaseAddr: Pointer; var BaseSize: DWORD): Boolean;
var
  hSnap: THandle;
  md: MODULEENTRY32;
begin
  Result := False;
  hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
  if hSnap = INVALID_HANDLE_VALUE Then
    Exit;
  md.dwSize := SizeOf(MODULEENTRY32);
  if Module32First(hSnap, md) then
  begin
    if LowerCase(ExtractFileName(md.szExePath)) = LowerCase(ModuleName) then
    begin
      Result := True;
      BaseAddr := Pointer(md.modBaseAddr);
      BaseSize := md.modBaseSize;
      CloseHandle(hSnap);
      Exit;
    end;
    while Module32Next(hSnap, md) Do
    begin
      if LowerCase(ExtractFileName(md.szExePath)) = LowerCase(ModuleName) then
      begin
        Result := True;
        BaseAddr := Pointer(md.modBaseAddr);
        BaseSize := md.modBaseSize;
        Break;
      end;
    end;
  end;
  CloseHandle(hSnap);
end;

{procedure FindMem(Mem: Pointer; MemSz: DWORD; Buf: Pointer; BufSz: DWORD;
  From: DWORD; var A: IntArray);
var
  I: Integer;
begin
  SetLength(A, 0);
  I:=From;
  if From>0 then
    Inc(PByte(Mem), From);
  while I < MemSz - BufSz + 1 do
  begin
    if (not IsBadReadPtr(Mem, BufSz)) and (CompareMem(Mem, Buf, BufSz)) then
    begin
      SetLength(A, Length(A)+1);
      A[Length(A)-1] := I;
    end;
    Inc(I);
    Inc(PByte(Mem));
  end;
end;}

function GetModuleVersion(const ModuleName: TFileName; var FileVersion: FILE_VERSION): Boolean;
type
  VS_VERSIONINFO = record
    wLength, wValueLength, wType: Word;
    szKey: Array[1..16] of WideChar;
    Padding1: Word;
    Value: VS_FIXEDFILEINFO;
    Padding2, Children: Word;
  end;
  PVS_VERSIONINFO = ^VS_VERSIONINFO;
const
  VFF_DEBUG = 1;
  VFF_PRERELEASE = 2;
  VFF_PRIVATE = 8;
  VFF_SPECIAL = 32;
var
  hMod: HMODULE;
  hResourceInfo: HRSRC;
  VersionInfo: PVS_VERSIONINFO;
begin
  Result := False;

  if ModuleName = '' then
    hMod := GetModuleHandle(nil)
  else
    hMod := GetModuleHandle(PWideChar(ModuleName));
  if hMod = 0 then
    Exit;

  hResourceInfo := FindResource(hMod, PWideChar(1), PWideChar($10));
  if hResourceInfo = 0 then
    Exit;

  VersionInfo := Pointer(LoadResource(hMod, hResourceInfo));
  if VersionInfo = nil then
    Exit;

  FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
  FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
  FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
  FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
  FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
  FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
  FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;

  Result := True;
end;

function GetFileVersion(const FileName: TFileName; var FileVersion: FILE_VERSION): Boolean;
type
  VS_VERSIONINFO = record
    wLength, wValueLength, wType: Word;
    szKey: Array[1..16] of WideChar;
    Padding1: Word;
    Value: VS_FIXEDFILEINFO;
    Padding2, Children: Word;
  end;
  PVS_VERSIONINFO = ^VS_VERSIONINFO;
const
  VFF_DEBUG = 1;
  VFF_PRERELEASE = 2;
  VFF_PRIVATE = 8;
  VFF_SPECIAL = 32;
var
  hFile: HMODULE;
  hResourceInfo: HRSRC;
  VersionInfo: PVS_VERSIONINFO;
begin
  Result := False;

  hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
  if hFile = 0 then
    Exit;

  hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10));
  if hResourceInfo = 0 then
    Exit;

  VersionInfo := Pointer(LoadResource(hFile, hResourceInfo));
  if VersionInfo = nil then
    Exit;

  FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS;
  FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16);
  FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS);
  FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG;
  FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE;
  FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE;
  FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL;

  Result := True;
end;

function OverrideSL(ValueName: String; var Value: DWORD): Boolean;
begin
  Result := True;
  // Allow Remote Connections
  if ValueName = 'TerminalServices-RemoteConnectionManager-AllowRemoteConnections' then begin
    Value := 1;
    Exit;
  end;
  // Allow Multiple Sessions
  if ValueName = 'TerminalServices-RemoteConnectionManager-AllowMultipleSessions' then begin
    Value := 1;
    Exit;
  end;
  // Allow Multiple Sessions (Application Server Mode)
  if ValueName = 'TerminalServices-RemoteConnectionManager-AllowAppServerMode' then begin
    Value := 1;
    Exit;
  end;
  // Allow Multiple Monitors
  if ValueName = 'TerminalServices-RemoteConnectionManager-AllowMultimon' then begin
    Value := 1;
    Exit;
  end;
  // Max User Sessions (0 = unlimited)
  if ValueName = 'TerminalServices-RemoteConnectionManager-MaxUserSessions' then begin
    Value := 0;
    Exit;
  end;
  // Max Debug Sessions (Win 8, 0 = unlimited)
  if ValueName = 'TerminalServices-RemoteConnectionManager-ce0ad219-4670-4988-98fb-89b14c2f072b-MaxSessions' then begin
    Value := 0;
    Exit;
  end;
  // Max Sessions
  // 0 - logon not possible even from console
  // 1 - only one active user (console or remote)
  // 2 - allow concurrent sessions
  if ValueName = 'TerminalServices-RemoteConnectionManager-45344fe7-00e6-4ac6-9f01-d01fd4ffadfb-MaxSessions' then begin
    Value := 2;
    Exit;
  end;
  // Allow Advanced Compression with RDP 7 Protocol
  if ValueName = 'TerminalServices-RDP-7-Advanced-Compression-Allowed' then begin
    Value := 1;
    Exit;
  end;
  // IsTerminalTypeLocalOnly = 0
  if ValueName = 'TerminalServices-RemoteConnectionManager-45344fe7-00e6-4ac6-9f01-d01fd4ffadfb-LocalOnly' then begin
    Value := 0;
    Exit;
  end;
  // Max Sessions (hard limit)
  if ValueName = 'TerminalServices-RemoteConnectionManager-8dc86f1d-9969-4379-91c1-06fe1dc60575-MaxSessions' then begin
    Value := 1000;
    Exit;
  end;
  // Allow Easy Print
  if ValueName = 'TerminalServices-DeviceRedirection-Licenses-TSEasyPrintAllowed' then begin
    Value := 1;
    Exit;
  end;
  Result := False;
end;

function New_SLGetWindowsInformationDWORD(pwszValueName: PWideChar;
  pdwValue: PDWORD): HRESULT; stdcall;
var
  dw: DWORD;
begin
  // wrapped SLGetWindowsInformationDWORD function
  // termsrv.dll will call this function instead of original SLC.dll

  // Override SL Policy

  WriteLog('Policy query: ' + pwszValueName);
  if OverrideSL(pwszValueName, dw) then begin
    pdwValue^ := dw;
    Result := S_OK;
    WriteLog('Rewrite: ' + IntToStr(pdwValue^));
    Exit;
  end;

  // If the requested value name is not defined above

  // revert to original SL Policy function
  WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
    @Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);

  // get result
  Result := SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
  if Result = S_OK then
    WriteLog('Result: ' + IntToStr(pdwValue^))
  else
    WriteLog('Failed');
  // wrap it back
  WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
    @Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
end;

function New_Win8SL(pwszValueName: PWideChar; pdwValue: PDWORD): HRESULT; register;
var
  dw: DWORD;
begin
  // wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
  // for Windows 8 support

  // Override SL Policy

  WriteLog('Policy query: ' + pwszValueName);
  if OverrideSL(pwszValueName, dw) then begin
    pdwValue^ := dw;
    Result := S_OK;
    WriteLog('Rewrite: ' + IntToStr(pdwValue^));
    Exit;
  end;

  // If the requested value name is not defined above
  // use function from SLC.dll

  Result := SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
  if Result = S_OK then
    WriteLog('Result: ' + IntToStr(pdwValue^))
  else
    WriteLog('Failed');
end;

function New_Win8SL_CP(eax: DWORD; pdwValue: PDWORD; ecx: DWORD; pwszValueName: PWideChar): HRESULT; register;
begin
  // wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
  // for Windows 8 Consumer Preview support

  Result := New_Win8SL(pwszValueName, pdwValue);
end;

function New_CSLQuery_Initialize: HRESULT; stdcall;
var
  bServerSku,
  bRemoteConnAllowed,
  bFUSEnabled,
  bAppServerAllowed,
  bMultimonAllowed,
  lMaxUserSessions,
  ulMaxDebugSessions,
  bInitialized: PDWORD;
begin
  bServerSku := nil;
  bRemoteConnAllowed := nil;
  bFUSEnabled := nil;
  bAppServerAllowed := nil;
  bMultimonAllowed := nil;
  lMaxUserSessions := nil;
  ulMaxDebugSessions := nil;
  bInitialized := nil;
  WriteLog('> CSLQuery::Initialize');
  if (FV.Release = 9431) and (FV.Build = 0) then begin
    bFUSEnabled :=        Pointer(Cardinal(TermSrvBase) + $A22A8);
    lMaxUserSessions :=   Pointer(Cardinal(TermSrvBase) + $A22AC);
    bAppServerAllowed :=  Pointer(Cardinal(TermSrvBase) + $A22B0);
    bInitialized :=       Pointer(Cardinal(TermSrvBase) + $A22B4);
    bMultimonAllowed :=   Pointer(Cardinal(TermSrvBase) + $A22B8);
    bServerSku :=         Pointer(Cardinal(TermSrvBase) + $A22BC);
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + $A22C0);
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + $A22C4);
  end;
  if (FV.Release = 9600) and (FV.Build = 16384) then begin
    bFUSEnabled :=        Pointer(Cardinal(TermSrvBase) + $C02A8);
    lMaxUserSessions :=   Pointer(Cardinal(TermSrvBase) + $C02AC);
    bAppServerAllowed :=  Pointer(Cardinal(TermSrvBase) + $C02B0);
    bInitialized :=       Pointer(Cardinal(TermSrvBase) + $C02B4);
    bMultimonAllowed :=   Pointer(Cardinal(TermSrvBase) + $C02B8);
    bServerSku :=         Pointer(Cardinal(TermSrvBase) + $C02BC);
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + $C02C0);
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + $C02C4);
  end;
  if (FV.Release = 9600) and (FV.Build = 17095) then begin
    bFUSEnabled :=        Pointer(Cardinal(TermSrvBase) + $C12A8);
    lMaxUserSessions :=   Pointer(Cardinal(TermSrvBase) + $C12AC);
    bAppServerAllowed :=  Pointer(Cardinal(TermSrvBase) + $C12B0);
    bInitialized :=       Pointer(Cardinal(TermSrvBase) + $C12B4);
    bMultimonAllowed :=   Pointer(Cardinal(TermSrvBase) + $C12B8);
    bServerSku :=         Pointer(Cardinal(TermSrvBase) + $C12BC);
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + $C12C0);
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + $C12C4);
  end;
  if (FV.Release = 9841) and (FV.Build = 0) then begin
    bFUSEnabled :=        Pointer(Cardinal(TermSrvBase) + $BF9F0);
    lMaxUserSessions :=   Pointer(Cardinal(TermSrvBase) + $BF9F4);
    bAppServerAllowed :=  Pointer(Cardinal(TermSrvBase) + $BF9F8);
    bInitialized :=       Pointer(Cardinal(TermSrvBase) + $BF9FC);
    bMultimonAllowed :=   Pointer(Cardinal(TermSrvBase) + $BFA00);
    bServerSku :=         Pointer(Cardinal(TermSrvBase) + $BFA04);
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + $BFA08);
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + $BFA0C);
  end;
  if (FV.Release = 9860) and (FV.Build = 0) then begin
    bFUSEnabled :=        Pointer(Cardinal(TermSrvBase) + $BF7E0);
    lMaxUserSessions :=   Pointer(Cardinal(TermSrvBase) + $BF7E4);
    bAppServerAllowed :=  Pointer(Cardinal(TermSrvBase) + $BF7E8);
    bInitialized :=       Pointer(Cardinal(TermSrvBase) + $BF7EC);
    bMultimonAllowed :=   Pointer(Cardinal(TermSrvBase) + $BF7F0);
    bServerSku :=         Pointer(Cardinal(TermSrvBase) + $BF7F4);
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + $BF7F8);
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + $BF7FC);
  end;
  if bServerSku <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bServerSku), 1)+'] bServerSku = 1');
    bServerSku^ := 1;
  end;
  if bRemoteConnAllowed <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bRemoteConnAllowed), 1)+'] bRemoteConnAllowed = 1');
    bRemoteConnAllowed^ := 1;
  end;
  if bFUSEnabled <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bFUSEnabled), 1)+'] bFUSEnabled = 1');
    bFUSEnabled^ := 1;
  end;
  if bAppServerAllowed <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bAppServerAllowed), 1)+'] bAppServerAllowed = 1');
    bAppServerAllowed^ := 1;
  end;
  if bMultimonAllowed <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bMultimonAllowed), 1)+'] bMultimonAllowed = 1');
    bMultimonAllowed^ := 1;
  end;
  if lMaxUserSessions <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(lMaxUserSessions), 1)+'] lMaxUserSessions = 0');
    lMaxUserSessions^ := 0;
  end;
  if ulMaxDebugSessions <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(ulMaxDebugSessions), 1)+'] ulMaxDebugSessions = 0');
    ulMaxDebugSessions^ := 0;
  end;
  if bInitialized <> nil then begin
    WriteLog('[0x'+IntToHex(DWORD(bInitialized), 1)+'] bInitialized = 1');
    bInitialized^ := 1;
  end;
  Result := S_OK;
end;

procedure HookFunctions;
var
  V: DWORD;
  TS_Handle, SLC_Handle: THandle;
  TermSrvSize: DWORD;
  SignPtr: Pointer;
  Results: IntArray;
  Jump: far_jmp;
  MovJump: mov_far_jmp;
  nop: DWORD;
  b: Byte;
begin
  { hook function ^^
     (called once)   }
  IsHooked := True;
  nop := $90909090;
  TSMain := nil;
  TSGlobals := nil;
  SLGetWindowsInformationDWORD := nil;
  WriteLog('init');

  // load termsrv.dll and get functions
  TS_Handle := LoadLibrary('termsrv.dll');
  if TS_Handle = 0 then begin
    WriteLog('Error: Failed to load Terminal Services library');
    Exit;
  end;
  WriteLog('Base addr:  0x'+IntToHex(TS_Handle, 8));
  TSMain := GetProcAddress(TS_Handle, 'ServiceMain');
  WriteLog('SvcMain:    termsrv.dll+0x'+IntToHex(Cardinal(@TSMain) - TS_Handle, 1));
  TSGlobals := GetProcAddress(TS_Handle, 'SvchostPushServiceGlobals');
  WriteLog('SvcGlobals: termsrv.dll+0x'+IntToHex(Cardinal(@TSGlobals) - TS_Handle, 1));

  V := 0;
  // check termsrv version
  if GetModuleVersion('termsrv.dll', FV) then
    V := Byte(FV.Version.w.Minor) or (Byte(FV.Version.w.Major) shl 8)
  else begin
    // check NT version
    // V := GetVersion; // deprecated
    // V := ((V and $FF) shl 8) or ((V and $FF00) shr 8);
  end;
  if V = 0 then begin
    WriteLog('Error: Failed to detect Terminal Services version');
    Exit;
  end;

  WriteLog('Version: '+IntToStr(FV.Version.w.Major)+'.'+IntToStr(FV.Version.w.Minor));
  WriteLog('Release: '+IntToStr(FV.Release));
  WriteLog('Build:   '+IntToStr(FV.Build));

  // temporarily freeze threads
  WriteLog('freeze');
  StopThreads();

  if (V = $0600) then begin
    // Windows Vista
    // uses SL Policy API (slc.dll)

    // load slc.dll and hook function
    SLC_Handle := LoadLibrary('slc.dll');
    SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');

    if @SLGetWindowsInformationDWORD <> nil then
    begin
      // rewrite original function to call our function (make hook)

      WriteLog('Hook SLGetWindowsInformationDWORD');
      Stub_SLGetWindowsInformationDWORD.PushOp := $68;
      Stub_SLGetWindowsInformationDWORD.PushArg := @New_SLGetWindowsInformationDWORD;
      Stub_SLGetWindowsInformationDWORD.RetOp := $C3;
      ReadProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
        @Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);
      WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
        @Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
    end;

    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      // Patch functions:
      // CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
      // CDefPolicy::Query

      if (FV.Release = 6000) and (FV.Build = 16386) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F320000
          .text:6F3360B9          lea     eax, [ebp+VersionInformation]
          .text:6F3360BF          inc     ebx            <- nop
          .text:6F3360C0          push    eax             ; lpVersionInformation
          .text:6F3360C1          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F3360CB          mov     [esi], ebx
          .text:6F3360CD          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $160BF);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $15CD8);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_edx_ecx[0],
        SizeOf(CDefPolicy_Query_edx_ecx), bw);
      end;
      if (FV.Release = 6001) and (FV.Build = 18000) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6E800000
          .text:6E8185DE          lea     eax, [ebp+VersionInformation]
          .text:6E8185E4          inc     ebx            <- nop
          .text:6E8185E5          push    eax             ; lpVersionInformation
          .text:6E8185E6          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6E8185F0          mov     [esi], ebx
          .text:6E8185F2          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $185E4);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $17FD8);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_edx_ecx[0],
        SizeOf(CDefPolicy_Query_edx_ecx), bw);
      end;
      if (FV.Release = 6002) and (FV.Build = 18005) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F580000
          .text:6F597FA2          lea     eax, [ebp+VersionInformation]
          .text:6F597FA8          inc     ebx            <- nop
          .text:6F597FA9          push    eax             ; lpVersionInformation
          .text:6F597FAA          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F597FB4          mov     [esi], ebx
          .text:6F597FB6          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $17FA8);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $179C0);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_edx_ecx[0],
        SizeOf(CDefPolicy_Query_edx_ecx), bw);
      end;
      if (FV.Release = 6002) and (FV.Build = 19214) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F580000
          .text:6F597FBE          lea     eax, [ebp+VersionInformation]
          .text:6F597FC4          inc     ebx            <- nop
          .text:6F597FC5          push    eax             ; lpVersionInformation
          .text:6F597FC6          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F597FD0          mov     [esi], ebx
          .text:6F597FD2          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $17FC4);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $179B8);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_edx_ecx[0],
        SizeOf(CDefPolicy_Query_edx_ecx), bw);
      end;
      if (FV.Release = 6002) and (FV.Build = 23521) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F580000
          .text:6F597FAE          lea     eax, [ebp+VersionInformation]
          .text:6F597FB4          inc     ebx            <- nop
          .text:6F597FB5          push    eax             ; lpVersionInformation
          .text:6F597FB6          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F597FC0          mov     [esi], ebx
          .text:6F597FC2          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $17FB4);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $179CC);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_edx_ecx[0],
        SizeOf(CDefPolicy_Query_edx_ecx), bw);
      end;
    end;
  end;
  if (V = $0601) then begin
    // Windows 7
    // uses SL Policy API (slc.dll)

    // load slc.dll and hook function
    SLC_Handle := LoadLibrary('slc.dll');
    SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');

    if @SLGetWindowsInformationDWORD <> nil then
    begin
      // rewrite original function to call our function (make hook)

      WriteLog('Hook SLGetWindowsInformationDWORD');
      Stub_SLGetWindowsInformationDWORD.PushOp := $68;
      Stub_SLGetWindowsInformationDWORD.PushArg := @New_SLGetWindowsInformationDWORD;
      Stub_SLGetWindowsInformationDWORD.RetOp := $C3;
      ReadProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
        @Old_SLGetWindowsInformationDWORD, SizeOf(OldCode), bw);
      WriteProcessMemory(GetCurrentProcess, @SLGetWindowsInformationDWORD,
        @Stub_SLGetWindowsInformationDWORD, SizeOf(far_jmp), bw);
    end;

    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      // Patch functions:
      // CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
      // CDefPolicy::Query

      if (FV.Release = 7600) and (FV.Build = 16385) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2F9E1F          lea     eax, [ebp+VersionInformation]
          .text:6F2F9E25          inc     ebx            <- nop
          .text:6F2F9E26          push    eax             ; lpVersionInformation
          .text:6F2F9E27          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2F9E31          mov     [esi], ebx
          .text:6F2F9E33          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19E25);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $196F3);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
      if (FV.Release = 7601) and (FV.Build = 17514) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2FA497          lea     eax, [ebp+VersionInformation]
          .text:6F2FA49D          inc     ebx            <- nop
          .text:6F2FA49E          push    eax             ; lpVersionInformation
          .text:6F2FA49F          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2FA4A9          mov     [esi], ebx
          .text:6F2FA4AB          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A49D);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19D53);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
      if (FV.Release = 7601) and (FV.Build = 18540) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2FA4DF          lea     eax, [ebp+VersionInformation]
          .text:6F2FA4E5          inc     ebx            <- nop
          .text:6F2FA4E6          push    eax             ; lpVersionInformation
          .text:6F2FA4E7          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2FA4F1          mov     [esi], ebx
          .text:6F2FA4F3          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A4E5);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19D9F);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
      if (FV.Release = 7601) and (FV.Build = 22750) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2FA64F          lea     eax, [ebp+VersionInformation]
          .text:6F2FA655          inc     ebx            <- nop
          .text:6F2FA656          push    eax             ; lpVersionInformation
          .text:6F2FA657          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2FA661          mov     [esi], ebx
          .text:6F2FA663          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A655);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19E21);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
      if (FV.Release = 7601) and (FV.Build = 18637) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2FA4D7          lea     eax, [ebp+VersionInformation]
          .text:6F2FA4DD          inc     ebx            <- nop
          .text:6F2FA4DE          push    eax             ; lpVersionInformation
          .text:6F2FA4DF          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2FA4E9          mov     [esi], ebx
          .text:6F2FA4EB          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A4DD);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19DBB);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
      if (FV.Release = 7601) and (FV.Build = 22843) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        { Imagebase: 6F2E0000
          .text:6F2FA64F          lea     eax, [ebp+VersionInformation]
          .text:6F2FA655          inc     ebx            <- nop
          .text:6F2FA656          push    eax             ; lpVersionInformation
          .text:6F2FA657          mov     [ebp+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:6F2FA661          mov     [esi], ebx
          .text:6F2FA663          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A655);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19E25);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);
      end;
    end;
  end;
  if V = $0602 then begin
    // Windows 8
    // uses SL Policy internal unexported function

    // load slc.dll and get function
    // (will be used on intercepting undefined values)
    SLC_Handle := LoadLibrary('slc.dll');
    SLGetWindowsInformationDWORD := GetProcAddress(SLC_Handle, 'SLGetWindowsInformationDWORD');

    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      // Patch functions:
      // CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
      // CDefPolicy::Query
      // Hook function:
      // SLGetWindowsInformationDWORDWrapper

      if (FV.Release = 8102) and (FV.Build = 0) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:1000F7E5          lea     eax, [esp+150h+VersionInformation]
          .text:1000F7E9          inc     esi            <- nop
          .text:1000F7EA          push    eax             ; lpVersionInformation
          .text:1000F7EB          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:1000F7F3          mov     [edi], esi
          .text:1000F7F5          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $F7E9);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $E47C);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1B909);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if (FV.Release = 8250) and (FV.Build = 0) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:100159C5          lea     eax, [esp+150h+VersionInformation]
          .text:100159C9          inc     esi            <- nop
          .text:100159CA          push    eax             ; lpVersionInformation
          .text:100159CB          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:100159D3          mov     [edi], esi
          .text:100159D5          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $159C9);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $13520);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1A0A9);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL_CP;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if (FV.Release = 8400) and (FV.Build = 0) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:1001547E          lea     eax, [esp+150h+VersionInformation]
          .text:10015482          inc     esi            <- nop
          .text:10015483          push    eax             ; lpVersionInformation
          .text:10015484          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:1001548C          mov     [edi], esi
          .text:1001548E          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $15482);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $13E48);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19629);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if (FV.Release = 9200) and (FV.Build = 16384) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:1001554E          lea     eax, [esp+150h+VersionInformation]
          .text:10015552          inc     esi            <- nop
          .text:10015553          push    eax             ; lpVersionInformation
          .text:10015554          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:1001555C          mov     [edi], esi
          .text:1001555E          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $15552);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $13F08);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19559);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if (FV.Release = 9200) and (FV.Build = 17048) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:1002058E          lea     eax, [esp+150h+VersionInformation]
          .text:10020592          inc     esi            <- nop
          .text:10020593          push    eax             ; lpVersionInformation
          .text:10020594          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:1002059C          mov     [edi], esi
          .text:1002059E          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $20592);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1F408);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $17059);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if (FV.Release = 9200) and (FV.Build = 21166) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:10015576          lea     eax, [esp+150h+VersionInformation]
          .text:1001557A          inc     esi            <- nop
          .text:1001557B          push    eax             ; lpVersionInformation
          .text:1001557C          mov     [esp+154h+VersionInformation.dwOSVersionInfoSize], 11Ch
          .text:10015584          mov     [edi], esi
          .text:10015586          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1557A);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $13F30);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_esi[0],
        SizeOf(CDefPolicy_Query_eax_esi), bw);

        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $19581);
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
    end;
  end;
  if V = $0603 then begin
    // Windows 8.1
    // uses SL Policy internal inline code

    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      // Patch functions:
      // CEnforcementCore::GetInstanceOfTSLicense
      // CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
      // CDefPolicy::Query
      // Hook function:
      // CSLQuery::Initialize

      if (FV.Release = 9431) and (FV.Build = 0) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        {
          .text:1008A604          call    ?IsLicenseTypeLocalOnly@CSLQuery@@SGJAAU_GUID@@PAH@Z ; CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *)
          .text:1008A609          test    eax, eax
          .text:1008A60B          js      short loc_1008A628
          .text:1008A60D          cmp     [ebp+var_8], 0
          .text:1008A611          jz      short loc_1008A628 <- jmp
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $8A611);
        b := $EB;
        WriteProcessMemory(GetCurrentProcess, SignPtr, @b, 1, bw);

        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:100306A4          lea     eax, [esp+150h+VersionInformation]
          .text:100306A8          inc     ebx            <- nop
          .text:100306A9          mov     [edi], ebx
          .text:100306AB          push    eax             ; lpVersionInformation
          .text:100306AC          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $306A8);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $2EA25);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_ecx[0],
        SizeOf(CDefPolicy_Query_eax_ecx), bw);

        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $196B0);
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;
      if (FV.Release = 9600) and (FV.Build = 16384) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        {
          .text:100A271C          call    ?IsLicenseTypeLocalOnly@CSLQuery@@SGJAAU_GUID@@PAH@Z ; CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *)
          .text:100A2721          test    eax, eax
          .text:100A2723          js      short loc_100A2740
          .text:100A2725          cmp     [ebp+var_8], 0
          .text:100A2729          jz      short loc_100A2740 <- jmp
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $A2729);
        b := $EB;
        WriteProcessMemory(GetCurrentProcess, SignPtr, @b, 1, bw);

        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:10018024          lea     eax, [esp+150h+VersionInformation]
          .text:10018028          inc     ebx            <- nop
          .text:10018029          mov     [edi], ebx
          .text:1001802B          push    eax             ; lpVersionInformation
          .text:1001802C          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $18028);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $16115);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_ecx[0],
        SizeOf(CDefPolicy_Query_eax_ecx), bw);

        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $1CEB0);
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;
      if (FV.Release = 9600) and (FV.Build = 17095) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        {
          .text:100A36C4          call    ?IsLicenseTypeLocalOnly@CSLQuery@@SGJAAU_GUID@@PAH@Z ; CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *)
          .text:100A36C9          test    eax, eax
          .text:100A36CB          js      short loc_100A36E8
          .text:100A36CD          cmp     [ebp+var_8], 0
          .text:100A36D1          jz      short loc_100A36E8 <- jmp
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $A36D1);
        b := $EB;
        WriteProcessMemory(GetCurrentProcess, SignPtr, @b, 1, bw);

        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:10036BA5          lea     eax, [esp+150h+VersionInformation]
          .text:10036BA9          inc     ebx            <- nop
          .text:10036BAA          mov     [edi], ebx
          .text:10036BAC          push    eax             ; lpVersionInformation
          .text:10036BAD          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $36BA9);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $37529);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_ecx[0],
        SizeOf(CDefPolicy_Query_eax_ecx), bw);

        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $117F1);
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;

    end;
  end;
  if V = $0604 then begin
    // Windows 10
    // uses SL Policy internal inline code

    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      // Patch functions:
      // CEnforcementCore::GetInstanceOfTSLicense
      // CSessionArbitrationHelper::IsSingleSessionPerUserEnabled
      // CDefPolicy::Query
      // Hook function:
      // CSLQuery::Initialize

      if (FV.Release = 9841) and (FV.Build = 0) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        {
          .text:1009569B          call    sub_100B7EE5
          .text:100956A0          test    eax, eax
          .text:100956A2          js      short loc_100956BF
          .text:100956A4          cmp     [ebp+var_C], 0
          .text:100956A8          jz      short loc_100956BF <- jmp
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $956A8);
        b := $EB;
        WriteProcessMemory(GetCurrentProcess, SignPtr, @b, 1, bw);

        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:10030121          lea     eax, [esp+150h+VersionInformation]
          .text:10030125          inc     ebx            <- nop
          .text:10030126          mov     [edi], ebx
          .text:10030128          push    eax             ; lpVersionInformation
          .text:10030129          call    ds:GetVersionExW
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $30125);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $3B989);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_ecx[0],
        SizeOf(CDefPolicy_Query_eax_ecx), bw);

        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $46A68);
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;

      if (FV.Release = 9860) and (FV.Build = 0) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        {
          .text:100962BB          call    ?IsLicenseTypeLocalOnly@CSLQuery@@SGJAAU_GUID@@PAH@Z ; CSLQuery::IsLicenseTypeLocalOnly(_GUID &,int *)
          .text:100962C0          test    eax, eax
          .text:100962C2          js      short loc_100962DF
          .text:100962C4          cmp     [ebp+var_C], 0
          .text:100962C8          jz      short loc_100962DF <- jmp
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $962C8);
        b := $EB;
        WriteProcessMemory(GetCurrentProcess, SignPtr, @b, 1, bw);

        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        {
          .text:10030841          lea     eax, [esp+150h+VersionInformation]
          .text:10030845          inc     ebx            <- nop
          .text:10030846          mov     [edi], ebx
          .text:10030848          push    eax             ; lpVersionInformation
          .text:10030849          call    ds:__imp__GetVersionExW@4 ; GetVersionExW(x)
        }
        SignPtr := Pointer(Cardinal(TermSrvBase) + $30845);
        WriteProcessMemory(GetCurrentProcess, SignPtr, @nop, 1, bw);

        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $3BEC9);
        WriteProcessMemory(GetCurrentProcess, SignPtr,
        @CDefPolicy_Query_eax_ecx[0],
        SizeOf(CDefPolicy_Query_eax_ecx), bw);

        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + $46F18);
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;

    end;
  end;

  // unfreeze threads
  WriteLog('resume');
  RunThreads();
end;

function TermServiceMain(dwArgc: DWORD; lpszArgv: PWideChar): DWORD; stdcall;
begin
  // wrap ServiceMain function
  WriteLog('> ServiceMain');
  if not IsHooked then
    HookFunctions;
  Result := 0;
  if @TSMain <> nil then
    Result := TSMain(dwArgc, lpszArgv);
end;

function TermServiceGlobals(lpGlobalData: Pointer): DWORD; stdcall;
begin
  // wrap SvchostPushServiceGlobals function
  WriteLog('> SvchostPushServiceGlobals');
  if not IsHooked then
    HookFunctions;
  Result := 0;
  if @TSGlobals <> nil then
    Result := TSGlobals(lpGlobalData);
end;

// export section

exports
  TermServiceMain index 1 name 'ServiceMain';
exports
  TermServiceGlobals index 2 name 'SvchostPushServiceGlobals';

begin
  // DllMain procedure is not used
end.