/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2011 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "commonheaders.h"

#define MMI_SIZE_V1 (4*sizeof(void*))
#define MMI_SIZE_V2 (7*sizeof(void*))

int InitPathUtils(void);
int InitialiseModularEngine(void);
int LoadDefaultModules(void);
void DestroyModularEngine(void);
void UnloadNewPluginsModule(void);
void UnloadDefaultModules(void);
void RecalculateTime(void);

HINSTANCE GetInstByAddress( void* codePtr );

pfnMyMonitorFromPoint MyMonitorFromPoint;
pfnMyMonitorFromRect MyMonitorFromRect;
pfnMyMonitorFromWindow MyMonitorFromWindow;
pfnMyGetMonitorInfo MyGetMonitorInfo;

typedef DWORD (WINAPI *pfnMsgWaitForMultipleObjectsEx)(DWORD,CONST HANDLE*,DWORD,DWORD,DWORD);
pfnMsgWaitForMultipleObjectsEx msgWaitForMultipleObjectsEx;

pfnSHAutoComplete shAutoComplete;
pfnSHGetSpecialFolderPathA shGetSpecialFolderPathA;
pfnSHGetSpecialFolderPathW shGetSpecialFolderPathW;

pfnOpenInputDesktop openInputDesktop;
pfnCloseDesktop closeDesktop;

pfnAnimateWindow animateWindow;
pfnSetLayeredWindowAttributes setLayeredWindowAttributes;

pfnOpenThemeData openThemeData;
pfnIsThemeBackgroundPartiallyTransparent isThemeBackgroundPartiallyTransparent;
pfnDrawThemeParentBackground drawThemeParentBackground;
pfnDrawThemeBackground drawThemeBackground;
pfnDrawThemeText drawThemeText;
pfnDrawThemeTextEx drawThemeTextEx;
pfnGetThemeBackgroundContentRect getThemeBackgroundContentRect;
pfnGetThemeFont getThemeFont;
pfnCloseThemeData closeThemeData;
pfnEnableThemeDialogTexture enableThemeDialogTexture;
pfnSetWindowTheme setWindowTheme;
pfnSetWindowThemeAttribute setWindowThemeAttribute;
pfnIsThemeActive isThemeActive;
pfnBufferedPaintInit bufferedPaintInit;
pfnBufferedPaintUninit bufferedPaintUninit;
pfnBeginBufferedPaint beginBufferedPaint;
pfnEndBufferedPaint endBufferedPaint;
pfnGetBufferedPaintBits getBufferedPaintBits;

pfnDwmExtendFrameIntoClientArea dwmExtendFrameIntoClientArea;
pfnDwmIsCompositionEnabled dwmIsCompositionEnabled;

ITaskbarList3 * pTaskbarInterface;

static DWORD MsgWaitForMultipleObjectsExWorkaround(DWORD nCount, const HANDLE *pHandles,
	DWORD dwMsecs, DWORD dwWakeMask, DWORD dwFlags);

static HANDLE hOkToExitEvent,hModulesLoadedEvent;
static HANDLE hShutdownEvent,hPreShutdownEvent;
static HANDLE hWaitObjects[MAXIMUM_WAIT_OBJECTS-1];
static char *pszWaitServices[MAXIMUM_WAIT_OBJECTS-1];
static int waitObjectCount=0;
HANDLE hStackMutex,hMirandaShutdown,hThreadQueueEmpty;
HINSTANCE hMirandaInst;

/////////////////////////////////////////////////////////////////////////////////////////
// exception handling

static DWORD __cdecl sttDefaultFilter( DWORD, EXCEPTION_POINTERS* )
{
	return EXCEPTION_EXECUTE_HANDLER;
}

pfnExceptionFilter pMirandaExceptFilter = sttDefaultFilter;

static INT_PTR GetExceptionFilter( WPARAM, LPARAM )
{
	return ( INT_PTR )pMirandaExceptFilter;
}

static INT_PTR SetExceptionFilter( WPARAM, LPARAM lParam )
{
	pfnExceptionFilter oldOne = pMirandaExceptFilter;
	if ( lParam != 0 )
		pMirandaExceptFilter = ( pfnExceptionFilter )lParam;
	return ( INT_PTR )oldOne;
}

/////////////////////////////////////////////////////////////////////////////////////////
// thread support functions

typedef struct
{
	DWORD dwThreadId;	// valid if hThread isn't signalled
	HANDLE hThread;
	HINSTANCE hOwner;
	void* pObject;
	PVOID addr;
}
THREAD_WAIT_ENTRY;

static LIST<THREAD_WAIT_ENTRY> threads( 10, NumericKeySortT );

struct FORK_ARG {
	HANDLE hEvent;
	pThreadFunc threadcode;
	pThreadFuncEx threadcodeex;
	void *arg, *owner;
};

/////////////////////////////////////////////////////////////////////////////////////////
// forkthread - starts a new thread

void __cdecl forkthread_r(void * arg)
{
	struct FORK_ARG * fa = (struct FORK_ARG *) arg;
	void (*callercode)(void*)=fa->threadcode;
	void * cookie=fa->arg;
	CallService(MS_SYSTEM_THREAD_PUSH,0,(LPARAM)callercode);
	SetEvent(fa->hEvent);
	__try
	{
		callercode(cookie);
	}
	__except( pMirandaExceptFilter( GetExceptionCode(), GetExceptionInformation()))
	{
		Netlib_Logf( NULL, "Unhandled exception in thread %x", GetCurrentThreadId());
	}

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
	CallService(MS_SYSTEM_THREAD_POP,0,0);
	return;
}

UINT_PTR forkthread (
	void (__cdecl *threadcode)(void*),
	unsigned long stacksize,
	void *arg
	)
{
	UINT_PTR rc;
	struct FORK_ARG fa;
	fa.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
	fa.threadcode=threadcode;
	fa.arg=arg;
	rc=_beginthread(forkthread_r,stacksize,&fa);
	if ((UINT_PTR)-1L != rc)
		WaitForSingleObject(fa.hEvent,INFINITE);

	CloseHandle(fa.hEvent);
	return rc;
}

static INT_PTR ForkThreadService(WPARAM wParam, LPARAM lParam)
{
	return (INT_PTR)forkthread(( pThreadFunc )wParam, 0, ( void* )lParam );
}

/////////////////////////////////////////////////////////////////////////////////////////
// forkthreadex - starts a new thread with the extended info and returns the thread id

unsigned __stdcall forkthreadex_r(void * arg)
{
	struct FORK_ARG *fa = (struct FORK_ARG *)arg;
	pThreadFuncEx threadcode = fa->threadcodeex;
	pThreadFuncOwner threadcodeex = ( pThreadFuncOwner )fa->threadcodeex;
	void *cookie = fa->arg;
	void *owner = fa->owner;
	unsigned long rc = 0;

	CallService(MS_SYSTEM_THREAD_PUSH, (WPARAM)fa->owner, (LPARAM)threadcode);
	SetEvent(fa->hEvent);
	__try
	{
		if ( owner )
			rc = threadcodeex( owner, cookie );
		else
			rc = threadcode( cookie );
	}
	__except( pMirandaExceptFilter( GetExceptionCode(), GetExceptionInformation()))
	{
		Netlib_Logf( NULL, "Unhandled exception in thread %x", GetCurrentThreadId());
	}

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
	CallService(MS_SYSTEM_THREAD_POP,0,0);
	return rc;
}

UINT_PTR forkthreadex(
	void *sec,
	unsigned stacksize,
	unsigned (__stdcall *threadcode)(void*),
	void* owner,
	void *arg,
	unsigned *thraddr )
{
	UINT_PTR rc;
	struct FORK_ARG fa = { 0 };
	fa.threadcodeex = threadcode;
	fa.arg = arg;
	fa.owner = owner;
	fa.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	rc = _beginthreadex(sec,stacksize,forkthreadex_r,(void *)&fa,0,thraddr);
	if (rc)
		WaitForSingleObject(fa.hEvent,INFINITE);

	CloseHandle(fa.hEvent);
	return rc;
}

static INT_PTR ForkThreadServiceEx(WPARAM wParam, LPARAM lParam)
{
	FORK_THREADEX_PARAMS* params = (FORK_THREADEX_PARAMS*)lParam;
	if ( params == NULL )
		return 0;

	UINT threadID;
	return forkthreadex( NULL, params->iStackSize, params->pFunc, ( void* )wParam, params->arg, params->threadID ? params->threadID : &threadID );
}

/////////////////////////////////////////////////////////////////////////////////////////
// APC and mutex functions

static void __stdcall DummyAPCFunc(ULONG_PTR)
{
	/* called in the context of thread that cleared it's APC queue */
	return;
}

static int MirandaWaitForMutex(HANDLE hEvent)
{
	for (;;) {
		// will get WAIT_IO_COMPLETE for QueueUserAPC() which isnt a result
		DWORD rc = MsgWaitForMultipleObjectsExWorkaround(1, &hEvent, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE);
		if ( rc == WAIT_OBJECT_0 + 1 ) {
			MSG msg;
			while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
				if ( IsDialogMessage(msg.hwnd, &msg) ) continue;
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		} else if ( rc==WAIT_OBJECT_0 ) {
			// got object
			return 1;
		} else if ( rc==WAIT_ABANDONED_0 || rc == WAIT_FAILED ) return 0;
	}
}

VOID CALLBACK KillAllThreads(HWND, UINT, UINT_PTR, DWORD)
{
	if ( MirandaWaitForMutex( hStackMutex )) {
		for ( int j=0; j < threads.getCount(); j++ ) {
			THREAD_WAIT_ENTRY* p = threads[j];
			char szModuleName[ MAX_PATH ];
			GetModuleFileNameA( p->hOwner, szModuleName, sizeof(szModuleName));
			Netlib_Logf( NULL, "Thread %p was abnormally terminated because module '%s' didn't release it. Entry point: %p",
				p->hThread, szModuleName, p->addr );
			TerminateThread( p->hThread, 9999 );
			CloseHandle(p->hThread);
			mir_free( p );
		}

		threads.destroy();

		ReleaseMutex( hStackMutex );
		SetEvent( hThreadQueueEmpty );
	}	
}

void KillObjectThreads( void* owner )
{
	if ( owner == NULL )
		return;

	WaitForSingleObject( hStackMutex, INFINITE );

	HANDLE* threadPool = ( HANDLE* )alloca( threads.getCount()*sizeof( HANDLE ));
	int threadCount = 0;

	for ( int j = threads.getCount(); j--; ) {
		THREAD_WAIT_ENTRY* p = threads[j];
		if ( p->pObject == owner )
			threadPool[ threadCount++ ] = p->hThread;
	}
	ReleaseMutex(hStackMutex); 

	// is there anything to kill?
	if ( threadCount > 0 ) {
		if ( WaitForMultipleObjects( threadCount, threadPool, TRUE, 5000 ) == WAIT_TIMEOUT ) {
			// forcibly kill all remaining threads after 5 secs
			WaitForSingleObject( hStackMutex, INFINITE );
			for ( int j = threads.getCount()-1; j >= 0; j-- ) {
				THREAD_WAIT_ENTRY* p = threads[j];
				if ( p->pObject == owner ) {
					TerminateThread( p->hThread, 9999 );
					CloseHandle( p->hThread );
					threads.remove( j );
					mir_free( p );
				}
			}
			ReleaseMutex(hStackMutex);
		}
	}
}

static void UnwindThreadWait(void)
{
	// acquire the list and wake up any alertable threads
	if ( MirandaWaitForMutex(hStackMutex) ) {
		int j;
		for ( j=0; j < threads.getCount(); j++ )
			QueueUserAPC(DummyAPCFunc,threads[j]->hThread, 0);
		ReleaseMutex(hStackMutex);
	}

	// give all unclosed threads 5 seconds to close
	SetTimer( NULL, 0, 5000, KillAllThreads );

	// wait til the thread list is empty
	MirandaWaitForMutex(hThreadQueueEmpty);
}

/////////////////////////////////////////////////////////////////////////////////////////

typedef LONG (WINAPI *pNtQIT)(HANDLE, LONG, PVOID, ULONG, PULONG);
#define ThreadQuerySetWin32StartAddress 9

void* GetCurrentThreadEntryPoint()
{
	LONG  ntStatus;
	HANDLE hDupHandle, hCurrentProcess;
	DWORD_PTR dwStartAddress;

	pNtQIT NtQueryInformationThread = (pNtQIT)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtQueryInformationThread" );
	if(NtQueryInformationThread == NULL) return 0;

	hCurrentProcess = GetCurrentProcess();
	if(!DuplicateHandle(hCurrentProcess, GetCurrentThread(), hCurrentProcess, &hDupHandle, THREAD_QUERY_INFORMATION, FALSE, 0)){
		SetLastError(ERROR_ACCESS_DENIED);
		return NULL;
	}
	ntStatus = NtQueryInformationThread(hDupHandle, ThreadQuerySetWin32StartAddress, &dwStartAddress, sizeof(DWORD_PTR), NULL);
	CloseHandle(hDupHandle);

	if(ntStatus != ERROR_SUCCESS) return 0;
	return ( void* )dwStartAddress;
}

INT_PTR UnwindThreadPush(WPARAM wParam,LPARAM lParam)
{
	ResetEvent(hThreadQueueEmpty); // thread list is not empty
	if (WaitForSingleObject(hStackMutex, INFINITE) == WAIT_OBJECT_0)
	{
		THREAD_WAIT_ENTRY* p = (THREAD_WAIT_ENTRY*)mir_calloc(sizeof(THREAD_WAIT_ENTRY));

		DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &p->hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
		p->dwThreadId = GetCurrentThreadId();
		p->pObject = (void*)wParam;
		p->hOwner = GetInstByAddress((void*)lParam);
		p->addr = (void*)lParam;
		threads.insert(p);

		//Netlib_Logf( NULL, "*** pushing thread %x[%x] (%d)", hThread, GetCurrentThreadId(), threads.count );
		ReleaseMutex(hStackMutex);
	} //if
	return 0;
}

INT_PTR UnwindThreadPop(WPARAM,LPARAM)
{
	if (WaitForSingleObject(hStackMutex,INFINITE)==WAIT_OBJECT_0)
	{
		DWORD dwThreadId=GetCurrentThreadId();
		int j;
		//Netlib_Logf( NULL, "*** popping thread %x, %d threads left", dwThreadId, threads.count);
		for ( j=0; j < threads.getCount(); j++ ) {
			THREAD_WAIT_ENTRY* p = threads[j];
			if ( p->dwThreadId == dwThreadId ) {
				SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );
				CloseHandle( p->hThread );
				threads.remove( j );
				mir_free( p );

				if ( !threads.getCount()) {
					threads.destroy();
					ReleaseMutex( hStackMutex );
					SetEvent(hThreadQueueEmpty); // thread list is empty now
					return 0;
				} 

				ReleaseMutex(hStackMutex);
				return 0;
			} //if
		} //for
		ReleaseMutex(hStackMutex);
	} //if
	return 1;
}

INT_PTR MirandaIsTerminated(WPARAM, LPARAM)
{
	return WaitForSingleObject(hMirandaShutdown,0)==WAIT_OBJECT_0;
}

static void __cdecl compactHeapsThread(void*)
{
	while (!Miranda_Terminated())
	{
		HANDLE hHeaps[256];
		DWORD hc;
		SleepEx((1000*60)*5,TRUE); // every 5 minutes
		hc=GetProcessHeaps(255,(PHANDLE)&hHeaps);
		if (hc != 0 && hc < 256) {
			DWORD j;
			for (j=0;j<hc;j++) HeapCompact(hHeaps[j],0);
		}
	} //while
}

LRESULT CALLBACK APCWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (msg==WM_NULL) SleepEx(0,TRUE);
	if (msg == WM_TIMECHANGE) RecalculateTime();
	return DefWindowProc(hwnd,msg,wParam,lParam);
}

HWND hAPCWindow=NULL;
void (*SetIdleCallback) (void)=NULL;

static INT_PTR SystemSetIdleCallback(WPARAM, LPARAM lParam)
{
	if (lParam && SetIdleCallback==NULL) {
		SetIdleCallback=(void (*)(void))lParam;
		return 1;
	}
	return 0;
}

static DWORD dwEventTime=0;
void checkIdle(MSG * msg)
{
	switch(msg->message) {
	case WM_MOUSEACTIVATE:
	case WM_MOUSEMOVE:
	case WM_CHAR:
		dwEventTime = GetTickCount();
	}
}

static INT_PTR SystemGetIdle(WPARAM, LPARAM lParam)
{
	if ( lParam ) *(DWORD*)lParam = dwEventTime;
	return 0;
}

static DWORD MsgWaitForMultipleObjectsExWorkaround(DWORD nCount, const HANDLE *pHandles,
	DWORD dwMsecs, DWORD dwWakeMask, DWORD dwFlags)
{
	DWORD rc;
	if ( msgWaitForMultipleObjectsEx != NULL )
		return msgWaitForMultipleObjectsEx(nCount, pHandles, dwMsecs, dwWakeMask, dwFlags);
	rc=MsgWaitForMultipleObjects(nCount, pHandles, FALSE, 50, QS_ALLINPUT);
	if ( rc == WAIT_TIMEOUT ) rc=WaitForMultipleObjectsEx(nCount, pHandles, FALSE, 20, TRUE);
	return rc;
}

static int SystemShutdownProc(WPARAM, LPARAM)
{
	UnloadDefaultModules();
	return 0;
}

#define MIRANDA_PROCESS_WAIT_TIMEOUT        60000
#define MIRANDA_PROCESS_WAIT_RESOLUTION     1000
#define MIRANDA_PROCESS_WAIT_STEPS          (MIRANDA_PROCESS_WAIT_TIMEOUT/MIRANDA_PROCESS_WAIT_RESOLUTION)
static INT_PTR CALLBACK WaitForProcessDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_INITDIALOG:
		TranslateDialogDefault( hwnd );
		SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
		SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETRANGE, 0, MAKELPARAM(0, MIRANDA_PROCESS_WAIT_STEPS));
		SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETSTEP, 1, 0);
		SetTimer(hwnd, 1, MIRANDA_PROCESS_WAIT_RESOLUTION, NULL);
		break;

	case WM_TIMER:
		if (SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_STEPIT, 0, 0) == MIRANDA_PROCESS_WAIT_STEPS)
			EndDialog(hwnd, 0);
		if (WaitForSingleObject((HANDLE)GetWindowLongPtr(hwnd, GWLP_USERDATA), 1) != WAIT_TIMEOUT)
		{
			SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETPOS, MIRANDA_PROCESS_WAIT_STEPS, 0);
			EndDialog(hwnd, 0);
		}
		break;

	case WM_COMMAND:
		if (HIWORD(wParam) == IDCANCEL)
		{
			SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETPOS, MIRANDA_PROCESS_WAIT_STEPS, 0);
			EndDialog(hwnd, 0);
		}
		break;
	}
	return FALSE;
}

static void ParseCommandLine()
{
	char* cmdline = GetCommandLineA();
	char* p = strstr( cmdline, "/restart:" );
	if ( p ) {
		HANDLE hProcess = OpenProcess( SYNCHRONIZE, FALSE, atol( p+9 ));
		if ( hProcess ) {
			DialogBoxParam(hMirandaInst, MAKEINTRESOURCE(IDD_WAITRESTART), NULL, WaitForProcessDlgProc, (LPARAM)hProcess);
			CloseHandle( hProcess );
		}	
	}	
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int )
{
	DWORD myPid=0;
	int messageloop=1;
	HMODULE hUser32, hThemeAPI, hDwmApi, hShFolder = NULL;
	int result = 0;

	hMirandaInst = hInstance;

	setlocale(LC_ALL, "");

#ifdef _DEBUG
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	hUser32 = GetModuleHandleA("user32");
	openInputDesktop = (pfnOpenInputDesktop)GetProcAddress (hUser32, "OpenInputDesktop");
	closeDesktop = (pfnCloseDesktop)GetProcAddress (hUser32, "CloseDesktop");
	msgWaitForMultipleObjectsEx = (pfnMsgWaitForMultipleObjectsEx)GetProcAddress(hUser32, "MsgWaitForMultipleObjectsEx");
	animateWindow =(pfnAnimateWindow)GetProcAddress(hUser32, "AnimateWindow");
	setLayeredWindowAttributes =(pfnSetLayeredWindowAttributes)GetProcAddress(hUser32, "SetLayeredWindowAttributes");

	MyMonitorFromPoint = (pfnMyMonitorFromPoint)GetProcAddress(hUser32, "MonitorFromPoint");
	MyMonitorFromRect = (pfnMyMonitorFromRect)GetProcAddress(hUser32, "MonitorFromRect");
	MyMonitorFromWindow = (pfnMyMonitorFromWindow)GetProcAddress(hUser32, "MonitorFromWindow");
#ifdef _UNICODE
	MyGetMonitorInfo = (pfnMyGetMonitorInfo)GetProcAddress(hUser32, "GetMonitorInfoW");
#else
	MyGetMonitorInfo = (pfnMyGetMonitorInfo)GetProcAddress(hUser32, "GetMonitorInfoA");
#endif

	hShFolder = GetModuleHandleA("shell32");
	shGetSpecialFolderPathA = (pfnSHGetSpecialFolderPathA)GetProcAddress(hShFolder,"SHGetSpecialFolderPathA");
	shGetSpecialFolderPathW = (pfnSHGetSpecialFolderPathW)GetProcAddress(hShFolder,"SHGetSpecialFolderPathW");
	if (shGetSpecialFolderPathA == NULL)
	{
		hShFolder = LoadLibraryA("ShFolder.dll");
		shGetSpecialFolderPathA = (pfnSHGetSpecialFolderPathA)GetProcAddress(hShFolder,"SHGetSpecialFolderPathA");
		shGetSpecialFolderPathW = (pfnSHGetSpecialFolderPathW)GetProcAddress(hShFolder,"SHGetSpecialFolderPathW");
	}

	shAutoComplete = (pfnSHAutoComplete)GetProcAddress(GetModuleHandleA("shlwapi"),"SHAutoComplete");

	if (IsWinVerXPPlus())
	{
		hThemeAPI = LoadLibraryA("uxtheme.dll");
		if (hThemeAPI)
		{
			openThemeData = (pfnOpenThemeData)GetProcAddress(hThemeAPI, "OpenThemeData");
			isThemeBackgroundPartiallyTransparent = (pfnIsThemeBackgroundPartiallyTransparent)GetProcAddress(hThemeAPI, "IsThemeBackgroundPartiallyTransparent");
			drawThemeParentBackground  = (pfnDrawThemeParentBackground)GetProcAddress(hThemeAPI, "DrawThemeParentBackground");
			drawThemeBackground = (pfnDrawThemeBackground)GetProcAddress(hThemeAPI, "DrawThemeBackground");
			drawThemeText = (pfnDrawThemeText)GetProcAddress(hThemeAPI, "DrawThemeText");
			drawThemeTextEx = (pfnDrawThemeTextEx)GetProcAddress(hThemeAPI, "DrawThemeTextEx");
			getThemeBackgroundContentRect = (pfnGetThemeBackgroundContentRect)GetProcAddress(hThemeAPI ,"GetThemeBackgroundContentRect");
			getThemeFont = (pfnGetThemeFont)GetProcAddress(hThemeAPI, "GetThemeFont");
			closeThemeData  = (pfnCloseThemeData)GetProcAddress(hThemeAPI, "CloseThemeData");
			enableThemeDialogTexture = (pfnEnableThemeDialogTexture)GetProcAddress(hThemeAPI, "EnableThemeDialogTexture");
			setWindowTheme = (pfnSetWindowTheme)GetProcAddress(hThemeAPI, "SetWindowTheme");
			setWindowThemeAttribute = (pfnSetWindowThemeAttribute)GetProcAddress(hThemeAPI, "SetWindowThemeAttribute");
			isThemeActive = (pfnIsThemeActive)GetProcAddress(hThemeAPI, "IsThemeActive");
			bufferedPaintInit = (pfnBufferedPaintInit)GetProcAddress(hThemeAPI, "BufferedPaintInit");
			bufferedPaintUninit = (pfnBufferedPaintUninit)GetProcAddress(hThemeAPI, "BufferedPaintUninit");
			beginBufferedPaint = (pfnBeginBufferedPaint)GetProcAddress(hThemeAPI, "BeginBufferedPaint");
			endBufferedPaint = (pfnEndBufferedPaint)GetProcAddress(hThemeAPI, "EndBufferedPaint");
			getBufferedPaintBits = (pfnGetBufferedPaintBits)GetProcAddress(hThemeAPI, "GetBufferedPaintBits");
		}
	}

	if (IsWinVerVistaPlus())
	{
		hDwmApi = LoadLibraryA("dwmapi.dll");
		if (hDwmApi)
		{
			dwmExtendFrameIntoClientArea = (pfnDwmExtendFrameIntoClientArea)GetProcAddress(hDwmApi,"DwmExtendFrameIntoClientArea");
			dwmIsCompositionEnabled = (pfnDwmIsCompositionEnabled)GetProcAddress(hDwmApi,"DwmIsCompositionEnabled");
		}
	}

	if (bufferedPaintInit) bufferedPaintInit();

	OleInitialize(NULL);

	if (IsWinVer7Plus())
		CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_ALL, IID_ITaskbarList3, (void**)&pTaskbarInterface);

	InitialiseModularEngine();
	ParseCommandLine();

	if (LoadDefaultModules()) {
		NotifyEventHooks(hShutdownEvent,0,0);
		UnloadDefaultModules();

		result = 1;
		goto exit;
	}
	NotifyEventHooks(hModulesLoadedEvent,0,0);

	// ensure that the kernel hooks the SystemShutdownProc() after all plugins
	HookEvent(ME_SYSTEM_SHUTDOWN,SystemShutdownProc);

	forkthread(compactHeapsThread,0,NULL);
	CreateServiceFunction(MS_SYSTEM_SETIDLECALLBACK,SystemSetIdleCallback);
	CreateServiceFunction(MS_SYSTEM_GETIDLE, SystemGetIdle);
	dwEventTime=GetTickCount();
	myPid=GetCurrentProcessId();
	for(;messageloop;) {
		MSG msg;
		DWORD rc;
		BOOL dying=FALSE;
		rc=MsgWaitForMultipleObjectsExWorkaround(waitObjectCount, hWaitObjects, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE);
		if ( rc >= WAIT_OBJECT_0 && rc < WAIT_OBJECT_0 + waitObjectCount) {
			rc -= WAIT_OBJECT_0;
			CallService(pszWaitServices[rc], (WPARAM) hWaitObjects[rc], 0);
		}
		//
		while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
			if ( msg.message != WM_QUIT ) {
				HWND h=GetForegroundWindow();
				DWORD pid = 0;
				checkIdle(&msg);
				if ( h != NULL && GetWindowThreadProcessId(h, &pid) && pid == myPid && 
					GetClassLongPtr(h, GCW_ATOM)==32770 ) 
				{
					if ( IsDialogMessage(h, &msg) ) 
						continue;
				}
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if ( SetIdleCallback != NULL )
					SetIdleCallback();
			} else if ( !dying ) {
				dying++;
				SetEvent(hMirandaShutdown);
				NotifyEventHooks(hPreShutdownEvent, 0, 0);

				// this spins and processes the msg loop, objects and APC.
				UnwindThreadWait();
				NotifyEventHooks(hShutdownEvent, 0, 0);
				// if the hooks generated any messages, it'll get processed before the second WM_QUIT
				PostQuitMessage(0);
			} else if ( dying ) {
				messageloop=0;
			}
		} // while
	}

exit:
	UnloadNewPluginsModule();
	DestroyModularEngine();
	CloseHandle(hStackMutex);
	CloseHandle(hMirandaShutdown);
	CloseHandle(hThreadQueueEmpty);
	DestroyWindow(hAPCWindow);

	if (pTaskbarInterface)
		pTaskbarInterface->Release();

	OleUninitialize();

	if (bufferedPaintUninit) bufferedPaintUninit();

	if (hDwmApi) FreeLibrary(hDwmApi);
	if (hThemeAPI) FreeLibrary(hThemeAPI);
	if (hShFolder) FreeLibrary(hShFolder);

	return result;
}

static INT_PTR OkToExit(WPARAM, LPARAM)
{
	return NotifyEventHooks(hOkToExitEvent,0,0)==0;
}

static INT_PTR GetMirandaVersion(WPARAM, LPARAM)
{
	TCHAR filename[MAX_PATH];
	DWORD unused;
	DWORD verInfoSize;
	UINT blockSize;
	PVOID pVerInfo;
	VS_FIXEDFILEINFO *vsffi;
	DWORD ver;

	GetModuleFileName(NULL,filename,SIZEOF(filename));
	verInfoSize=GetFileVersionInfoSize(filename,&unused);
	pVerInfo=mir_alloc(verInfoSize);
	GetFileVersionInfo(filename,0,verInfoSize,pVerInfo);
	VerQueryValue(pVerInfo,_T("\\"),(PVOID*)&vsffi,&blockSize);
	ver=(((vsffi->dwProductVersionMS>>16)&0xFF)<<24)|
		((vsffi->dwProductVersionMS&0xFF)<<16)|
		(((vsffi->dwProductVersionLS>>16)&0xFF)<<8)|
		(vsffi->dwProductVersionLS&0xFF);
	mir_free(pVerInfo);
	return (INT_PTR)ver;
}

static INT_PTR GetMirandaVersionText(WPARAM wParam,LPARAM lParam)
{
	TCHAR filename[MAX_PATH], *productVersion;
	DWORD unused;
	DWORD verInfoSize;
	UINT blockSize;
	PVOID pVerInfo;

	GetModuleFileName(NULL,filename,SIZEOF(filename));
	verInfoSize=GetFileVersionInfoSize(filename,&unused);
	pVerInfo=mir_alloc(verInfoSize);
	GetFileVersionInfo(filename,0,verInfoSize,pVerInfo);
	VerQueryValue(pVerInfo,_T("\\StringFileInfo\\000004b0\\ProductVersion"),(LPVOID*)&productVersion,&blockSize);
#if defined( _WIN64 )
	mir_snprintf(( char* )lParam, wParam, "%S x64 Unicode", productVersion );
#elif defined( _UNICODE )
	mir_snprintf(( char* )lParam, wParam, "%S Unicode", productVersion );
#else
	lstrcpyn((char*)lParam,productVersion,wParam);
#endif
	mir_free(pVerInfo);
	return 0;
}

INT_PTR WaitOnHandle(WPARAM wParam,LPARAM lParam)
{
	if(waitObjectCount>=MAXIMUM_WAIT_OBJECTS-1) return 1;
	hWaitObjects[waitObjectCount]=(HANDLE)wParam;
	pszWaitServices[waitObjectCount]=(char*)lParam;
	waitObjectCount++;
	return 0;
}

static INT_PTR RemoveWait(WPARAM wParam, LPARAM)
{
	int i;

	for(i=0;i<waitObjectCount;i++)
		if(hWaitObjects[i]==(HANDLE)wParam) break;
	if(i==waitObjectCount) return 1;
	waitObjectCount--;
	MoveMemory(&hWaitObjects[i],&hWaitObjects[i+1],sizeof(HANDLE)*(waitObjectCount-i));
	MoveMemory(&pszWaitServices[i],&pszWaitServices[i+1],sizeof(char*)*(waitObjectCount-i));
	return 0;
}

INT_PTR GetMemoryManagerInterface(WPARAM, LPARAM lParam)
{
	struct MM_INTERFACE *mmi = (struct MM_INTERFACE*) lParam;
	if ( mmi == NULL )
		return 1;

	mmi->mmi_malloc = mir_alloc;
	mmi->mmi_realloc = mir_realloc;
	mmi->mmi_free = mir_free;

	switch( mmi->cbSize ) {
	case sizeof(struct MM_INTERFACE):
		mmi->mir_snprintf = mir_snprintf;
		mmi->mir_sntprintf = mir_sntprintf;
		mmi->mir_vsnprintf = mir_vsnprintf;
		mmi->mir_vsntprintf = mir_vsntprintf;
		mmi->mir_a2u_cp = mir_a2u_cp;
		mmi->mir_a2u = mir_a2u;
		mmi->mir_u2a_cp = mir_u2a_cp;
		mmi->mir_u2a = mir_u2a;
		// fall through

	case MMI_SIZE_V2:
		mmi->mmi_calloc = mir_calloc;
		mmi->mmi_strdup = mir_strdup;
		mmi->mmi_wstrdup = mir_wstrdup;
		// fall through

	case MMI_SIZE_V1:
		break;

	default:
#if defined( _DEBUG )
		DebugBreak();
#endif
		return 1;
	}

	return 0;
}

INT_PTR GetListInterface(WPARAM, LPARAM lParam)
{
	struct LIST_INTERFACE *li = (struct LIST_INTERFACE*) lParam;
	if ( li == NULL )
		return 1;

	switch(li->cbSize) {
	case LIST_INTERFACE_V3_SIZE:
		li->List_Copy      = List_Copy;
		li->List_ObjCopy   = List_ObjCopy;

	case LIST_INTERFACE_V2_SIZE:
		li->List_InsertPtr = List_InsertPtr;
		li->List_RemovePtr = List_RemovePtr;

	case LIST_INTERFACE_V1_SIZE:
		li->List_Create   = List_Create;
		li->List_Destroy  = List_Destroy;
		li->List_Find     = List_Find;
		li->List_GetIndex = List_GetIndex;
		li->List_Insert   = List_Insert;
		li->List_Remove   = List_Remove;
		li->List_IndexOf  = List_IndexOf;
		return 0;
	}
	return 1;
}

INT_PTR GetUtfInterface(WPARAM, LPARAM lParam)
{
	struct UTF8_INTERFACE *utfi = (struct UTF8_INTERFACE*) lParam;
	if ( utfi == NULL )
		return 1;

	switch( utfi->cbSize ) {
	case UTF8_INTERFACE_SIZEOF_V1:
	case UTF8_INTERFACE_SIZEOF_V2:
	case sizeof( struct UTF8_INTERFACE ):
		break;

	default:
		return 1;
	}

	utfi->utf8_decode   = Utf8Decode;
	utfi->utf8_decodecp = Utf8DecodeCP;
	utfi->utf8_encode   = Utf8Encode;
	utfi->utf8_encodecp = Utf8EncodeCP;
	utfi->utf8_encodeW  = Utf8EncodeUcs2;
	if (utfi->cbSize > UTF8_INTERFACE_SIZEOF_V1)
		utfi->utf8_decodeW = Utf8DecodeUcs2;
	if (utfi->cbSize > UTF8_INTERFACE_SIZEOF_V2)
		utfi->utf8_lenW = Ucs2toUtf8Len;

	return 0;
}

int LoadSystemModule(void)
{
	INITCOMMONCONTROLSEX icce = {0};
	icce.dwSize = sizeof(icce);
	icce.dwICC = ICC_WIN95_CLASSES | ICC_USEREX_CLASSES;
	InitCommonControlsEx(&icce);

	if (IsWinVerXPPlus()) {
		hAPCWindow=CreateWindowEx(0,_T("ComboLBox"),NULL,0, 0,0,0,0, NULL,NULL,NULL,NULL);
		SetClassLongPtr(hAPCWindow, GCL_STYLE, GetClassLongPtr(hAPCWindow, GCL_STYLE) | CS_DROPSHADOW);
		DestroyWindow(hAPCWindow);
		hAPCWindow = NULL;
	}

	hAPCWindow=CreateWindowEx(0,_T("STATIC"),NULL,0, 0,0,0,0, NULL,NULL,NULL,NULL); // lame
	SetWindowLongPtr(hAPCWindow,GWLP_WNDPROC,(LONG_PTR)APCWndProc);
	hStackMutex=CreateMutex(NULL,FALSE,NULL);
	hMirandaShutdown=CreateEvent(NULL,TRUE,FALSE,NULL);
	hThreadQueueEmpty=CreateEvent(NULL,TRUE,TRUE,NULL);

	hShutdownEvent=CreateHookableEvent(ME_SYSTEM_SHUTDOWN);
	hPreShutdownEvent=CreateHookableEvent(ME_SYSTEM_PRESHUTDOWN);
	hModulesLoadedEvent=CreateHookableEvent(ME_SYSTEM_MODULESLOADED);
	hOkToExitEvent=CreateHookableEvent(ME_SYSTEM_OKTOEXIT);

	CreateServiceFunction(MS_SYSTEM_FORK_THREAD,ForkThreadService);
	CreateServiceFunction(MS_SYSTEM_FORK_THREAD_EX,ForkThreadServiceEx);
	CreateServiceFunction(MS_SYSTEM_THREAD_PUSH,UnwindThreadPush);
	CreateServiceFunction(MS_SYSTEM_THREAD_POP,UnwindThreadPop);
	CreateServiceFunction(MS_SYSTEM_TERMINATED,MirandaIsTerminated);
	CreateServiceFunction(MS_SYSTEM_OKTOEXIT,OkToExit);
	CreateServiceFunction(MS_SYSTEM_GETVERSION,GetMirandaVersion);
	CreateServiceFunction(MS_SYSTEM_GETVERSIONTEXT,GetMirandaVersionText);
	CreateServiceFunction(MS_SYSTEM_WAITONHANDLE,WaitOnHandle);
	CreateServiceFunction(MS_SYSTEM_REMOVEWAIT,RemoveWait);
	CreateServiceFunction(MS_SYSTEM_GET_LI,GetListInterface);
	CreateServiceFunction(MS_SYSTEM_GET_MMI,GetMemoryManagerInterface);
	CreateServiceFunction(MS_SYSTEM_GET_UTFI,GetUtfInterface);
	CreateServiceFunction(MS_SYSTEM_GETEXCEPTFILTER,GetExceptionFilter);
	CreateServiceFunction(MS_SYSTEM_SETEXCEPTFILTER,SetExceptionFilter);

	InitPathUtils();
	return 0;
}
