/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-12 Miranda IM, 2012-13 Miranda NG 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"

#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;
	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 HANDLE hLogger = NULL;

static const TCHAR* szTimeFormats[] = 
{
	LPGENT("No times"), 
	LPGENT("Standard hh:mm:ss times"), 
	LPGENT("Times in milliseconds"), 
	LPGENT("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, db_get_b(NULL, "Netlib", "ShowLogOptsAtStart", 0)?BST_CHECKED:BST_UNCHECKED);
		{
			DBVARIANT dbv;
			if ( !db_get_s(NULL, "Netlib", "RunAtStart", &dbv)) {
				SetDlgItemTextA(hwndDlg, IDC_RUNATSTART, dbv.pszVal);
				db_free(&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) {
				if ((HWND)lParam == GetFocus())
					CheckDlgButton(hwndDlg, IDC_TOFILE, BST_CHECKED);

				TCHAR path[MAX_PATH];
				GetWindowText((HWND)lParam, path, MAX_PATH);

				PathToAbsoluteT( VARST(path), path);
				SetDlgItemText(hwndDlg, IDC_PATH, path);
			}
			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);
				db_set_ts(NULL, "Netlib", "RunAtStart", str);
				db_set_b(NULL, "Netlib", "ShowLogOptsAtStart", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SHOWTHISDLGATSTART));

				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);
			}
			{
				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)
						db_set_dw(NULL, "Netlib", "NLlog", checked);
					}
					else
					if (tvi.lParam < netlibUser.getCount()) {
						netlibUser[tvi.lParam]->toLog = checked;
						if (logOptions.save)
							db_set_dw(NULL, netlibUser[tvi.lParam]->user.szSettingsModule, "NLlog", checked);
					}

					tvi.hItem = TreeView_GetNextSibling(hwndFilter, tvi.hItem);
				}
			}

			if (logOptions.save) {
				db_set_b(NULL, "Netlib", "DumpRecv", (BYTE)logOptions.dumpRecv);
				db_set_b(NULL, "Netlib", "DumpSent", (BYTE)logOptions.dumpSent);
				db_set_b(NULL, "Netlib", "DumpProxy", (BYTE)logOptions.dumpProxy);
				db_set_b(NULL, "Netlib", "DumpSsl", (BYTE)logOptions.dumpSsl);
				db_set_b(NULL, "Netlib", "TextDumps", (BYTE)logOptions.textDumps);
				db_set_b(NULL, "Netlib", "AutoDetectText", (BYTE)logOptions.autoDetectText);
				db_set_b(NULL, "Netlib", "TimeFormat", (BYTE)logOptions.timeFormat);
				db_set_b(NULL, "Netlib", "ShowUser", (BYTE)logOptions.showUser);
				db_set_b(NULL, "Netlib", "ToOutputDebugString", (BYTE)logOptions.toOutputDebugString);
				db_set_b(NULL, "Netlib", "ToFile", (BYTE)logOptions.toFile);
				db_set_ts(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(hInst, 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)
{
	NetlibUser *nlu = (NetlibUser*)wParam;
	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 the Netlib user handle is NULL, just pretend its not */
	if (nlu == NULL) {
		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]) {
		size_t len = strlen(pszMsg);
		mir_writeLogA(hLogger, "%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n");
	}

	LOGMSG logMsg = { szHead, pszMsg };
	NotifyFastHook(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 = Utf8EncodeW(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(NetlibConnection *nlc, PBYTE buf, int len, int sent, int flags)
{
	char szTitleLine[128];
	char *szBuf;
	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 || GetSubscribersCount(hLogEvent) != 0 || (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);
	NetlibUser *nlu = nlc ? nlc->nlu : NULL;
	int 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;

	bool isText = true;
	if ( !logOptions.textDumps)
		isText = false;
	else if ( !(flags & MSG_DUMPASTEXT)) {
		if (logOptions.autoDetectText) {
			for (int i=0; i < len; i++) {
				if ((buf[i] < ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') || buf[i] >= 0x80) {
					isText = false;
					break;
				}
			}
		}
		else isText = false;
	}

	// 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;
		int sz = titleLineLen + ((len+16)>>4) * 78 + 1;
		useStack = sz <= 8192;

		szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz));
		CopyMemory(szBuf, szTitleLine, titleLineLen);
		char *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);

	logOptions.dumpRecv = db_get_b(NULL, "Netlib", "DumpRecv", 1);
	logOptions.dumpSent = db_get_b(NULL, "Netlib", "DumpSent", 1);
	logOptions.dumpProxy = db_get_b(NULL, "Netlib", "DumpProxy", 1);
	logOptions.dumpSsl = db_get_b(NULL, "Netlib", "DumpSsl", 0);
	logOptions.textDumps = db_get_b(NULL, "Netlib", "TextDumps", 1);
	logOptions.autoDetectText = db_get_b(NULL, "Netlib", "AutoDetectText", 1);
	logOptions.timeFormat = db_get_b(NULL, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS);
	logOptions.showUser = db_get_b(NULL, "Netlib", "ShowUser", 1);
	logOptions.toOutputDebugString = db_get_b(NULL, "Netlib", "ToOutputDebugString", 0);
	logOptions.toFile = db_get_b(NULL, "Netlib", "ToFile", 0);
	logOptions.toLog = db_get_dw(NULL, "Netlib", "NLlog", 1);

	if ( !db_get_ts(NULL, "Netlib", "File", &dbv)) {
		logOptions.szUserFile = mir_tstrdup(dbv.ptszVal);

		TCHAR path[MAX_PATH];
		PathToAbsoluteT( VARST(dbv.ptszVal), path);
		logOptions.szFile = mir_tstrdup(path);

		db_free(&dbv);
	}
	else {
		logOptions.szUserFile = mir_tstrdup(_T("%miranda_logpath%\\netlog.txt"));
		logOptions.szFile = Utils_ReplaceVarsT(logOptions.szUserFile);
	}

	hLogger = mir_createLog("Netlib", LPGENT("Standard netlib log"), logOptions.szFile, 0);

	if (db_get_b(NULL, "Netlib", "ShowLogOptsAtStart", 0))
		NetlibLogShowOptions();

	if ( !db_get_ts(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);
		db_free(&dbv);
	}
}

void NetlibLogShutdown(void)
{
	bIsActive = FALSE;
	DestroyHookableEvent(hLogEvent); hLogEvent = NULL;
	if (IsWindow(logOptions.hwndOpts))
		DestroyWindow(logOptions.hwndOpts);
	mir_free(logOptions.szFile);
	mir_free(logOptions.szUserFile);
}