{
  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,
  LiteINI;

{$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
  INI: INIFile;
  LogFile: String = '\rdpwrap.txt';
  bw: DWORD;
  IsHooked: Boolean = False;

// 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;

var
  Stub_SLGetWindowsInformationDWORD: far_jmp;
  Old_SLGetWindowsInformationDWORD: OldCode;

// Main code

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

function GetModuleHandleEx(dwFlags: DWORD; lpModuleName: PWideChar;
  var phModule: HMODULE): BOOL; stdcall; external kernel32 name 'GetModuleHandleExW';

function GetCurrentModule: HMODULE;
const
  GET_MODULE_HANDLE_EX_FLAG_PIN = 1;
  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2;
  GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4;
begin
  Result := 0;
  GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, @GetCurrentModule, Result);
end;

function GetBinaryPath: String;
var
  Buf: Array[0..511] of Byte;
begin
  ZeroMemory(@Buf[0], Length(Buf));
  GetModuleFileName(GetCurrentModule, PWideChar(@Buf[0]), Length(Buf));
  Result := PWideChar(@Buf[0]);
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: String; 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: String; 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;
  if INIValueExists(INI, 'SLPolicy', ValueName) then begin
    Value := INIReadDWord(INI, 'SLPolicy', ValueName, 0);
    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('Policy 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('Policy result: ' + IntToStr(pdwValue^))
  else
    WriteLog('Policy request 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('Policy 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('Policy result: ' + IntToStr(pdwValue^))
  else
    WriteLog('Policy request 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
  Sect: String;
  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');
  Sect := IntToStr(FV.Version.w.Major)+'.'+IntToStr(FV.Version.w.Minor)+'.'+
          IntToStr(FV.Release)+'.'+IntToStr(FV.Build)+'-SLInit';
  if INISectionExists(INI, Sect) then begin
    bServerSku := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bServerSku.x86', 0));
    bRemoteConnAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bRemoteConnAllowed.x86', 0));
    bFUSEnabled := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bFUSEnabled.x86', 0));
    bAppServerAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bAppServerAllowed.x86', 0));
    bMultimonAllowed := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bMultimonAllowed.x86', 0));
    lMaxUserSessions := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'lMaxUserSessions.x86', 0));
    ulMaxDebugSessions := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'ulMaxDebugSessions.x86', 0));
    bInitialized := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'bInitialized.x86', 0));
  end;

  if bServerSku <> nil then begin
    bServerSku^ := INIReadDWord(INI, 'SLInit', 'bServerSku', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bServerSku), 1)+'] bServerSku = ' + IntToStr(bServerSku^));
  end;
  if bRemoteConnAllowed <> nil then begin
    bRemoteConnAllowed^ := INIReadDWord(INI, 'SLInit', 'bRemoteConnAllowed', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bRemoteConnAllowed), 1)+'] bRemoteConnAllowed = ' + IntToStr(bRemoteConnAllowed^));
  end;
  if bFUSEnabled <> nil then begin
    bFUSEnabled^ := INIReadDWord(INI, 'SLInit', 'bFUSEnabled', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bFUSEnabled), 1)+'] bFUSEnabled = ' + IntToStr(bFUSEnabled^));
  end;
  if bAppServerAllowed <> nil then begin
    bAppServerAllowed^ := INIReadDWord(INI, 'SLInit', 'bAppServerAllowed', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bAppServerAllowed), 1)+'] bAppServerAllowed = ' + IntToStr(bAppServerAllowed^));
  end;
  if bMultimonAllowed <> nil then begin
    bMultimonAllowed^ := INIReadDWord(INI, 'SLInit', 'bMultimonAllowed', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bMultimonAllowed), 1)+'] bMultimonAllowed = ' + IntToStr(bMultimonAllowed^));
  end;
  if lMaxUserSessions <> nil then begin
    lMaxUserSessions^ := INIReadDWord(INI, 'SLInit', 'lMaxUserSessions', 0);
    WriteLog('SLInit [0x'+IntToHex(DWORD(lMaxUserSessions), 1)+'] lMaxUserSessions = ' + IntToStr(lMaxUserSessions^));
  end;
  if ulMaxDebugSessions <> nil then begin
    ulMaxDebugSessions^ := INIReadDWord(INI, 'SLInit', 'ulMaxDebugSessions', 0);
    WriteLog('SLInit [0x'+IntToHex(DWORD(ulMaxDebugSessions), 1)+'] ulMaxDebugSessions = ' + IntToStr(ulMaxDebugSessions^));
  end;
  if bInitialized <> nil then begin
    bInitialized^ := INIReadDWord(INI, 'SLInit', 'bInitialized', 1);
    WriteLog('SLInit [0x'+IntToHex(DWORD(bInitialized), 1)+'] bInitialized = ' + IntToStr(bInitialized^));
  end;
  Result := S_OK;
  WriteLog('<<< CSLQuery::Initialize');
end;

procedure HookFunctions;
var
  ConfigFile, Sect, FuncName: String;
  V: DWORD;
  TS_Handle, SLC_Handle: THandle;
  TermSrvSize: DWORD;
  SignPtr: Pointer;
  I: Integer;
  PatchList: SList;
  Patch: Array of TBytes;
  Jump: far_jmp;
  MovJump: mov_far_jmp;
begin
  { hook function ^^
     (called once)   }
  IsHooked := True;
  TSMain := nil;
  TSGlobals := nil;
  SLGetWindowsInformationDWORD := nil;

  WriteLog('Loading configuration...');
  ConfigFile := ExtractFilePath(GetBinaryPath) + 'rdpwrap.ini';
  WriteLog('Configuration file: ' + ConfigFile);
  INILoad(INI, ConfigFile);
  if Length(INI) = 0 then begin
    WriteLog('Error: Failed to load configuration');
    Exit;
  end;

  LogFile := INIReadString(INI, 'Main', 'LogFile', ExtractFilePath(GetBinaryPath) + 'rdpwrap.txt');
  WriteLog('Initializing RDP Wrapper...');

  // 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;
  TSMain := GetProcAddress(TS_Handle, 'ServiceMain');
  TSGlobals := GetProcAddress(TS_Handle, 'SvchostPushServiceGlobals');
  WriteLog(
    'Base addr:  0x' + IntToHex(TS_Handle, 8) + #13#10 +
    'SvcMain:    termsrv.dll+0x' + IntToHex(Cardinal(@TSMain) - TS_Handle, 1) + #13#10 +
    '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)+'.'+
  IntToStr(FV.Release)+'.'+
  IntToStr(FV.Build));

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

  WriteLog('Caching patch codes...');
  PatchList := INIReadSection(INI, 'PatchCodes');
  SetLength(Patch, Length(PatchList));
  for I := 0 to Length(Patch) - 1 do begin
    Patch[I] := INIReadBytes(INI, 'PatchCodes', PatchList[I]);
    if Length(Patch[I]) > 16 then  // for security reasons
      SetLength(Patch[I], 16);     // not more than 16 bytes
  end;

  if (V = $0600) and (INIReadBool(INI, 'Main', 'SLPolicyHookNT60', True)) 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;
  end;
  if (V = $0601) and (INIReadBool(INI, 'Main', 'SLPolicyHookNT61', True)) 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;
  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');
  end;
  if V = $0603 then begin
    // Windows 8.1
    // uses SL Policy internal inline code
  end;
  if V = $0604 then begin
    // Windows 10
    // uses SL Policy internal inline code
  end;

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

  if INISectionExists(INI, Sect) then
    if GetModuleAddress('termsrv.dll', GetCurrentProcessId, TermSrvBase, TermSrvSize) then begin
      if INIReadBool(INI, Sect, 'LocalOnlyPatch.x86', False) then begin
        WriteLog('Patch CEnforcementCore::GetInstanceOfTSLicense');
        SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'LocalOnlyOffset.x86', 0));
        I := SListFind(PatchList, INIReadString(INI, Sect, 'LocalOnlyCode.x86', ''));
        if I >= 0 then
          WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
      end;
      if INIReadBool(INI, Sect, 'SingleUserPatch.x86', False) then begin
        WriteLog('Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled');
        SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SingleUserOffset.x86', 0));
        I := SListFind(PatchList, INIReadString(INI, Sect, 'SingleUserCode.x86', ''));
        if I >= 0 then
          WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
      end;
      if INIReadBool(INI, Sect, 'DefPolicyPatch.x86', False) then begin
        WriteLog('Patch CDefPolicy::Query');
        SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'DefPolicyOffset.x86', 0));
        I := SListFind(PatchList, INIReadString(INI, Sect, 'DefPolicyCode.x86', ''));
        if I >= 0 then
          WriteProcessMemory(GetCurrentProcess, SignPtr, @Patch[I][0], Length(Patch[I]), bw);
      end;
      if INIReadBool(INI, Sect, 'SLPolicyInternal.x86', False) then begin
        WriteLog('Hook SLGetWindowsInformationDWORDWrapper');
        SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SLPolicyOffset.x86', 0));
        MovJump.MovOp := $89;  // mov eax, ecx
        MovJump.MovArg := $C8; // __msfastcall compatibility
        MovJump.PushOp := $68;
        MovJump.PushArg := @New_Win8SL;
        MovJump.RetOp := $C3;
        FuncName := INIReadString(INI, Sect, 'SLPolicyFunc.x86', 'New_Win8SL');
        if FuncName = 'New_Win8SL' then
          MovJump.PushArg := @New_Win8SL;
        if FuncName = 'New_Win8SL_CP' then
          MovJump.PushArg := @New_Win8SL_CP;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @MovJump, SizeOf(mov_far_jmp), bw);
      end;
      if INIReadBool(INI, Sect, 'SLInitHook.x86', False) then begin
        WriteLog('Hook CSLQuery::Initialize');
        SignPtr := Pointer(Cardinal(TermSrvBase) + INIReadDWordHex(INI, Sect, 'SLInitOffset.x86', 0));
        Jump.PushOp := $68;
        Jump.PushArg := @New_CSLQuery_Initialize;
        Jump.RetOp := $C3;
        FuncName := INIReadString(INI, Sect, 'SLInitFunc.x86', 'New_CSLQuery_Initialize');
        if FuncName = 'New_CSLQuery_Initialize' then
          Jump.PushArg := @New_CSLQuery_Initialize;
        WriteProcessMemory(GetCurrentProcess, SignPtr,
          @Jump, SizeOf(far_jmp), bw);
      end;
    end;

  // unfreeze threads
  WriteLog('Resumimg threads...');
  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);
  WriteLog('<<< ServiceMain');
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);
  WriteLog('<<< SvchostPushServiceGlobals');
end;

// export section

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

begin
  // DllMain procedure is not used
end.