////////////////////////////////////////////////////////////////////////////
//
//	cmiquery.c
//
////////////////////////////////////////////////////////////////////////////
#define CMIQUERY_IMPLEMENTATION
#include <windows.h>
#include "wdcapi.h"
#include "win32cmi.h"

typedef struct
{
	short fci;
	short dlgOpts;
	short cArgs;
	short cRet;
	short retVal;
} CCI; // C-API Command Information

short 	vcbWdoprSize = sizeof(WDOPR);

///////////////////////////////////////////////////////////////////////////
#define PATHNAME_TIMEOUT	60000	// Wait 1 min for data from other process
#define SHARED_DATA_SIZE	131072	// Size of shared memory block 
///////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
struct shared
{
	HANDLE hEvt;
	BYTE abShared[];
};							// Template for memory-mapped file

///////////////////////////////////////////////////////////////////////////
#pragma data_seg(".sdata")
static struct
{
	HHOOK hHook;
	UINT wmIPC;
	DWORD idHookedThread;
} vshared = {0};			// "Set once" data shared by all processes.
#pragma data_seg()

static struct 
{
	BOOL fHookerProcess;
	HANDLE hEvt;
	HANDLE hMapping;
	struct shared* lpShared;
} vhooker;					// Data used only by hooking process

static HINSTANCE hinstDll;	// Data with different values in each process
///////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK LGetMsgProc(int, WPARAM, LPARAM);
HANDLE HDupRemoteHandle(HANDLE, HANDLE);
short cItemsFromArrayDef (short *pArrayDef);
void FCmiBlockFromWdoprs (LPVOID lpMem, short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn);
void FWdoprFromCmiBlock (LPVOID lpMem, short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn);
LPVOID lpvCmiBlockFromWdoprExtra (LPVOID lpMem, LPWDOPR lpwdopr);
LPVOID lpvWdoprFromCmiBlockExtra (LPVOID lpMem, LPWDOPR lpwdopr);
BYTE *LpbSetUpCmiPointers( LPWDOPR lpwopr, BYTE *lpMem);
int CbOfWdoprs (short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn);
int CbExtraWdoprSize (LPWDOPR lpwdopr);
///////////////////////////////////////////////////////////////////////////


/* D L L  M A I N */
/*----------------------------------------------------------------------------
    %%Function: DllMain

    Called by OS when DLL is loaded and unloaded.
----------------------------------------------------------------------------*/
BOOL WINAPI DllMain (HINSTANCE hinstDllThis, DWORD dwReason, LPVOID lpvRsvd)
{
	switch (dwReason)
		{
	case DLL_PROCESS_ATTACH:			
		hinstDll = hinstDllThis;
		if (vshared.wmIPC == WM_NULL)
			vshared.wmIPC = RegisterWindowMessage("IPC.Window.Message");
		break;

	case DLL_PROCESS_DETACH:
		if (vshared.hHook != NULL && vhooker.fHookerProcess)
			{
			UnhookWindowsHookEx(vshared.hHook);
			vshared.hHook = NULL;
			vshared.idHookedThread = 0;
			}
		if (vhooker.hEvt != NULL)
			CloseHandle(vhooker.hEvt);
		if (vhooker.hMapping != NULL)
			CloseHandle(vhooker.hMapping);
		if (vhooker.lpShared != NULL)
			UnmapViewOfFile(vhooker.lpShared);
		break;
		}

	return TRUE;
}



/* CMI COMMAND DISPATCH */
/*----------------------------------------------------------------------------
    %%Function: cmiCommandDispatch

    Send a C-API message to Word
----------------------------------------------------------------------------*/
CMIQUERYAPI short WINAPI cmiCommandDispatch(short fci, short dlgOpts, short cArgs,
                                    LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn)
{
	DWORD idThd, idProc;
	DWORD dwCode = ~WAIT_OBJECT_0;
	HWND hwndWord = FindWindow("OpusApp", NULL);
	CCI cci;
	BYTE *lpbCmiBlock;


	// make sure Word is running
	if  (!IsWindow (hwndWord) || !IsWindowVisible(hwndWord))
		return -1;

	// Make sure the shared memory block is big enough
	if (CbOfWdoprs(cArgs, lpwdoprArgs, lpwdoprReturn) > SHARED_DATA_SIZE - sizeof (HANDLE))
		return -2;

	idThd = GetWindowThreadProcessId(hwndWord, NULL);
	if (idThd == 0)
		return -3;

	if (vshared.hHook == NULL)
		{
		vshared.hHook = SetWindowsHookEx(WH_GETMESSAGE,LGetMsgProc,hinstDll,idThd);
		if (vshared.hHook == NULL)
			return -4;

		vhooker.fHookerProcess = TRUE;
		vshared.idHookedThread = idThd;
		}
	if (idThd != vshared.idHookedThread)
		return -5;


	if (vhooker.hEvt == NULL)
		{
		vhooker.hEvt = CreateEvent(NULL,FALSE,FALSE,NULL);
		if (vhooker.hEvt == NULL)
			return -6;
		}

	if (vhooker.hMapping == NULL)
		{
		vhooker.hMapping = CreateFileMapping(INVALID_HANDLE_VALUE,
			NULL, PAGE_READWRITE, 0, SHARED_DATA_SIZE, NULL);
		if (vhooker.hMapping == NULL)
			return -7;
		}

	if  (vhooker.lpShared == NULL)
		{
		vhooker.lpShared = MapViewOfFile(vhooker.hMapping, 
			FILE_MAP_ALL_ACCESS, 0,0,0);

		if (vhooker.lpShared == NULL)
			return -8;

		vhooker.lpShared->hEvt = vhooker.hEvt;
 		}

	cci.fci = fci;
	cci.dlgOpts = dlgOpts;
	cci.cArgs = cArgs;
	cci.cRet = lpwdoprReturn == lpwdoprNil ? 0 : 1;
	cci.retVal = 0;

	lpbCmiBlock = vhooker.lpShared->abShared;
	memcpy((LPVOID)lpbCmiBlock, (LPVOID)&cci, sizeof(CCI));

	if (cArgs > 0 || cci.cRet)
		FCmiBlockFromWdoprs (lpbCmiBlock + sizeof(CCI), cArgs, lpwdoprArgs, lpwdoprReturn);

	idProc = GetCurrentProcessId();
	PostMessage(hwndWord, vshared.wmIPC, (WPARAM)idProc,(LPARAM)vhooker.hMapping);

	dwCode = WaitForSingleObject(vhooker.hEvt, PATHNAME_TIMEOUT);
	if (dwCode == WAIT_OBJECT_0)
		{
		memcpy((LPVOID)&cci, (LPVOID)lpbCmiBlock, sizeof(CCI));

		// Get WDOPR info from shared memory
		FWdoprFromCmiBlock (lpbCmiBlock + sizeof(CCI), cArgs, lpwdoprArgs, lpwdoprReturn);

		return cci.retVal;
		}
	return -9;
}


/* L G E T  M S G  P R O C */
/*----------------------------------------------------------------------------
    %%Function: LGetMsgProc

    Hook function -- runs in context of hooked process.
----------------------------------------------------------------------------*/
LRESULT CALLBACK LGetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	HANDLE hProcHooker, hMapping, hMappingHooker, hEvtHooker, hEvt;
	struct shared* lpShared;
	LRESULT lStat;
	DWORD idProcHooker;
	CCI *pcci;
	LPWDOPR lpwdopr;
	LPWDOPR	lpwdoprArgs;
	LPWDOPR lpwdoprReturn;
	BYTE *lpbExtraInfo;
	short i;
	static short (WINAPI *pfn_wdCommandDispatch) ();

	if (nCode < 0)
		return CallNextHookEx(vshared.hHook, nCode, wParam, lParam);

	hProcHooker = NULL;
	hMapping = NULL;
	hMappingHooker = NULL;
	hEvtHooker = NULL;
	hEvt = NULL;
	lpShared = NULL;

	if (((LPMSG)lParam)->message == vshared.wmIPC && wParam != PM_NOREMOVE)
		{
		idProcHooker = (DWORD)((LPMSG)lParam)->wParam;
		hMappingHooker = (HANDLE)((LPMSG)lParam)->lParam;

		hProcHooker = OpenProcess(PROCESS_DUP_HANDLE,FALSE,idProcHooker);
		if (hProcHooker!=NULL)
			{
			hMapping = HDupRemoteHandle(hProcHooker,hMappingHooker);
			if (hMapping != NULL)
				{
				lpShared = MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0);
				if (lpShared != NULL)
					{
					hEvt = HDupRemoteHandle(hProcHooker,lpShared->hEvt);
					if (hEvt != NULL)
						{
						pcci = (CCI *)lpShared->abShared;

						// Set lpwoprArgs to the beginning of the mem block, then set
						// lpExtraInfo to point after all the WDOPRs (arguments)
						lpwdoprArgs = (LPWDOPR)(pcci + 1);
						lpbExtraInfo = (BYTE *)lpwdoprArgs + (pcci->cArgs * sizeof(WDOPR));

						// Set up  pointers	on CMI memory block. Arguments first.
						for (i=0, lpwdopr=lpwdoprArgs; i<pcci->cArgs; i++, lpwdopr++)
							lpbExtraInfo = LpbSetUpCmiPointers(lpwdopr, lpbExtraInfo);

						// Now the return
						if (pcci->cRet == 1)
							{
							lpwdoprReturn = (LPWDOPR)lpbExtraInfo;
							lpbExtraInfo += sizeof (WDOPR);
							LpbSetUpCmiPointers(lpwdoprReturn, lpbExtraInfo);
							}
						else
							lpwdoprReturn = lpwdoprNil;

						// Make the call
						if (pfn_wdCommandDispatch == NULL)
							(FARPROC) pfn_wdCommandDispatch = GetProcAddress(GetModuleHandle(NULL), "wdCommandDispatch");
						if (pfn_wdCommandDispatch)
							lStat = pcci->retVal = ((*pfn_wdCommandDispatch)(pcci->fci, pcci->dlgOpts, pcci->cArgs, lpwdoprArgs, lpwdoprReturn));
						else
							lStat = pcci->retVal = -1;

						CallNextHookEx(vshared.hHook, nCode, wParam, lParam);
						}
					}
				}
			}
		}
	else
		lStat = CallNextHookEx(vshared.hHook, nCode, wParam, lParam);

	if (hEvt)
		{
		SetEvent(hEvt);
		CloseHandle(hEvt);
		}
	if (lpShared)
		UnmapViewOfFile(lpShared);
	if (hMapping)
		CloseHandle(hMapping);
	if (hProcHooker)
		CloseHandle(hProcHooker);

	return lStat;
}

void FWdoprFromCmiBlock (LPVOID lpMem, short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn)
{
	WDOPR	wdopr;
	LPWDOPR lpwdopr;
	LPVOID	lpMemExtra;

	// Get the args
	lpwdopr = lpwdoprArgs;
	lpMemExtra = (LPSTR)lpMem + (cArgs * vcbWdoprSize);
	while (cArgs > 0)
		{
		// make a copy of the WDOPR
		memcpy (&wdopr, lpMem, vcbWdoprSize);
		(char *)lpMem += vcbWdoprSize;

		// Get extra information
		lpMemExtra = lpvWdoprFromCmiBlockExtra (lpMemExtra, lpwdopr);

		// copy important fields
		lpwdopr->Type = wdopr.Type;
		lpwdopr->BufferSize = wdopr.BufferSize;
		lpwdopr->BufferTooSmall = wdopr.BufferTooSmall;
		if (wdopr.Type != TypeString && !wdopr.IsArray)
			lpwdopr->Double = wdopr.Double;

		lpwdopr++;
		cArgs--;
		}

	if (lpwdoprReturn != lpwdoprNil)
		{
		// Make a copy of the WDOPR
		memcpy (&wdopr, lpMemExtra, vcbWdoprSize);
		(char *)lpMemExtra += vcbWdoprSize;

		// Get extra information
		lpvWdoprFromCmiBlockExtra (lpMemExtra, lpwdoprReturn);

		// copy important fields
		lpwdoprReturn->Type = wdopr.Type;
		lpwdoprReturn->BufferSize = wdopr.BufferSize;
		lpwdoprReturn->BufferTooSmall = wdopr.BufferTooSmall;
		if (wdopr.Type != TypeString && !wdopr.IsArray)
			lpwdoprReturn->Double = wdopr.Double;
		}
}

void FCmiBlockFromWdoprs (LPVOID lpMem, short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn)
{
	LPWDOPR lpwdopr;
	LPVOID	lpMemExtra;

	// write arguments
	lpwdopr = lpwdoprArgs;
	(LPSTR)lpMemExtra = (LPSTR)lpMem + (cArgs * vcbWdoprSize);
    while (cArgs > 0)
		{
		// Write WDOPR
		memcpy (lpMem, lpwdopr, vcbWdoprSize);
		(LPSTR)lpMem += vcbWdoprSize;

		// write extra info (string or arrays)
		lpMemExtra = lpvCmiBlockFromWdoprExtra (lpMemExtra, lpwdopr);
		lpwdopr++;
		cArgs--;
		}

	// Write return
	if (lpwdoprReturn != lpwdoprNil)
		{
		// Write WDOPR
		memcpy (lpMemExtra, lpwdoprReturn, vcbWdoprSize);
		(LPSTR)lpMemExtra += vcbWdoprSize;

		// write extra info (string or arrays)
		lpvCmiBlockFromWdoprExtra (lpMemExtra, lpwdoprReturn);
		}
}

LPVOID lpvCmiBlockFromWdoprExtra (LPVOID lpMem, LPWDOPR lpwdopr)
{
	unsigned short cb;
	short i, cItems;
	short cbArrayDef;

	// Make sure we don't have a NULL LPWDOPR
	if (lpwdopr == lpwdoprNil)
		return lpMem;

	// an array?
	if (lpwdopr->IsArray)
		{
		// Get the number of items on the array	and size of ArrayDef
		cItems = cItemsFromArrayDef ((short *)lpwdopr->ArrayDef);
		cbArrayDef = (*(short *)lpwdopr->ArrayDef + 1) * sizeof (short);

		// copy ArrayDef
		memcpy (lpMem, (LPVOID)lpwdopr->ArrayDef, cbArrayDef);
		(LPSTR)lpMem += cbArrayDef;

		// an array of strings?
		if (lpwdopr->Type == TypeString)
			{
			// advance lpMem the size of the array of pointers
			(LPSTR *)lpMem += cItems;

			// copy each of the strings
			cb = lpwdopr->BufferSize;
			for (i=0; i<cItems; i++)
				{
				memcpy (lpMem, lpwdopr->StringArray[i], cb);
				(LPSTR)lpMem += cb;
				}
			}
		// an array of doubles?
		else if (lpwdopr->Type == TypeDouble)
			{
			// copy the array of dobles
			cb = cItems * sizeof (double);
			memcpy (lpMem, lpwdopr->DoubleArray, cb);
			(char *)lpMem += cb;
			}
		}
	// a string?
	else if (lpwdopr->Type == TypeString)
		{
		// Get the size of the string buffer
		if ((cb = lpwdopr->BufferSize) == 0)
			cb = strlen (lpwdopr->String) + 1;

		// copy the buffer
		memcpy (lpMem, lpwdopr->String, cb);
		(char *)lpMem += cb;
		}
	return lpMem;
}

LPVOID lpvWdoprFromCmiBlockExtra (LPVOID lpMem, LPWDOPR lpwdopr)
{
	unsigned short cb;
	short i, cItems;

	// Make sure we don't have a NULL LPWDOPR
	if (lpwdopr == lpwdoprNil)
		return lpMem;

	// an array?
	if (lpwdopr->IsArray)
		{
		// Get the number of items on the array
		cItems = cItemsFromArrayDef ((short *)lpwdopr->ArrayDef);

		// Advance lpMem to pass ArrayDef
		(LPSTR)lpMem += (*(short *)lpwdopr->ArrayDef + 1) * sizeof (short);

		// an array of strings?
		if (lpwdopr->Type == TypeString)
			{
			// Advance lpMem to pass array of pointers
			(LPSTR)lpMem += cItems * sizeof (LPSTR);

			// copy each of the strings
			cb = lpwdopr->BufferSize;
			for (i=0; i<cItems; i++)
				{
				memcpy (lpwdopr->StringArray[i], lpMem, cb);
				(char *)lpMem += cb;
				}
			}
		// an array of doubles?
		else if (lpwdopr->Type == TypeDouble)
			{
			// copy the array of dobles
			cb = cItems * sizeof (double);
			memcpy (lpwdopr->DoubleArray, lpMem, cb);
			(char *)lpMem += cb;
			}
		}
	// a string?
	else if (lpwdopr->Type == TypeString)
		{
		// Get the size of the string buffer
		if ((cb = lpwdopr->BufferSize) == 0)
			cb = strlen (lpwdopr->String) + 1;

		// copy the buffer
		memcpy (lpwdopr->String, lpMem, cb);
		(char *)lpMem += cb;
		}
	return lpMem;
}

short cItemsFromArrayDef (short *pArrayDef)
{
	short cItems;
	short i;

	for (i=1, cItems=1; i<=pArrayDef[0]; i++)
		cItems *= pArrayDef[i];
	
	return cItems;
}



/* H D U P  R E M O T E  H A N D L E */
/*----------------------------------------------------------------------------
    %%Function: HDupRemoteHandle
    %%Contact: lenoxb

    Returns a duplicated handle usable in this process.
----------------------------------------------------------------------------*/
static HANDLE HDupRemoteHandle(HANDLE hProcessRemote, HANDLE hRemote)
{
	HANDLE hLocal;
	BOOL fStat;
	
	fStat = DuplicateHandle(hProcessRemote, hRemote,
		GetCurrentProcess(), &hLocal, 0, FALSE, DUPLICATE_SAME_ACCESS
		);
	if (!fStat)
		hLocal = NULL;
	return hLocal;
}

/* LPB SETUP CMI POINTERS */
/*----------------------------------------------------------------------------
	%%Function: LpbSetUpCmiPointers
	%%Contact: juanbeau

----------------------------------------------------------------------------*/
BYTE *LpbSetUpCmiPointers( LPWDOPR lpwdopr, BYTE *lpMem)
{

	short	cItems;
	short	i;
	short	*pArrayDef;

	// we only care if we have a string or an array
	if (lpwdopr->IsArray)
		{
		// Setup ArrayDef
		lpwdopr->ArrayDef = (ARRAY_DEF *)lpMem;

		// Get number of items on the array and advance lpMem to pass ArrayDef
		pArrayDef = (short *)lpMem;
		i = *pArrayDef++;
		cItems = 1;
		while (i-- > 0)
			cItems *= *pArrayDef++;
		lpMem = (BYTE *)pArrayDef;

		// array of strings?
		if (lpwdopr->Type == TypeString)
			{
			// setup the pointer to the array of pointers
			lpwdopr->StringArray = (uchar **)lpMem;
			(LPSTR *)lpMem += cItems;

			// setup the pointers on the array
			for (i=0; i<cItems; i++)
				{
				lpwdopr->StringArray[i] = (LPSTR)lpMem;
				(LPSTR)lpMem += lpwdopr->BufferSize;
				}
			}
		else // array of doubles
			{
			lpwdopr->DoubleArray = (double *)lpMem;
			(double *)lpMem += cItems;
			}
		}
	// an string?
	else if (lpwdopr->Type == TypeString)
		{
		lpwdopr->String = (LPSTR)lpMem;

		if (lpwdopr->BufferSize == 0)
			(LPSTR)lpMem += (strlen((LPSTR)lpMem) + 1);
		else
			(LPSTR)lpMem += lpwdopr->BufferSize;
		}
	return (lpMem);
}

int CbOfWdoprs (short cArgs, LPWDOPR lpwdoprArgs, LPWDOPR lpwdoprReturn)
{
	int 	cb;
	LPWDOPR	lpwdopr;

	// Get the size of the arguments, WDOPR first and then extra memory
	// for strings and arrays
	cb = cArgs * vcbWdoprSize;

	lpwdopr = lpwdoprArgs;
	while (cArgs > 0)
		{
		cb += CbExtraWdoprSize (lpwdopr);
		cArgs--;
		lpwdopr++;
		}

	// Now lets do the same for the return
	if (lpwdoprReturn != lpwdoprNil)
		{
		cb += vcbWdoprSize;
		cb += CbExtraWdoprSize (lpwdoprReturn);
		}
	
	return cb;
}

int CbExtraWdoprSize (LPWDOPR lpwdopr)
{
	int		cb;
	short	cItems;

	if (lpwdopr == lpwdoprNil)
		return 0;

	if (lpwdopr->IsArray)
		{
		// Get the number of items on the array
		cItems = cItemsFromArrayDef ((short *)lpwdopr->ArrayDef);

		// Start with the size of ArrayDef
		cb = (*(short *)lpwdopr->ArrayDef + 1) * sizeof (short);

		if (lpwdopr->Type == TypeString)
			cb += cItems * lpwdopr->BufferSize;
		else if (lpwdopr->Type == TypeDouble)
			cb += cItems * sizeof (double);
		}
	else if (lpwdopr->Type == TypeString)
		{
		if ((cb = lpwdopr->BufferSize) == 0)
			cb = strlen (lpwdopr->String) + 1;
		}
	else
		cb = 0;

	return cb;
}
