/* Miranda IM: the free IM client for Microsoft* Windows* Copyright 2000-2009 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" // list of hooks static int compareHooks(const THook* p1, const THook* p2) { return strcmp(p1->name, p2->name); } static LIST hooks(50, compareHooks); struct THookToMainThreadItem { THook* hook; HANDLE hDoneEvent; WPARAM wParam; LPARAM lParam; int result; }; // list of services struct TService { DWORD nameHash; HINSTANCE hOwner; union { MIRANDASERVICE pfnService; MIRANDASERVICEPARAM pfnServiceParam; MIRANDASERVICEOBJ pfnServiceObj; MIRANDASERVICEOBJPARAM pfnServiceObjParam; }; int flags; LPARAM lParam; void* object; char name[1]; }; LIST services(100, NumericKeySortT); typedef struct { HANDLE hDoneEvent; WPARAM wParam; LPARAM lParam; int result; const char *name; } TServiceToMainThreadItem; // other static variables static BOOL bServiceMode = FALSE; static CRITICAL_SECTION csHooks, csServices; static DWORD mainThreadId; static int hookId = 1; static HANDLE hMainThread; static HANDLE hMissingService; static THook *pLastHook = NULL; ///////////////////////////////////////////////////////////////////////////////////////// __forceinline HANDLE getThreadEvent() { HANDLE pData = (HANDLE)TlsGetValue(mir_tls); if (pData == NULL) { pData = CreateEvent(NULL, FALSE, FALSE, NULL); TlsSetValue(mir_tls, pData); } return pData; } static int QueueMainThread(PAPCFUNC pFunc, void* pParam, HANDLE hDoneEvent) { int result = QueueUserAPC(pFunc, hMainThread, (ULONG_PTR)pParam); PostMessage(hAPCWindow, WM_NULL, 0, 0); // let this get processed in its own time if (hDoneEvent) WaitForSingleObject(hDoneEvent, INFINITE); return result; } /////////////////////////////////////////////////////////////////////////////// // HOOKS MIR_CORE_DLL(HANDLE) CreateHookableEvent(const char *name) { if (name == NULL) return NULL; mir_cslock lck(csHooks); int idx; if ((idx = hooks.getIndex((THook*)name)) != -1) return hooks[idx]; THook* newItem = (THook*)mir_alloc(sizeof(THook)); strncpy(newItem->name, name, sizeof(newItem->name)); newItem->name[ MAXMODULELABELLENGTH-1 ] = 0; newItem->id = hookId++; newItem->subscriberCount = 0; newItem->subscriber = NULL; newItem->pfnHook = NULL; newItem->secretSignature = HOOK_SECRET_SIGNATURE; InitializeCriticalSection(&newItem->csHook); hooks.insert(newItem); return (HANDLE)newItem; } MIR_CORE_DLL(int) DestroyHookableEvent(HANDLE hEvent) { if (pLastHook == (THook*)hEvent) pLastHook = NULL; mir_cslock lck(csHooks); int idx; if ((idx = hooks.getIndex((THook*)hEvent)) == -1) return 1; THook* p = hooks[idx]; p->secretSignature = 0; if (p->subscriberCount) { mir_free(p->subscriber); p->subscriber = NULL; p->subscriberCount = 0; } hooks.remove(idx); DeleteCriticalSection(&p->csHook); mir_free(p); return 0; } MIR_CORE_DLL(int) SetHookDefaultForHookableEvent(HANDLE hEvent, MIRANDAHOOK pfnHook) { THook* p = (THook*)hEvent; mir_cslock lck(csHooks); if (hooks.getIndex(p) != -1) p->pfnHook = pfnHook; return 0; } MIR_CORE_DLL(int) CallPluginEventHook(HINSTANCE hInst, HANDLE hEvent, WPARAM wParam, LPARAM lParam) { THook* p = (THook*)hEvent; if (p == NULL || hInst == NULL) return -1; mir_cslock lck(p->csHook); for (int i = 0; i < p->subscriberCount; i++) { THookSubscriber* s = &p->subscriber[i]; if (s->hOwner != hInst) continue; int returnVal; switch (s->type) { case 1: returnVal = s->pfnHook(wParam, lParam); break; case 2: returnVal = s->pfnHookParam(wParam, lParam, s->lParam); break; case 3: returnVal = s->pfnHookObj(s->object, wParam, lParam); break; case 4: returnVal = s->pfnHookObjParam(s->object, wParam, lParam, s->lParam); break; case 5: returnVal = SendMessage(s->hwnd, s->message, wParam, lParam); break; default: continue; } if (returnVal) return returnVal; } if (p->subscriberCount == 0 && p->pfnHook != 0) return p->pfnHook(wParam, lParam); return 0; } static int CallHookSubscribers(THook* p, WPARAM wParam, LPARAM lParam) { if (p == NULL) return -1; mir_cslock lck(p->csHook); // NOTE: We've got the critical section while all this lot are called. That's mostly safe, though. for (int i = 0; i < p->subscriberCount; i++) { THookSubscriber* s = &p->subscriber[i]; int returnVal; switch (s->type) { case 1: returnVal = s->pfnHook(wParam, lParam); break; case 2: returnVal = s->pfnHookParam(wParam, lParam, s->lParam); break; case 3: returnVal = s->pfnHookObj(s->object, wParam, lParam); break; case 4: returnVal = s->pfnHookObjParam(s->object, wParam, lParam, s->lParam); break; case 5: returnVal = SendMessage(s->hwnd, s->message, wParam, lParam); break; default: continue; } if (returnVal) return returnVal; } // call the default hook if any if (p->pfnHook != 0) return p->pfnHook(wParam, lParam); return 0; } enum { hookOk, hookEmpty, hookInvalid }; __forceinline int checkHook(THook* p) { if (p == NULL) return hookInvalid; int ret; __try { if (p->secretSignature != HOOK_SECRET_SIGNATURE) ret = hookInvalid; else if (p->subscriberCount == 0 && p->pfnHook == NULL) ret = hookEmpty; else ret = hookOk; } __except(EXCEPTION_EXECUTE_HANDLER) { ret = hookInvalid; } return ret; } static void CALLBACK HookToMainAPCFunc(ULONG_PTR dwParam) { THookToMainThreadItem* item = (THookToMainThreadItem*)dwParam; item->result = CallHookSubscribers(item->hook, item->wParam, item->lParam); SetEvent(item->hDoneEvent); } MIR_CORE_DLL(int) NotifyEventHooks(HANDLE hEvent, WPARAM wParam, LPARAM lParam) { switch ( checkHook((THook*)hEvent)) { case hookInvalid: return -1; case hookEmpty: return 0; } if ( GetCurrentThreadId() == mainThreadId) return CallHookSubscribers((THook*)hEvent, wParam, lParam); mir_ptr item; item->hDoneEvent = getThreadEvent(); item->hook = (THook*)hEvent; item->wParam = wParam; item->lParam = lParam; QueueMainThread(HookToMainAPCFunc, item, item->hDoneEvent); return item->result; } MIR_CORE_DLL(int) NotifyFastHook(HANDLE hEvent, WPARAM wParam, LPARAM lParam) { switch ( checkHook((THook*)hEvent)) { case hookInvalid: return -1; case hookEmpty: return 0; } return CallHookSubscribers((THook*)hEvent, wParam, lParam); } extern "C" MIR_CORE_DLL(int) GetSubscribersCount(THook* pHook) { switch ( checkHook(pHook)) { case hookInvalid: case hookEmpty: return 0; } return pHook->subscriberCount; } static HANDLE HookEventInt(int type, const char* name, MIRANDAHOOK hookProc, void* object, LPARAM lParam) { mir_cslock lck(csHooks); int idx; if ((idx = hooks.getIndex((THook*)name)) == -1) return NULL; THook* p = hooks[ idx ]; p->subscriber = (THookSubscriber*)mir_realloc(p->subscriber, sizeof(THookSubscriber)*(p->subscriberCount+1)); p->subscriber[ p->subscriberCount ].type = type; p->subscriber[ p->subscriberCount ].pfnHook = hookProc; p->subscriber[ p->subscriberCount ].object = object; p->subscriber[ p->subscriberCount ].lParam = lParam; p->subscriber[ p->subscriberCount ].hOwner = GetInstByAddress(hookProc); p->subscriberCount++; return (HANDLE)((p->id << 16) | p->subscriberCount); } MIR_CORE_DLL(HANDLE) HookEvent(const char* name, MIRANDAHOOK hookProc) { return HookEventInt(1, name, hookProc, 0, 0); } MIR_CORE_DLL(HANDLE) HookEventParam(const char* name, MIRANDAHOOKPARAM hookProc, LPARAM lParam) { return HookEventInt(2, name, (MIRANDAHOOK)hookProc, 0, lParam); } MIR_CORE_DLL(HANDLE) HookEventObj(const char* name, MIRANDAHOOKOBJ hookProc, void* object) { return HookEventInt(3, name, (MIRANDAHOOK)hookProc, object, 0); } MIR_CORE_DLL(HANDLE) HookEventObjParam(const char* name, MIRANDAHOOKOBJPARAM hookProc, void* object, LPARAM lParam) { return HookEventInt(4, name, (MIRANDAHOOK)hookProc, object, lParam); } MIR_CORE_DLL(HANDLE) HookEventMessage(const char* name, HWND hwnd, UINT message) { mir_cslock lck(csHooks); int idx; if ((idx = hooks.getIndex((THook*)name)) == -1) return NULL; THook* p = hooks[ idx ]; p->subscriber = (THookSubscriber*)mir_realloc(p->subscriber, sizeof(THookSubscriber)*(p->subscriberCount+1)); p->subscriber[ p->subscriberCount ].type = 5; p->subscriber[ p->subscriberCount ].hwnd = hwnd; p->subscriber[ p->subscriberCount ].message = message; p->subscriberCount++; return (HANDLE)((p->id << 16) | p->subscriberCount); } MIR_CORE_DLL(int) UnhookEvent(HANDLE hHook) { if (hHook == NULL) return 0; int hookId = (int)hHook >> 16; int subscriberId = ((int)hHook & 0xFFFF) - 1; mir_cslock lck(csHooks); THook* p = NULL; for (int i = 0; i < hooks.getCount(); i++) if (hooks[i]->id == hookId) { p = hooks[i]; break; } if (p == NULL) return 1; if (subscriberId >= p->subscriberCount || subscriberId < 0) return 1; p->subscriber[subscriberId].type = 0; p->subscriber[subscriberId].pfnHook = NULL; p->subscriber[subscriberId].hOwner = NULL; while (p->subscriberCount && p->subscriber[p->subscriberCount-1].type == 0) p->subscriberCount--; if (p->subscriberCount == 0) { if (p->subscriber) mir_free(p->subscriber); p->subscriber = NULL; } return 0; } MIR_CORE_DLL(void) KillModuleEventHooks(HINSTANCE hInst) { mir_cslock lck(csHooks); for (int i = hooks.getCount()-1; i >= 0; i--) { if (hooks[i]->subscriberCount == 0) continue; for (int j = hooks[i]->subscriberCount-1; j >= 0; j--) { if (hooks[i]->subscriber[j].hOwner != hInst) continue; char szModuleName[ MAX_PATH ]; GetModuleFileNameA(hooks[i]->subscriber[j].hOwner, szModuleName, sizeof(szModuleName)); UnhookEvent((HANDLE)((hooks[i]->id << 16) + j + 1)); if (hooks[i]->subscriberCount == 0) break; } } } MIR_CORE_DLL(void) KillObjectEventHooks(void* pObject) { mir_cslock lck(csHooks); for (int i = hooks.getCount()-1; i >= 0; i--) { if (hooks[i]->subscriberCount == 0) continue; for (int j = hooks[i]->subscriberCount-1; j >= 0; j--) { if (hooks[i]->subscriber[j].object == pObject) { UnhookEvent((HANDLE)((hooks[i]->id << 16) + j + 1)); if (hooks[i]->subscriberCount == 0) break; } } } } static void DestroyHooks() { mir_cslock lck(csHooks); for (int i=0; i < hooks.getCount(); i++) { THook* p = hooks[i]; if (p->subscriberCount) mir_free(p->subscriber); DeleteCriticalSection(&p->csHook); mir_free(p); } } /////////////////////SERVICES static __inline TService* FindServiceByName(const char *name) { unsigned hash = mir_hashstr(name); return services.find((TService*)&hash); } static HANDLE CreateServiceInt(int type, const char *name, MIRANDASERVICE serviceProc, void* object, LPARAM lParam) { if (name == NULL) return NULL; TService tmp; tmp.nameHash = mir_hashstr(name); mir_cslock lck(csServices); if (services.getIndex(&tmp) != -1) return NULL; TService* p = (TService*)mir_alloc(sizeof(*p) + strlen(name)); strcpy(p->name, name); p->nameHash = tmp.nameHash; p->pfnService = serviceProc; p->hOwner = GetInstByAddress(serviceProc); p->flags = type; p->lParam = lParam; p->object = object; services.insert(p); return (HANDLE)tmp.nameHash; } MIR_CORE_DLL(HANDLE) CreateServiceFunction(const char *name, MIRANDASERVICE serviceProc) { return CreateServiceInt(0, name, serviceProc, 0, 0); } MIR_CORE_DLL(HANDLE) CreateServiceFunctionParam(const char *name, MIRANDASERVICEPARAM serviceProc, LPARAM lParam) { return CreateServiceInt(1, name, (MIRANDASERVICE)serviceProc, 0, lParam); } MIR_CORE_DLL(HANDLE) CreateServiceFunctionObj(const char *name, MIRANDASERVICEOBJ serviceProc, void* object) { return CreateServiceInt(2, name, (MIRANDASERVICE)serviceProc, object, 0); } MIR_CORE_DLL(HANDLE) CreateServiceFunctionObjParam(const char *name, MIRANDASERVICEOBJPARAM serviceProc, void* object, LPARAM lParam) { return CreateServiceInt(3, name, (MIRANDASERVICE)serviceProc, object, lParam); } MIR_CORE_DLL(int) DestroyServiceFunction(HANDLE hService) { mir_cslock lck(csServices); int idx; if ((idx = services.getIndex((TService*)&hService)) != -1) { mir_free(services[idx]); services.remove(idx); } return 0; } MIR_CORE_DLL(int) ServiceExists(const char *name) { if (name == NULL) return FALSE; mir_cslock lck(csServices); return FindServiceByName(name) != NULL; } MIR_CORE_DLL(INT_PTR) CallService(const char *name, WPARAM wParam, LPARAM lParam) { if (name == NULL) return CALLSERVICE_NOTFOUND; TService *pService; { mir_cslock lck(csServices); if ((pService = FindServiceByName(name)) == NULL) return CALLSERVICE_NOTFOUND; } MIRANDASERVICE pfnService = pService->pfnService; int flags = pService->flags; LPARAM fnParam = pService->lParam; void* object = pService->object; switch(flags) { case 1: return ((MIRANDASERVICEPARAM)pfnService)(wParam, lParam, fnParam); case 2: return ((MIRANDASERVICEOBJ)pfnService)(object, wParam, lParam); case 3: return ((MIRANDASERVICEOBJPARAM)pfnService)(object, wParam, lParam, fnParam); default: return pfnService(wParam, lParam); } } static void CALLBACK CallServiceToMainAPCFunc(ULONG_PTR dwParam) { TServiceToMainThreadItem *item = (TServiceToMainThreadItem*) dwParam; item->result = CallService(item->name, item->wParam, item->lParam); SetEvent(item->hDoneEvent); } MIR_CORE_DLL(INT_PTR) CallServiceSync(const char *name, WPARAM wParam, LPARAM lParam) { if (name == NULL) return CALLSERVICE_NOTFOUND; // the service is looked up within the main thread, since the time it takes // for the APC queue to clear the service being called maybe removed. // even thou it may exists before the call, the critsec can't be locked between calls. if (GetCurrentThreadId() == mainThreadId) return CallService(name, wParam, lParam); mir_ptr item; item->wParam = wParam; item->lParam = lParam; item->name = name; item->hDoneEvent = getThreadEvent(); QueueMainThread(CallServiceToMainAPCFunc, item, item->hDoneEvent); return item->result; } MIR_CORE_DLL(int) CallFunctionAsync(void (__stdcall *func)(void *), void *arg) { QueueMainThread((PAPCFUNC)func, arg, 0); return 0; } MIR_CORE_DLL(void) KillModuleServices(HINSTANCE hInst) { mir_cslock lck(csServices); for (int i = services.getCount()-1; i >= 0; i--) { if (services[i]->hOwner == hInst) { char szModuleName[ MAX_PATH ]; GetModuleFileNameA(services[i]->hOwner, szModuleName, sizeof(szModuleName)); DestroyServiceFunction((HANDLE)services[i]->nameHash); } } } MIR_CORE_DLL(void) KillObjectServices(void* pObject) { mir_cslock lck(csServices); for (int i = services.getCount()-1; i >= 0; i--) if (services[i]->object == pObject) DestroyServiceFunction((HANDLE)services[i]->nameHash); } static void DestroyServices() { mir_cslock lck(csServices); for (int j=0; j < services.getCount(); j++) mir_free(services[j]); } /////////////////////////////////////////////////////////////////////////////// static int sttComparePlugins(const HINSTANCE__* p1, const HINSTANCE__* p2) { if (p1 == p2) return 0; return (p1 < p2) ? -1 : 1; } LIST pluginListAddr(10, sttComparePlugins); MIR_CORE_DLL(void) RegisterModule(HINSTANCE hInst) { pluginListAddr.insert(hInst); } MIR_CORE_DLL(void) UnregisterModule(HINSTANCE hInst) { pluginListAddr.remove(hInst); } MIR_CORE_DLL(HINSTANCE) GetInstByAddress(void* codePtr) { if (pluginListAddr.getCount() == 0) return NULL; int idx; List_GetIndex((SortedList*)&pluginListAddr, codePtr, &idx); if (idx > 0) idx--; HINSTANCE result = pluginListAddr[idx]; if (result < hInst && codePtr > hInst) result = hInst; else if (idx == 0 && codePtr < (void*)result) result = NULL; return result; } /////////////////////////////////////////////////////////////////////////////// int InitialiseModularEngine(void) { InitializeCriticalSection(&csHooks); InitializeCriticalSection(&csServices); mainThreadId = GetCurrentThreadId(); DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hMainThread, 0, FALSE, DUPLICATE_SAME_ACCESS); hMissingService = CreateHookableEvent(ME_SYSTEM_MISSINGSERVICE); return 0; } void DestroyModularEngine(void) { DestroyHooks(); hooks.destroy(); DeleteCriticalSection(&csHooks); DestroyServices(); services.destroy(); DeleteCriticalSection(&csServices); pluginListAddr.destroy(); CloseHandle(hMainThread); }