Click here to Skip to main content
15,897,519 members
Articles / Programming Languages / C++

WindowsNT Event Log Viewer

Rate me:
Please Sign up or sign in to vote.
3.38/5 (12 votes)
30 Nov 19992 min read 265.9K   5.6K   39  
#include "EventLogThreads.h"
#include "_Constants.h"
#include "_GlobalVars.h"
#include "_Utils.h"
#include "SysMain.h"
#include "resource.h"
#include <ctype.h>

#define MAX_MSG_LENGTH 1024

unsigned int __stdcall 
FillEventLogList(LPVOID lpParam)
{
	EVENTLOGFILTER *pelf = 0;
	int nRetVal = 0;
	HWND hParentWnd = 0, hwndDlg = 0, hwndLV = 0, hwndProgr = 0;
	HANDLE hEventLog = 0;
	DWORD dwEventLogRecords = 0, dwOldestEventLogRecord = 0, dwEvLogCounter = 0, dwNumberOfBytesToRead = 0, 
		dwBytesRead = 0, dwMinNumberOfBytesNeeded = 0, dwCancel = 0, dwClose = 0;
	LPVOID lpEventLogRecordBuffer = 0;
	TCHAR chFakeBuffer;
	BOOL bRetVal = FALSE;
	BOOL fExit = FALSE;
	UINT uStep = 0, uStepAt = 0, uPos = 0, uOffset = 0;
	TCHAR lpUNCServerName[_MAX_PATH + 1], lpszEventLogSourceName[_MAX_PATH + 1], lpszErrMsg[1024];

	// get thread parameter structure address...
	pelf = (EVENTLOGFILTER *)lpParam;
	// ...and retrieve the appropriate handles
	hwndDlg	= pelf->hwndDlg;
	hwndLV	= pelf->hwndLV;
	hwndProgr = pelf->hwndProgr;

	// get parent window...
	hParentWnd = GetParent(hwndDlg);
	// ...and set user data to 1 (the window has thread running) - will be reset to 0 when thread will terminate
	SetWindowLong(hParentWnd, GWL_USERDATA, (LONG)pelf); 

	// resize dialog
	MDIChild_ResizeDlg(hwndDlg, TRUE);

	// format UNC machine name to work with
	wsprintf(lpUNCServerName, _T("\\\\%s"), pelf->lpszComputerName);

	// establish what kind of event log section will show the list
	if(g_fApplication)
		_tcscpy(lpszEventLogSourceName, _T("Application"));				//	APPLICATION
	else if(g_fSystem)
		_tcscpy(lpszEventLogSourceName, _T("System"));					//	SYSTEM
	else if(g_fSecurity)
		_tcscpy(lpszEventLogSourceName, _T("Security"));				//	SECURITY
	else if(g_fCustom)
		_tcscpy(lpszEventLogSourceName, pelf->lpszCustomEventFileName);	//	CUSTOM
	else
	{
		nRetVal = -1;
		goto _cleanup_;
	}

	dwCancel = WaitForSingleObject(pelf->hCancelEvent, 0);
	dwClose = WaitForSingleObject(pelf->hCloseEvent, 0);
	while(!fExit)
	{
		if(g_fCustom)
			hEventLog = OpenBackupEventLog((LPCTSTR)lpUNCServerName, (LPCTSTR)lpszEventLogSourceName);
		else
			hEventLog = OpenEventLog((LPCTSTR)lpUNCServerName, (LPCTSTR)lpszEventLogSourceName);

		if(hEventLog)
		{
			if(GetNumberOfEventLogRecords(hEventLog, &dwEventLogRecords) && 
				GetOldestEventLogRecord(hEventLog, &dwOldestEventLogRecord))
			{
				SendMessage(hwndProgr, PBM_SETRANGE, (WPARAM)0, (LPARAM)MAKELPARAM(0, 100));
				uStepAt = (dwEventLogRecords / 100) + 1;

				for(dwEvLogCounter = dwOldestEventLogRecord; 
					dwEvLogCounter < (dwOldestEventLogRecord + dwEventLogRecords); 
					dwEvLogCounter++)
				{
					uStep++;
					if(uStep % uStepAt == 0)
						hwndProgr && SendMessage(hwndProgr, PBM_SETPOS, (WPARAM)++uPos, 0);

					dwCancel = WaitForSingleObject(pelf->hCancelEvent, 0);
					if(dwCancel == WAIT_OBJECT_0)
						goto _canceled_;
					dwClose = WaitForSingleObject(pelf->hCloseEvent, 0);
					if(dwClose == WAIT_OBJECT_0)
						goto _close_;

					lpEventLogRecordBuffer		= (LPVOID)&chFakeBuffer;
					dwNumberOfBytesToRead		= 1;
					dwMinNumberOfBytesNeeded	= 1;

	_retry_:
					bRetVal = ReadEventLog(hEventLog, EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ, dwEvLogCounter, 
						lpEventLogRecordBuffer, dwNumberOfBytesToRead, &dwBytesRead, &dwMinNumberOfBytesNeeded);

					if(!bRetVal)
					{
						g_dwLastError = GetLastError();

						if(g_dwLastError == ERROR_INSUFFICIENT_BUFFER)
						{
							lpEventLogRecordBuffer = (LPVOID)GlobalAlloc(GPTR, dwMinNumberOfBytesNeeded);
							if(lpEventLogRecordBuffer == (void *)0)
								goto _allocationfailure_;

							dwNumberOfBytesToRead = dwMinNumberOfBytesNeeded;
							goto _retry_;
						}
						else
							goto _unknownerror_;
					}
					else
					{
						PEVENTLOGRECORD pELR = 0;
						TCHAR *lpszSourceName = 0, lpszUserName[_MAX_PATH + 1], *lpszComputerName = 0,
							lpszRefDomainName[_MAX_PATH + 1], *szSIDType = 0, *szSIDName = 0, sz2[32],
							*szExpandedString = 0, szSubmitTime[32], szWriteTime[32];
						DWORD dwSourceNameLen = 0, dwComputerNameLen = 0, cbName = _MAX_PATH + 1, 
							cbRefDomainName = _MAX_PATH + 1, dwSIDTypeLen = 0, dwSidSize = 0, dwEventTypeLen = 0;
						PSID pUserSID = 0;
						SID_NAME_USE _SidNameUse = (SID_NAME_USE)(SidTypeUser - 1);
						BOOL bRetVal = FALSE;
						LPBYTE pStrings = 0, pData = 0;
						UINT x = 0, uSize, uStringOffset, uStepOfString = 0, uImage = 0;

						pELR	= (PEVENTLOGRECORD)lpEventLogRecordBuffer;

						uOffset	= sizeof(EVENTLOGRECORD);
						lpszSourceName = (TCHAR *)GlobalAlloc(GPTR, (_MAX_PATH + 1) * sizeof(TCHAR));
						strcpy(lpszSourceName, (LPTSTR)((LPBYTE)pELR + uOffset));
						dwSourceNameLen = strlen(lpszSourceName);

						uOffset	+= strlen(lpszSourceName) + sizeof(TCHAR);
						lpszComputerName = (TCHAR *)GlobalAlloc(GPTR, (_MAX_PATH + 1) * sizeof(TCHAR));
						strcpy(lpszComputerName, (LPTSTR)((LPBYTE)pELR + uOffset));
						dwComputerNameLen = strlen(lpszComputerName);

						uOffset += strlen(lpszComputerName) + sizeof(TCHAR);

						dwSIDTypeLen = 32;
						szSIDType = (TCHAR *)GlobalAlloc(GPTR, (dwSIDTypeLen + 1) * sizeof(TCHAR));

						if(pELR->UserSidLength > 0)
						{
							pUserSID = (SID *)GlobalAlloc(GPTR, pELR->UserSidLength);
							memcpy(pUserSID, (PSID)((LPBYTE)pELR + pELR->UserSidOffset), pELR->UserSidLength);
							
							cbName = cbRefDomainName = _MAX_PATH + 1;
							*lpszRefDomainName = *lpszUserName = '\0';

							bRetVal = LookupAccountSid(0, pUserSID, 
								lpszUserName, &cbName, 
								lpszRefDomainName, &cbRefDomainName, 
								&_SidNameUse);

							if(bRetVal)
							{
								if(bRetVal)
								{
									dwSIDTypeLen = 32;
									GetNameUse(_SidNameUse, szSIDType, &dwSIDTypeLen);

									dwSidSize = (15 + 12 + (12 * (*GetSidSubAuthorityCount(pUserSID))) + 1) * sizeof(TCHAR);
									szSIDName = (TCHAR *)GlobalAlloc(GPTR, (dwSidSize + 1) * sizeof(TCHAR));
									ConvertSid(pUserSID, szSIDName, &dwSidSize); 
								}
								else
								{
									strcpy(lpszRefDomainName, "N/A");
									strcpy(lpszUserName, "N/A");
									strcpy(szSIDType, "N/A");
								}
							}
							else
							{
							}
						}
						else
						{
							strcpy(lpszRefDomainName, "N/A");
							strcpy(lpszUserName, "N/A");
							strcpy(szSIDType, "N/A");
						}

						uSize = 0, uStringOffset = pELR->StringOffset;
						uSize = pELR->DataOffset - pELR->StringOffset;

						// Strings
						if(uSize > 0)
						{
							pStrings = (LPBYTE)GlobalAlloc(GPTR, uSize * sizeof(BYTE));
							memcpy(pStrings, (LPBYTE)pELR + uStringOffset, uSize);

							//	Strings
							uStepOfString = 0;
							szExpandedString = (TCHAR *)GlobalAlloc(GPTR, (uSize + MAX_MSG_LENGTH) * sizeof(TCHAR));
							for(x = 0; x < pELR->NumStrings; x++)
							{
								if(x == 0)
								{
									strcpy(szExpandedString, (TCHAR *)pStrings + uStepOfString);
									if(x < (UINT)pELR->NumStrings - 1)
										strcat(szExpandedString, ",");
								}
								else
									strcat(szExpandedString, (TCHAR *)pStrings + uStepOfString);

								uStepOfString = strlen((TCHAR *)pStrings + uStepOfString) + 1;
							}
						}

						//	Data
						pData = (LPBYTE)GlobalAlloc(GPTR, pELR->DataLength * sizeof(BYTE));
						memcpy(pData, (LPBYTE)((LPBYTE)pELR + pELR->DataOffset), pELR->DataLength);

						dwEventTypeLen = 32;
						GetEventLogType(sz2, pELR->EventType, &dwEventTypeLen);
						GetEventLogImage(&uImage, pELR->EventType);

						lstrcpyn(szSubmitTime, asctime(localtime((time_t *)&(pELR->TimeGenerated))), 25);
						lstrcpyn(szWriteTime, asctime(localtime((time_t *)&(pELR->TimeWritten))), 25);

						InsertRowInList(hwndLV, 9, &dwEvLogCounter, 
							lpszSourceName, 
							lpszUserName, 
							szSIDName, 
							lpszRefDomainName, 
							sz2, uImage, 
							szSubmitTime, szWriteTime);

						SafeDeletePointer(pData, pELR->DataLength);
						SafeDeletePointer(szExpandedString, uSize); 
						SafeDeletePointer(pStrings, pELR->DataOffset - pELR->StringOffset);
						SafeDeletePointer(szSIDName, dwSidSize + 1);
						SafeDeletePointer(szSIDType, dwSIDTypeLen + 1);
						SafeDeletePointer(lpszSourceName, dwSourceNameLen);
						SafeDeletePointer(lpszComputerName, dwComputerNameLen);
						SafeDeletePointer(pUserSID, pELR->UserSidLength);
						SafeDeletePointer(lpEventLogRecordBuffer, dwNumberOfBytesToRead);
					}
				}

				goto _cleanup_;
			}
			else
				ReportLastError(0, 0, TRUE);

	_unknownerror_:
			ReportLastError(lpszErrMsg, 0, TRUE);
			goto _cleanup_;

	_allocationfailure_:
			LoadString(g_hInstance, IDS_ERR_ALLOCATIONFAILURE, lpszErrMsg, 1024);
			MessageBox(0, lpszErrMsg, 0, MB_OK | MB_ICONSTOP);
			goto _cleanup_;

	_canceled_:
			nRetVal = 1;
			goto _cleanup_;

	_close_:
			nRetVal = 2;
			goto _cleanup_;

	_cleanup_:
			fExit = TRUE;

			CloseEventLog(hEventLog);
			hEventLog = 0;
		}
		else
		{
			fExit = TRUE;
			ReportLastError(0, 0, TRUE);
		}
	}

	// final cleanup on dialog
	if(nRetVal != 2)
	{
		if(IsWindow(hwndDlg))
			MDIChild_ResizeDlg(hwndDlg, FALSE);
		if(IsWindow(hParentWnd))
			SetWindowLong(hParentWnd, GWL_USERDATA, (LONG)0);
	}

	CloseHandle(pelf->hCancelEvent);
	CloseHandle(pelf->hCloseEvent);

	GlobalFree(pelf);
	pelf = 0;

	return nRetVal;
}

int CALLBACK 
CompareItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	int nIdx1	= (int)lParam1;
	int nIdx2	= (int)lParam2;
	
	int nRetVal = 0;
	
	DLGSORTDATA *pSortData	= (DLGSORTDATA *)lParamSort;
	
	HWND hDlg	= (HWND)(pSortData->hDlg);
	HWND hwndLV = (HWND)(GetDlgItem(hDlg, IDL_EVENTS));
	
	int nSubItemColIdx		= pSortData->nColIdx;
	int nSortOrder			= pSortData->nSortOrder;
	int nSortType			= pSortData->nSortType;

	TCHAR lpsz1[_MAX_PATH + 1], lpsz2[_MAX_PATH + 1];

	ListView_GetItemText(hwndLV, nIdx1, nSubItemColIdx, lpsz1, _MAX_PATH + 1);
	ListView_GetItemText(hwndLV, nIdx2, nSubItemColIdx, lpsz2, _MAX_PATH + 1);

	if(nSortOrder == ASCENDING)
	{
		if(nSortType == STRING)
			nRetVal = _tcsicmp(lpsz1, lpsz2);
		else if(nSortType == NUMERIC)
		{
			int n1 = atoi(lpsz1), n2 = atoi(lpsz2);
			nRetVal = n1 < n2 ? -1 : n1 == n2 ? 0 : 1;
		}
		else
			nRetVal = 0;
	}
	else if(nSortOrder == DESCENDING)
	{
		if(nSortType == STRING)
			nRetVal = -1 * _tcsicmp(lpsz1, lpsz2);
		else if(nSortType == NUMERIC)
		{
			int n1 = atoi(lpsz1), n2 = atoi(lpsz2);
			nRetVal = (n1 < n2) ? 1 : ((n1 == n2) ? 0 : -1);
		}
		else
			nRetVal = 0;
	}
	else
		nRetVal = 0; // none

	return nRetVal;
}

unsigned int __stdcall 
ShowEventData(LPVOID lpParam)
{
	LPEVENTID peid = (LPEVENTID)lpParam;

//	if(pdt->pDlg->m_strSource.IsEmpty())
//	{
//		LocalFree(pdt);
//		return 0L;
//	}

	int   nRetVal = 0;

	HWND  hwndDlg			= peid->hwndDlg;
	HWND  hwndEditStrings	= GetDlgItem(hwndDlg, IDE_STRINGS);
	HWND  hwndEditData		= GetDlgItem(hwndDlg, IDE_DATA);
	DWORD dwRecId			= peid->dwEventId;

	TCHAR  lpUNCServerName[_MAX_PATH + 1];
	TCHAR  lpSourceName[_MAX_PATH + 1];

	HANDLE  hEventLog				= 0;
	
	DWORD  dwEventLogRecords		= 0;
	DWORD  dwOldestEventLogRecord	= 0;
	DWORD  dwEvLogCounter			= 0;

	LPVOID lpEventLogRecordBuffer	= 0;
	char   chFakeBuffer				= ' ';
	DWORD  dwNumberOfBytesToRead	= 0;
	DWORD  dwBytesRead				= 0;
	DWORD  dwMinNumberOfBytesNeeded	= 0;
	BOOL    bRetVal					= FALSE;
	TCHAR	lpszEventLogSourceName[_MAX_PATH + 1];

	wsprintf(lpUNCServerName, _T("\\\\%s"), peid->lpszMachineName);
	wsprintf(lpSourceName, _T("%s"), peid->lpszEventName);

	if(g_fApplication)
		_tcscpy(lpszEventLogSourceName, _T("Application"));
	else if(g_fSystem)
		_tcscpy(lpszEventLogSourceName, _T("System"));
	else if(g_fSecurity)
		_tcscpy(lpszEventLogSourceName, _T("Security"));
//	else if(g_fCustom)
//		_tcscpy(lpszEventLogSourceName, _T("Application"));
	else
	{
		nRetVal = -1;
		goto _cleanup_;
	}

	hEventLog = OpenEventLog((LPCTSTR)lpUNCServerName, (LPCTSTR)lpszEventLogSourceName);
	if(hEventLog)
	{
		if(GetNumberOfEventLogRecords(hEventLog, &dwEventLogRecords) && 
			GetOldestEventLogRecord(hEventLog, &dwOldestEventLogRecord))
		{
			for(dwEvLogCounter = dwOldestEventLogRecord; 
				dwEvLogCounter <= (dwOldestEventLogRecord + dwEventLogRecords); 
				dwEvLogCounter++)
			{
				if(dwEvLogCounter != dwRecId)
					continue;

				lpEventLogRecordBuffer		= (LPVOID)&chFakeBuffer;
				dwNumberOfBytesToRead		= 1;
				dwMinNumberOfBytesNeeded	= 0;

_retry_:
				bRetVal = ReadEventLog(hEventLog, EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ, dwEvLogCounter, 
					lpEventLogRecordBuffer, dwNumberOfBytesToRead, &dwBytesRead, &dwMinNumberOfBytesNeeded);

				if(!bRetVal)
				{
					g_dwLastError = GetLastError();

					if(g_dwLastError == ERROR_INSUFFICIENT_BUFFER)
					{
						lpEventLogRecordBuffer = (LPVOID)GlobalAlloc(GPTR, dwMinNumberOfBytesNeeded);
						if(lpEventLogRecordBuffer == (void *)0)
							goto _allocationfailure_;

						dwNumberOfBytesToRead = dwMinNumberOfBytesNeeded;
						goto _retry_;
					}
					else
						goto _unknownerror_;
				}
				else
				{
					PEVENTLOGRECORD pELR = 0;
					LPBYTE			pData = 0;
					HMODULE hModule = 0;
					TCHAR szExeFile[_MAX_PATH + 1], szExeFilePath[_MAX_PATH + 1];
					HKEY   hk						= (HKEY)0;
					TCHAR  szKeyName[_MAX_PATH + 1];
					DWORD dwMaxPath;
					DWORD dwType;
					LPBYTE			pStrings = 0;
					UINT uStringOffset;
					TCHAR *szExpandedString;
					LPVOID lpszBuffer = 0;
					
					pELR	= (PEVENTLOGRECORD)lpEventLogRecordBuffer;

					pData = (LPBYTE)GlobalAlloc(GPTR, pELR->DataLength * sizeof(BYTE));
					memcpy(pData, (LPBYTE)((LPBYTE)pELR + pELR->DataOffset), pELR->DataLength);

					{
						UINT x, uStepOfString = 0;

						pStrings = (LPBYTE)GlobalAlloc(GPTR, pELR->DataOffset - pELR->StringOffset * sizeof(BYTE));
						memcpy(pStrings, (LPBYTE)pELR + pELR->StringOffset, pELR->DataOffset - pELR->StringOffset);

						szExpandedString = (TCHAR *)GlobalAlloc(GPTR, (pELR->DataOffset - pELR->StringOffset + 1024) * sizeof(TCHAR));
						for(x = 0; x < pELR->NumStrings; x++)
						{
							if(x == 0)
							{
								strcpy(szExpandedString, (TCHAR *)pStrings + uStepOfString);
								if(x < (UINT)pELR->NumStrings - 1)
									strcat(szExpandedString, ",");
							}
							else
								strcat(szExpandedString, (TCHAR *)pStrings + uStepOfString);

							uStepOfString = strlen((TCHAR *)pStrings + uStepOfString) + 1;
						}

						wsprintf(szKeyName, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s"), 
							lpszEventLogSourceName, peid->lpszEventName);
						if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKeyName, 0L, KEY_READ, &hk) == NOERROR)
						{
							dwMaxPath = _MAX_PATH + 1;
							if(RegQueryValueEx(hk, _T("EventMessageFile"), 0, &dwType, (LPBYTE)szExeFile, &dwMaxPath) == NOERROR)
							{
								if(ExpandEnvironmentStrings(szExeFile, szExeFilePath, _MAX_PATH + 1) == 0)
									strcpy(szExeFilePath, szExeFile);

								hModule = LoadLibraryEx(szExeFilePath, 0, DONT_RESOLVE_DLL_REFERENCES);
								if(hModule)
								{
									TCHAR **_sz = (TCHAR**)GlobalAlloc(GPTR, (pELR->NumStrings) * sizeof(TCHAR *));
									register UINT z;

									uStringOffset = 0;
									for(z = 0; z < pELR->NumStrings; z++)
									{
										_sz[z] = (TCHAR *)GlobalAlloc(GPTR, 
											(strlen((TCHAR *)pStrings + uStringOffset) + 1) * sizeof(TCHAR));
										strcpy(_sz[z], (TCHAR *)pStrings + uStringOffset);

										uStringOffset += strlen((TCHAR *)pStrings + uStringOffset) + 1;
									}

									FormatMessage(
										FORMAT_MESSAGE_ALLOCATE_BUFFER | 
										FORMAT_MESSAGE_FROM_HMODULE | 
										FORMAT_MESSAGE_FROM_SYSTEM | 
										FORMAT_MESSAGE_ARGUMENT_ARRAY,
										hModule, pELR->EventID, 0, (LPTSTR)&lpszBuffer, 1024, 
										_sz
									);

									for(z = 0; z < pELR->NumStrings; z++)
									{
										SafeDeletePointer(_sz[z], strlen(_sz[z]));
										_sz[z] = 0;
									}
									SafeDeletePointer(_sz, (pELR->NumStrings) * sizeof(TCHAR *));
									_sz = 0;

									if(lpszBuffer)
									{
										strcpy(szExpandedString, (TCHAR *)lpszBuffer);
										uStringOffset = strlen(szExpandedString);
									}

									if(lpszBuffer)
										LocalFree(lpszBuffer);

									FreeLibrary(hModule);
								}
							}						
							RegCloseKey(hk);
						}

						SendMessage(hwndEditStrings, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szExpandedString);

						SafeDeletePointer(szExpandedString, strlen(szExpandedString));
					}

					{
						TCHAR _str[1024];
						_tcscpy(_str, _T(""));
						if(pELR->DataLength > 0)
						{
							register UINT x;

							for(x = 0; x < pELR->DataLength; x += 8)
							{
								TCHAR _strAux[1024];
								register UINT y;

								wsprintf(_strAux, "%.4x: ", x);
								_tcscat(_str, _strAux);

								for(y = x; y < x + 8; y++)
								{
									wsprintf(_strAux, "%.2x ", pData[y]);
									_tcscat(_str, _strAux);
								}
								_tcscat(_str, _T("  "));

								for(y = x; y < x + 8; y++)
								{
									if(!isprint((int)pData[y]))
										_tcscat(_str, _T("."));
									else
									{
										TCHAR s[2];
										s[0] = (TCHAR)pData[y];
										s[1] = '\0';
										_tcscat(_str, s);
									}
								}
								_tcscat(_str, _T("\r\n"));
							}
						}
						else
							_tcscat(_str, _T("No data available."));

						SendMessage(hwndEditData, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)_str);
					}
				}
			}

			goto _cleanup_;
		}
		else
			ReportLastError(0, 0, TRUE);

_unknownerror_:
		MessageBox(0, TEXT("Unknown error."), 0, MB_OK | MB_ICONSTOP);
		goto _cleanup_;

_allocationfailure_:
		MessageBox(0, TEXT("Allocation failure."), 0, MB_OK | MB_ICONSTOP);
		goto _cleanup_;

_cleanup_:
		CloseEventLog(hEventLog);
		hEventLog = 0;
	}
	else
		ReportLastError(0, 0, TRUE);

	#pragma warning(disable:4127)
	SafeDeletePointer(peid, sizeof(EVENTID));

	return 0L;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions