#include "stdafx.h" #import "Flash.tlb" no_namespace exclude("IServiceProvider") PLUGININFOEX pluginInfo = { sizeof(PLUGININFOEX), __PLUGIN_NAME, PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), __DESCRIPTION, __AUTHOR, __AUTHOREMAIL, __COPYRIGHT, __AUTHORWEB, UNICODE_AWARE, // {72765A6F-B017-42F1-B30F-5E0941273A3F} {0x72765a6f, 0xb017, 0x42f1, {0xb3, 0xf, 0x5e, 0x9, 0x41, 0x27, 0x3a, 0x3f}} }; /* a strcmp() that likes NULL */ int __fastcall strcmpnull(const char *str1, const char *str2) { if (str1 && str2) return strcmp(str1, str2); if (!str1 && !str2) return 0; return 1; } struct flash_avatar_item : public MZeroedObject { HANDLE hContact; FLASHAVATAR hFA; IShockwaveFlash* pFlash; char* getProto() { return (hFA.cProto) ? hFA.cProto : GetContactProto(hFA.hContact); } flash_avatar_item(HANDLE contact, FLASHAVATAR& fa, IShockwaveFlash *flash) { hContact = contact; hFA = fa; pFlash = flash; } }; static int CompareFlashItems(const flash_avatar_item* p1, const flash_avatar_item* p2) { if (p1->hContact < p2->hContact) return -1; if (p1->hContact > p2->hContact) return 1; int cProto = strcmpnull(p1->hFA.cProto, p2->hFA.cProto); if (cProto) return cProto; return (p1->hFA.id > p2->hFA.id) ? -1 : (p1->hFA.id == p2->hFA.id) ? 0 : 1; }; HINSTANCE g_hInst = 0; int hLangpack; HANDLE hNetlibUser; static char pluginName[64]; static CriticalSection cs; static HANDLE hAvatarsFolder = NULL; static LIST FlashList(5, CompareFlashItems); typedef HRESULT (WINAPI *LPAtlAxAttachControl)(IUnknown* pControl, HWND hWnd, IUnknown** ppUnkContainer); LPAtlAxAttachControl MyAtlAxAttachControl; #define getFace() \ char* face;\ switch (status) {\ case ID_STATUS_OFFLINE:\ face = AV_OFFLINE;\ break;\ case ID_STATUS_ONLINE:\ case ID_STATUS_INVISIBLE:\ face = AV_NORMAL;\ break;\ default:\ face = AV_BUSY;\ break;\ } static bool DownloadFlashFile(char *url, const TCHAR* save_file, int recurse_count /*=0*/) { if (!url || recurse_count > 5) return false; NETLIBHTTPREQUEST req = {0}; req.cbSize = sizeof(req); req.requestType = REQUEST_GET; req.szUrl = url; req.flags = 0;//NLHRF_HTTP11; NETLIBHTTPREQUEST *resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUser, (LPARAM)&req); if(resp) { if(resp->resultCode == 200) { HANDLE hSaveFile = CreateFile(save_file, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if(hSaveFile != INVALID_HANDLE_VALUE) { unsigned long bytes_written = 0; if(WriteFile(hSaveFile, resp->pData, resp->dataLength, &bytes_written, NULL) == TRUE) { CloseHandle(hSaveFile); CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); resp = 0; return true; } CloseHandle(hSaveFile); } } else if(resp->resultCode >= 300 && resp->resultCode < 400) { // get new location bool ret = false; for(int i = 0; i < resp->headersCount; i++) { if(strcmpnull(resp->headers[i].szName, "Location") == 0) { ret = DownloadFlashFile(resp->headers[i].szValue, save_file, recurse_count + 1); break; } } CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); resp = 0; return ret; } CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); resp = 0; } return false; } /* static wchar_t *u2w(const char *utfs) { if(utfs) { int size = MultiByteToWideChar(CP_UTF8, 0, utfs, -1, 0, 0); wchar_t *buff = new wchar_t[size]; MultiByteToWideChar(CP_UTF8, 0, utfs, -1, buff, size); return buff; } else return 0; } */ static void __cdecl loadFlash_Thread(void *p) { debug("Avatar thread executed...\n"); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); flash_avatar_item* fai = (flash_avatar_item*)p; IShockwaveFlash* flash = fai->pFlash; if ( _tcschr(fai->hFA.cUrl, '?') == NULL) { // make hash of url debug("Making TTH hash from URL...\n"); TigerHash th; th.update(fai->hFA.cUrl, _tcslen(fai->hFA.cUrl)); th.finalize(); // create local path name TCHAR name[MAX_PATH], path[MAX_PATH]; TCHAR tth[((TigerHash::HASH_SIZE * 8) / 5) + 2]; FOLDERSGETDATA fgd = {0}; fgd.cbSize = sizeof(FOLDERSGETDATA); fgd.nMaxPathSize = MAX_PATH; fgd.szPathT = path; fgd.flags = FF_TCHAR; if (!hAvatarsFolder || CallService(MS_FOLDERS_GET_PATH, (WPARAM)hAvatarsFolder, (LPARAM)&fgd)) { if(ServiceExists(MS_UTILS_REPLACEVARS)) { TCHAR *tmpPath = Utils_ReplaceVarsT(_T("%miranda_avatarcache%")); mir_sntprintf(path, MAX_PATH, _T("%s\\%s"), tmpPath, _T("Flash")); mir_free(tmpPath); } else PathToAbsoluteT( _T("Flash"), path); } else { if(_tcslen(path) && path[_tcslen(path)-1]=='\\') path[_tcslen(path)-1] = 0; } CreateDirectory(path, NULL); // create directory if it doesn't exist _sntprintf(name, MAX_PATH, _T("%s\\%s.swf"), path, th.toBase32(tth)); // download remote file if it doesn't exist if (GetFileAttributes(name) == 0xFFFFFFFF) { debug("Downloading flash file...\n"); DownloadFlashFile( _T2A(fai->hFA.cUrl), name, 0); } // load and play local flash movie debug("Loading flash movie...\n"); flash->LoadMovie(0, _bstr_t(name).copy()); } Sleep(100); flash->Play(); // change flash frame according user's status int status; if (fai->hFA.hContact) status = db_get_w(fai->hFA.hContact, fai->getProto(), "Status", ID_STATUS_OFFLINE); else status = CallProtoService(fai->getProto(), PS_GETSTATUS, 0, 0); getFace(); flash->SetVariable(L"face.emotion", _bstr_t(face).copy()); flash->Release(); } static void ShowBalloon(TCHAR *title, TCHAR *msg, int icon) { MIRANDASYSTRAYNOTIFY msn = {0}; msn.cbSize = sizeof(MIRANDASYSTRAYNOTIFY); msn.dwInfoFlags = icon | NIIF_INTERN_UNICODE; msn.tszInfo = TranslateTS(msg); msn.tszInfoTitle = TranslateTS(title); msn.uTimeout = 5000; CLIST_INTERFACE* c = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, (LPARAM)g_hInst); if (c) c->pfnCListTrayNotify(&msn); } static void prepareFlash(char* pProto, const TCHAR* pUrl, FLASHAVATAR& fa, IShockwaveFlash* flash) { debug("Preparing flash...\n"); if(flash == NULL) { // Flash component is not registered in the system ShowBalloon(LPGENT("Flash.ocx not registered!"), LPGENT("You don't have installed ShockwaveFlash interface in your system."), NIIF_ERROR); DestroyWindow(fa.hWindow); fa.hWindow = 0; return; } if(flash->FlashVersion() == 0x80000) { // Flash Version 8 has a bug which causes random crashes :( ShowBalloon(LPGENT("Bugged Flash detected!"), LPGENT("You have installed Flash 8.\r\nThis version of Flash contains a bug which can causes random crashes.\r\nIt is recommended to upgrade or downgrade your Flash library"), NIIF_WARNING); } // attach flash object to window debug("Attaching flash to its window...\n"); MyAtlAxAttachControl(flash, fa.hWindow, 0); // store avatar info debug("Storing avatar info...\n"); fa.cProto = pProto; fa.cUrl = mir_tstrdup(pUrl); // create flash record flash_avatar_item *flash_item = new flash_avatar_item(fa.hContact, fa, flash); { Lock l(cs); FlashList.insert(flash_item); } // avatar contains parameter, load it from remote place if ( _tcschr(fa.cUrl, '?')) { debug("Flash with parameters, loading...\n"); flash->LoadMovie(0, fa.cUrl); } _bstr_t // refresh avatar's parent window // InvalidateRect(fa.hParentWindow, NULL, FALSE); // create thread to download/load flash avatar debug("Creating avatar thread...\n"); flash->AddRef(); mir_forkthread(&loadFlash_Thread, (void*)flash_item); //loadFlash(new FlashPair(make_pair(fa, flash))); } static INT_PTR destroyAvatar(WPARAM wParam, LPARAM) { flash_avatar_item key(((FLASHAVATAR*)wParam)->hContact, *(FLASHAVATAR*)wParam, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item) { if (item->pFlash) item->pFlash->Release(); if (item->hFA.hWindow) DestroyWindow(item->hFA.hWindow); mir_free(item->hFA.cUrl); FlashList.remove(item); delete item; } return 0; } static INT_PTR makeAvatar(WPARAM wParam, LPARAM) { debug("Searching for flash avatar...\n"); FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; PROTO_AVATAR_INFORMATIONT AI = {0}; AI.cbSize = sizeof(AI); AI.hContact = hFA->hContact; AI.format = PA_FORMAT_UNKNOWN; flash_avatar_item key(hFA->hContact, *hFA, NULL); bool avatarOK = false; if(hFA->hContact) avatarOK = (int)CallProtoService(key.getProto(), PS_GETAVATARINFOT, 0, (LPARAM)&AI) == GAIR_SUCCESS; else { avatarOK = (int)CallProtoService(key.getProto(), PS_GETMYAVATART, (WPARAM)AI.filename, (LPARAM)255) == 0; if(avatarOK) { TCHAR* ext = _tcsrchr(AI.filename, _T('.')); if(ext && (_tcsicmp(ext, _T(".xml")) == 0)) AI.format = PA_FORMAT_XML; } } if (!avatarOK) return 0; debug("Avatar found...\n"); TCHAR url[MAX_PATH]; switch(AI.format) { case PA_FORMAT_SWF: _tcsncpy(url, AI.filename, SIZEOF(url)); break; case PA_FORMAT_XML: { int src = _topen(AI.filename, _O_BINARY | _O_RDONLY); if(src != -1) { char pBuf[2048]; char* urlBuf; _read(src, pBuf, sizeof(pBuf)); _close(src); urlBuf = strstr(pBuf, ""); if(urlBuf) _tcsncpy(url, _A2T(strtok(urlBuf + 5, "\r\n <")), SIZEOF(url)); else return 0; } else { return 0; } break; } default: destroyAvatar(wParam, 0); return 0; } Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item) { debug("Flash already exists...\n"); hFA->hWindow = item->hFA.hWindow; ShowWindow(hFA->hWindow, SW_SHOW); if ( _tcsicmp(item->hFA.cUrl, url) != 0) { debug("Refreshing flash...\n"); IShockwaveFlash* flash = item->pFlash; mir_free(item->hFA.cUrl); FlashList.remove(item); delete item; prepareFlash(key.getProto(), url, *hFA, flash); } } else { debug("Creating new flash...\n"); RECT rc; GetWindowRect(hFA->hParentWindow, &rc); hFA->hWindow = CreateWindowEx(WS_EX_TOPMOST, _T("AtlAxWin"), _T(""), WS_VISIBLE | WS_CHILD, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hFA->hParentWindow, (HMENU) 0, g_hInst, NULL); IShockwaveFlash* flash = NULL; debug("Creating flash instance...\n"); CoCreateInstance(__uuidof(ShockwaveFlash),0,CLSCTX_ALL, __uuidof(IShockwaveFlash), (void **)&flash); debug("Initialized.\n"); prepareFlash(key.getProto(), url, *hFA, flash); } return 0; } static INT_PTR resizeAvatar(WPARAM wParam, LPARAM lParam) { FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; RECT rc = *((LPRECT)lParam); flash_avatar_item key(hFA->hContact, *hFA, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item) SetWindowPos(item->hFA.hWindow, HWND_TOP, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); return 0; } static INT_PTR setPos(WPARAM wParam, LPARAM lParam) { FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; RECT rc = *((LPRECT)lParam); flash_avatar_item key(hFA->hContact, *hFA, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item) SetWindowPos(item->hFA.hWindow, HWND_TOP, rc.left, rc.top, rc.right, rc.bottom, SWP_SHOWWINDOW); return 0; } static INT_PTR getInfo(WPARAM wParam, LPARAM) { FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; flash_avatar_item key(hFA->hContact, *hFA, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item) { //IShockwaveFlash* flash = item->pFlash; hFA->hWindow = item->hFA.hWindow; hFA->cUrl = item->hFA.cUrl; hFA->cProto = item->hFA.cProto; } return 0; } static INT_PTR setEmoFace(WPARAM wParam, LPARAM lParam) { FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; flash_avatar_item key(hFA->hContact, *hFA, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item && item->pFlash) { IShockwaveFlash* flash = item->pFlash; flash->SetVariable(L"face.emotion", (BSTR)lParam); } return 0; } static INT_PTR setBkColor(WPARAM wParam, LPARAM lParam) { FLASHAVATAR* hFA = (FLASHAVATAR*)wParam; COLORREF clr = (COLORREF)lParam; flash_avatar_item key(hFA->hContact, *hFA, NULL); Lock l(cs); flash_avatar_item *item = FlashList.find(&key); if (item && item->pFlash) { IShockwaveFlash* flash = item->pFlash; char buf[10]; _snprintf(buf, sizeof(buf), "%02X%02X%02X", LOBYTE(LOWORD(clr)), HIBYTE(LOWORD(clr)), LOBYTE(HIWORD(clr))); flash->put_BGColor(_bstr_t(buf)); } return 0; } static int ownStatusChanged(WPARAM wParam, LPARAM lParam) { WORD status = (WORD)wParam; const char* proto = (char*)lParam; Lock l(cs); for(int i = 0; i < FlashList.getCount(); i++) { flash_avatar_item *item = FlashList[i]; if(item->hContact == NULL && (!proto || (strcmpnull(item->hFA.cProto, proto) == 0))) { IShockwaveFlash* flash = item->pFlash; if (flash) { getFace(); flash->SetVariable(L"face.emotion", _bstr_t(face).copy()); } } else if (item->hContact) break; // the list is sorted by hContact } return 0; } static int statusChanged(WPARAM wParam, LPARAM lParam) { WORD status = HIWORD(lParam); Lock l(cs); for(int i = 0; i < FlashList.getCount(); i++) { flash_avatar_item *item = FlashList[i]; if (item->hContact == (HANDLE)wParam) { IShockwaveFlash* flash = item->pFlash; if (flash) { getFace(); flash->SetVariable(L"face.emotion", _bstr_t(face).copy()); } } else if (item->hContact > (HANDLE)wParam) break; // the list is sorted by hContact } return 0; } static int eventAdded(WPARAM wParam, LPARAM lParam) { DBEVENTINFO dbei = { sizeof(dbei) }; dbei.cbBlob = db_event_getBlobSize((HANDLE)lParam); if (dbei.cbBlob == 0xFFFFFFFF) return 0; dbei.pBlob = new BYTE[dbei.cbBlob]; db_event_get((HANDLE)lParam, &dbei); if (dbei.eventType == EVENTTYPE_MESSAGE && !(dbei.flags & DBEF_READ)) { Lock l(cs); if(FlashList.getCount() > 0) { //size_t aLen = strlen((char *)dbei.pBlob)+1; char* face = NULL; if ( (strstr((char*)dbei.pBlob, (char*)":-)") != NULL) || (strstr((char*)dbei.pBlob, (char*)":)") != NULL) || (strstr((char*)dbei.pBlob, (char*)";)") != NULL) || (strstr((char*)dbei.pBlob, (char*)";-)") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*THUMBS UP*") != NULL) || (strstr((char*)dbei.pBlob, (char*)"O:-)") != NULL) || (strstr((char*)dbei.pBlob, (char*)":P") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-P") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*Drink*") != NULL)) { face = AV_SMILE; } else if ( (strstr((char*)dbei.pBlob, (char*)":-(") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-$") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-!") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-X") != NULL)) { face = AV_SAD; } else if ( (strstr((char*)dbei.pBlob, (char*)"*JOKINGLY*") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-D") != NULL)) { face = AV_LAUGH; } else if ( (strstr((char*)dbei.pBlob, (char*)":'(") != NULL) || (strstr((char*)dbei.pBlob, (char*)":'-(") != NULL)) { face = AV_CRY; } else if ( (strstr((char*)dbei.pBlob, (char*)">:o") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-@") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*STOP*") != NULL) || (strstr((char*)dbei.pBlob, (char*)"]:->") != NULL) || (strstr((char*)dbei.pBlob, (char*)"@=") != NULL)) { face = AV_MAD; } else if ( (strstr((char*)dbei.pBlob, (char*)":-*") != NULL) || (strstr((char*)dbei.pBlob, (char*)":-[") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*KISSED*") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*KISSING*") != NULL) || (strstr((char*)dbei.pBlob, (char*)"@}->--") != NULL) || (strstr((char*)dbei.pBlob, (char*)"*IN LOVE*") != NULL)) { face = AV_LOVE; } else { face = AV_NORMAL; } HANDLE hContact = (dbei.flags & DBEF_SENT) ? 0 : (HANDLE)wParam; for(int i=0; ihContact == hContact && !strcmpnull(dbei.szModule, item->getProto())) { IShockwaveFlash* flash = item->pFlash; flash->SetVariable(L"face.emotion", _bstr_t(face).copy()); //break; } else if (item->hContact > hContact) break; // the list is sorted } } } delete[] dbei.pBlob; return 0; } typedef BOOL (__stdcall *pfnAtlAxWinInit)( void ); static int systemModulesLoaded(WPARAM /*wParam*/, LPARAM /*lParam*/) { HMODULE hAtl = LoadLibrary(_T("atl")); pfnAtlAxWinInit init = (pfnAtlAxWinInit)GetProcAddress(hAtl, "AtlAxWinInit"); if (init) init(); MyAtlAxAttachControl = (LPAtlAxAttachControl)GetProcAddress(hAtl, "AtlAxAttachControl"); CreateServiceFunction(MS_FAVATAR_DESTROY, destroyAvatar); CreateServiceFunction(MS_FAVATAR_MAKE, makeAvatar); CreateServiceFunction(MS_FAVATAR_RESIZE, resizeAvatar); CreateServiceFunction(MS_FAVATAR_SETPOS, setPos); CreateServiceFunction(MS_FAVATAR_GETINFO, getInfo); CreateServiceFunction(MS_FAVATAR_SETEMOFACE, setEmoFace); CreateServiceFunction(MS_FAVATAR_SETBKCOLOR, setBkColor); HookEvent(ME_DB_EVENT_ADDED, eventAdded); HookEvent("Miranda/StatusChange/ContactStatusChanged", statusChanged); // NewStatusNotify HookEvent(ME_CLIST_STATUSMODECHANGE, ownStatusChanged); NETLIBUSER nl_user = {0}; nl_user.cbSize = sizeof(nl_user); nl_user.szSettingsModule = "FlashAvatars"; nl_user.flags = NUF_OUTGOING | NUF_HTTPCONNS; nl_user.szDescriptiveName = Translate("Flash Avatars"); hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nl_user); TCHAR path[MAX_PATH]; if (ServiceExists(MS_UTILS_REPLACEVARS)) { // default Avatar Cache path for MIM 0.8+ TCHAR *tmpPath = Utils_ReplaceVarsT(_T("%miranda_avatarcache%")); mir_sntprintf(path, MAX_PATH, _T("%s\\%s\\"), tmpPath, _T("Flash")); mir_free(tmpPath); } // default for older Mirandas else PathToAbsoluteT( _T("Flash\\"), path); hAvatarsFolder = FoldersRegisterCustomPathT(LPGEN("Avatars"), LPGEN("Flash Avatars"), path); return 0; } extern "C" __declspec(dllexport) PLUGININFOEX * MirandaPluginInfoEx(DWORD mirandaVersion) { return &pluginInfo; } extern "C" int __declspec(dllexport) Load(void) { mir_getLP(&pluginInfo); HookEvent(ME_SYSTEM_MODULESLOADED, systemModulesLoaded); return 0; } extern "C" int __declspec(dllexport) Unload(void) { // Shutdown cleanup { Lock l(cs); for (int i = FlashList.getCount()-1; i >= 0; i--) { flash_avatar_item *item = FlashList[i]; if (item->pFlash) item->pFlash->Release(); if (item->hFA.hWindow) DestroyWindow(item->hFA.hWindow); mir_free(item->hFA.cUrl); delete item; } FlashList.destroy(); } if (hNetlibUser) CallService(MS_NETLIB_CLOSEHANDLE, (WPARAM)hNetlibUser, 0); return 0; } extern "C" BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD /*dwReason*/, LPVOID /*reserved*/) { g_hInst = hInstDLL; return TRUE; }