other-licenses/nsis/Contrib/UAC/uac.cpp
author Steve Snyder <swsnyder@snydernet.net>
Thu, 01 Oct 2009 14:19:41 -0400
changeset 33346 8a43f01f1d6478ad072c1f4a41fb633f8a5e3a32
parent 23964 ba15a84af7e6c364570feb7b5a66cea87ba460c8
permissions -rw-r--r--
Bug 512865. qcms: Improve SSE2 performance, add SSE support. r=jrmuizel This patch greatly improves the performance of QCMS transformations on x86 & x86_64 systems. Some notes: 0. On 32-bit x86 systems it does runtime selection between non-SIMD, SSE, and SSE2 code paths. 1. On x86_64 systems the SSE2 code path is always taken. The non-SIMD and SSE code paths are left intact, but contemporary versions of the GCC and MSVC compilers will see that they cannot be reached and optimize them away. 2. The execution of the SSE2 code path is reduced by 67%, relative to the original Intel/Microsoft formatted ASM code. The relative performance is seen on a Pentium4 (Northwood) 2.4GHz CPU with DDR1 RAM. 3. The SSE code path provides a 80% reduction in execution time, relative to the non-SIMD code path. The relative performance is seen on a Pentium3 (Coppermine) 1.26GHz CPU with SDRAM. 4. The code has been split out into separate files so that it can be built with different cflags (-msse, and -msse2) when using gcc. 5. Try to land again, this time with __attribute__((__force_align_arg_pointer__)) to avoid crashes on linux.

//Copyright (C) 2007 Anders Kjersem. Licensed under the zlib/libpng license, see License.txt for details.
/*
UAC plugin for NSIS
===================
Compiled with VC6+PlatformSDK (StdCall & MinimizeSize)


Todo:
-----
�GetCurrentDir in elevated parent and pass along to outer process for Exec* (or somekind of ipc to request it if workingdir param is empty)
�Check if secondary logon service is running in SysElevationPresent() on NT5
�Use IsUserAnAdmin? MAKEINTRESOURCE(680) export on 2k //http://forums.winamp.com/showthread.php?s=&threadid=195020
�AllowSetForegroundWindow
�Use RPC instead of WM_COPYDATA for IPC
�Autodetect "best" default admin user in MyRunAs
�Use ChangeWindowMessageFilter so we can get >WM_USER msg success feedback and possibly fill the log with detailprints
�Hide IPC window inside inner instance window? (Could this add unload problems?)
�CREATE_PRESERVE_CODE_AUTHZ_LEVEL? http://msdn2.microsoft.com/en-us/library/ms684863.aspx
�UpdateProcThreadAttribute?
�Consent UI on XP ?
�All langs in single file; [MyRunAsStrings]>LangSections != 0 then load strings from [langid] sections
�BroadcastSystemMessage to help with SetForeground
�UAC::StackPop


Notes:
------
Primary integrity levels:
Name					SID				RID 
Low Mandatory Level		S-1-16-4096		0x1000
Medium Mandatory Level	S-1-16-8192		0x2000
High Mandatory Level	S-1-16-12288	0x3000
System Mandatory Level	S-1-16-16384	0x4000

*/

#define UAC_HACK_Clammerz	//ugly messagebox focus hack for .onInit
#define UAC_HACK_FGWND1		//super ugly fullscreen invisible window for focus tricks

#define UAC_INITIMPORTS
#include "UAC.h"
#include <objbase.h>//CoInitialize
#include "NSISUtil.h"
using namespace NSIS;
NSISUTIL_INIT();


#define ERRAPP_BADIPCSRV (0x20000000|1)
#define SW_INVALID ((WORD)-1)
#define IPCTOUT_DEF (1000*3) //default timeout for IPC messages
#define IPCTOUT_SHORT 1500
#define IPCTOUT_LONG (IPCTOUT_DEF*2)

enum _IPCSRVWNDMSG 
{
	IPC_GETEXITCODE=WM_USER,	//Get exit-code of the process spawned by the last call to ExecWait/ShellExecWait
	IPC_ELEVATEAGAIN,
	IPC_GETSRVPID,				//Get PID of outer process
	IPC_GETSRVHWND,				//Get $HWNDParent of outer process
	IPC_EXECCODESEGMENT,		//wp:pos | lp:hwnd | returns ErrorCode+1

#ifdef UAC_HACK_FGWND1
	IPC_HACKFINDRUNAS,
#endif
};

enum _COPYDATAID 
{
	CDI_SHEXEC=666,	//returns WindowsErrorCode+1
	CDI_SYNCVAR,
	CDI_STACKPUSH,
};

typedef struct 
{
	UINT VarId;
	NSISCH buf[ANYSIZE_ARRAY];
} IPC_SYNCVAR;

typedef struct 
{
	HWND hwnd;
	bool Wait;
	bool UseCreateProcess;
	WORD ShowMode;	
	NSISCH*strExec;
	NSISCH*strParams;
	NSISCH*strWorkDir;
	NSISCH*strVerb;
	NSISCH buf[ANYSIZE_ARRAY];
} IPC_SHEXEC;


typedef struct 
{
	HINSTANCE	hInstance;
	HWND		hSrvIPC;
	BYTE		DllRef;
	bool		UseIPC;
	bool		CheckedIPCParam;
	UINT NSISStrLen;
	//IPC Server stuff:
	HANDLE		threadIPC;
	DWORD		LastWaitExitCode;//Exit code of process started from last call to ExecWait/ShellExecWait
	NSIS::extra_parameters*pXParams;
	bool		ElevateAgain;
	//DelayLoadedModules:
	HMODULE		hModAdvAPI;
} GLOBALS;


GLOBALS g = {0};


void StopIPCSrv();
DWORD _GetElevationType(TOKEN_ELEVATION_TYPE* pTokenElevType);
bool GetIPCSrvWndFromParams(HWND&hwndOut);



#if _MSC_VER >= 1400 //MSVC 2005 wants to pull in the CRT, let's try to help it out
void* __cdecl memset(void*mem,int c,size_t len) 
{
	char *p=(char*)mem;
	while (len-- > 0){*p++=c;}
	return mem;
}
#endif



FORCEINLINE NSISCH* GetIPCWndClass() { return _T("NSISUACIPC"); }
FORCEINLINE bool StrCmpI(LPCTSTR s1,LPCTSTR s2) {return 0==lstrcmpi(s1,s2);}
LPTSTR StrNextChar(LPCTSTR Str) { return CharNext(Str); }
bool StrContainsWhiteSpace(LPCTSTR s) { if (s) {while(*s && *s>_T(' '))s=StrNextChar(s);if (*s)return true;}return false; }

DWORD GetSysVer(bool Major=true) 
{
	OSVERSIONINFO ovi = { sizeof(ovi) };
	if ( !GetVersionEx(&ovi) ) return 0;
	return Major ? ovi.dwMajorVersion : ovi.dwMinorVersion;
}
#define GetOSVerMaj() (GetSysVer(true))
#define GetOSVerMin() (GetSysVer(false))

UINT_PTR StrToUInt(LPTSTR s,bool ForceHEX=false,BOOL*pFoundBadChar=0) 
{
	UINT_PTR v=0;
	BYTE base=ForceHEX?16:10;	
	if (pFoundBadChar)*pFoundBadChar=false;
	if ( !ForceHEX && *s=='0' && ((*(s=StrNextChar(s)))&~0x20)=='X' && (s=StrNextChar(s)) )base=16;
	for (TCHAR c=*s; c; c=*(s=StrNextChar(s)) ) 
	{
		if (c >= _T('0') && c <= _T('9')) c-='0';
		else if (base==16 && (c & ~0x20) >= 'A' && (c & ~0x20) <= 'F') c=(c & 7) +9;
		else 
		{
			if (pFoundBadChar /*&& c!=' '*/)*pFoundBadChar=true;
			break;
		}
		v*=base;v+=c;
	}
	return v;
}

LPTSTR FindExePathEnd(LPTSTR p) 
{
	if ( *p=='"' && *(++p) ) 
	{
		while( *p && *p!='"' )p=StrNextChar(p);
		if (*p)
			p=StrNextChar(p);
		else 
			return 0;
	}
	else 
		if ( *p!='/' )while( *p && *p!=' ' )p=StrNextChar(p);
	return p;
}


#ifdef FEAT_MSRUNASDLGMODHACK
HHOOK g_MSRunAsHook;
void MSRunAsDlgMod_Unload(void*hook) 
{
	if (hook) 
	{
		//ASSERT(g_MSRunAsHook==hook);
		UnhookWindowsHookEx((HHOOK)hook);
		//g_MSRunAsHook=0;
	}
}
LRESULT CALLBACK MSRunAsDlgMod_ShellProc(int nCode,WPARAM wp,LPARAM lp) 
{
	CWPRETSTRUCT*pCWPS;
	if (nCode >= 0 && (pCWPS=(CWPRETSTRUCT*)lp) && WM_INITDIALOG==pCWPS->message)
	{
		TCHAR buf[30];
		GetClassName(pCWPS->hwnd,buf,COUNTOF(buf));
		if (!lstrcmpi(buf,_T("#32770"))) 
		{ 
			const UINT IDC_USRSAFER=0x106,IDC_OTHERUSER=0x104,IDC_SYSCRED=0x105;
			GetClassName(GetDlgItem(pCWPS->hwnd,IDC_SYSCRED),buf,COUNTOF(buf));
			if (!lstrcmpi(buf,_T("SysCredential"))) //make sure this is the run as dialog
			{
				MySndDlgItemMsg(pCWPS->hwnd,IDC_USRSAFER,BM_SETCHECK,BST_UNCHECKED);
				MySndDlgItemMsg(pCWPS->hwnd,IDC_OTHERUSER,BM_CLICK);
			}
		}
	}
	return CallNextHookEx(g_MSRunAsHook,nCode,wp,lp);
}
void* MSRunAsDlgMod_Init() 
{
	if(GetOSVerMaj()!=5 || GetOSVerMin()<1)return NULL;//only XP/2003
	return g_MSRunAsHook=SetWindowsHookEx(WH_CALLWNDPROCRET,MSRunAsDlgMod_ShellProc,0,GetCurrentThreadId());
}
#endif

DWORD DllSelfAddRef() 
{
	NSISCH buf[MAX_PATH*5];//Lets hope $pluginsdir is shorter than this, only special builds could break this
	DWORD len=GetModuleFileName(g.hInstance,buf,MAX_PATH*5);
	if ( len && len<MAX_PATH*5 && LoadLibrary(buf) ) 
	{
		if (!g.DllRef)g.DllRef++;
		return NO_ERROR;
	}
	ASSERT(!"DllSelfAddRef failed!");
	return ERROR_BUFFER_OVERFLOW;
}

FORCEINLINE DWORD MaintainDllSelfRef() //Call this from every exported function to prevent NSIS from unloading our plugin
{ 
	if(!g.CheckedIPCParam && !g.DllRef) 
	{
		HWND hSrv;
		g.CheckedIPCParam=true;
		g.UseIPC=GetIPCSrvWndFromParams(hSrv);
		if(g.UseIPC) 
		{
			g.DllRef++;
			g.hSrvIPC=hSrv;
		}
	}
	return (g.DllRef)?DllSelfAddRef():NO_ERROR;
}


DWORD SendIPCMsg(UINT Msg,WPARAM wp,LPARAM lp,DWORD_PTR&MsgRet,DWORD tout=IPCTOUT_DEF,const HWND hIPCSrv=g.hSrvIPC) 
{
	if (tout==INFINITE) //BUGFIX: SendMessageTimeout(...,INFINITE,...) seems to be broken, SendMessageTimeout(...,SMTO_NORMAL,0,..) seems to work but why take the chance
	{
		MsgRet=SendMessage(hIPCSrv,Msg,wp,lp);
		return NO_ERROR;
	}
	if ( SendMessageTimeout(hIPCSrv,Msg,wp,lp,SMTO_NORMAL,tout,&MsgRet) )return NO_ERROR;
	return (tout=GetLastError()) ? tout : ERROR_TIMEOUT; 
}

void _Unload() 
{
	StopIPCSrv();
	if (g.DllRef) 
	{
		g.DllRef=0;
		FreeLibrary(g.hInstance);
		//Why bother?> FreeLibrary(g.hModAdvAPI);
	}
}

DWORD DelayLoadGetProcAddr(void**ppProc,HMODULE hLib,LPCSTR Export) 
{
	ASSERT(ppProc && hLib && Export);
	if (!*ppProc) 
	{
		*ppProc=GetProcAddress(hLib,Export);
		if (!*ppProc)return GetLastError();
	}
	return NO_ERROR;
}

DWORD DelayLoadDlls() 
{

#ifdef UNICODE
#	define __DLD_FUNCSUFFIX "W"
#	else
#	define __DLD_FUNCSUFFIX "A"
#	endif

	if (!g.hModAdvAPI) //using g.hModAdvAPI to test if this is the first time we have been called
	{
		struct 
		{
			HMODULE*pMod;
			LPCSTR DllName;//NOTE: Always using ANSI strings to save a couple of bytes
		} 
		dld[]=
		{ 
			{&g.hModAdvAPI,"AdvAPI32"},
			{0}
		};
		DWORD ec;
		UINT o;

		for (o=0; dld[o].pMod; ++o)
			if ( !(*dld[o].pMod=LoadLibraryA(dld[o].DllName)) )
				return GetLastError();

		struct 
		{
			HMODULE hMod;
			void**ppProc;
			LPCSTR Export;
			bool Optional;
		} 
		dldprocs[]=
		{
			{GetModuleHandle(_T("USER32")),(void**)&_AllowSetForegroundWindow,"AllowSetForegroundWindow",true},
			{g.hModAdvAPI,(void**)&_OpenProcessToken,			"OpenProcessToken"},
			{g.hModAdvAPI,(void**)&_OpenThreadToken,			"OpenThreadToken"},
			{g.hModAdvAPI,(void**)&_GetTokenInformation,		"GetTokenInformation"},
			{g.hModAdvAPI,(void**)&_AllocateAndInitializeSid,	"AllocateAndInitializeSid"},
			{g.hModAdvAPI,(void**)&_FreeSid,					"FreeSid"},
			{g.hModAdvAPI,(void**)&_EqualSid,					"EqualSid"},
			{g.hModAdvAPI,(void**)&_CheckTokenMembership,		"CheckTokenMembership",true},
			#ifdef FEAT_CUSTOMRUNASDLG
			{g.hModAdvAPI,(void**)&_GetUserName,			"GetUserName" __DLD_FUNCSUFFIX},
			{g.hModAdvAPI,(void**)&_CreateProcessWithLogonW,"CreateProcessWithLogonW",true},
			{LoadLibraryA("SECUR32"),(void**)&_GetUserNameEx,"GetUserNameEx" __DLD_FUNCSUFFIX,true},//We never free this library
			#endif
			{0}
		};
#undef __DLD_FUNCSUFFIX
		for (o=0; dldprocs[o].hMod; ++o)
			if (ec=DelayLoadGetProcAddr(dldprocs[o].ppProc,dldprocs[o].hMod,dldprocs[o].Export) && !dldprocs[o].Optional) 
			{
				TRACEF("DelayLoadDlls failed to find %s in %X\n",dldprocs[o].Export,dldprocs[o].hMod);
				return ec;
			}
	}
	return NO_ERROR;
}

void AllowOuterInstanceWindowFocus() 
{
	if (g.UseIPC) 
	{
		DWORD_PTR MsgRet;
		if (!SendIPCMsg(IPC_GETSRVPID,0,0,MsgRet,IPCTOUT_SHORT) && MsgRet && _AllowSetForegroundWindow)_AllowSetForegroundWindow(MsgRet);
	}
}

DWORD SyncVars(HWND hwndNSIS) 
{
	DWORD i,ec=NO_ERROR;
	IPC_SYNCVAR*pSV=0;
	if (!g.UseIPC)return NO_ERROR;
	g.NSISStrLen=NSIS::StrSize;
	TRACEF("SyncVars: g.NSISStrLen=%d\n",g.NSISStrLen);ASSERT(g.NSISStrLen>10);
	DWORD cbStruct=FIELD_OFFSET(IPC_SYNCVAR,buf[g.NSISStrLen+1]);
	pSV=(IPC_SYNCVAR*)MemAlloc(cbStruct);
	if (!pSV)
		goto die_GLE;
	else 
	{
		COPYDATASTRUCT cds={CDI_SYNCVAR,cbStruct,pSV};
		for (i=0;i<__INST_LAST && !ec;++i) 
		{
			pSV->VarId=i;
			lstrcpyn(pSV->buf,GetVar(i),g.NSISStrLen);
			DWORD MsgRet;//TRACEF("SyncVars: (%d)%s|\n",i,pSV->buf);
			if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,3000 )))ec=MsgRet-1;
		}	
	}
	return ec;
die_GLE:
	return GetLastError();
}

DWORD _Exec(HWND hwnd,NSISCH*Verb,NSISCH*Exec,NSISCH*Params,NSISCH*WorkDir,WORD ShowWnd,bool Wait,bool UseCreateProcess) 
{
	DWORD ec;
	NSISCH*buf=0;
	SHELLEXECUTEINFO sei={sizeof(SHELLEXECUTEINFO)};
	sei.hwnd		=hwnd;
	sei.nShow		=(ShowWnd!=SW_INVALID)?ShowWnd:SW_NORMAL;
	sei.fMask		=SEE_MASK_FLAG_DDEWAIT;
	sei.lpFile		=(Exec&&*Exec)			?Exec:0;
	sei.lpParameters=(Params&&*Params)		?Params:0;
	sei.lpDirectory	=(WorkDir&&*WorkDir)	?WorkDir:0;
	sei.lpVerb		=(Verb&&*Verb)			?Verb:0;
	TRACEF("_Exec:%X|%s|%s|%s|wait=%d useCreateProc=%d ShowWnd=%d useShowWnd=%d\n",hwnd,Exec,Params,WorkDir,Wait,UseCreateProcess,ShowWnd,ShowWnd!=SW_INVALID);
	if (UseCreateProcess) 
	{
		STARTUPINFO si={sizeof(STARTUPINFO)};
		if (ShowWnd != SW_INVALID) 
		{
			si.dwFlags|=STARTF_USESHOWWINDOW;
			si.wShowWindow=sei.nShow;
		}
		PROCESS_INFORMATION pi;
		const NSISCH*Q=( (*Exec!='"') && (*Params) && StrContainsWhiteSpace(Exec)) ? _T("\"") : _T("");//Add extra quotes to program part of command-line?
		const DWORD len= ((*Q)?2:0) + lstrlen(Exec) + 1 + lstrlen(Params) + 1;
		buf=(NSISCH*)NSIS::MemAlloc(len*sizeof(NSISCH));
		if (!buf)return ERROR_OUTOFMEMORY;
		//Build string for CreateProcess, "[Q]<Exec>[Q][Space]<Params>"
		wsprintf(buf,_T("%s%s%s%s%s"),Q,Exec,Q,((*Params)?_T(" "):_T("")),Params);
		TRACEF("_Exec: calling CreateProcess>%s< in >%s< addedQ=%d show=%u\n",buf,sei.lpDirectory,*Q,sei.nShow);
		if ( !CreateProcess(0,buf,0,0,false,0,0,sei.lpDirectory,&si,&pi) ) goto die_GLE;
		CloseHandle(pi.hThread);
		sei.hProcess=pi.hProcess;
	}
	else 
	{
		sei.fMask|=SEE_MASK_NOCLOSEPROCESS;
		TRACEF("_Exec: calling ShellExecuteEx...\n");
		if ( !ShellExecuteEx(&sei) )goto die_GLE;
	}
	if (Wait) 
	{
		WaitForSingleObject(sei.hProcess,INFINITE);
		GetExitCodeProcess(sei.hProcess,&g.LastWaitExitCode);
	}
	else WaitForInputIdle(sei.hProcess,1500);//wait a little bit so the finish page window does not go away too fast and cause focus problems

	CloseHandle(sei.hProcess);
	ec=NO_ERROR;
ret:
	if (buf)NSIS::MemFree(buf);
	return ec;
die_GLE:
	ec=GetLastError();
	TRACEF("_Exec>%s failed with error %u (%s)\n",UseCreateProcess?"CreateProcess":"ShExec",ec,buf);
	goto ret;
}

WORD GetShowWndCmdFromStr(NSISCH*s) 
{
	//NOTE: Little used modes are still supported, just not with strings, you must use the actual number or ${SW_xx} defines from WinMessages.h
	struct {NSISCH*id;WORD cmd;} swcm[] = {
		{_T("SW_HIDE"),				SW_HIDE},
		{_T("SW_SHOW"),				SW_SHOW},
		{_T("SW_RESTORE"),			SW_RESTORE},
		{_T("SW_MAXIMIZE"),			SW_MAXIMIZE},
		{_T("SW_MINIMIZE"),			SW_MINIMIZE},
		//	{_T("SW_MAX"),			SW_MAXIMIZE},
		//	{_T("SW_MIN"),			SW_MINIMIZE},
		{_T("SW_SHOWNORMAL"),		SW_SHOWNORMAL},
		//{_T("SW_NORMAL"),			SW_NORMAL},
		//{_T("SW_SHOWMINIMIZED"),	SW_SHOWMINIMIZED},
		//{_T("SW_SHOWMAXIMIZED"),	SW_SHOWMAXIMIZED},
		//{_T("SW_SHOWNOACTIVATE"),	SW_SHOWNOACTIVATE},
		//{_T("SW_SHOWNA"),			SW_SHOWNA},
		//{_T("SW_SHOWMINNOACTIVE"),	SW_SHOWMINNOACTIVE},
		//{_T("SW_SHOWDEFAULT"),		SW_SHOWDEFAULT},
		//{_T("SW_FORCEMINIMIZE"),	SW_FORCEMINIMIZE},
		{0}
	};
	for (int i=0; swcm[i].id; ++i) if (StrCmpI(s,swcm[i].id)) return swcm[i].cmd;
	return SW_INVALID;
}

void HandleExecExport(bool CreateProc,bool Wait,HWND&hwndNSIS,int&StrSize,NSISCH*&Vars,stack_t**&StackTop,NSIS::extra_parameters*pXParams) 
{
	DWORD ec=NO_ERROR,ForkExitCode=ERROR_INVALID_FUNCTION;
	UINT cch=0,cbStruct;
	WORD ShowWnd;
	IPC_SHEXEC*pISE=0;
	stack_t* const pSIVerb=CreateProc?0:StackPop();//Only ShellExec supports verb's
	stack_t* const pSIShowWnd	=StackPop();
	stack_t* const pSIExec		=StackPop();
	stack_t* const pSIParams	=StackPop();
	stack_t* const pSIWorkDir	=StackPop();

	if (ec=MaintainDllSelfRef())goto ret;
	if (!pSIExec || !pSIParams || !pSIWorkDir || !pSIShowWnd || (!pSIVerb && !CreateProc)) 
	{
		TRACE("If you see this you probably forgot that all parameters are required!\n");
		ec=ERROR_INVALID_PARAMETER;
		goto ret;
	}
	ShowWnd=GetShowWndCmdFromStr(pSIShowWnd->text);
	if (ShowWnd==SW_INVALID && *pSIShowWnd->text) 
	{
		BOOL BadCh;
		ShowWnd=StrToUInt(pSIShowWnd->text,false,&BadCh);
		if (BadCh)ShowWnd=SW_INVALID;
	}
	TRACEF("HandleExecExport: ipc=%X (%X)\n",g.UseIPC,g.hSrvIPC);
	SyncVars(hwndNSIS);
	if (!g.UseIPC) //No IPC Server, we are not elevated with UAC
	{
		ec=_Exec(hwndNSIS,pSIVerb?pSIVerb->text:0,pSIExec->text,pSIParams->text,pSIWorkDir->text,ShowWnd,Wait,CreateProc);
		if (Wait)ForkExitCode=g.LastWaitExitCode;
		goto ret;
	}	
	cch+=lstrlen(pSIExec->text)+1;
	cch+=lstrlen(pSIParams->text)+1;
	cch+=lstrlen(pSIWorkDir->text)+1;
	if (pSIVerb)cch+=lstrlen(pSIVerb->text)+1;
	cbStruct=FIELD_OFFSET( IPC_SHEXEC, buf[cch*sizeof(TCHAR)] );
	pISE=(IPC_SHEXEC*)NSIS::MemAlloc(cbStruct);
	if (!pISE)ec=GetLastError();
	if (!ec) 
	{
		DWORD_PTR MsgRet;
		pISE->hwnd		=hwndNSIS;
		pISE->Wait		=Wait;
		pISE->ShowMode	=ShowWnd;
		pISE->UseCreateProcess=CreateProc;
		//Just offsets at this point
		pISE->strExec	=(NSISCH*)0;
		pISE->strParams	=(NSISCH*)(lstrlen(pSIExec->text)	+pISE->strExec+1);
		pISE->strWorkDir=(NSISCH*)(lstrlen(pSIParams->text)	+pISE->strParams+1);
		pISE->strVerb=	 (NSISCH*)(lstrlen(pSIWorkDir->text)+pISE->strWorkDir+1);
		lstrcpy(pISE->buf,pSIExec->text);
		lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strParams],	pSIParams->text);
		lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strWorkDir],pSIWorkDir->text);
		if (pSIVerb)lstrcpy(&pISE->buf[(DWORD_PTR)pISE->strVerb],	pSIVerb->text);
		COPYDATASTRUCT cds;
		cds.dwData=CDI_SHEXEC;
		cds.cbData=cbStruct;
		cds.lpData=pISE;
		AllowOuterInstanceWindowFocus();
		if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,Wait?(INFINITE):(IPCTOUT_LONG) )))ec=MsgRet-1;
		TRACEF("HandleExecExport: IPC returned %X, ec=%d\n",MsgRet,ec);
		if (Wait && NO_ERROR==ec) 
		{
			ec=SendIPCMsg(IPC_GETEXITCODE,0,0,ForkExitCode);
			TRACEF("HandleExecExport(Wait): Spawned process exit code=%d",ForkExitCode);
		}
	}
ret:
	NSIS::MemFree(pISE);
	StackFreeItem(pSIShowWnd);
	StackFreeItem(pSIExec);
	StackFreeItem(pSIParams);
	StackFreeItem(pSIWorkDir);
	StackFreeItem(pSIVerb);
	SetVarUINT(INST_0,ec);
	if (ec)SetErrorFlag(pXParams);
	if (Wait)SetVarUINT(INST_1,ForkExitCode);
}


bool _SupportsUAC(bool VersionTestOnly=false) 
{
	TOKEN_ELEVATION_TYPE tet;
	OSVERSIONINFO ovi={sizeof(ovi)};
	if (!GetVersionEx(&ovi)) 
	{
		ASSERT(!"_SupportsUAC>GetVersionEx");
		return false;
	}
	if (VersionTestOnly)return ovi.dwMajorVersion>=6;
	if (ovi.dwMajorVersion>=6 && _GetElevationType(&tet)==NO_ERROR) 
	{
		const bool ret=tet!=TokenElevationTypeDefault && tet!=NULL;
		TRACEF("_SupportsUAC tet=%d, returning %d\n",tet,ret);
		return ret;
	}
	DBGONLY(TRACEF("_SupportsUAC returning false! ver=%d _GetElevationType.ret=%u\n",ovi.dwMajorVersion,_GetElevationType(&tet)));
	return false;
}

DWORD _GetElevationType(TOKEN_ELEVATION_TYPE*pTokenElevType) 
{
	DWORD ec=ERROR_ACCESS_DENIED;
	HANDLE hToken=0;
	DWORD RetLen;
	if (!pTokenElevType)return ERROR_INVALID_PARAMETER;
	if (ec=DelayLoadDlls())return ec;
	*pTokenElevType=(TOKEN_ELEVATION_TYPE)NULL;
	if (!_SupportsUAC(true))return NO_ERROR;
	if (!_OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken))goto dieLastErr;
	if (!_GetTokenInformation(hToken,(_TOKEN_INFORMATION_CLASS)TokenElevationType,pTokenElevType,sizeof(TOKEN_ELEVATION_TYPE),&RetLen))goto dieLastErr;
	SetLastError(NO_ERROR);
dieLastErr:
	ec=GetLastError();
	CloseHandle(hToken);
	TRACEF("_GetElevationType ec=%u type=%d\n",ec,*pTokenElevType);
	return ec;
}


bool _IsUACEnabled() 
{
	HKEY hKey;
	bool ret=false;
	if (GetSysVer()>=6 && NO_ERROR==RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"),0,KEY_READ,&hKey)) 
	{
		DWORD val,type,size=sizeof(DWORD);
		if (NO_ERROR==RegQueryValueEx(hKey,_T("EnableLUA"),0,&type,(LPBYTE)&val,&size) && type==REG_DWORD && val!=0) ret=true;
		RegCloseKey(hKey);
	}
	return ret;
}


bool SysElevationPresent() //Will return false on Vista if UAC is off
{ 
	const DWORD vmaj=GetSysVer();
	ASSERT(vmaj<=6 && vmaj>=4);
	if (vmaj==5) return true; //TODO:Check if secondary logon service is running?
	if (vmaj>=6) return _IsUACEnabled();
	return false;
}

FORCEINLINE bool SysSupportsRunAs() 
{ 
	return GetSysVer()>=5;
}





bool _IsAdmin() 
{
	
#ifdef BUILD_XPTEST
	static int _dbgOld=-1;
	unsigned _dbg=(unsigned)FindExePathEnd(GetCommandLine());
	if (_dbgOld==-1){_dbg=(_dbg && *((TCHAR*)_dbg))?MessageBoxA(0,"Debug: Pretend to be admin?",GetCommandLine(),MB_YESNOCANCEL):IDCANCEL;} else _dbg=_dbgOld;_dbgOld=_dbg;TRACEF("_IsAdmin=%d|%d\n",_dbg,_dbg==IDYES);
	if (_dbg!=IDCANCEL){SetLastError(0);return _dbg==IDYES;}
#endif

	BOOL isAdmin=false;
	DWORD ec;
	OSVERSIONINFO ovi={sizeof(ovi)};	
	if (!GetVersionEx(&ovi))return false;
	if (VER_PLATFORM_WIN32_NT != ovi.dwPlatformId) //Not NT
	{
		SetLastError(NO_ERROR);
		return true;
	}
	if (ec=DelayLoadDlls()) 
	{
		TRACEF("DelayLoadDlls failed in _IsAdmin() with err x%X\n",ec);
		SetLastError(ec);
		return false;
	}

	ASSERT(_OpenThreadToken && _OpenProcessToken && _AllocateAndInitializeSid && _EqualSid && _FreeSid);
	HANDLE hToken;
	if (_OpenThreadToken(GetCurrentThread(),TOKEN_QUERY,FALSE,&hToken) || _OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken)) 
	{
		SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY;
		PSID psid=0;
		if (_AllocateAndInitializeSid(&SystemSidAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,&psid)) 
		{
			if (_CheckTokenMembership) 
			{
				if (!_CheckTokenMembership(0,psid,&isAdmin))isAdmin=false;
			}
			else 
			{
				DWORD cbTokenGrps;
				if (!_GetTokenInformation(hToken,TokenGroups,0,0,&cbTokenGrps)&&GetLastError()==ERROR_INSUFFICIENT_BUFFER) 
				{
					TOKEN_GROUPS*ptg=0;
					if (ptg=(TOKEN_GROUPS*)NSIS::MemAlloc(cbTokenGrps)) 
					{
						if (_GetTokenInformation(hToken,TokenGroups,ptg,cbTokenGrps,&cbTokenGrps)) 
						{
							for (UINT i=0; i<ptg->GroupCount;i++) 
							{								
								if (_EqualSid(ptg->Groups[i].Sid,psid))isAdmin=true;
							}
						}
						NSIS::MemFree(ptg);
					}
				}
			}			
			_FreeSid(psid);
		}
		CloseHandle(hToken);
	}
	if (isAdmin) //UAC Admin with split token check
	{
		if (_SupportsUAC()) 
		{
			TOKEN_ELEVATION_TYPE tet;
			if (_GetElevationType(&tet) || tet==TokenElevationTypeLimited)isAdmin=false;
		}
		else SetLastError(NO_ERROR);
	}
	return FALSE != isAdmin;
}


LRESULT CALLBACK IPCSrvWndProc(HWND hwnd,UINT uMsg,WPARAM wp,LPARAM lp) 
{
	switch(uMsg) 
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_CLOSE:
		return DestroyWindow(hwnd);
	case WM_COPYDATA:
		if (lp) 
		{
			const COPYDATASTRUCT*pCDS=(COPYDATASTRUCT*)lp;
			if (pCDS->dwData==CDI_SHEXEC && pCDS->lpData) 
			{
				if ( pCDS->cbData < sizeof(IPC_SHEXEC) )break;
				g.LastWaitExitCode=ERROR_INVALID_FUNCTION;
				IPC_SHEXEC& ise=*((IPC_SHEXEC*)pCDS->lpData);
				SetForegroundWindow(ise.hwnd);				
				DWORD ec=_Exec(
					ise.hwnd,
					&ise.buf[(DWORD_PTR)ise.strVerb],
					&ise.buf[(DWORD_PTR)ise.strExec],
					&ise.buf[(DWORD_PTR)ise.strParams],
					&ise.buf[(DWORD_PTR)ise.strWorkDir],
					ise.ShowMode,ise.Wait,ise.UseCreateProcess
					);
				TRACEF("IPCSrvWndProc>IPC_SHEXEC>_ShExec=%d\n",ec);
				return ec+1;
			}
			else if (pCDS->dwData==CDI_SYNCVAR && pCDS->lpData && pCDS->cbData>1) 
			{
				IPC_SYNCVAR*pSV=(IPC_SYNCVAR*)pCDS->lpData;
				if (pSV->VarId>=__INST_LAST)return 1+ERROR_INVALID_PARAMETER;
				TRACEF("WM_COPYDATA: CDI_SYNCVAR:%d=%s|\n",pSV->VarId,&pSV->buf[0]);
				lstrcpy(GetVar(pSV->VarId),&pSV->buf[0]);
				return NO_ERROR+1;
			}
			else if (pCDS->dwData==CDI_STACKPUSH && pCDS->lpData && pCDS->cbData>=1) 
			{
				TRACEF("WM_COPYDATA: CDI_STACKPUSH:%s|\n",pCDS->lpData);
				return StackPush((NSISCH*)pCDS->lpData)+1;
			}
		}
		break;
	case IPC_GETEXITCODE:
		return g.LastWaitExitCode;
	case IPC_ELEVATEAGAIN:
		TRACE("IPCSrvWndProc>IPC_ELEVATEAGAIN\n");
		return (g.ElevateAgain=true);
	case IPC_GETSRVPID:
		return GetCurrentProcessId();//FUCKO?
	case IPC_GETSRVHWND:
		return GetWindowLongPtr(hwnd,GWLP_USERDATA);
	case IPC_EXECCODESEGMENT:
		return 1+(g.pXParams ? ExecuteCodeSegment(g.pXParams,wp,(HWND)lp) : ERROR_INVALID_FUNCTION);
#ifdef UAC_HACK_FGWND1
	case IPC_HACKFINDRUNAS: //super ugly hack to get title of run as dialog
		if (wp<200) 
		{
			HWND hRA=GetLastActivePopup((HWND)lp);
			TRACEF("IPC_HACKFINDRUNAS:%d %X %X\n",wp,lp,hRA);
			if (hRA && hRA !=(HWND)lp ) return PostMessage((HWND)lp,WM_APP,0,(LONG_PTR)hRA);
			Sleep(10);PostMessage(hwnd,uMsg,wp+1,lp);
		}
		break;
#endif
	}
	return DefWindowProc(hwnd,uMsg,wp,lp);
}


DWORD WINAPI IPCSrvThread(LPVOID lpParameter) 
{
	CoInitialize(0);
	const DWORD WStyle=WS_VISIBLE DBGONLY(|(WS_CAPTION));
	const int PosOffset=32700;
	MSG msg;
	WNDCLASS wc={0};
	wc.lpszClassName=GetIPCWndClass();
	wc.lpfnWndProc=IPCSrvWndProc;
	wc.hInstance=g.hInstance;
	if (!RegisterClass(&wc))goto dieLastErr;	
	if (!(g.hSrvIPC=CreateWindowEx(WS_EX_TOOLWINDOW DBGONLY(&~WS_EX_TOOLWINDOW),
		GetIPCWndClass(),
		DBGONLY(_T("Debug: NSIS.UAC")+)0,
		WStyle,
		-PosOffset DBGONLY(+PosOffset),-PosOffset DBGONLY(+PosOffset),DBGONLY(150+)1,DBGONLY(10+)1,
		0,0,wc.hInstance,0
		)))goto dieLastErr;		
	SetWindowLongPtr(g.hSrvIPC,GWLP_USERDATA,(LONG_PTR)lpParameter);
	TRACEF("IPCSrv=%X server created...\n",g.hSrvIPC);
	while (GetMessage(&msg,0,0,0)>0)DispatchMessage(&msg);
	SetLastError(NO_ERROR);
dieLastErr:
	CoUninitialize();
	return g.LastWaitExitCode=GetLastError();
}

DWORD InitIPC(HWND hwndNSIS,NSIS::extra_parameters*pXParams,UINT NSISStrLen) 
{
	if (g.threadIPC)return NO_ERROR;
	TRACEF("InitIPC StrSize=%u vs %u\n",NSIS::StrSize,NSISStrLen);
	DWORD tid;
	ASSERT(!g.pXParams && pXParams);
	ASSERT(NSISStrLen>0 && NSISStrLen==NSIS::StrSize);
	g.NSISStrLen=NSISStrLen;
	g.pXParams=pXParams;
	g.threadIPC=CreateThread(0,0,IPCSrvThread,hwndNSIS,0,&tid);
	if (g.threadIPC) 
	{
		while(!g.hSrvIPC && !g.LastWaitExitCode)Sleep(20);
		return g.hSrvIPC ? NO_ERROR : g.LastWaitExitCode;
	}
	return GetLastError();
}

void StopIPCSrv() 
{
	if (g.threadIPC) 
	{
		TRACEF("StopIPCSrv h=%X \n",g.hSrvIPC);
#ifdef UAC_HACK_Clammerz
		if ( GetSysVer()>=5 )
		{
			//WINBUGFIX: This ugly thing supposedly solves the problem of messagebox'es in .OnInit appearing behind other windows in Vista
			HWND hack=CreateWindowEx(WS_EX_TRANSPARENT|WS_EX_LAYERED,_T("Button"),NULL,NULL,0,0,0,0,NULL,NULL,NULL,0);
			ShowWindow(hack,SW_SHOW);
			SetForegroundWindow(hack);
			DestroyWindow(hack);		
		}
#endif
		PostMessage(g.hSrvIPC,WM_CLOSE,0,0);
		WaitForSingleObject(g.threadIPC,INFINITE);
		CloseHandle(g.threadIPC);
		UnregisterClass(GetIPCWndClass(),g.hInstance);//DLL can be loaded more than once, so make sure RegisterClass doesn't fail
		g.hSrvIPC=0;
		g.threadIPC=0;
	}
}

#ifdef UAC_HACK_FGWND1 
LRESULT CALLBACK HackWndSubProc(HWND hwnd,UINT Msg,WPARAM wp,LPARAM lp) 
{
	switch(Msg) 
	{
	case WM_APP:
		GetWindowText((HWND)lp,GetVar(0),NSIS::StrSize);
		if (*GetVar(0))SendMessage(hwnd,WM_SETTEXT,0,(LONG_PTR)GetVar(0));
		break;
	}
	return DefWindowProc(hwnd,Msg,wp,lp);
}
#endif

inline bool MustUseInternalRunAs() 
{ 
#ifdef BUILD_DBGSELECTELVMODE
	TCHAR dbgb[MAX_PATH*4];wsprintf(dbgb,_T("%s.ini"),GetVar(VIDX_EXEPATH));
	static int dbg_answer=GetPrivateProfileInt(_T("UACDBG"),_T("MustUseInternalRunAs"),2,dbgb);
	if (dbg_answer<2)return !!dbg_answer;WritePrivateProfileString(_T("UACDBG"),_T("MustUseInternalRunAs"),"",dbgb);
	if (MessageBox(GetActiveWindow(),"MustUseInternalRunAs?",dbgb,MB_YESNO)==IDYES)return true;
#endif
	return GetSysVer()>=6 && !SysElevationPresent(); 
}

DWORD ForkSelf(HWND hParent,DWORD&ForkExitCode,NSIS::extra_parameters*pXParams,UINT NSISStrLen) 
{
	DWORD ec=ERROR_ACCESS_DENIED;
	SHELLEXECUTEINFO sei={sizeof(sei)};
	STARTUPINFO startInfo={sizeof(STARTUPINFO)};
	LPTSTR pszExePathBuf=0;
	LPTSTR pszParamBuf=0;
	LPTSTR p,pCL=GetCommandLine();
	UINT len;
#ifdef UAC_HACK_FGWND1
	HWND hHack=0;
#endif
	ASSERT(pXParams);
	
	GetStartupInfo(&startInfo);
	if (ec=InitIPC(hParent,pXParams,NSISStrLen))goto ret;
	ASSERT(IsWindow(g.hSrvIPC));
	sei.hwnd=hParent;
	sei.nShow=(startInfo.dwFlags&STARTF_USESHOWWINDOW) ? startInfo.wShowWindow : SW_SHOWNORMAL;
	sei.fMask=SEE_MASK_NOCLOSEPROCESS|SEE_MASK_NOZONECHECKS;
	sei.lpVerb=_T("runas");
	p=FindExePathEnd(pCL);
	len=p-pCL;
	if (!p || !len) 
	{
		ec=ERROR_FILE_NOT_FOUND;
		goto ret;
	}
	for (;;) 
	{
		NSIS::MemFree(pszExePathBuf);
		if (!(pszExePathBuf=(LPTSTR)NSIS::MemAlloc((++len)*sizeof(TCHAR))))goto dieOOM;
		if ( GetModuleFileName(0,pszExePathBuf,len) < len )break; //FUCKO: what if GetModuleFileName returns 0?
		len+=MAX_PATH;
	}
	sei.lpFile=pszExePathBuf;	
	len=lstrlen(p);
	len+=20;//space for "/UAC:xxxxxx /NCRC\0"
	if (!(pszParamBuf=(LPTSTR)NSIS::MemAlloc(len*sizeof(TCHAR))))goto dieOOM;
	wsprintf(pszParamBuf,_T("/UAC:%X /NCRC%s"),g.hSrvIPC,p);//NOTE: The argument parser depends on our special flag appearing first
	sei.lpParameters=pszParamBuf;

#ifdef UAC_HACK_FGWND1
	if ( GetSysVer()>=5 && !sei.hwnd ) 
	{
		//sei.nShow=SW_SHOW;//forced, do we HAVE to do this?
		hHack=CreateWindowEx(WS_EX_TRANSPARENT|WS_EX_LAYERED|WS_EX_TOOLWINDOW|WS_EX_APPWINDOW,_T("Button"),GetVar(VIDX_EXEFILENAME),0|WS_MAXIMIZE,0,0,0,0,NULL,NULL,NULL,0);
		
		SetWindowLongPtr(hHack,GWLP_WNDPROC,(LONG_PTR)HackWndSubProc);
		if (GetSysVer()<6 || MustUseInternalRunAs())
			PostMessage(g.hSrvIPC,IPC_HACKFINDRUNAS,0,(LONG_PTR)hHack);
		else
			SetWindowLongPtr(hHack,GWL_EXSTYLE,GetWindowLongPtr(hHack,GWL_EXSTYLE)&~WS_EX_APPWINDOW);//kill taskbar btn on vista
		HICON hIcon=(HICON)LoadImage(GetModuleHandle(0),MAKEINTRESOURCE(103),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_SHARED);	
		SendMessage(hHack,WM_SETICON,ICON_BIG,(LONG_PTR)hIcon);
		ShowWindow(hHack,SW_SHOW);
		SetForegroundWindow(hHack);
		sei.hwnd=hHack;
	}
#endif

	if (hParent)SetForegroundWindow(hParent);//try to force taskbar button active (for RunElevatedAndProcessMessages, really important on Vista)

#ifdef FEAT_CUSTOMRUNASDLG
	if (MustUseInternalRunAs()) 
	{ 
		ec=MyRunAs(g.hInstance,sei);
	}
	else 
#endif
	{
		if (GetSysVer()>=6) 
		{
			////////sei.nShow=SW_SHOW;
			//if ( _SupportsUAC() )sei.hwnd=0; //Vista does not like it when we provide a HWND
			//if (_AllowSetForegroundWindow) _AllowSetForegroundWindow(ASFW_ANY);//TODO: is GrantClientWindowInput() enough?
		}
#ifdef FEAT_MSRUNASDLGMODHACK
		void* hook=MSRunAsDlgMod_Init();
#endif
		TRACEF("ForkSelf:calling ShExec:app=%s|params=%s|vrb=%s|hwnd=%X\n",sei.lpFile,sei.lpParameters,sei.lpVerb,sei.hwnd);
		ec=(ShellExecuteEx(&sei)?NO_ERROR:GetLastError());
		TRACEF("ForkSelf: ShExec->Runas returned %d hInstApp=%d\n",ec,sei.hInstApp);
#ifdef FEAT_MSRUNASDLGMODHACK
		MSRunAsDlgMod_Unload(hook);
#endif
	}
#ifdef UAC_HACK_FGWND1
	DestroyWindow(hHack);
#endif
	if (ec)goto ret;
	TRACEF("ForkSelf: waiting for process %X (%s|%s|%s)sei.hwnd=%X\n",(sei.hProcess),sei.lpFile,sei.lpParameters,sei.lpVerb,sei.hwnd);
	ASSERT(sei.hProcess);
	ASSERT(NO_ERROR==ec);
	ShowWindow(g.hSrvIPC,SW_HIDE);
	
	if (!IsWindow(sei.hwnd))
	{
		DWORD w=WaitForSingleObject(sei.hProcess,INFINITE);
		if (w==WAIT_OBJECT_0)
			VERIFY(GetExitCodeProcess(sei.hProcess,&ForkExitCode));
		else 
		{
			ec=GetLastError();
			TRACEF("ForkSelf:WaitForSingleObject failed ec=%d w=%d\n",ec,w);ASSERT(!"ForkSelf:WaitForSingleObject");
		}
	}
	else 
	{
		bool abortWait=false;
		const DWORD waitCount=1;
		const HANDLE handles[waitCount]={sei.hProcess};
		do 
		{
			DWORD w=MsgWaitForMultipleObjects(waitCount,handles,false,INFINITE,QS_ALLEVENTS|QS_ALLINPUT);
			switch(w)
			{
			case WAIT_OBJECT_0:
				VERIFY(GetExitCodeProcess(sei.hProcess,&ForkExitCode));
				abortWait=true;
				break;
			case WAIT_OBJECT_0+waitCount:
				{
					const HWND hwnd=sei.hwnd;
					MSG msg;
					while( !ec && PeekMessage(&msg,hwnd,0,0,PM_REMOVE) ) 
					{
						if (msg.message==WM_QUIT) 
						{
							ASSERT(0);
							ec=ERROR_CANCELLED;
							break;
						}
						if (!IsDialogMessage(hwnd,&msg)) 
						{
							TranslateMessage(&msg);
							DispatchMessage(&msg);
						}
					}
				}
				break;
			default:
				abortWait=true;
				ec=GetLastError();
				TRACEF("ForkSelf:MsgWaitForMultipleObjects failed, ec=%u w=%u\n",ec,w);
			}
		} while( NO_ERROR==ec && !abortWait );
	} 
	
	TRACEF("ForkSelf: wait complete, ec=%d forkexitcode=%u\n",ec,ForkExitCode);
	goto ret;
dieOOM:
	ec=ERROR_OUTOFMEMORY;
ret:
	StopIPCSrv();
	CloseHandle(sei.hProcess);
	NSIS::MemFree(pszExePathBuf);
	NSIS::MemFree(pszParamBuf);
	return ec;
}

bool GetIPCSrvWndFromParams(HWND&hwndOut) 
{
	LPTSTR p=FindExePathEnd(GetCommandLine());
	while(p && *p==' ')p=CharNext(p);TRACEF("GetIPCSrvWndFromParams:%s|\n",p);
	if (p && *p++=='/'&&*p++=='U'&&*p++=='A'&&*p++=='C'&&*p++==':') 
	{
		hwndOut=(HWND)StrToUInt(p,true);
		return !!IsWindow(hwndOut);
	}
	return false;
}


/*** RunElevated
Return:	r0:	Windows error code (0 on success, 1223 if user aborted elevation dialog, anything else should be treated as a fatal error)	
		r1: If r0==0, r1 is:
				0 if UAC is not supported by the OS, 
				1 if UAC was used to elevate and the current process should act like a wrapper (Call Quit in .OnInit without any further processing), 
				2 if the process is (already?) running @ HighIL (Member of admin group on other systems),
				3 if you should call RunElevated again (This can happen if a user without admin priv. is used in the runas dialog),
		r2: If r0==0 && r1==1: r2 is the ExitCode of the elevated fork process (The NSIS errlvl is also set to the ExitCode)
		r3: If r0==0: r3 is 1 if the user is a member of the admin group or 0 otherwise
*/
EXPORTNSISFUNC RunElevated(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);

	BYTE UACMode=0;
	bool UserIsAdmin=false;
	DWORD ec=ERROR_ACCESS_DENIED,ForkExitCode;
	TOKEN_ELEVATION_TYPE tet;
	
	UserIsAdmin=_IsAdmin();
	TRACEF("RunElevated:Init: IsAdmin=%d\n",UserIsAdmin);
#ifdef BUILD_XPTEST
	if (!UserIsAdmin)goto DbgXPAsUAC;//Skip UAC detection for special debug builds and force a call to runas on <NT6 systems
#endif
	if (!_SupportsUAC() && !SysSupportsRunAs())goto noUAC;
	if ((ec=DelayLoadDlls()))goto ret;
		
	if (GetIPCSrvWndFromParams(g.hSrvIPC)) 
	{
		if (ec=DllSelfAddRef())goto ret;
		if (!IsWindow(g.hSrvIPC))ec=ERRAPP_BADIPCSRV;
		UACMode=2;
		g.UseIPC=true;
		if (!UserIsAdmin) //Elevation used, but we are not Admin, let the wrapper know so it can try again...
		{ 		
			UACMode=0xFF;//Special invalid mode
			DWORD_PTR MsgRet;
			if (SendIPCMsg(IPC_ELEVATEAGAIN,0,0,MsgRet) || !MsgRet)ec=ERRAPP_BADIPCSRV;//if we could not notify the need for re-elevation, the IPCSrv must be bad
		}
		goto ret;
	}
	
	if ( (ec=DllSelfAddRef()) || (ec=_GetElevationType(&tet)) )goto ret;
	if ( tet==TokenElevationTypeFull || UserIsAdmin ) 
	{
		UserIsAdmin=true;
		UACMode=2;
		goto ret;
	}

	DBGONLY(DBG_RESETDBGVIEW());
	
#ifdef BUILD_XPTEST
DbgXPAsUAC:VERIFY(!DllSelfAddRef());
#endif
	//OS supports UAC and we need to elevate...
	ASSERT(!UserIsAdmin);
	UACMode=1;
	
	ec=ForkSelf(hwndNSIS,ForkExitCode,XParams,StrSize);
	if (!ec && !g.ElevateAgain) 
	{
		SetVarUINT(INST_2,ForkExitCode);
		SetErrLvl(XParams,ForkExitCode);
	}
	goto ret;
noUAC:
	ec=NO_ERROR;ASSERT(UACMode==0);
ret:
	if (ec==ERROR_CANCELLED) 
	{
		if (UACMode!=1)ec=ERROR_INVALID_FUNCTION;
		if (UACMode<2)g.UseIPC=false;
	}
	if (UACMode==0xFF && !ec) //We called IPC_ELEVATEAGAIN, so we need to quit so the wrapper gains control
	{
		ASSERT(g.UseIPC);
		UACMode=1;//We pretend to be the wrapper so Quit gets called in .OnInit
		SetErrLvl(XParams,0);
		_Unload();
	}
	if (g.ElevateAgain) 
	{
		ASSERT(!g.UseIPC);
		UACMode=3;//Fork called IPC_ELEVATEAGAIN, we need to change our UACMode so the wrapper(our instance) can try to elevate again if it wants to
	}
	if (!g.UseIPC)
		_Unload();//The wrapper can call quit in .OnInit without calling UAC::Unload, so we do it here
	
	SetVarUINT(INST_0,ec);
	SetVarUINT(INST_1,UACMode);
	SetVarUINT(INST_3,UserIsAdmin);
	TRACEF("RunElevated returning ec=%X UACMode=%d g.UseIPC=%d g.ElevateAgain=%d IsAdmin=%d\n",ec,UACMode,g.UseIPC,g.ElevateAgain,UserIsAdmin);

	NSISFUNCEND();
}



/*** Exec
Notes:
		� ErrorFlag is also set on error
STACK:	<ShowWindow> <File> <Parameters> <WorkingDir>
Return:	windows error code in r0, 0 on success
*/
EXPORTNSISFUNC Exec(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	HandleExecExport(true,false,hwndNSIS,StrSize,Vars,StackTop,XParams);
	NSISFUNCEND();
}

/*** ExecWait
Notes:
		� ErrorFlag is also set on error
STACK:	<ShowWindow> <File> <Parameters> <WorkingDir>
Return:	
		r0: windows error code, 0 on success
		r1: exitcode of new process 
*/
EXPORTNSISFUNC ExecWait(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	HandleExecExport(true,true,hwndNSIS,StrSize,Vars,StackTop,XParams);
	NSISFUNCEND();
}

/*** ShellExec
Notes:
		� ErrorFlag is also set on error
STACK:	<Verb> <ShowWindow> <File> <Parameters> <WorkingDir>
Return:	windows error code in r0, 0 on success
*/
EXPORTNSISFUNC ShellExec(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	HandleExecExport(false,false,hwndNSIS,StrSize,Vars,StackTop,XParams);
	NSISFUNCEND();
}

/*** ShellExecWait
Notes:
		� ErrorFlag is also set on error
STACK:	<Verb> <ShowWindow> <File> <Parameters> <WorkingDir>
Return:	
		r0: windows error code, 0 on success
		r1: exitcode of new process 
*/
EXPORTNSISFUNC ShellExecWait(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	HandleExecExport(false,true,hwndNSIS,StrSize,Vars,StackTop,XParams);
	NSISFUNCEND();
}


/*** GetElevationType
Notes:
		TokenElevationTypeDefault=1	:User is not using a split token (UAC disabled)
		TokenElevationTypeFull=2	:UAC enabled, the process is elevated
		TokenElevationTypeLimited=3	:UAC enabled, the process is not elevated
Return:	r0:	(TOKEN_ELEVATION_TYPE)TokenType, The error flag is set if the function fails and r0==0
*/
EXPORTNSISFUNC GetElevationType(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	TOKEN_ELEVATION_TYPE tet=(TOKEN_ELEVATION_TYPE)NULL; //Default to invalid value
	if (MaintainDllSelfRef() || /*!_SupportsUAC() ||*/ _GetElevationType(&tet)) SetErrorFlag(XParams);
	SetVarUINT(INST_0,tet);
	NSISFUNCEND();
}



/*** SupportsUAC
Notes:	Check if the OS supports UAC (And the user has UAC turned on)
Return:	r0:	(BOOL)Result 
*/
EXPORTNSISFUNC SupportsUAC(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	BOOL present=false;
	MaintainDllSelfRef();
	if (_SupportsUAC())present=true;	
	SetVarUINT(INST_0,present);
	NSISFUNCEND();
}


/*** IsAdmin
Return:	r0:	(BOOL)Result 
*/
EXPORTNSISFUNC IsAdmin(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	bool Admin=false;
	DWORD ec;
	if ( !(ec=MaintainDllSelfRef()) ) 
	{
		Admin=_IsAdmin();
		if ( ec=GetLastError() ) 
		{
			TRACEF("IsAdmin failed with %d\n",ec);
			SetErrorFlag(XParams);
			Admin=false;
		}
	}
	SetVarUINT(INST_0,Admin);
	NSISFUNCEND();
}



/*** ExecCodeSegment
Notes:	Sets error flag on error
		There is currently no way to transfer state to/from the executed code segment!
		If you use instructions that alter the UI or the stack/variables in the code segment (StrCpy,Push/Pop/Exch,DetailPrint,HideWindow etc.) they will affect the hidden wrapper installer and not "your" installer instance!
*/
EXPORTNSISFUNC ExecCodeSegment(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);

	DWORD ec;
	if (!(ec=DllSelfAddRef())) //force AddRef since our plugin could be called inside the executed code segment!
	{
		ec=ERROR_INVALID_PARAMETER;
		stack_t* pSI=StackPop();
		if (pSI) 
		{
			BOOL badCh;
			UINT pos=StrToUInt(pSI->text,false,&badCh);
			TRACEF("ExecCodeSegment %d (%s) badinput=%d\n",pos-1,pSI->text,badCh);
			if (!badCh && pos--) 
			{
				if (!g.UseIPC)
					ec=NSIS::ExecuteCodeSegment(XParams,pos);
				else 
				{
					SyncVars(hwndNSIS);
					DWORD_PTR MsgRet;
					AllowOuterInstanceWindowFocus();
					if (!(ec=SendIPCMsg(IPC_EXECCODESEGMENT,pos,0,MsgRet,INFINITE)))ec=MsgRet-1;
				}
			}
			StackFreeItem(pSI);
		}
	}
	if (ec)SetErrorFlag(XParams);

	NSISFUNCEND();
}



/*** StackPush   */
EXPORTNSISFUNC StackPush(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);

	DWORD ec;
	if (!(ec=DllSelfAddRef()) && g.UseIPC) 
	{
		stack_t* pSI=StackPop();
		ec=ERROR_INVALID_PARAMETER;
		if (pSI) 
		{
			DWORD_PTR MsgRet;
			COPYDATASTRUCT cds={CDI_STACKPUSH,(lstrlen(pSI->text)+1)*sizeof(NSISCH),pSI->text};
			if (!(ec=SendIPCMsg(WM_COPYDATA,(WPARAM)hwndNSIS,(LPARAM)&cds,MsgRet,5000 )))ec=MsgRet-1;
			StackFreeItem(pSI);
		}
	}
	if (ec)SetErrorFlag(XParams);

	NSISFUNCEND();
}



/*** GetOuterHwnd   */
EXPORTNSISFUNC GetOuterHwnd(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop,NSIS::extra_parameters* XParams) 
{
	NSISFUNCSTART(hwndNSIS,StrSize,Vars,StackTop,XParams);
	MaintainDllSelfRef();
	DWORD_PTR MsgRet;HWND hSrvIPC;
	if (!GetIPCSrvWndFromParams(hSrvIPC)||!IsWindow(hSrvIPC)||SendIPCMsg(IPC_GETSRVHWND,0,0,MsgRet,IPCTOUT_DEF,hSrvIPC))MsgRet=0;
	SetVarUINT(INST_0,MsgRet);
	NSISFUNCEND();
}



/*** Unload
Notes:	Call in .OnInstFailed AND .OnInstSuccess !
*/
EXPORTNSISFUNC Unload(HWND hwndNSIS,int StrSize,NSISCH*Vars,NSIS::stack_t **StackTop) 
{
	NSISFUNCSTART4(hwndNSIS,StrSize,Vars,StackTop);
	if (!MaintainDllSelfRef())_Unload(); else ASSERT(!"MaintainDllSelfRef failed in Unload!");
	NSISFUNCEND();
}



#ifdef _DEBUG
BOOL WINAPI DllMain(HINSTANCE hInst,DWORD Event,LPVOID lpReserved) 
#else
extern "C" BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInst,ULONG Event,LPVOID lpReserved) 
#endif
{
	if (Event==DLL_PROCESS_ATTACH) 
	{
		TRACEF("************************************ DllMain %u\n",GetCurrentProcessId());
		ASSERT(!_OpenProcessToken && !_EqualSid);
		g.hInstance=hInst;
	}
	DBGONLY( if (Event==DLL_PROCESS_DETACH){ASSERT(g.DllRef==0);}TRACE("DLL_PROCESS_DETACH\n"); );//Make sure we unloaded so we don't lock $pluginsdir
	return TRUE;
}