/*
  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.
*/

#include "stdafx.h"
#include "IniFile.h"
#include <stdlib.h>

typedef struct
{
	union
	{
		struct
		{
			WORD Minor;
			WORD Major;
		} wVersion;
		DWORD dwVersion;
	};
	WORD Release;
	WORD Build;
} FILE_VERSION;

#ifdef _WIN64
typedef unsigned long long PLATFORM_DWORD;
struct FARJMP
{	// x64 far jump | opcode | assembly
	BYTE MovOp;		// 48	mov rax, ptr
	BYTE MovRegArg;	// B8
	DWORD64 MovArg;	// PTR
	BYTE PushRaxOp; // 50	push rax
	BYTE RetOp;		// C3	retn
};
#else
typedef unsigned long PLATFORM_DWORD;
struct FARJMP
{	// x86 far jump | opcode | assembly
	BYTE PushOp;	// 68	push ptr
	DWORD PushArg;	// PTR
	BYTE RetOp;		// C3	retn
};
#endif

FARJMP Old_SLGetWindowsInformationDWORD, Stub_SLGetWindowsInformationDWORD;
SLGETWINDOWSINFORMATIONDWORD _SLGetWindowsInformationDWORD;

INI_FILE *IniFile;
wchar_t LogFile[256] = L"\\rdpwrap.txt\0";
HMODULE hTermSrv;
HMODULE hSLC;
PLATFORM_DWORD TermSrvBase;
FILE_VERSION FV;
SERVICEMAIN _ServiceMain;
SVCHOSTPUSHSERVICEGLOBALS _SvchostPushServiceGlobals;
bool AlreadyHooked = false;

DWORD INIReadDWordHex(INI_FILE *IniFile, char *Sect, char *VariableName, PLATFORM_DWORD Default)
{
	INI_VAR_DWORD Variable;

	if(IniFile->GetVariableInSection(Sect, VariableName, &Variable))
	{
		return Variable.ValueHex;
	}
	return Default;
}

void INIReadString(INI_FILE *IniFile, char *Sect, char *VariableName, char *Default, char *Ret, DWORD RetSize)
{
	INI_VAR_STRING Variable;

	memset(Ret, 0x00, RetSize);
	if(!IniFile->GetVariableInSection(Sect, VariableName, &Variable))
	{
		strcpy_s(Ret, RetSize, Default);
		return;
	}
	strcpy_s(Ret, RetSize, Variable.Value);
}

void WriteToLog(LPSTR Text)
{
	DWORD dwBytesOfWritten;

	HANDLE hFile = CreateFile(LogFile, GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) return;

	SetFilePointer(hFile, 0, 0, FILE_END);
	WriteFile(hFile, Text, strlen(Text), &dwBytesOfWritten, NULL);
	CloseHandle(hFile);
}

HMODULE GetCurrentModule()
{
	HMODULE hModule = NULL;
	GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)GetCurrentModule, &hModule);
	return hModule;
}

/*PLATFORM_DWORD SearchAddressBySignature(char *StartPosition, PLATFORM_DWORD Size, char *Signature, int SignatureSize)
{
	PLATFORM_DWORD AddressReturn = -1;

	for (PLATFORM_DWORD i = 0; i < Size; i++)
	{
		for (int j = 0; StartPosition[i+j] == Signature[j] && j < SignatureSize; j++)
		{
			if (j == SignatureSize-1) AddressReturn = (PLATFORM_DWORD)&StartPosition[i];
		}
	}

	return AddressReturn;
}*/

bool GetModuleCodeSectionInfo(HMODULE hModule, PLATFORM_DWORD *BaseAddr, PLATFORM_DWORD *BaseSize)
{
	PIMAGE_DOS_HEADER		pDosHeader;
	PIMAGE_FILE_HEADER      pFileHeader;
	PIMAGE_OPTIONAL_HEADER  pOptionalHeader;

	if (hModule == NULL) return false;

	pDosHeader = (PIMAGE_DOS_HEADER)hModule;
	pFileHeader = (PIMAGE_FILE_HEADER)(((PBYTE)hModule)+pDosHeader->e_lfanew+4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)(pFileHeader+1);

	*BaseAddr = (PLATFORM_DWORD)hModule;
	*BaseSize = (PLATFORM_DWORD)pOptionalHeader->SizeOfCode;

	if (*BaseAddr <= 0 || *BaseSize <= 0) return false;
	return true;
}

void SetThreadsState(bool Resume)
{
	HANDLE h, hThread;
	DWORD CurrTh, CurrPr;
	THREADENTRY32 Thread;

	CurrTh = GetCurrentThreadId();
	CurrPr = GetCurrentProcessId();

	h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (h != INVALID_HANDLE_VALUE)
	{
		Thread.dwSize = sizeof(THREADENTRY32);
		Thread32First(h, &Thread);
		do
		{
			if (Thread.th32ThreadID != CurrTh && Thread.th32OwnerProcessID == CurrPr)
			{
				hThread = OpenThread(THREAD_SUSPEND_RESUME, false, Thread.th32ThreadID);
				if (hThread != INVALID_HANDLE_VALUE)
				{
					if (Resume)		ResumeThread(hThread);
					else			SuspendThread(hThread);
					CloseHandle(hThread);
				}
			}
		} while (Thread32Next(h, &Thread));
		CloseHandle(h);
	}
}

BOOL __stdcall GetModuleVersion(LPCWSTR lptstrModuleName, FILE_VERSION *FileVersion)
{
	typedef struct
	{
		WORD             wLength;
		WORD             wValueLength;
		WORD             wType;
		WCHAR            szKey[16];
		WORD             Padding1;
		VS_FIXEDFILEINFO Value;
		WORD             Padding2;
		WORD             Children;
	} VS_VERSIONINFO;

	HMODULE hMod = GetModuleHandle(lptstrModuleName);
	if(!hMod)
	{
		return false;
	}

	HRSRC hResourceInfo = FindResourceW(hMod, (LPCWSTR)1, (LPCWSTR)0x10);
	if(!hResourceInfo)
	{
		return false;
	}

	VS_VERSIONINFO *VersionInfo = (VS_VERSIONINFO*)LoadResource(hMod, hResourceInfo);
	if(!VersionInfo)
	{
		return false;
	}

	FileVersion->dwVersion = VersionInfo->Value.dwFileVersionMS;
	FileVersion->Release = (WORD)(VersionInfo->Value.dwFileVersionLS >> 16);
	FileVersion->Build = (WORD)VersionInfo->Value.dwFileVersionLS;

	return true;
}

BOOL __stdcall GetFileVersion(LPCWSTR lptstrFilename, FILE_VERSION *FileVersion)
{
	typedef struct
	{
		WORD             wLength;
		WORD             wValueLength;
		WORD             wType;
		WCHAR            szKey[16];
		WORD             Padding1;
		VS_FIXEDFILEINFO Value;
		WORD             Padding2;
		WORD             Children;
	} VS_VERSIONINFO;

	HMODULE hFile = LoadLibraryExW(lptstrFilename, NULL, LOAD_LIBRARY_AS_DATAFILE);
	if(!hFile)
	{
		return false;
	}

	HRSRC hResourceInfo = FindResourceW(hFile, (LPCWSTR)1, (LPCWSTR)0x10);
	if(!hResourceInfo)
	{
		return false;
	}

	VS_VERSIONINFO *VersionInfo = (VS_VERSIONINFO*)LoadResource(hFile, hResourceInfo);
	if(!VersionInfo)
	{
		return false;
	}

	FileVersion->dwVersion = VersionInfo->Value.dwFileVersionMS;
	FileVersion->Release = (WORD)(VersionInfo->Value.dwFileVersionLS >> 16);
	FileVersion->Build = (WORD)VersionInfo->Value.dwFileVersionLS;

	return true;
}

bool OverrideSL(LPWSTR ValueName, DWORD *Value)
{
	INI_VAR_DWORD Variable = {0};

	if (IniFile->VariableExists(L"SLPolicy", ValueName))
	{
		if (!(IniFile->GetVariableInSection(L"SLPolicy", ValueName, &Variable))) *Value = 0;
		else *Value = Variable.ValueDec;
		return true;
	}
	return false;
}

HRESULT WINAPI New_SLGetWindowsInformationDWORD(PWSTR pwszValueName, DWORD *pdwValue)
{
	// wrapped SLGetWindowsInformationDWORD function
	// termsrv.dll will call this function instead of original SLC.dll

	// Override SL Policy

	extern FARJMP Old_SLGetWindowsInformationDWORD, Stub_SLGetWindowsInformationDWORD;
	extern SLGETWINDOWSINFORMATIONDWORD _SLGetWindowsInformationDWORD;

	char *Log;
	DWORD dw;
	SIZE_T bw;
	HRESULT Result;

	Log = new char[1024];
	wsprintfA(Log, "Policy query: %S\r\n", pwszValueName);
	WriteToLog(Log);
	delete[] Log;

	if (OverrideSL(pwszValueName, &dw))
	{
		*pdwValue = dw;

		Log = new char[1024];
		wsprintfA(Log, "Policy rewrite: %i\r\n", dw);
		WriteToLog(Log);
		delete[] Log;

		return S_OK;
	}

	WriteProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Old_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);
	Result = _SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
	if (Result == S_OK)
	{
		Log = new char[1024];
		wsprintfA(Log, "Policy result: %i\r\n", dw);
		WriteToLog(Log);
		delete[] Log;
	} else {
		WriteToLog("Policy request failed\r\n");
	}
	WriteProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Stub_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);

	return Result;
}

HRESULT __fastcall New_Win8SL(PWSTR pwszValueName, DWORD *pdwValue)
{
	// wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
	// for Windows 8 support

	// Override SL Policy

	extern SLGETWINDOWSINFORMATIONDWORD _SLGetWindowsInformationDWORD;

	char *Log;
	DWORD dw;
	HRESULT Result;

	Log = new char[1024];
	wsprintfA(Log, "Policy query: %S\r\n", pwszValueName);
	WriteToLog(Log);
	delete[] Log;

	if (OverrideSL(pwszValueName, &dw))
	{
		*pdwValue = dw;

		Log = new char[1024];
		wsprintfA(Log, "Policy rewrite: %i\r\n", dw);
		WriteToLog(Log);
		delete[] Log;

		return S_OK;
	}

	Result = _SLGetWindowsInformationDWORD(pwszValueName, pdwValue);
	if (Result == S_OK)
	{
		Log = new char[1024];
		wsprintfA(Log, "Policy result: %i\r\n", dw);
		WriteToLog(Log);
		delete[] Log;
	} else {
		WriteToLog("Policy request failed\r\n");
	}

	return Result;
}

#ifndef _WIN64
HRESULT __fastcall New_Win8SL_CP(DWORD arg1, DWORD *pdwValue, PWSTR pwszValueName, DWORD arg4)
{
	// wrapped unexported function SLGetWindowsInformationDWORDWrapper in termsrv.dll
	// for Windows 8 Consumer Preview support

	return New_Win8SL(pwszValueName, pdwValue);
}
#endif

HRESULT WINAPI New_CSLQuery_Initialize()
{
	extern PLATFORM_DWORD TermSrvBase;
	extern FILE_VERSION FV;

	char *Log;
	DWORD *bServerSku = NULL;
	DWORD *bRemoteConnAllowed = NULL;
	DWORD *bFUSEnabled = NULL;
	DWORD *bAppServerAllowed = NULL;
	DWORD *bMultimonAllowed = NULL;
	DWORD *lMaxUserSessions = NULL;
	DWORD *ulMaxDebugSessions = NULL;
	DWORD *bInitialized = NULL;

	WriteToLog(">>> CSLQuery::Initialize\r\n");

	char *Sect;
	Sect = new char[256];
	memset(Sect, 0x00, 256);
	wsprintfA(Sect, "%d.%d.%d.%d-SLInit", FV.wVersion.Major, FV.wVersion.Minor, FV.Release, FV.Build);

	if (IniFile->SectionExists(Sect))
	{
		#ifdef _WIN64
		bServerSku = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bServerSku.x64", 0));
		bRemoteConnAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bRemoteConnAllowed.x64", 0));
		bFUSEnabled = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bFUSEnabled.x64", 0));
		bAppServerAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bAppServerAllowed.x64", 0));
		bMultimonAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bMultimonAllowed.x64", 0));
		lMaxUserSessions = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "lMaxUserSessions.x64", 0));
		ulMaxDebugSessions = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "ulMaxDebugSessions.x64", 0));
		bInitialized = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bInitialized.x64", 0));
		#else
		bServerSku = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bServerSku.x86", 0));
		bRemoteConnAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bRemoteConnAllowed.x86", 0));
		bFUSEnabled = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bFUSEnabled.x86", 0));
		bAppServerAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bAppServerAllowed.x86", 0));
		bMultimonAllowed = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bMultimonAllowed.x86", 0));
		lMaxUserSessions = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "lMaxUserSessions.x86", 0));
		ulMaxDebugSessions = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "ulMaxDebugSessions.x86", 0));
		bInitialized = (DWORD*)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "bInitialized.x86", 0));
		#endif
	}
	delete[] Sect;

	if (bServerSku)
	{
		*bServerSku = INIReadDWordHex(IniFile, "SLInit", "bServerSku", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bServerSku = %d\r\n", bServerSku, *bServerSku);
		WriteToLog(Log);
		delete[] Log;
	}
	if (bRemoteConnAllowed)
	{
		*bRemoteConnAllowed = INIReadDWordHex(IniFile, "SLInit", "bRemoteConnAllowed", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bRemoteConnAllowed = %d\r\n", bRemoteConnAllowed, *bRemoteConnAllowed);
		WriteToLog(Log);
		delete[] Log;
	}
	if (bFUSEnabled)
	{
		*bFUSEnabled = INIReadDWordHex(IniFile, "SLInit", "bFUSEnabled", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bFUSEnabled = %d\r\n", bFUSEnabled, *bFUSEnabled);
		WriteToLog(Log);
		delete[] Log;
	}
	if (bAppServerAllowed)
	{
		*bAppServerAllowed = INIReadDWordHex(IniFile, "SLInit", "bAppServerAllowed", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bAppServerAllowed = %d\r\n", bAppServerAllowed, *bAppServerAllowed);
		WriteToLog(Log);
		delete[] Log;
	}
	if (bMultimonAllowed)
	{
		*bMultimonAllowed = INIReadDWordHex(IniFile, "SLInit", "bMultimonAllowed", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bMultimonAllowed = %d\r\n", bMultimonAllowed, *bMultimonAllowed);
		WriteToLog(Log);
		delete[] Log;
	}
	if (lMaxUserSessions)
	{
		*lMaxUserSessions = INIReadDWordHex(IniFile, "SLInit", "lMaxUserSessions", 0);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] lMaxUserSessions = %d\r\n", lMaxUserSessions, *lMaxUserSessions);
		WriteToLog(Log);
		delete[] Log;
	}
	if (ulMaxDebugSessions)
	{
		*ulMaxDebugSessions = INIReadDWordHex(IniFile, "SLInit", "ulMaxDebugSessions", 0);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] ulMaxDebugSessions = %d\r\n", ulMaxDebugSessions, *ulMaxDebugSessions);
		WriteToLog(Log);
		delete[] Log;
	}
	if (bInitialized)
	{
		*bInitialized = INIReadDWordHex(IniFile, "SLInit", "bInitialized", 1);

		Log = new char[1024];
		wsprintfA(Log, "SLInit [0x%p] bInitialized = %d\r\n", bInitialized, *bInitialized);
		WriteToLog(Log);
		delete[] Log;
	}
	WriteToLog("<<< CSLQuery::Initialize\r\n");
	return S_OK;
}

void Hook()
{
	extern FARJMP Old_SLGetWindowsInformationDWORD, Stub_SLGetWindowsInformationDWORD;
	extern SLGETWINDOWSINFORMATIONDWORD _SLGetWindowsInformationDWORD;
	extern HMODULE hTermSrv;
	extern HMODULE hSLC;
	extern PLATFORM_DWORD TermSrvBase;
	extern FILE_VERSION FV;
	extern wchar_t LogFile[256];

	AlreadyHooked = true;
	char *Log;

	wchar_t ConfigFile[256] = { 0x00 };
	WriteToLog("Loading configuration...\r\n");

	GetModuleFileName(GetCurrentModule(), ConfigFile, 255);
	for (DWORD i = wcslen(ConfigFile); i > 0; i--)
	{
		if (ConfigFile[i] == '\\')
		{
			memset(&ConfigFile[i + 1], 0x00, ((256 - (i + 1))) * 2);
			memcpy(&ConfigFile[i + 1], L"rdpwrap.ini", strlen("rdpwrap.ini") * 2);
			break;
		}
	}

	Log = new char[1024];
	wsprintfA(Log, "Configuration file: %S\r\n", ConfigFile);
	WriteToLog(Log);
	delete[] Log;

	IniFile = new INI_FILE(ConfigFile);
	// TODO: implement this
	if (IniFile == NULL)
	{
		WriteToLog("Error: Failed to load configuration\r\n");
		return;
	}

	INI_VAR_STRING LogFileVar;

	if(!(IniFile->GetVariableInSection("Main", "LogFile", &LogFileVar)))
	{
		GetModuleFileName(GetCurrentModule(), LogFile, 255);
		for(DWORD i = wcslen(LogFile); i > 0; i--)
		{
			if(LogFile[i] == '\\')
			{
				memset(&LogFile[i+1], 0x00, ((256-(i+1)))*2);
				memcpy(&LogFile[i+1], L"rdpwrap.txt", strlen("rdpwrap.txt")*2);
				break;
			}
		}
	}
	else
	{
		// TODO: Change it before add UNICODE in IniFile
		wchar_t wcLogFile[256];
		memset(wcLogFile, 0x00, 256);
		mbstowcs(wcLogFile, LogFileVar.Value, 255);
		wcscpy(LogFile, wcLogFile);
	}

	SIZE_T bw;
	WORD Ver = 0;
	PLATFORM_DWORD TermSrvSize, SignPtr;
	FARJMP Jump;

	WriteToLog("Initializing RDP Wrapper...\r\n");

	hTermSrv = LoadLibrary(L"termsrv.dll");
	if (hTermSrv == 0)
	{
		WriteToLog("Error: Failed to load Terminal Services library\r\n");
		return;
	}
	_ServiceMain = (SERVICEMAIN)GetProcAddress(hTermSrv, "ServiceMain");
	_SvchostPushServiceGlobals = (SVCHOSTPUSHSERVICEGLOBALS)GetProcAddress(hTermSrv, "SvchostPushServiceGlobals");

	Log = new char[4096];
	wsprintfA(Log,
		"Base addr:  0x%p\r\n"
		"SvcMain:    termsrv.dll+0x%p\r\n"
		"SvcGlobals: termsrv.dll+0x%p\r\n",
		hTermSrv,
		(PLATFORM_DWORD)_ServiceMain - (PLATFORM_DWORD)hTermSrv,
		(PLATFORM_DWORD)_SvchostPushServiceGlobals - (PLATFORM_DWORD)hTermSrv);
	WriteToLog(Log);
	delete[] Log;

	// check termsrv version
	if (GetModuleVersion(L"termsrv.dll", &FV))
	{
		Ver = (BYTE)FV.wVersion.Minor | ((BYTE)FV.wVersion.Major << 8);
	} else {
		// check NT version
		// Ver = GetVersion(); // deprecated
		// Ver = ((Ver & 0xFF) << 8) | ((Ver & 0xFF00) >> 8);
	}
	if (Ver == 0)
	{
		WriteToLog("Error: Failed to detect Terminal Services version\r\n");
		return;
	}

	Log = new char[1024];
	wsprintfA(Log, "Version:    %d.%d.%d.%d\r\n", FV.wVersion.Major, FV.wVersion.Minor, FV.Release, FV.Build);
	WriteToLog(Log);
	delete[] Log;

	// temporarily freeze threads
	WriteToLog("Freezing threads...\r\n");
	SetThreadsState(false);

	bool Bool;
	if (!(IniFile->GetVariableInSection("Main", "SLPolicyHookNT60", &Bool))) Bool = true;

	if ((Ver == 0x0600) && Bool)
	{
		// Windows Vista
		// uses SL Policy API (slc.dll)

		// load slc.dll and hook function
		hSLC = LoadLibrary(L"slc.dll");
		_SLGetWindowsInformationDWORD = (SLGETWINDOWSINFORMATIONDWORD)GetProcAddress(hSLC, "SLGetWindowsInformationDWORD");
		if (_SLGetWindowsInformationDWORD != INVALID_HANDLE_VALUE)
		{
			// rewrite original function to call our function (make hook)

			WriteToLog("Hook SLGetWindowsInformationDWORD\r\n");
			#ifdef _WIN64
			Stub_SLGetWindowsInformationDWORD.MovOp = 0x48;
			Stub_SLGetWindowsInformationDWORD.MovRegArg = 0xB8;
			Stub_SLGetWindowsInformationDWORD.MovArg = (PLATFORM_DWORD)New_SLGetWindowsInformationDWORD;
			Stub_SLGetWindowsInformationDWORD.PushRaxOp = 0x50;
			Stub_SLGetWindowsInformationDWORD.RetOp = 0xC3;
			#else
			Stub_SLGetWindowsInformationDWORD.PushOp = 0x68;
			Stub_SLGetWindowsInformationDWORD.PushArg = (PLATFORM_DWORD)New_SLGetWindowsInformationDWORD;
			Stub_SLGetWindowsInformationDWORD.RetOp = 0xC3;
			#endif

			ReadProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Old_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);
			WriteProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Stub_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);
		}
	}

	if (!(IniFile->GetVariableInSection("Main", "SLPolicyHookNT61", &Bool))) Bool = true;

	if ((Ver == 0x0601) && Bool)
	{
		// Windows 7
		// uses SL Policy API (slc.dll)

		// load slc.dll and hook function
		hSLC = LoadLibrary(L"slc.dll");
		_SLGetWindowsInformationDWORD = (SLGETWINDOWSINFORMATIONDWORD)GetProcAddress(hSLC, "SLGetWindowsInformationDWORD");
		if (_SLGetWindowsInformationDWORD != INVALID_HANDLE_VALUE)
		{
			// rewrite original function to call our function (make hook)

			WriteToLog("Hook SLGetWindowsInformationDWORD\r\n");
			#ifdef _WIN64
			Stub_SLGetWindowsInformationDWORD.MovOp = 0x48;
			Stub_SLGetWindowsInformationDWORD.MovRegArg = 0xB8;
			Stub_SLGetWindowsInformationDWORD.MovArg = (PLATFORM_DWORD)New_SLGetWindowsInformationDWORD;
			Stub_SLGetWindowsInformationDWORD.PushRaxOp = 0x50;
			Stub_SLGetWindowsInformationDWORD.RetOp = 0xC3;
			#else
			Stub_SLGetWindowsInformationDWORD.PushOp = 0x68;
			Stub_SLGetWindowsInformationDWORD.PushArg = (PLATFORM_DWORD)New_SLGetWindowsInformationDWORD;
			Stub_SLGetWindowsInformationDWORD.RetOp = 0xC3;
			#endif

			ReadProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Old_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);
			WriteProcessMemory(GetCurrentProcess(), _SLGetWindowsInformationDWORD, &Stub_SLGetWindowsInformationDWORD, sizeof(FARJMP), &bw);
		}
	}
	if (Ver == 0x0602)
	{
		// Windows 8
		// uses SL Policy internal unexported function

		// load slc.dll and get function
		// (will be used on intercepting undefined values)
		hSLC = LoadLibrary(L"slc.dll");
		_SLGetWindowsInformationDWORD = (SLGETWINDOWSINFORMATIONDWORD)GetProcAddress(hSLC, "SLGetWindowsInformationDWORD");
	}
	if (Ver == 0x0603)
	{
		// Windows 8.1
		// uses SL Policy internal inline code
	}
	if (Ver == 0x0604)
	{
		// Windows 10
		// uses SL Policy internal inline code
	}

	char *Sect;
	INI_VAR_STRING PatchName;
	INI_VAR_BYTEARRAY Patch;
	Sect = new char[256];
	memset(Sect, 0x00, 256);
	wsprintfA(Sect, "%d.%d.%d.%d", FV.wVersion.Major, FV.wVersion.Minor, FV.Release, FV.Build);

	if (IniFile->SectionExists(Sect))
	{
		if (GetModuleCodeSectionInfo(hTermSrv, &TermSrvBase, &TermSrvSize))
		{
			#ifdef _WIN64
			if (!(IniFile->GetVariableInSection(Sect, "LocalOnlyPatch.x64", &Bool))) Bool = false;
			#else
			if (!(IniFile->GetVariableInSection(Sect, "LocalOnlyPatch.x86", &Bool))) Bool = false;
			#endif
			if (Bool)
			{
				WriteToLog("Patch CEnforcementCore::GetInstanceOfTSLicense\r\n");
				Bool = false;
				#ifdef _WIN64
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "LocalOnlyOffset.x64", 0));
				Bool = IniFile->GetVariableInSection(Sect, "LocalOnlyCode.x64", &PatchName);
				#else
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "LocalOnlyOffset.x86", 0));
				Bool = IniFile->GetVariableInSection(Sect, "LocalOnlyCode.x86", &PatchName);
				#endif
				if (Bool) Bool = IniFile->GetVariableInSection("PatchCodes", PatchName.Value, &Patch);
				if (Bool && (SignPtr > TermSrvBase)) WriteProcessMemory(GetCurrentProcess(), (LPVOID)SignPtr, Patch.Value, Patch.ArraySize, &bw);
			}
			#ifdef _WIN64
			if (!(IniFile->GetVariableInSection(Sect, "SingleUserPatch.x64", &Bool))) Bool = false;
			#else
			if (!(IniFile->GetVariableInSection(Sect, "SingleUserPatch.x86", &Bool))) Bool = false;
			#endif
			if (Bool)
			{
				WriteToLog("Patch CSessionArbitrationHelper::IsSingleSessionPerUserEnabled\r\n");
				Bool = false;
				#ifdef _WIN64
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SingleUserOffset.x64", 0));
				Bool = IniFile->GetVariableInSection(Sect, "SingleUserCode.x64", &PatchName);
				#else
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SingleUserOffset.x86", 0));
				Bool = IniFile->GetVariableInSection(Sect, "SingleUserCode.x86", &PatchName);
				#endif
				if (Bool) Bool = IniFile->GetVariableInSection("PatchCodes", PatchName.Value, &Patch);
				if (Bool && (SignPtr > TermSrvBase)) WriteProcessMemory(GetCurrentProcess(), (LPVOID)SignPtr, Patch.Value, Patch.ArraySize, &bw);
			}
			#ifdef _WIN64
			if (!(IniFile->GetVariableInSection(Sect, "DefPolicyPatch.x64", &Bool))) Bool = false;
			#else
			if (!(IniFile->GetVariableInSection(Sect, "DefPolicyPatch.x86", &Bool))) Bool = false;
			#endif
			if (Bool)
			{
				WriteToLog("Patch CDefPolicy::Query\r\n");
				Bool = false;
				#ifdef _WIN64
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "DefPolicyOffset.x64", 0));
				Bool = IniFile->GetVariableInSection(Sect, "DefPolicyCode.x64", &PatchName);
				#else
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "DefPolicyOffset.x86", 0));
				Bool = IniFile->GetVariableInSection(Sect, "DefPolicyCode.x86", &PatchName);
				#endif
				if (Bool) Bool = IniFile->GetVariableInSection("PatchCodes", PatchName.Value, &Patch);
				if (Bool && (SignPtr > TermSrvBase)) WriteProcessMemory(GetCurrentProcess(), (LPVOID)SignPtr, Patch.Value, Patch.ArraySize, &bw);
			}
			#ifdef _WIN64
			if (!(IniFile->GetVariableInSection(Sect, "SLPolicyInternal.x64", &Bool))) Bool = false;
			#else
			if (!(IniFile->GetVariableInSection(Sect, "SLPolicyInternal.x86", &Bool))) Bool = false;
			#endif
			if (Bool)
			{
				WriteToLog("Hook SLGetWindowsInformationDWORDWrapper\r\n");
				char *FuncName;
				FuncName = new char[1024];
				#ifdef _WIN64
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SLPolicyOffset.x64", 0));
				Jump.MovOp = 0x48;
				Jump.MovRegArg = 0xB8;
				Jump.MovArg = (PLATFORM_DWORD)New_Win8SL;
				Jump.PushRaxOp = 0x50;
				Jump.RetOp = 0xC3;

				INIReadString(IniFile, Sect, "SLPolicyFunc.x64", "New_Win8SL", FuncName, 1024);

				if (strcmp(FuncName, "New_Win8SL"))
				{
					Jump.MovArg = (PLATFORM_DWORD)New_Win8SL;
				}
				#else
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SLPolicyOffset.x86", 0));
				Jump.PushOp = 0x68;
				Jump.PushArg = (PLATFORM_DWORD)New_Win8SL;
				Jump.RetOp = 0xC3;

				INIReadString(IniFile, Sect, "SLPolicyFunc.x86", "New_Win8SL", FuncName, 1024);

				if (strcmp(FuncName, "New_Win8SL"))
				{
					Jump.PushArg = (PLATFORM_DWORD)New_Win8SL;
				}
				if (strcmp(FuncName, "New_Win8SL_CP"))
				{
					Jump.PushArg = (PLATFORM_DWORD)New_Win8SL_CP;
				}
				#endif
				delete[] FuncName;
				if (SignPtr > TermSrvBase) WriteProcessMemory(GetCurrentProcess(), (LPVOID)SignPtr, &Jump, sizeof(FARJMP), &bw);
			}
			#ifdef _WIN64
			if (!(IniFile->GetVariableInSection(Sect, "SLInitHook.x64", &Bool))) Bool = false;
			#else
			if (!(IniFile->GetVariableInSection(Sect, "SLInitHook.x86", &Bool))) Bool = false;
			#endif
			if (Bool)
			{
				WriteToLog("Hook CSLQuery::Initialize\r\n");
				char *FuncName;
				FuncName = new char[1024];
				#ifdef _WIN64
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SLInitOffset.x64", 0));
				Jump.MovOp = 0x48;
				Jump.MovRegArg = 0xB8;
				Jump.MovArg = (PLATFORM_DWORD)New_CSLQuery_Initialize;
				Jump.PushRaxOp = 0x50;
				Jump.RetOp = 0xC3;

				INIReadString(IniFile, Sect, "SLInitFunc.x64", "New_CSLQuery_Initialize", FuncName, 1024);

				if (strcmp(FuncName, "New_CSLQuery_Initialize"))
				{
					Jump.MovArg = (PLATFORM_DWORD)New_CSLQuery_Initialize;
				}
				#else
				SignPtr = (PLATFORM_DWORD)(TermSrvBase + INIReadDWordHex(IniFile, Sect, "SLInitOffset.x86", 0));
				Jump.PushOp = 0x68;
				Jump.PushArg = (PLATFORM_DWORD)New_CSLQuery_Initialize;
				Jump.RetOp = 0xC3;

				INIReadString(IniFile, Sect, "SLInitFunc.x86", "New_CSLQuery_Initialize", FuncName, 1024);

				if (strcmp(FuncName, "New_CSLQuery_Initialize"))
				{
					Jump.PushArg = (PLATFORM_DWORD)New_CSLQuery_Initialize;
				}
				#endif
				delete[] FuncName;
				if (SignPtr > TermSrvBase) WriteProcessMemory(GetCurrentProcess(), (LPVOID)SignPtr, &Jump, sizeof(FARJMP), &bw);
			}
		}
	}
	delete[] Sect;

	WriteToLog("Resumimg threads...\r\n");
	SetThreadsState(true);
	return;
}

void WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
	WriteToLog(">>> ServiceMain\r\n");
	if (!AlreadyHooked) Hook();

	if (_ServiceMain != NULL) _ServiceMain(dwArgc, lpszArgv);
	WriteToLog("<<< ServiceMain\r\n");
}

void WINAPI SvchostPushServiceGlobals(void *lpGlobalData)
{
	WriteToLog(">>> SvchostPushServiceGlobals\r\n");
	if (!AlreadyHooked) Hook();

	if (_SvchostPushServiceGlobals != NULL) _SvchostPushServiceGlobals(lpGlobalData);
	WriteToLog("<<< SvchostPushServiceGlobals\r\n");
}