//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1999 - 2000
//
//  File:       filedata.cpp
//
//--------------------------------------------------------------------------

// FileData.cpp: implementation of the CFileData class.
//
//////////////////////////////////////////////////////////////////////


#ifndef NO_STRICT
#ifndef STRICT
#define STRICT 1
#endif
#endif /* NO_STRICT */

#include <WINDOWS.H>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <wtypes.h>
#include <winnt.h>

#include <time.h>
#include "FileData.h"
#include "Globals.h"
#include "Version.h"
#include "Processes.h"
#include "ProcessInfo.h"
#include "Modules.h"
#include "UtilityFunctions.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CFileData::CFileData()
{
	m_dwGetLastError = 0;
	m_hFileHandle = INVALID_HANDLE_VALUE;
	m_tszFilePath = NULL;
	m_szLINEBUFFER[0] = 0;
	m_hFileMappingObject = NULL;
	m_lpBaseAddress = NULL;
	m_lpCurrentFilePointer = NULL;
	m_lpCurrentLocationInLINEBUFFER = NULL;
}

CFileData::~CFileData()
{
	if (m_tszFilePath)
		delete [] m_tszFilePath;

	if (m_lpBaseAddress)
		UnmapViewOfFile(m_lpBaseAddress);

	if (m_hFileMappingObject)
		CloseHandle(m_hFileMappingObject);
}

bool CFileData::SetFilePath(LPTSTR tszFilePath)
{
	// Did we get a proper string?
	if (!tszFilePath)
		return false;

	if (m_tszFilePath)
		delete [] m_tszFilePath;

	m_tszFilePath = new TCHAR[(_tcsclen(tszFilePath)+1)];

	if (!m_tszFilePath)
		return false;

	_tcscpy(m_tszFilePath, tszFilePath);
	return true;
}

LPTSTR CFileData::GetFilePath()
{
	return m_tszFilePath;
}

bool CFileData::VerifyFileDirectory()
{
	if (!m_tszFilePath)
		return false;

	TCHAR tszDrive[_MAX_DRIVE];
	TCHAR tszDirectory[_MAX_DIR];

	TCHAR tszDirectoryPath[_MAX_PATH];

	// Get just the directory...
	_tsplitpath(m_tszFilePath, tszDrive, tszDirectory, NULL, NULL);

	// Now, recompose this into a directory path...
	_tcscpy(tszDirectoryPath, tszDrive);
	_tcscat(tszDirectoryPath, tszDirectory);
	_tcscat(tszDirectoryPath, TEXT("*.*"));

	WIN32_FIND_DATA FindFileData;

	HANDLE hDirectoryHandle = FindFirstFile(tszDirectoryPath, &FindFileData);
	
	if (hDirectoryHandle == INVALID_HANDLE_VALUE)
	{
		// Failure to find the directory...
		SetLastError();
		return false;
	}

	// Close this now that we're done...
	FindClose(hDirectoryHandle);
	return true;
}

/*
DWORD CFileData::GetLastError()
{
	return m_dwGetLastError;
}
*/
bool CFileData::OpenFile(DWORD dwCreateOption, bool fReadOnlyMode)
{
	if (!m_tszFilePath)
	{
		return false;
	}

	// Open the file for read/write
	m_hFileHandle = CreateFile(m_tszFilePath, 
							  fReadOnlyMode ? ( GENERIC_READ )
										    : ( GENERIC_READ | GENERIC_WRITE ),
							  0,	// Not shareable
							  NULL, // Default security descriptor
							  dwCreateOption,
							  FILE_ATTRIBUTE_NORMAL,
							  NULL);

	if (m_hFileHandle == INVALID_HANDLE_VALUE)
	{
		SetLastError();
		return false;
	}

	return true;
}

bool CFileData::CloseFile()
{
	if (m_hFileHandle == INVALID_HANDLE_VALUE)
	{
		return false;
	}

	if (!CloseHandle(m_hFileHandle))
	{
		SetLastError();
		return false;
	}
	
	m_hFileHandle = INVALID_HANDLE_VALUE;
	return true;
}

bool CFileData::WriteString(LPTSTR tszString, bool fHandleQuotes /* = false */)
{
	DWORD dwByteCount = 0;
	DWORD dwBytesWritten;
	LPSTR szStringBuffer = NULL; // Pointer to the ANSI string (after conversion if necessary)
	bool fReturn = false;

	if (m_hFileHandle == INVALID_HANDLE_VALUE)
	{
		goto cleanup;
	}

	// We'll first convert the string if we need to...

	szStringBuffer = CUtilityFunctions::CopyTSTRStringToAnsi(tszString);

	if (!szStringBuffer)
		goto cleanup;

	dwByteCount = _tcsclen(tszString); // This is the number of characters (not bytes!)

	// See if we were asked to handle quotes, and if there exists a comma or quote in the string
	if ( fHandleQuotes == true && ((strchr(szStringBuffer, ',') || strchr(szStringBuffer, '"' ))) )
	{
		unsigned int iQuotedStringIndex = 0;
		unsigned int iStringBufferIndex = 0;
		
		// Special processing is required... this doesn't happen often, so this 
		// allocation which I'm about to make won't be done regularly...
		LPSTR szQuotedStringBuffer = new char[1024];

		// Did we successfully allocate storage?
		if (!szQuotedStringBuffer)
			goto cleanup;
			
		// Keep going until we're at the end of the string...

		// We start by adding a quote (since we know that we have a comma or quote somewhere...

		szQuotedStringBuffer[iQuotedStringIndex++] = '\"';

		// Keep going until the end of the string...
		while (szStringBuffer[iStringBufferIndex] != '\0')
		{
			// We found a quote
			if (szStringBuffer[iStringBufferIndex] == '"')
			{
				// We found a quote... I'll copy another quote in, and the quote already here
				// will ensure we have two quotes together "" which in a CSV file represents a
				// single quote...
				szQuotedStringBuffer[iQuotedStringIndex++] = '\"';
			}

			// Copy the source char to the dest...
			szQuotedStringBuffer[iQuotedStringIndex++] = szStringBuffer[iStringBufferIndex++];
		}

		// Append the final quote (and \0)...
		szQuotedStringBuffer[iQuotedStringIndex++] = '\"';
		szQuotedStringBuffer[iQuotedStringIndex++] = '\0';

		// Just write out the data the nice, fast way...
		if (!WriteFile(m_hFileHandle, szQuotedStringBuffer, strlen(szQuotedStringBuffer), &dwBytesWritten, NULL))
		{
			delete [] szQuotedStringBuffer;
			goto cleanup;
		}

		delete [] szQuotedStringBuffer;
	} else
	{
		// Just write out the data the nice, fast way...
		if (!WriteFile(m_hFileHandle, szStringBuffer, dwByteCount, &dwBytesWritten, NULL))
		{
			goto cleanup;
		}
	}

	fReturn = true;

cleanup:

	if (szStringBuffer)
		delete [] szStringBuffer;

	return fReturn;
}

bool CFileData::WriteDWORD(DWORD dwNumber)
{
	TCHAR tszBuffer[10+1]; // 0xFFFFFFFF == 4294967295 (10 characters) + 1 for the \0

	_stprintf(tszBuffer, TEXT("%u"), dwNumber);
	
	if (!WriteString(tszBuffer))
		return false;

	return true;
}

bool CFileData::WriteTimeDateString(time_t Time)
{
	enum {BUFFERSIZE = 128};

	TCHAR tszBuffer[BUFFERSIZE];
	struct tm * localTime = localtime(&Time);

	if (localTime)
	{
		// This top version seems to be better Y2K friendly as I spit out the full year...
		_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%B %d, %Y %H:%M:%S"), localTime);
		//_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%c"), localtime(&Time));

		if (!WriteString(tszBuffer, true))
			return false;
	} else
	{	// A bad TimeDate stamp was provided
		if (!WriteString(TEXT("<INVALID DATE>"), true))
			return false;
	}
	
	return true;
}

bool CFileData::WriteFileHeader()
{
	enum {BUFFERSIZE = 128};
	TCHAR tszBuffer[BUFFERSIZE];
	DWORD dwNum = BUFFERSIZE;

	// Write the Checksym version info...
	_stprintf(tszBuffer, TEXT("CHECKSYM, (%d.%d:%d.%d)\r\n"), VERSION_FILEVERSION);

	if (!WriteString(tszBuffer))
		return false;

	// Write the current date/time info...

	if (!WriteString(TEXT("Created:,")))
		return false;

	time_t Time;
	time(&Time);

	if (!WriteTimeDateString(Time))
		return false;

	// Write the carriage-return line-feed combo... 
	if (!WriteString(TEXT("\r\n")))
		return false;

	// Spit out the computername
	if (!GetComputerName(tszBuffer, &dwNum))
		return false;

	if (!WriteString(TEXT("Computer:,")))
		return false;

	if (!WriteString(tszBuffer))
		return false;

	// Write the carriage-return line-feed combo... (a couple of times)...
	if (!WriteString(TEXT("\r\n")))
		return false;


	return true;
}

void CFileData::PrintLastError()
{
	CUtilityFunctions::PrintMessageString(GetLastError());
}

bool CFileData::CreateFileMapping()
{
	m_hFileMappingObject = ::CreateFileMapping(m_hFileHandle,
											   NULL,
											   PAGE_READONLY | SEC_COMMIT,
											   0,
											   0,
											   NULL);

	if (m_hFileMappingObject == NULL)
	{
		SetLastError();
		return false;
	}

	// Okay, we'll map the view as well...
	m_lpBaseAddress = MapViewOfFile(m_hFileMappingObject,
							   	    FILE_MAP_READ,
									0,
									0,
									0);

	if (m_lpBaseAddress == NULL)
	{
		SetLastError();
		return false;
	}

	m_lpCurrentFilePointer = (LPSTR) m_lpBaseAddress;

	return true;
}

bool CFileData::ReadFileHeader()
{
	// For starters, let's read a line...
	if (!ReadFileLine())
	     return false;

	enum { BUFFER_SIZE = 128};
	char szTemporaryBuffer[BUFFER_SIZE];
	DWORD cbBytesRead;
		
	cbBytesRead = ReadString(szTemporaryBuffer, BUFFER_SIZE);

	// We gotta read something?
	if (0 == cbBytesRead)
		return false;

	// Look for our "Magic" Value
	if (_stricmp(szTemporaryBuffer, "CHECKSYM"))
	{
		_tprintf(TEXT("Error: Input file has invalid header.  Missing CHECKSYM keyword!\n"));
		return false;
	}

	// Read version number
	// We'll do this later if needed...

	// Read Created Time
	if (!ReadFileLine())
	     return false;

	// Read Computer this was created on
	if (!ReadFileLine())
	     return false;

	return true;
}

bool CFileData::ReadFileLine()
{
	// We're ansi oriented (since this is a CSV file -- in case you were wondering)
	size_t pos;

	// Find the first \r or \n character (if we're point to \0, we'll figure that out)
	pos = strcspn(m_lpCurrentFilePointer, "\r\n");

	// Hmm... we don't read a line that starts on \r\n very well...
	if (pos == 0)
	{
		m_szLINEBUFFER[0] = '\0';
		ResetBufferPointerToStart();
		return false; 
	}

	// Read the line into our buffer
	strncpy(m_szLINEBUFFER, m_lpCurrentFilePointer, pos);

	// Null terminate for ease of use...
	m_szLINEBUFFER[pos] = '\0'; 

	ResetBufferPointerToStart();

	// Advance the current file pointer to just beyond the last character we read...
	// This should advance to the \r\n or \0
	m_lpCurrentFilePointer += pos;

	// We want this file pointer to advance beyond any \r \n chars we may have found...
	while (*m_lpCurrentFilePointer)
	{
		// Advance pointer to non- \r or \n
		if ( (*m_lpCurrentFilePointer == '\r') ||
			 (*m_lpCurrentFilePointer == '\n') )
		{
			 m_lpCurrentFilePointer++;
		}
		else
		{
			break; // Found either the \0 or something else...
		}
	}

	return true;
}

DWORD CFileData::ReadString(LPSTR szStringBuffer, DWORD iStringBufferSize)
{
	// If we give a buffer size, we have to give a buffer...
	if ( szStringBuffer == NULL && iStringBufferSize )
		return 0;

	// The ReadFileLine() call puts us at the start of a line (after
	// the \r \n combinations...  It's possible that we're at the
	// end...

	// If we're pointing to the end of the file, let's bail...
	if (*m_lpCurrentLocationInLINEBUFFER == '\0')
		return 0;

	DWORD iBytesCopied = 0;
	bool fFinished = false;
	bool fFoundSeparatorChars = false; // These might be '\r', '\n', or ','
	bool fQuoteMode = false;

	while (!fFinished)
	{
		switch (*m_lpCurrentLocationInLINEBUFFER)
		{
			case '"':
				// Okay, we found a quote... that's cool.. but are we quoting a quote,
				// or... are we in quote mode?

				// Probe ahead... is the next char a '"' also?
				if ( *(m_lpCurrentLocationInLINEBUFFER+1) == '"')
				{
					// Yes it is... so go ahead and copy the quote
					CopyCharIfRoom(iStringBufferSize, szStringBuffer, &iBytesCopied, &fFinished);
					if (!fFinished)
						*(m_lpCurrentLocationInLINEBUFFER++);	// Skip the quote
				}
				else
				{
					*(m_lpCurrentLocationInLINEBUFFER++);
					fQuoteMode = !fQuoteMode; // Toggle the quote mode...
					continue;
				}

			case '\0':
				fFinished = true;
				break;

			case ',':
				if (!fQuoteMode)
				{   // If we're not in quote mode, then this marks the end of a field...
					fFinished = true;
					fFoundSeparatorChars = true;
					*(m_lpCurrentLocationInLINEBUFFER++);
				}
				else
				{
					// Okay, this marks a new character that happens to be a comma...
					CopyCharIfRoom(iStringBufferSize, szStringBuffer, &iBytesCopied, &fFinished);
				}
				break;

			case '\r':
			case '\n':
				// We note that we found these, and simply advance the pointer...
				fFoundSeparatorChars = true;
				*(m_lpCurrentLocationInLINEBUFFER++);
				break;

			default:

				if (fFoundSeparatorChars)
				{
					// We were scanning... found a separator after some data... so we bail
					fFinished = true;
					break;
				}

				CopyCharIfRoom(iStringBufferSize, szStringBuffer, &iBytesCopied, &fFinished);
		}
	}

	if (iStringBufferSize) // We only NULL terminate a buffer if one was provided...
		szStringBuffer[iBytesCopied] = '\0'; // Null terminate this puppy...

	return iBytesCopied;
}

//
// This function is responsible for reading through the CSV file and creating any necessary
// objects and populating them with data...
//
bool CFileData::DispatchCollectionObject(CProcesses ** lplpProcesses, CProcessInfo ** lplpProcess, CModules ** lplpModules, CModules ** lplpKernelModeDrivers, CModuleInfoCache * lpModuleInfoCache, CFileData * lpOutputFile)
{
	enum { BUFFER_SIZE = 128};
	char szTemporaryBuffer[BUFFER_SIZE];
	TCHAR tszTemporaryBuffer[BUFFER_SIZE];
	DWORD cbBytesRead;
	bool fContinueReading = true;

	// Read the Output Type
	if (!ReadFileLine())
		 return false;

	while (fContinueReading)
	{
		// If this is the second iteration (or more) we may not be at the
		// start of our buffer (causing the read of the output type to fail)
		ResetBufferPointerToStart();

		// Read the Output Type line...
		cbBytesRead = ReadString(szTemporaryBuffer, BUFFER_SIZE);

		// We gotta read something?
		if (0 == cbBytesRead)
			return true;
		
		// I hate to do this... but we read this stuff as ASCII... may need to
		// convert to a TCHAR format to be neutral...
		CUtilityFunctions::CopyAnsiStringToTSTR(szTemporaryBuffer, tszTemporaryBuffer, cbBytesRead+1);

		// Printout the section we're attempting to read...
		if (!g_lpProgramOptions->GetMode(CProgramOptions::QuietMode))
			_tprintf(TEXT("  Reading %s data...\n"), tszTemporaryBuffer);

		if ( _tcsicmp(g_tszCollectionArray[Processes].tszCSVLabel, tszTemporaryBuffer) == 0 )
		{
			/*
				[PROCESSES]
			*/

			// Read to the end of the line
			if (!ReadFileLine())
				return false;

			// Yup, it is... let's create a Processes Object
			if (*lplpProcesses == NULL)
			{
				// Allocate a structure for our Processes Object.
				*lplpProcesses = new CProcesses();
				
				if (!*lplpProcesses)
				{
					_tprintf(TEXT("Unable to allocate memory for the processes object!\n"));
					goto cleanup;
				}

				// The Processes Object will init differently depending on what
				// Command-Line arguments have been provided... 
				if (!(*lplpProcesses)->Initialize(lpModuleInfoCache, this, lpOutputFile))
				{
					_tprintf(TEXT("Unable to initialize Processes Object!\n"));
					goto cleanup;
				}
			}

			// Okay, go get the Process Data...
			(*lplpProcesses)->GetProcessesData();

		} else
		if ( _tcsicmp(g_tszCollectionArray[Process].tszCSVLabel, tszTemporaryBuffer) == 0 )
		{
			/*
				[PROCESS]
			*/
			// Read to the end of the line
			if (!ReadFileLine())
				return false;

			// Yup, it is... let's create a ProcessInfo Object
			if (*lplpProcess== NULL)
			{
				// Allocate a structure for our ProcessInfo Object.
				*lplpProcess = new CProcessInfo();
				
				if (!*lplpProcess)
				{
					_tprintf(TEXT("Unable to allocate memory for the processinfo object!\n"));
					goto cleanup;
				}

				// The Modules Object will init differently depending on what
				// Command-Line arguments have been provided... 
				if (!(*lplpProcess)->Initialize(lpModuleInfoCache, this, lpOutputFile, NULL))
				{
					_tprintf(TEXT("Unable to initialize Modules Object!\n"));
					goto cleanup;
				}
			}

			// Okay, go get the Process Data
			(*lplpProcess)->GetProcessData();
		} else
		if ( _tcsicmp(g_tszCollectionArray[Modules].tszCSVLabel, tszTemporaryBuffer) == 0 )
		{
			/*
				[MODULES]
			*/
			// Read to the end of the line
			if (!ReadFileLine())
				return false;

			// Yup, it is... let's create a Modules Object
			if (*lplpModules == NULL)
			{
				// Allocate a structure for our Modules Object.
				*lplpModules = new CModules();
				
				if (!*lplpModules)
				{
					_tprintf(TEXT("Unable to allocate memory for the modules object!\n"));
					goto cleanup;
				}

				// The Modules Object will init differently depending on what
				// Command-Line arguments have been provided... 
				if (!(*lplpModules)->Initialize(lpModuleInfoCache, this, lpOutputFile, NULL))
				{
					_tprintf(TEXT("Unable to initialize Modules Object!\n"));
					goto cleanup;
				}
			}

			// Okay, go get the Modules Data (collected from the filesystem)
			(*lplpModules)->GetModulesData(CProgramOptions::InputModulesDataFromFileSystemMode, true);
		} else
		if ( _tcsicmp(g_tszCollectionArray[KernelModeDrivers].tszCSVLabel, tszTemporaryBuffer) == 0 )
		{
			/*
				[KERNEL-MODE DRIVERS]
			*/
			// Read to the end of the line
			if (!ReadFileLine())
				return false;

			// Yup, it is... let's create a Modules Object
			if (*lplpKernelModeDrivers == NULL)
			{
				// Allocate a structure for our Modules Object.
				*lplpKernelModeDrivers = new CModules();
				
				if (!*lplpKernelModeDrivers)
				{
					_tprintf(TEXT("Unable to allocate memory for the modules object!\n"));
					goto cleanup;
				}

				// The Modules Object will init differently depending on what
				// Command-Line arguments have been provided... 
				if (!(*lplpKernelModeDrivers)->Initialize(lpModuleInfoCache, this, lpOutputFile, NULL))
				{
					_tprintf(TEXT("Unable to initialize Modules Object!\n"));
					goto cleanup;
				}
			}

			// Okay, go get the Modules Data (collected from the filesystem)
			(*lplpKernelModeDrivers)->GetModulesData(CProgramOptions::InputDriversFromLiveSystemMode, true);
		} else
		{
			_tprintf(TEXT("Unrecognized section %s found!\n"), tszTemporaryBuffer);
			return false;
		}
	}

cleanup:
	return false;
}

bool CFileData::ReadDWORD(LPDWORD lpDWORD)
{
	char szTempBuffer[10+1]; // 0xFFFFFFFF == 4294967295 (10 characters) + 1 for the \0

	if (!ReadString(szTempBuffer, 10+1))
		return false;

	// Convert it... baby...
	*lpDWORD = atoi(szTempBuffer);

	return true;
}

bool CFileData::CopyCharIfRoom(DWORD iStringBufferSize, LPSTR szStringBuffer, LPDWORD piBytesCopied, bool *pfFinished)
{
	if (iStringBufferSize)
	{
		// If we have room to copy the data... let's do it...
		if (*piBytesCopied < iStringBufferSize)
		{
			szStringBuffer[(*piBytesCopied)++] = *(m_lpCurrentLocationInLINEBUFFER++);
		} else
		{
			// No room... we're done.
			*pfFinished = true;
		}
	} else
	{
		// Just advance the pointer... we have no buffer to copy to...
		*(m_lpCurrentLocationInLINEBUFFER++);
	}

	return true;
}

bool CFileData::ResetBufferPointerToStart()
{
	// Reset the Pointer with our line buffer to the start of this buffer
	m_lpCurrentLocationInLINEBUFFER = m_szLINEBUFFER;

	return true;
}

bool CFileData::EndOfFile()
{
	//return (*m_lpCurrentFilePointer == '\0');
	return (*m_lpCurrentLocationInLINEBUFFER == '\0');
}

bool CFileData::WriteFileTimeString(FILETIME ftFileTime)
{
	enum {BUFFERSIZE = 128};

	TCHAR tszBuffer[BUFFERSIZE];
	FILETIME ftLocalFileTime;
	SYSTEMTIME lpSystemTime;
	int cch = 0;

	// Let's convert this to a local file time first...
	if (!FileTimeToLocalFileTime(&ftFileTime, &ftLocalFileTime))
		return false;

	FileTimeToSystemTime( &ftLocalFileTime, &lpSystemTime );

	
	cch = GetDateFormat( LOCALE_USER_DEFAULT,
						 0,
						 &lpSystemTime,
						 TEXT("MMMM d',' yyyy"),
						 tszBuffer,
						 BUFFERSIZE );

	if (!cch)
		return false;

	tszBuffer[cch-1] = TEXT(' '); 

	// 
    // Get time and format to characters 
    // 
 
    GetTimeFormat( LOCALE_USER_DEFAULT, 
				   0, 
				   &lpSystemTime,   // use current time 
				   NULL,   // use default format 
				   tszBuffer + cch, 
				   BUFFERSIZE - cch ); 
 

	// <Full Month Name> <day>, <Year with Century> <Hour>:<Minute>:<Second>
	//_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%B %d, %Y %H:%M:%S"), localtime(&Time));
	//_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%c"), localtime(&Time));

	if (!WriteString(tszBuffer, true))
		return false;

	return true;
}

// Exception Monitor prefers a MM/DD/YYYY HH:MM:SS format...
bool CFileData::WriteTimeDateString2(time_t Time)
{
	enum {BUFFERSIZE = 128};

	TCHAR tszBuffer[BUFFERSIZE];

	// This top version seems to be better Y2K friendly as I spit out the full year...
	_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%m/%d/%Y %H:%M:%S"), localtime(&Time));
	//_tcsftime(tszBuffer, BUFFERSIZE, TEXT("%c"), localtime(&Time));

	if (!WriteString(tszBuffer, true))
		return false;

	return true;
}

// Exception Monitor prefers a MM/DD/YYYY HH:MM:SS format...
bool CFileData::WriteFileTimeString2(FILETIME ftFileTime)
{
	enum {BUFFERSIZE = 128};

	TCHAR tszBuffer[BUFFERSIZE];
	FILETIME ftLocalFileTime;
	SYSTEMTIME lpSystemTime;
	int cch = 0;

	// Let's convert this to a local file time first...
	if (!FileTimeToLocalFileTime(&ftFileTime, &ftLocalFileTime))
		return false;

	FileTimeToSystemTime( &ftLocalFileTime, &lpSystemTime );

	
	cch = GetDateFormat( LOCALE_USER_DEFAULT,
						 0,
						 &lpSystemTime,
						 TEXT("MM/dd/yyyy"),
						 tszBuffer,
						 BUFFERSIZE );

	if (!cch)
		return false;

	tszBuffer[cch-1] = TEXT(' '); 

	// 
    // Get time and format to characters 
    // 
 
    GetTimeFormat( LOCALE_USER_DEFAULT, 
				   0, 
				   &lpSystemTime,   // use current time 
				   TEXT("HH:mm:ss"),   // use default format 
				   tszBuffer + cch, 
				   BUFFERSIZE - cch ); 

	if (!WriteString(tszBuffer, true))
		return false;

	return true;
}

//#endif