/* 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 "..\..\core\commonheaders.h" #include "netlib.h" #include "../srfile/file.h" #define MS_NETLIB_LOGWIN "Netlib/Log/Win" extern HANDLE hConnectionHeaderMutex; #define TIMEFORMAT_NONE 0 #define TIMEFORMAT_HHMMSS 1 #define TIMEFORMAT_MILLISECONDS 2 #define TIMEFORMAT_MICROSECONDS 3 struct { HWND hwndOpts; int toOutputDebugString; int toFile; int toLog; TCHAR* szFile; TCHAR* szUserFile; int timeFormat; int showUser; int dumpSent, dumpRecv, dumpProxy, dumpSsl; int textDumps, autoDetectText; CRITICAL_SECTION cs; int save; } logOptions = {0}; typedef struct { const char* pszHead; const char* pszMsg; } LOGMSG; static __int64 mirandaStartTime, perfCounterFreq; static int bIsActive = TRUE; static HANDLE hLogEvent = NULL; static const TCHAR* szTimeFormats[] = { _T( "No times" ), _T( "Standard hh:mm:ss times" ), _T( "Times in milliseconds" ), _T( "Times in microseconds" ) }; static INT_PTR CALLBACK LogOptionsDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: logOptions.hwndOpts=hwndDlg; TranslateDialogDefault(hwndDlg); CheckDlgButton(hwndDlg, IDC_DUMPRECV, logOptions.dumpRecv?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_DUMPSENT, logOptions.dumpSent?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_DUMPPROXY, logOptions.dumpProxy?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_DUMPSSL, logOptions.dumpSsl?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_TEXTDUMPS, logOptions.textDumps?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_AUTODETECTTEXT, logOptions.autoDetectText?BST_CHECKED:BST_UNCHECKED); { int i; for ( i=0; i < SIZEOF(szTimeFormats); i++ ) SendDlgItemMessage(hwndDlg, IDC_TIMEFORMAT, CB_ADDSTRING, 0, (LPARAM)TranslateTS( szTimeFormats[i] )); } SendDlgItemMessage(hwndDlg, IDC_TIMEFORMAT, CB_SETCURSEL, logOptions.timeFormat, 0); CheckDlgButton(hwndDlg, IDC_SHOWNAMES, logOptions.showUser?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_TOOUTPUTDEBUGSTRING, logOptions.toOutputDebugString?BST_CHECKED:BST_UNCHECKED); CheckDlgButton(hwndDlg, IDC_TOFILE, logOptions.toFile?BST_CHECKED:BST_UNCHECKED); SetDlgItemText(hwndDlg, IDC_FILENAME, logOptions.szUserFile); SetDlgItemText(hwndDlg, IDC_PATH, logOptions.szFile); CheckDlgButton(hwndDlg, IDC_SHOWTHISDLGATSTART, DBGetContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart", 0)?BST_CHECKED:BST_UNCHECKED); { DBVARIANT dbv; if (!DBGetContactSettingString(NULL, "Netlib", "RunAtStart", &dbv)) { SetDlgItemTextA(hwndDlg, IDC_RUNATSTART, dbv.pszVal); DBFreeVariant(&dbv); } } logOptions.save = 0; { TVINSERTSTRUCT tvis = {0}; int i; HWND hwndFilter = GetDlgItem(hwndDlg, IDC_FILTER); SetWindowLongPtr(hwndFilter, GWL_STYLE, GetWindowLongPtr(hwndFilter, GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES)); tvis.hParent=NULL; tvis.hInsertAfter=TVI_SORT; tvis.item.mask=TVIF_PARAM|TVIF_TEXT|TVIF_STATE; tvis.item.stateMask=TVIS_STATEIMAGEMASK; for (i = 0; i < netlibUser.getCount(); ++i) { tvis.item.pszText=netlibUser[i]->user.ptszDescriptiveName; tvis.item.lParam=i; tvis.item.state=INDEXTOSTATEIMAGEMASK((netlibUser[i]->toLog) ? 2 : 1 ); TreeView_InsertItem(hwndFilter, &tvis); } tvis.item.lParam=-1; tvis.item.pszText=TranslateT("(Miranda Core Logging)"); tvis.item.state=INDEXTOSTATEIMAGEMASK((logOptions.toLog) ? 2 : 1 ); TreeView_InsertItem(hwndFilter, &tvis); } return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { /* case IDC_DUMPRECV: case IDC_DUMPSENT: case IDC_DUMPPROXY: case IDC_TEXTDUMPS: case IDC_AUTODETECTTEXT: case IDC_TIMEFORMAT: case IDC_SHOWNAMES: case IDC_TOOUTPUTDEBUGSTRING: case IDC_TOFILE: case IDC_SHOWTHISDLGATSTART: case IDC_RUNATSTART: break; */ case IDC_FILENAME: if (HIWORD(wParam) != EN_CHANGE) break; if ((HWND)lParam == GetFocus()) CheckDlgButton(hwndDlg, IDC_TOFILE, BST_CHECKED); { TCHAR path[MAX_PATH]; GetWindowText((HWND)lParam, path, MAX_PATH); TCHAR *pszNewPath = Utils_ReplaceVarsT(path); pathToAbsoluteT(pszNewPath, path, NULL); SetDlgItemText(hwndDlg, IDC_PATH, path); mir_free(pszNewPath); } break; case IDC_FILENAMEBROWSE: case IDC_RUNATSTARTBROWSE: { TCHAR str[MAX_PATH+2]; OPENFILENAME ofn={0}; TCHAR filter[512], *pfilter; GetWindowText(GetWindow((HWND)lParam, GW_HWNDPREV), str, SIZEOF(str)); ofn.lStructSize=OPENFILENAME_SIZE_VERSION_400; ofn.hwndOwner=hwndDlg; ofn.Flags=OFN_HIDEREADONLY | OFN_DONTADDTORECENT; if (LOWORD(wParam) == IDC_FILENAMEBROWSE) { ofn.lpstrTitle=TranslateT("Select where log file will be created"); } else { ofn.Flags|=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST; ofn.lpstrTitle=TranslateT("Select program to be run"); } _tcscpy(filter, TranslateT("All Files")); _tcscat(filter, _T(" (*)")); pfilter=filter+lstrlen(filter)+1; _tcscpy(pfilter, _T("*")); pfilter=pfilter+lstrlen(pfilter)+1; *pfilter='\0'; ofn.lpstrFilter=filter; ofn.lpstrFile=str; ofn.nMaxFile=SIZEOF(str)-2; ofn.nMaxFileTitle=MAX_PATH; if (LOWORD(wParam) == IDC_FILENAMEBROWSE) { if (!GetSaveFileName(&ofn)) return 1; } else { if (!GetOpenFileName(&ofn)) return 1; } if (LOWORD(wParam) == IDC_RUNATSTARTBROWSE && _tcschr(str, ' ') != NULL) { MoveMemory(str+1, str, SIZEOF(str)-2); str[0]='"'; lstrcat(str, _T("\"")); } SetWindowText(GetWindow((HWND)lParam, GW_HWNDPREV), str); break; } case IDC_RUNNOW: { TCHAR str[MAX_PATH+1]; STARTUPINFO si={0}; PROCESS_INFORMATION pi; GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, MAX_PATH); si.cb=sizeof(si); if (str[0]) CreateProcess(NULL, str, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); } break; case IDC_SAVE: logOptions.save = 1; // case IDOK: { TCHAR str[MAX_PATH]; GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, MAX_PATH); DBWriteContactSettingTString(NULL, "Netlib", "RunAtStart", str); DBWriteContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SHOWTHISDLGATSTART)); EnterCriticalSection(&logOptions.cs); mir_free(logOptions.szUserFile); GetWindowText(GetDlgItem(hwndDlg, IDC_FILENAME), str, MAX_PATH ); logOptions.szUserFile = mir_tstrdup(str); mir_free(logOptions.szFile); GetWindowText(GetDlgItem(hwndDlg, IDC_PATH), str, MAX_PATH ); logOptions.szFile = mir_tstrdup(str); logOptions.dumpRecv=IsDlgButtonChecked(hwndDlg, IDC_DUMPRECV); logOptions.dumpSent=IsDlgButtonChecked(hwndDlg, IDC_DUMPSENT); logOptions.dumpProxy=IsDlgButtonChecked(hwndDlg, IDC_DUMPPROXY); logOptions.dumpSsl=IsDlgButtonChecked(hwndDlg, IDC_DUMPSSL); logOptions.textDumps=IsDlgButtonChecked(hwndDlg, IDC_TEXTDUMPS); logOptions.autoDetectText=IsDlgButtonChecked(hwndDlg, IDC_AUTODETECTTEXT); logOptions.timeFormat=SendDlgItemMessage(hwndDlg, IDC_TIMEFORMAT, CB_GETCURSEL, 0, 0); logOptions.showUser=IsDlgButtonChecked(hwndDlg, IDC_SHOWNAMES); logOptions.toOutputDebugString=IsDlgButtonChecked(hwndDlg, IDC_TOOUTPUTDEBUGSTRING); logOptions.toFile=IsDlgButtonChecked(hwndDlg, IDC_TOFILE); LeaveCriticalSection(&logOptions.cs); } { HWND hwndFilter = GetDlgItem(logOptions.hwndOpts, IDC_FILTER); TVITEM tvi={0}; BOOL checked; tvi.mask=TVIF_HANDLE|TVIF_PARAM|TVIF_STATE|TVIF_TEXT; tvi.hItem=TreeView_GetRoot(hwndFilter); while (tvi.hItem) { TreeView_GetItem(hwndFilter, &tvi); checked = ((tvi.state&TVIS_STATEIMAGEMASK)>>12 == 2); if (tvi.lParam == -1) { logOptions.toLog = checked; if ( logOptions.save ) DBWriteContactSettingDword(NULL, "Netlib", "NLlog", checked); } else if (tvi.lParam < netlibUser.getCount()) { netlibUser[tvi.lParam]->toLog = checked; if ( logOptions.save ) DBWriteContactSettingDword(NULL, netlibUser[tvi.lParam]->user.szSettingsModule, "NLlog", checked); } tvi.hItem=TreeView_GetNextSibling(hwndFilter, tvi.hItem); } } if ( logOptions.save ) { DBWriteContactSettingByte(NULL, "Netlib", "DumpRecv", (BYTE)logOptions.dumpRecv); DBWriteContactSettingByte(NULL, "Netlib", "DumpSent", (BYTE)logOptions.dumpSent); DBWriteContactSettingByte(NULL, "Netlib", "DumpProxy", (BYTE)logOptions.dumpProxy); DBWriteContactSettingByte(NULL, "Netlib", "DumpSsl", (BYTE)logOptions.dumpSsl); DBWriteContactSettingByte(NULL, "Netlib", "TextDumps", (BYTE)logOptions.textDumps); DBWriteContactSettingByte(NULL, "Netlib", "AutoDetectText", (BYTE)logOptions.autoDetectText); DBWriteContactSettingByte(NULL, "Netlib", "TimeFormat", (BYTE)logOptions.timeFormat); DBWriteContactSettingByte(NULL, "Netlib", "ShowUser", (BYTE)logOptions.showUser); DBWriteContactSettingByte(NULL, "Netlib", "ToOutputDebugString", (BYTE)logOptions.toOutputDebugString); DBWriteContactSettingByte(NULL, "Netlib", "ToFile", (BYTE)logOptions.toFile); DBWriteContactSettingTString(NULL, "Netlib", "File", logOptions.szFile ? logOptions.szUserFile: _T("")); logOptions.save = 0; } else DestroyWindow(hwndDlg); break; case IDCANCEL: DestroyWindow(hwndDlg); break; } break; case WM_CLOSE: DestroyWindow(hwndDlg); break; case WM_DESTROY: ImageList_Destroy(TreeView_GetImageList(GetDlgItem(hwndDlg, IDC_FILTER), TVSIL_STATE)); logOptions.hwndOpts=NULL; break; } return FALSE; } void NetlibLogShowOptions(void) { if (logOptions.hwndOpts == NULL) logOptions.hwndOpts=CreateDialog(hMirandaInst, MAKEINTRESOURCE(IDD_NETLIBLOGOPTS), NULL, LogOptionsDlgProc); SetForegroundWindow(logOptions.hwndOpts); } static INT_PTR ShowOptions(WPARAM, LPARAM) { NetlibLogShowOptions(); return 0; } static INT_PTR NetlibLog(WPARAM wParam, LPARAM lParam) { struct NetlibUser *nlu = (struct NetlibUser*)wParam; struct NetlibUser nludummy; const char *pszMsg = (const char*)lParam; char szTime[32], szHead[128]; LARGE_INTEGER liTimeNow; DWORD dwOriginalLastError; if (!bIsActive) return 0; if ((nlu != NULL && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } if (nlu == NULL) /* if the Netlib user handle is NULL, just pretend its not */ { if (!logOptions.toLog) return 1; nlu = &nludummy; nlu->user.szSettingsModule = "(NULL)"; } else if (!nlu->toLog) return 1; dwOriginalLastError = GetLastError(); switch (logOptions.timeFormat) { case TIMEFORMAT_HHMMSS: GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, NULL, NULL, szTime, SIZEOF(szTime)); break; case TIMEFORMAT_MILLISECONDS: QueryPerformanceCounter(&liTimeNow); liTimeNow.QuadPart -= mirandaStartTime; mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%03I64u", liTimeNow.QuadPart / perfCounterFreq, 1000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); break; case TIMEFORMAT_MICROSECONDS: QueryPerformanceCounter(&liTimeNow); liTimeNow.QuadPart -= mirandaStartTime; mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%06I64u", liTimeNow.QuadPart / perfCounterFreq, 1000000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); break; default: szTime[0] = '\0'; break; } if (logOptions.timeFormat || logOptions.showUser) mir_snprintf(szHead, SIZEOF(szHead) - 1, "[%s%s%s] ", szTime, (logOptions.showUser && logOptions.timeFormat) ? " " : "", logOptions.showUser ? nlu->user.szSettingsModule : ""); else szHead[0]=0; if (logOptions.toOutputDebugString) { if (szHead[0]) OutputDebugStringA(szHead); OutputDebugStringA(pszMsg); OutputDebugStringA("\n"); } if (logOptions.toFile && logOptions.szFile[0]) { EnterCriticalSection(&logOptions.cs); FILE *fp; fp = _tfopen(logOptions.szFile, _T("ab")); if (!fp) { CreatePathToFileT(logOptions.szFile); fp = _tfopen(logOptions.szFile, _T("at")); } if (fp) { size_t len = strlen(pszMsg); fprintf(fp, "%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n"); fclose(fp); } LeaveCriticalSection(&logOptions.cs); } if (((THook*)hLogEvent)->subscriberCount) { LOGMSG logMsg = { szHead, pszMsg }; CallHookSubscribers(hLogEvent, (WPARAM)nlu, (LPARAM)&logMsg); } SetLastError(dwOriginalLastError); return 1; } static INT_PTR NetlibLogW(WPARAM wParam, LPARAM lParam) { const wchar_t *pszMsg = (const wchar_t*)lParam; char* szMsg = Utf8EncodeUcs2(pszMsg); INT_PTR res = NetlibLog(wParam, (LPARAM)szMsg); mir_free(szMsg); return res; } void NetlibLogf(NetlibUser* nlu, const char *fmt, ...) { if (nlu == NULL) { if (!logOptions.toLog) return; } else if (!nlu->toLog) return; va_list va; char szText[1024]; va_start(va, fmt); mir_vsnprintf(szText, sizeof(szText), fmt, va); va_end(va); NetlibLog((WPARAM)nlu, (LPARAM)szText); } void NetlibDumpData(struct NetlibConnection *nlc, PBYTE buf, int len, int sent, int flags) { int isText=1; char szTitleLine[128]; char *szBuf; int titleLineLen; struct NetlibUser *nlu; bool useStack = false; // This section checks a number of conditions and aborts // the dump if the data should not be written to the log // Check packet flags if (flags & (MSG_PEEK | MSG_NODUMP)) return; // Check user's log settings if (!(logOptions.toOutputDebugString || ((THook*)hLogEvent)->subscriberCount || (logOptions.toFile && logOptions.szFile[0]))) return; if ((sent && !logOptions.dumpSent) || (!sent && !logOptions.dumpRecv)) return; if ((flags & MSG_DUMPPROXY) && !logOptions.dumpProxy) return; if ((flags & MSG_DUMPSSL) && !logOptions.dumpSsl) return; WaitForSingleObject(hConnectionHeaderMutex, INFINITE); nlu = nlc ? nlc->nlu : NULL; titleLineLen = mir_snprintf(szTitleLine, SIZEOF(szTitleLine), "(%p:%u) Data %s%s\r\n", nlc, nlc ? nlc->s : 0, sent ? "sent" : "received", flags & MSG_DUMPPROXY ? " (proxy)" : ""); ReleaseMutex(hConnectionHeaderMutex); // check filter settings if (nlu == NULL) { if (!logOptions.toLog) return; } else if (!nlu->toLog) return; if (!logOptions.textDumps) isText = 0; else if (!(flags&MSG_DUMPASTEXT)) { if (logOptions.autoDetectText) { int i; for (i = 0; i=0x80) { isText = 0; break; } } } else isText = 0; } // Text data if ( isText ) { int sz = titleLineLen + len + 1; useStack = sz <= 8192; szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); CopyMemory( szBuf, szTitleLine, titleLineLen ); CopyMemory( szBuf + titleLineLen, (const char*)buf, len ); szBuf[titleLineLen + len] = '\0'; } // Binary data else { int line, col, colsInLine; char *pszBuf; int sz = titleLineLen + ((len+16)>>4) * 78 + 1; useStack = sz <= 8192; szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); CopyMemory(szBuf, szTitleLine, titleLineLen); pszBuf = szBuf + titleLineLen; for ( line = 0;; line += 16 ) { colsInLine = min(16, len - line); if (colsInLine == 16) { PBYTE p = buf + line; pszBuf += wsprintfA( pszBuf, "%08X: %02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X ", line, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15] ); } else { pszBuf += wsprintfA(pszBuf, "%08X: ", line); // Dump data as hex for (col = 0; col < colsInLine; col++) pszBuf += wsprintfA(pszBuf, "%02X%c", buf[line + col], ((col&3) == 3 && col != 15)?'-':' '); // Fill out last line with blanks for (; col<16; col++) { lstrcpyA(pszBuf, " "); pszBuf += 3; } *pszBuf++ = ' '; } for (col = 0; col < colsInLine; col++) *pszBuf++ = buf[line+col]<' '?'.':(char)buf[line+col]; if (len-line<=16) break; *pszBuf++ = '\r'; // End each line with a break *pszBuf++ = '\n'; // End each line with a break } *pszBuf = '\0'; } NetlibLog((WPARAM)nlu, (LPARAM)szBuf); if (!useStack) mir_free(szBuf); } void NetlibLogInit(void) { DBVARIANT dbv; LARGE_INTEGER li; QueryPerformanceFrequency( &li ); perfCounterFreq = li.QuadPart; QueryPerformanceCounter( &li ); mirandaStartTime = li.QuadPart; CreateServiceFunction( MS_NETLIB_LOGWIN, ShowOptions ); CreateServiceFunction( MS_NETLIB_LOG, NetlibLog ); CreateServiceFunction( MS_NETLIB_LOGW, NetlibLogW ); hLogEvent = CreateHookableEvent( ME_NETLIB_FASTDUMP ); InitializeCriticalSection(&logOptions.cs); logOptions.dumpRecv = DBGetContactSettingByte( NULL, "Netlib", "DumpRecv", 1 ); logOptions.dumpSent = DBGetContactSettingByte( NULL, "Netlib", "DumpSent", 1 ); logOptions.dumpProxy = DBGetContactSettingByte( NULL, "Netlib", "DumpProxy", 1 ); logOptions.dumpSsl = DBGetContactSettingByte( NULL, "Netlib", "DumpSsl", 0 ); logOptions.textDumps = DBGetContactSettingByte( NULL, "Netlib", "TextDumps", 1 ); logOptions.autoDetectText = DBGetContactSettingByte( NULL, "Netlib", "AutoDetectText", 1 ); logOptions.timeFormat = DBGetContactSettingByte( NULL, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS ); logOptions.showUser = DBGetContactSettingByte( NULL, "Netlib", "ShowUser", 1 ); logOptions.toOutputDebugString = DBGetContactSettingByte( NULL, "Netlib", "ToOutputDebugString", 0 ); logOptions.toFile = DBGetContactSettingByte( NULL, "Netlib", "ToFile", 0 ); logOptions.toLog = DBGetContactSettingDword( NULL, "Netlib", "NLlog", 1 ); if (!DBGetContactSettingTString(NULL, "Netlib", "File", &dbv)) { logOptions.szUserFile = mir_tstrdup(dbv.ptszVal); TCHAR *pszNewPath = Utils_ReplaceVarsT(dbv.ptszVal); TCHAR path[MAX_PATH]; pathToAbsoluteT(pszNewPath, path, NULL); logOptions.szFile = mir_tstrdup(path); mir_free(pszNewPath); DBFreeVariant(&dbv); } else { logOptions.szUserFile = mir_tstrdup(_T("%miranda_logpath%\\netlog.txt")); logOptions.szFile = Utils_ReplaceVarsT(logOptions.szUserFile); } if ( logOptions.toFile && logOptions.szFile[0] ) { FILE *fp; fp = _tfopen( logOptions.szFile, _T("wt")); if ( fp ) fclose(fp); } if ( DBGetContactSettingByte( NULL, "Netlib", "ShowLogOptsAtStart", 0 )) NetlibLogShowOptions(); if ( !DBGetContactSettingTString( NULL, "Netlib", "RunAtStart", &dbv )) { STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi; si.cb = sizeof( si ); if ( dbv.ptszVal[0] ) CreateProcess( NULL, dbv.ptszVal, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); DBFreeVariant( &dbv ); } } void NetlibLogShutdown(void) { bIsActive = FALSE; DestroyHookableEvent( hLogEvent ); hLogEvent = NULL; if ( IsWindow( logOptions.hwndOpts )) DestroyWindow( logOptions.hwndOpts ); DeleteCriticalSection( &logOptions.cs ); mir_free( logOptions.szFile ); mir_free( logOptions.szUserFile ); }