;################################################################

format PE GUI 4.0 DLL
entry DllMain

;################################################################

include '%fasminc%\win32ax.inc'

;################################################################

S_OK				equ 0
CP_ACP				equ 0
MAX_PATH			equ 260
REGDB_E_INVALIDVALUE		equ 0x80040153
ERROR_NO_MORE_ITEMS		equ 259
CLASS_E_CLASSNOTAVAILABLE	equ 0x80040111
CLASS_E_NOAGGREGATION		equ 0x80040110
E_OUTOFMEMORY			equ 0x8007000E
E_NOINTERFACE			equ 0x80004002
E_INVALIDARG			equ 0x80070057
DVASPECT_CONTENT		equ 1
CMF_DEFAULTONLY			equ 00000001h
GCS_HELPTEXTA			equ 1
GCS_HELPTEXTW			equ 5
TYMED_HGLOBAL			equ 1
SEVERITY_SUCCESS		equ 0
FACILITY_NULL			equ 0

;################################################################

section '.data' data readable writeable

;----------------------------------------------------------------;
; macros                                                         ;
;----------------------------------------------------------------;
macro		MAKE_HR severity,facility,code
{
		mov	eax,(severity shl 31) or (facility shl 16) or (code)
}

;----------------------------------------------------------------;
; declare GUID structure                                         ;
;----------------------------------------------------------------;
struct			GUID
Data1			dd ?
Data2			dw ?
Data3			dw ?
Data4			db 8 dup (?)
ends

;----------------------------------------------------------------;
; interfaces                                                     ;
;----------------------------------------------------------------;
interface	IClassFactory,\
		QueryInterface,\
		AddRef,\
		Release,\
		CreateInstance,\
		LockServer

interface	IShellExtInit,\
		QueryInterface,\
		AddRef,\
		Release,\
		Initialize

interface	IContextMenu,\
		QueryInterface,\
		AddRef,\
		Release,\
		QueryContextMenu,\
		InvokeCommand,\
		GetCommandString

interface	IDataObject,\
		QueryInterface,\
		AddRef,\
		Release,\
		GetData,\
		RemoteGetData,\
		GetDataHere,\
		RemoteGetDataHere,\
		QueryGetData,\
		GetCanonicalFormatEtc,\
		SetData,\
		RemoteSetData,\
		EnumFormatEtc,\
		DAdvise,\
		DUnadvise,\
		EnumDAdvise

;----------------------------------------------------------------;
; define the CLSID and IID                                       ;
;----------------------------------------------------------------;
CLSID_ShellExt		GUID <0x02a325c2>,<0x648f>,<0x11d5>,<0xa6,0x5a,0,0xe0,0x29,7,0x4d,0x77>
IID_IUnknown		GUID <0x00000000>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>
IID_IClassFactory	GUID <0x00000001>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>
IID_IContextMenu	GUID <0x000214e4>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>
IID_IShellExtInit	GUID <0x000214e8>,<0>,<0>,<0xc0,0,0,0,0,0,0,0x46>

;----------------------------------------------------------------;
; declare the IClassFactory object structure (static)            ;
;----------------------------------------------------------------;
struct			oCF
lpVtblIClassFactory	dd ?						; IClassFactory vtable
ICFRefCount		dd ?						; reference count
ends

;----------------------------------------------------------------;
; declare the IShellExtInit and ContextMenu objects structure    ;
;----------------------------------------------------------------;
struct			oSE
lpVtblShellExtInit	dd ?						; IShellExtInit vtable
lpVtblContextMenu	dd ?						; IContextMenu vtable
m_nRefCount		dd ?						; object reference count
m_hMenuString		dd ?
m_szFile		db MAX_PATH dup (?)
ends

;----------------------------------------------------------------;
; declare vtables                                                ;
;----------------------------------------------------------------;
IClassFactoryVT		dd QueryInterface_CF
			dd AddRef_CF
			dd Release_CF
			dd CreateInstance
			dd LockServer

IShellExtInitVT		dd QueryInterface
			dd AddRef_SEI
			dd Release_SEI
			dd Initialize

IContextMenuVT		dd QueryInterface
			dd AddRef_CM
			dd Release_CM
			dd QueryContextMenu
			dd InvokeCommand
			dd GetCommandString

;----------------------------------------------------------------;
; declare miscellaneous structures                               ;
;----------------------------------------------------------------;
struct			FORMATETC
cfFormat		dw ?
			dw ?
ptd			dd ?
dwAspect		dd ?
lindex			dd ?
tymed			dd ?
ends

struct			STGMEDIUM
tymed			dd ?
union
	hBitmap		dd ?
	hMetaFilePict	dd ?
	hEnhMetaFile	dd ?
	hGlobal		dd ?
	lpszFileName	dd ?
	pstm		dd ?
	pstg		dd ?
ends
pUnkForRelease		dd ?
ends

struct			MENUITEMINFOA
cbSize			dd ?
fMask			dd ?
fType			dd ?
fState			dd ?
wID			dd ?
hSubMenu		dd ?
hbmpChecked		dd ?
hbmpUnchecked		dd ?
dwItemData		dd ?
dwTypeData		dd ?
cch			dd ?
ends

struct			CMINVOKECOMMANDINFO
cbSize			dd ?
fMask			dd ?
hwnd			dd ?
lpVerb			dd ?
lpParameters		dd ?
lpDirectory		dd ?
nShow			dd ?
dwHotKey		dd ?
hIcon			dd ?
ends

;----------------------------------------------------------------;
; other data                                                     ;
;----------------------------------------------------------------;
szSampleDesc		db 'Sample shell extention server',0
szInprocServer32	db 'InProcServer32',0
szServerRegPath		db '*\shellex\ContextMenuHandlers\CustomContext',0
szThreadModel		db 'ThreadingModel',0
szThreadType		db 'Apartment',0
szCLSID			db 'CLSID',0
szMenuString		db '&Display file name',0
szContextMenuHandler	db 'Context Menu Handler',0
szHelpText		db 'Displays selected file name',0

g_cRefThisDll		dd ?						; DLL reference count
g_hmodThisDll		dd ?						; DLL hInstance
pszFilePath 		dd ?
BufLen			dd ?

pICF			oCF IClassFactoryVT,0

;################################################################

section '.code' code readable executable

;----------------------------------------------------------------;
; DLL entry point                                                ;
;----------------------------------------------------------------;
proc		DllMain hinstDLL,fdwReason,lpvReserved
		.if	[fdwReason] = DLL_PROCESS_ATTACH
			mov	eax,[hinstDLL]
			mov	[g_hmodThisDll],eax
			mov	eax,TRUE
		.elseif
			mov	eax,FALSE
		.endif
		ret
endp

;----------------------------------------------------------------;
; DllRegisterServer                                              ;
;----------------------------------------------------------------;
proc		DllRegisterServer
local		hKey:DWORD,\
		hKey2:DWORD,\
		hKey3:DWORD,\
		sBuf[MAX_PATH]:BYTE,\
		wsBuf[MAX_PATH]:WORD,\
		psBuf:DWORD,\
		pwsBuf:DWORD,\
		pti:DWORD

; assign the pointers to the local text buffers
		lea	eax,[sBuf]
		mov	[psBuf],eax
		lea	eax,[wsBuf]
		mov	[pwsBuf],eax

; convert GUID
		invoke	StringFromGUID2,CLSID_ShellExt,[pwsBuf],MAX_PATH
		invoke	WideCharToMultiByte,CP_ACP,0,[pwsBuf],-1,[psBuf],MAX_PATH,0,0

; create HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\%ServerName%
		lea	eax,[hKey]
		invoke	RegCreateKey,HKEY_CLASSES_ROOT,szServerRegPath,eax
		.if	eax <> 0
			jmp	@f
		.endif

; set Default value as GUID
		invoke	lstrlen,[psBuf]
		invoke	RegSetValue,[hKey],0,REG_SZ,[psBuf],eax
		.if	eax <> 0
			jmp	@f
		.endif

; close key
		invoke	RegCloseKey,[hKey]

; open HKEY_CLASSES_ROOT\CLSID
		lea	eax,[hKey]
		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,eax
		.if	eax <> 0
			jmp	@f
		.endif

; create HKEY_CLASSES_ROOT\CLSID\%GUID%
		lea	eax,[hKey2]
		invoke	RegCreateKey,[hKey],[psBuf],eax
		.if	eax <> 0
			jmp	@f
		.endif

; set Default value as GUID
		invoke	lstrlen,[psBuf]
		invoke	RegSetValue,[hKey2],0,REG_SZ,[psBuf],eax
		.if	eax <> 0
			jmp	@f
		.endif

; create HKEY_CLASSES_ROOT\CLSID\%GUID%\InprocServer32
		lea	eax,[hKey3]
		invoke	RegCreateKey,[hKey2],szInprocServer32,eax
		.if	eax <> 0
			jmp	@f
		.endif

; get module file path
		invoke	GetModuleFileName,[g_hmodThisDll],[psBuf],MAX_PATH
		.if	eax = 0
			mov	eax,1
			jmp	@f
		.endif

; set Default value as module file path
		invoke	RegSetValue,[hKey3],0,REG_SZ,[psBuf],MAX_PATH
		.if	eax <> 0
			jmp	@f
		.endif

; set the threading type value
		invoke	lstrlen,szThreadType
		invoke	RegSetValueEx,[hKey3],szThreadModel,0,REG_SZ,szThreadType,eax
		.if	eax <> 0
			jmp	@f
		.endif

; close keys
		invoke	RegCloseKey,[hKey3]
		.if	[hKey] <> 0
			invoke	RegCloseKey,[hKey]
		.endif
		.if	[hKey2] <> 0
			invoke	RegCloseKey,[hKey2]
		.endif
		.if	[hKey3] <> 0
			invoke	RegCloseKey,[hKey3]
		.endif
		mov	eax,S_OK
@@:		ret
endp

;----------------------------------------------------------------;
; DllUnregisterServer                                            ;
;----------------------------------------------------------------;
proc		DllUnregisterServer
local		hSubkey:DWORD,\
		hSubkey2:DWORD,\
		sBuf[MAX_PATH]:BYTE,\
		psBuf:DWORD,\
		wsBuf[MAX_PATH]:WORD,\
		pwsBuf:DWORD

; assign the pointers to the local text buffers
		lea	eax,[sBuf]
		mov	[psBuf],eax
		lea	eax,[wsBuf]
		mov	[pwsBuf],eax

; convert GUID
		invoke	StringFromGUID2,CLSID_ShellExt,[pwsBuf],MAX_PATH
		invoke	WideCharToMultiByte,CP_ACP,0,[pwsBuf],-1,[psBuf],MAX_PATH,0,0

; recursively delete *\shellex\ContextMenuHandlers\CustomContext
		stdcall	GuardedDeleteKey,HKEY_CLASSES_ROOT,szServerRegPath
		.if	eax <> 0
			jmp	@f
		.endif

; open HKEY_CLASSES_ROOT\CLSID
		lea	eax,[hSubkey]
		invoke	RegOpenKey,HKEY_CLASSES_ROOT,szCLSID,eax

; recursively delete HKEY_CLASSES_ROOT\CLSID\%GUID%
		stdcall	GuardedDeleteKey,[hSubkey],[psBuf]
		.if	eax <> 0
			jmp	@f
		.endif

; close keys
		invoke	RegCloseKey,[hSubkey]
		mov	eax,S_OK
@@:		ret
endp

;----------------------------------------------------------------;
; GuardedDeleteKey                                               ;
;----------------------------------------------------------------;
proc		GuardedDeleteKey hKey,lpszSubKey
local		szSubKeyName[MAX_PATH+1]:BYTE,\
		hSubkey:DWORD

; open \key\subkey
		lea	eax,[hSubkey]
		invoke	RegOpenKey,[hKey],[lpszSubKey],eax
		.if	eax <> 0
			mov	eax,REGDB_E_INVALIDVALUE
			jmp	@f
		.endif

KillNextSubkey:	lea	eax,[szSubKeyName]				; check for a subkey
		invoke	RegEnumKey,[hSubkey],0,eax,MAX_PATH+1
		.if	eax <> ERROR_NO_MORE_ITEMS			; subkeys exist, delete the subkey throught recusion
			lea	eax,[szSubKeyName]
			stdcall	GuardedDeleteKey,[hSubkey],eax
			jmp	KillNextSubkey
		.else
		.endif
		invoke	RegCloseKey,[hSubkey]

; no more subkeys, delete the specfied key
		invoke	RegDeleteKey,[hKey],[lpszSubKey]
		.if	eax = 0
			mov	eax,S_OK
		.endif
@@:		ret
endp

;----------------------------------------------------------------;
; DllCanUnloadNow                                                ;
;----------------------------------------------------------------;
proc		DllCanUnloadNow
		mov	ecx,[g_cRefThisDll]
		xor	eax,eax
		test	ecx,ecx						; if global reference count = 0
		setne	al						; set eax to 0
		ret
endp

;----------------------------------------------------------------;
; DllGetClassObject                                              ;
;----------------------------------------------------------------;
proc		DllGetClassObject pCLSID,pIID,ppv
local		hr:DWORD

		invoke	IsEqualGUID,[pCLSID],CLSID_ShellExt
		.if	eax = TRUE
			inc	[g_cRefThisDll]				; increase global reference count

			mov	eax,pICF				; get requested pointer (QueryInterface,pIID,ppv)
			push	[ppv]
			push	[pIID]
			push	eax
			mov	eax,[eax]
			call	[eax + IClassFactory.QueryInterface]
			mov	[hr],eax				; return result of QueryInterface
		.else
			mov	[hr],CLASS_E_CLASSNOTAVAILABLE
		.endif
		mov	eax,[hr]
		ret
endp

;----------------------------------------------------------------;
; IClassFactory::QueryInterface                                  ;
;----------------------------------------------------------------;
proc		QueryInterface_CF this,pRIID,ppv
local		TEMP:DWORD

		invoke	IsEqualGUID,[pRIID],IID_IUnknown
		mov	[TEMP],eax
		invoke	IsEqualGUID,[pRIID],IID_IClassFactory
		or	eax,[TEMP]
			.if	eax = TRUE				; asking for IUnknown or IClassFactory
				mov	eax,[this]
				mov	edx,[ppv]
				mov	[edx],eax			; so we point to ourselves

				push	eax
				mov	eax,[eax]
				call	[eax + IClassFactory.AddRef]	; increase reference count for new pointer

				mov	eax,S_OK			; signal all is well
				jmp	@f
			.endif
		mov	[ppv],0						; set pointer as NULL (bad)
		mov	eax,E_NOINTERFACE				; signal interface not supported
@@:		ret
endp

;----------------------------------------------------------------;
; IClassFactory::AddRef                                          ;
;----------------------------------------------------------------;
proc		AddRef_CF this
		inc	[pICF.ICFRefCount]
		mov	eax,[pICF.ICFRefCount]
		ret
endp

;----------------------------------------------------------------;
; IClassFactory::Release                                         ;
;----------------------------------------------------------------;
proc		Release_CF this
		dec	[pICF.ICFRefCount]
		mov	eax,[pICF.ICFRefCount]
		.if	eax = 0
			dec	[g_cRefThisDll]
		.endif
		ret
endp

;----------------------------------------------------------------;
; IClassFactory::CreateInstance                                  ;
;----------------------------------------------------------------;
proc		CreateInstance this,pUnknownOuter,iid,ppv
local		hr:DWORD

		.if	[pUnknownOuter] <> 0
			mov	eax,CLASS_E_NOAGGREGATION		; no support for aggregation
		.else
			invoke	CoTaskMemAlloc,sizeof.oSE		; create new ShellExt object
			.if	eax <> 0
				mov	[eax + oSE.lpVtblShellExtInit],IShellExtInitVT	; initialize new object
				mov	[eax + oSE.lpVtblContextMenu],IContextMenuVT
				mov	[eax + oSE.m_nRefCount],0
				mov	[eax + oSE.m_hMenuString],szMenuString
				inc	[g_cRefThisDll]
			.endif
			.if	eax = 0					; create failed
				mov	eax,E_OUTOFMEMORY
			.else
				inc	[pICF.ICFRefCount]		; increase reference count of new object

				push	[ppv]				; query new object for client-requested interface
				push	[iid]
				push	eax
				mov	eax,[eax]
				call	[eax + IShellExtInit.QueryInterface]
				mov	[hr],eax
			.endif
		.endif
		mov	eax,[hr]
		ret
endp

;----------------------------------------------------------------;
; IClassFactory::LockServer                                      ;
;----------------------------------------------------------------;
proc		LockServer bLockServer
		xor	eax,eax
		ret
endp

;----------------------------------------------------------------;
; IShellExtInit::QueryInterface & IContextMenu::QueryInterface   ;
;----------------------------------------------------------------;
proc		QueryInterface this,pRIID,ppv
local		TEMP:DWORD,\
		ppvt:DWORD

		invoke	IsEqualGUID,[pRIID],IID_IShellExtInit		; check for supported interfaces
		mov	[TEMP],eax
		invoke	IsEqualGUID,[pRIID],IID_IUnknown
		or	eax,[TEMP]
		.if	eax = TRUE					; asking for IUnknown or IShellExtInit
			mov	eax,[this]
			mov	edx,[ppv]
			mov	[edx],eax				; so we point to ourselves
			mov	[ppvt],eax				; *ppv = (LPSHELLEXTINIT)this;

			mov	eax,[ppvt]				; increase reference count for new pointer
			push	eax
			mov	eax,[eax]
			call	[eax + IShellExtInit.AddRef]

			mov	eax,S_OK				; signal all is well
			jmp	@f
		.endif

		invoke	IsEqualGUID,[pRIID],IID_IContextMenu
		.if	eax = TRUE					; asking for IContextMenu
			mov	eax,[this]				; increase reference count for new pointer
			push	eax
			mov	eax,[eax]
			call	[eax + IContextMenu.AddRef]

			mov	edx,[ppv]
			mov	eax,[this]
			add	eax,4					; interface cast
			mov	[edx],eax
			mov	[ppvt],eax				; *ppv = (LPCONTEXTMENU)this;

			mov	eax,S_OK				; signal all is well
			jmp	@f
		.endif

		mov	edx,[ppv]					; return NULL (bad) pointer
		mov	dword [edx],0
		mov	eax,E_NOINTERFACE				; signal interface is not supported
@@:		ret
endp

;----------------------------------------------------------------;
; IShellExtInit::AddRef                                          ;
;----------------------------------------------------------------;
proc		AddRef_SEI this
		mov	eax,[this]
		inc	[eax + oSE.m_nRefCount]
		mov	eax,[eax + oSE.m_nRefCount]
		ret							; return object count
endp

;----------------------------------------------------------------;
; IShellExtInit::Release                                         ;
;----------------------------------------------------------------;
proc		Release_SEI this
		mov	eax,[this]
		dec	[eax + oSE.m_nRefCount]
		mov	eax,[eax + oSE.m_nRefCount]
		.if	eax = 0						; reference count has dropped to zero, delete it
			invoke	CoTaskMemFree,[this]
			dec	[pICF.ICFRefCount]
			xor	eax,eax					; count = 0
		.endif
		ret							; return object count
endp

;----------------------------------------------------------------;
; IShellExtInit::Initialize                                      ;
;----------------------------------------------------------------;
proc		Initialize this,pidlFolder,pdtobj,hkeyProgID
local		fmt:FORMATETC,\
		stg:STGMEDIUM,\
		HRESULT:DWORD

		mov	eax,[this]

		lea	eax,[eax + oSE.m_szFile]
		mov	[pszFilePath],eax

		mov	[fmt.cfFormat],CF_HDROP				; initialize FORMATETC structure
		mov	[fmt.ptd],0
		mov	[fmt.dwAspect],DVASPECT_CONTENT
		mov	[fmt.lindex],-1
		mov	[fmt.tymed],TYMED_HGLOBAL
		mov	[stg.tymed],TYMED_HGLOBAL			; initialize STGMEDIUM structure

		lea	eax,[stg]					; pointer to STGMEDIUM structure
		push	eax
		lea	eax,[fmt]					; pointer to FORMATETC structure
		push	eax
		mov	eax,[pdtobj]					; pointer to an IDataObject interface object
		push	eax
		mov	eax,[eax]
		call	[eax + IDataObject.GetData]			; look for CF_HDROP data in the data object

		test	eax,eax						; S_OK?
		jns	@f
		mov	[HRESULT],E_INVALIDARG
		jmp	InitEnd

@@:		cmp	[stg.hGlobal],0					; valid global handle?
		jne	@f
		mov	[HRESULT],E_INVALIDARG
		jmp	InitEnd

@@:		invoke	DragQueryFile,[stg.hGlobal],-1,0,0		; get file count
		test	eax,eax						; no files?
		jne	@f
		lea	eax,[stg]					; release medium and end on error
		invoke	ReleaseStgMedium,eax
		mov	[HRESULT],E_INVALIDARG
		jmp	InitEnd

@@:		invoke	DragQueryFile,[stg.hGlobal],0,[pszFilePath],MAX_PATH	; get filename 0
		test	eax,eax						; return value = copied characters count
		jne	@f
		mov	[HRESULT],E_INVALIDARG
		jmp	InitEnd

@@:		lea	eax,[stg]					; release medium and end on success
		invoke	ReleaseStgMedium,eax
		mov	[HRESULT],S_OK

InitEnd:	mov	eax,[HRESULT]
		ret
endp

;----------------------------------------------------------------;
; IContextMenu::AddRef                                           ;
;----------------------------------------------------------------;
proc		AddRef_CM this
		mov	eax,[this]
		sub	eax,oSE.lpVtblContextMenu			; adjust 'this'
		inc	[eax + oSE.m_nRefCount]
		mov	eax,[eax + oSE.m_nRefCount]
		ret							; return object count
endp

;----------------------------------------------------------------;
; IContextMenu::Release                                          ;
;----------------------------------------------------------------;
proc		Release_CM this
		mov	eax,[this]
		sub	eax,oSE.lpVtblContextMenu			; adjust 'this'
		dec	[eax + oSE.m_nRefCount]
		mov	eax,[eax + oSE.m_nRefCount]
		.if	eax = 0						; reference count has dropped to zero, delete it
			invoke	CoTaskMemFree,[this]			; ????
			dec	[pICF.ICFRefCount]
			xor	eax,eax					; count = 0
		.endif
		ret							; return object count
endp

;----------------------------------------------------------------;
; IContextMenu::QueryContextMenu                                 ;
;----------------------------------------------------------------;
proc		QueryContextMenu uses esi,\
		this,hMenu,indexMenu,idCmdFirst,idCmdLast,uFlags
local		HRESULT:DWORD,\
		mii:MENUITEMINFOA,\
		iCmd:DWORD

		mov	eax,[this]
		sub	eax,oSE.lpVtblContextMenu			; adjust 'this'

		test	[uFlags],CMF_DEFAULTONLY			; user selected default command?
		je	@f
		MAKE_HR	SEVERITY_SUCCESS,FACILITY_NULL,0
		mov	[HRESULT],eax
		jmp	QueryCMEnd					; return control to the shell

@@:		lea	esi,[mii]
		mov	[esi + MENUITEMINFOA.cbSize],sizeof.MENUITEMINFOA
		mov	[esi + MENUITEMINFOA.fMask],(MIIM_ID+MIIM_STRING)
		push	[idCmdFirst]
		pop	[esi + MENUITEMINFOA.wID]
		push	[eax + oSE.m_hMenuString]
		pop	[esi + MENUITEMINFOA.dwTypeData]

		invoke	InsertMenuItem,[hMenu],[indexMenu],TRUE,esi

		MAKE_HR	SEVERITY_SUCCESS,FACILITY_NULL,1
		mov	[HRESULT],eax

QueryCMEnd:	mov	eax,[HRESULT]
		ret
endp

;----------------------------------------------------------------;
; IContextMenu::InvokeCommand                                    ;
;----------------------------------------------------------------;
proc		InvokeCommand uses esi,\
		this,lpcmi
local		HRESULT:DWORD

		mov	esi,[lpcmi]					; if lpVerb points to a string, ignore function
		mov	ecx,[esi + CMINVOKECOMMANDINFO.lpVerb]
		push	ecx
		mov	eax,ecx						; retrieve high word from double word argument
		shr	eax,16						; shift 16 for high word to set to high word
		.if	eax <> 0
			mov	[HRESULT],E_INVALIDARG
			jmp	InvkCmdEnd
		.endif

		pop	ecx						; get the command index
		mov	eax,ecx						; retrieve low word from double word argument
	  	and	eax,0xffff					; set to low word
		.if	eax  = 0					; 0 (only one menu item so far)
			invoke	MessageBoxEx,[esi + CMINVOKECOMMANDINFO.hwnd],[pszFilePath],szContextMenuHandler,MB_OK,0
			mov	[HRESULT],S_OK
		.else
			mov	[HRESULT],E_INVALIDARG
		.endif
InvkCmdEnd:	mov	eax,[HRESULT]
		ret
endp

;----------------------------------------------------------------;
; IContextMenu::GetCommandString                                 ;
;----------------------------------------------------------------;
proc		GetCommandString uses esi edi,\
		this,idCmd,uFlags,Sreserved,pszName,cchMax
local		HRESULT:DWORD,\
		wcszHelpString[MAX_PATH]:WORD

		mov	eax,[idCmd]					; idCmd = offset from idCmdFirst
		.if	eax = 0						; 0 (only one menu item so far)
			mov	esi,szHelpText
			.if	[uFlags] = GCS_HELPTEXTA
				invoke	lstrcpyn,[pszName],esi,[cchMax]
			.elseif	[uFlags] = GCS_HELPTEXTW
				lea	edi,[wcszHelpString]
				invoke	WideCharToMultiByte,CP_ACP,0,esi,-1,edi,[BufLen],0,0
				mov	ecx,eax
				invoke	lstrcpynW,[pszName],ecx,[cchMax]
			.endif
		mov	[HRESULT],S_OK
		.else
			mov	[HRESULT],E_INVALIDARG
		.endif
		mov	eax,[HRESULT]
		ret
endp

;################################################################

section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',user32,'USER32.DLL',shell32,'SHELL32.DLL',advapi32,'ADVAPI32.DLL',ole32,'OLE32.DLL'

include '%fasminc%\api\kernel32.inc'
include '%fasminc%\api\user32.inc'
include '%fasminc%\api\shell32.inc'
include '%fasminc%\api\advapi32.inc'

import ole32,\
       StringFromGUID2,'StringFromGUID2',\
       IsEqualGUID,'IsEqualGUID',\
       CoTaskMemAlloc,'CoTaskMemAlloc',\
       CoTaskMemFree,'CoTaskMemFree',\
       ReleaseStgMedium,'ReleaseStgMedium'

;################################################################

section '.edata' export data readable
export 'dll.dll',\
	DllCanUnloadNow,'DllCanUnloadNow',\
	DllGetClassObject,'DllGetClassObject',\
	DllRegisterServer,'DllRegisterServer',\
	DllUnregisterServer,'DllUnregisterServer'

;################################################################

section '.reloc' fixups data discardable

;################################################################