// FILEUTIL.CPP
//
// File utility routines
//
// Copyright (c) 1991-2000 by QUALCOMM, Incorporated
/* Copyright (c) 2016, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

//

#include "stdafx.h"

// Support for GetLongPathName on Windows95
#define COMPILE_NEWAPIS_STUBS
#define WANT_GETLONGPATHNAME_WRAPPER
#include "NewAPIs.h"

#include <QCUtils.h>

#include "fileutil.h"
#include "resource.h"
#include "guiutils.h"

#include "rs.h"  //for CRString, EudoraEnvironVar, SetupINIFilename

#include "DebugNewHelpers.h"


JJFile::JJFile(int _size /*= BUF_SIZE*/, BOOL bDisplayErrors /*= TRUE*/) :
	JJFileMT(_size),
	m_bDisplayErrors(bDisplayErrors)
{
	//ASSERT(::IsMainThreadMT());
	ErrorCallback = 0;
}


JJFile::JJFile(char* _buf, int _size, BOOL bDisplayErrors /*= TRUE*/) :
	JJFileMT(_buf, _size),
	m_bDisplayErrors(bDisplayErrors)
{
	//ASSERT(::IsMainThreadMT());
		ErrorCallback = 0;
}


JJFile::~JJFile()
{
	//ASSERT(::IsMainThreadMT());
}



////////////////////////////////////////////////////////////////////////
// Open [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Open(const char* pszFilename, int mode)
{
	//ASSERT(::IsMainThreadMT());

	// We'll repeat the operation if we get the "right" sort of error;
	// Setup the repeat count & interval SD 3/10/99
	int repeats = min(10L,max(1L,GetIniLong(IDS_INI_OPEN_RETRY_COUNT)));
	int interval = min(1000L,max(10L,GetIniLong(IDS_INI_OPEN_RETRY_INTERVAL)));

	HRESULT hr = JJFileMT::Open(pszFilename, mode, repeats, interval);

	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}


////////////////////////////////////////////////////////////////////////
// Flush [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Flush()
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Flush();
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}


////////////////////////////////////////////////////////////////////////
// Seek [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Seek(long lOffset, int nMode /*= SEEK_SET*/, long* plNewOffset /*= NULL*/)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Seek(lOffset, nMode, plNewOffset);
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}


////////////////////////////////////////////////////////////////////////
// Delete [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Delete()
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Delete();
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}



////////////////////////////////////////////////////////////////////////
// Rename [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Rename(const char* NewName, BOOL bReplace /*= TRUE*/)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Rename(NewName, bReplace);
	if (FAILED(hr))
		DisplayErrorDialog_(hr, NewName);
	return hr;
}




////////////////////////////////////////////////////////////////////////
// JJBlockMove [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::JJBlockMove(long Start, long Length, JJFileMT* Target)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::JJBlockMove(Start, Length, Target);
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}


////////////////////////////////////////////////////////////////////////
// GetLine [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::GetLine(char* pBuffer, long lBufferSize, long* plNumBytesRead /*= NULL*/)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::GetLine(pBuffer, lBufferSize, plNumBytesRead);
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}



////////////////////////////////////////////////////////////////////////
// Read [public, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Read(char* pBuffer, long lSize)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Read(pBuffer, lSize);
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}


////////////////////////////////////////////////////////////////////////
// Write_ [protected, virtual]
//
////////////////////////////////////////////////////////////////////////
HRESULT JJFile::Write_(const char* pBuffer, long lNumBytesToWrite)
{
	//ASSERT(::IsMainThreadMT());

	HRESULT hr = JJFileMT::Write_(pBuffer, lNumBytesToWrite);
	if (FAILED(hr))
		DisplayErrorDialog_(hr);
	return hr;
}





void JJFile::ReportError(UINT StringID, ...)
{
	
	if(!m_bDisplayErrors)
		return;

	char buf[1024] = {0};
		
	va_list argList;
	va_start(argList, StringID);
	_vsnprintf(buf, sizeof(buf) - 1, CRString(StringID), argList);
	va_end(argList);

	if( ErrorCallback)
		ErrorCallback(buf);
	else
	{
		ASSERT(::IsMainThreadMT());
		AlertDialog(IDD_ERROR_DIALOG, buf);
	}
}


////////////////////////////////////////////////////////////////////////
// DisplayErrorDialog_ [protected]
//
////////////////////////////////////////////////////////////////////////
void JJFile::DisplayErrorDialog_(HRESULT hr, const char* pszExtra /*= NULL*/)
{
	//ASSERT(::IsMainThreadMT());

	if (! m_bDisplayErrors)
		return;
	
	//
	// Get the filename.
	//
	CString strFilename("???");
	{
		BSTR bstrFilename = NULL;
		if (SUCCEEDED(GetFName(&bstrFilename)))
		{
			strFilename = bstrFilename;
			::SysFreeString(bstrFilename);
		}
	}

	CString strError("???");
	switch (HRESULT_CODE(hr))
	{
	case QCUTIL_E_FILE_WRITE:
		::AfxFormatString1(strError, IDS_ERR_FILE_WRITE, (const char *) strFilename);
		break;
	case QCUTIL_E_FILE_READ:
		::AfxFormatString1(strError, IDS_ERR_FILE_READ, (const char *) strFilename);
		break;
	case QCUTIL_E_FILE_OPEN_WRITING:
		::AfxFormatString2(strError, IDS_ERR_FILE_OPEN, (const char *) strFilename, (const char *) CRString(IDS_ERR_FILE_OPEN_WRITING));
		break;
	case QCUTIL_E_FILE_OPEN_READING:
		::AfxFormatString2(strError, IDS_ERR_FILE_OPEN, (const char *) strFilename, (const char *) CRString(IDS_ERR_FILE_OPEN_READING));
		break;
	case QCUTIL_E_FILE_RENAME:
		ASSERT(pszExtra != NULL);
		::AfxFormatString2(strError, IDS_ERR_FILE_RENAME, (const char *) strFilename, (const char *) pszExtra);
		break;
	case QCUTIL_E_FILE_DELETE:
		::AfxFormatString1(strError, IDS_ERR_FILE_DELETE, (const char *) strFilename);
		break;
	case QCUTIL_E_FILE_CLOSE:
		break;
	default:
		LPTSTR lpBuffer = NULL;
		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
						NULL, hr, 0, (LPTSTR)&lpBuffer, 0, NULL);
		::AfxFormatString2(strError, IDS_ERR_FILE_UNKNOWN, (const char *) strFilename, lpBuffer);
		LocalFree(lpBuffer);
		break;
	}

	//
	// GetLastError() is presumed to return the value of the global
	// ::errno (assumed to be in the range 0 thru 36) for displaying
	// extra information about WHY the file I/O call failed.
	//
	int nError = GetLastError_();
	if (nError < 0 || nError > 36)
		nError = 37;		// unknown error

	const int IDS_FILE_BASE_ERROR = 8500;
	ReportError(IDS_DOS_ERR_FORMAT, 
					(const char *) strError,
					(const char*) CRString(IDS_FILE_BASE_ERROR + nError), nError);
}




int EudoraDirLen = 0;
CString EudoraDir;
CString EudoraDirNoBackslash;
CString ExecutableDir;
CString TempDir;

BOOL CheckMailDirectory(const char* dir, const char* Location)
{
	char szShortEDir[_MAX_PATH + 1];
	char Edir[_MAX_PATH + 1];

	// fully qualify the path
	if ( ( dir[ 1 ] != ':' ) && ( strstr( dir, "\\\\" ) != dir ) )
	{
		int drive = _getdrive();
		szShortEDir[ 0 ] = (char)(drive + 'A' - 1);	// drive 1 = A, 2 = B, etc.
		szShortEDir[ 1 ] = ':';
		szShortEDir[ 2 ] = '\0';
	}
	else
	{
		szShortEDir[ 0 ] = '\0';
	}

	strcat(szShortEDir, dir);
	::TrimWhitespaceMT(szShortEDir);

	GetLongPathName(szShortEDir, Edir, _MAX_PATH);

	EudoraDirLen = strlen(Edir);
	if (Edir[EudoraDirLen - 1] == SLASH)
       	Edir[EudoraDirLen - 1] = 0;

	// Check for root directories because access() will barf otherwise
	if (Edir[0] == 0)
		;
	else if (isalpha((int)(unsigned char)(Edir[0])) && Edir[1] == ':' && Edir[2] == 0)
	{	
		Edir[2] = SLASH; 
		Edir[3] = 0;	
	}
        
    //
    // Check to make sure that the directory exists and is *writable*.
	//
    if (!::FileExistsMT(Edir))	
	{
		ErrorDialog(IDS_ERR_BAD_EUDORA_DIR, Location, dir);
		return (FALSE);
    }
		  
	// If this is a directory, then add the trailing backslash
	if (strlen(Edir) > 3)
	{
		struct stat FileAttr;
		int status = stat(Edir, &FileAttr);
		if ( status == 0 && FileAttr.st_mode & S_IFDIR )
		{
			// Add trailing backslash
			strcat(Edir, SLASHSTR);
		}
	}
	
	EudoraDir = Edir;
	EudoraDirLen = EudoraDir.GetLength();
	return (TRUE);
}

//////////////////////////////////////////////////////////////
BOOL GetDirs(char* CmdLine)
{
	int	done = 0;
	char buf[_MAX_PATH + 1]; 
	int status = FALSE;
	BOOL validTempDir = TRUE;
	char *Ini = NULL, *temp;
	CRString Temp1(IDS_TEMP1_ENVIRON_VAR), Temp2(IDS_TEMP2_ENVIRON_VAR);
	CRString EudoraEnvironVar(IDS_EUDORA_ENVIRON_VAR);

	//The Win32 call GetTempPath returns the directory specified by TMP or TEMP or 
	//the Windows dir if neither tmp or temp env vars were found. But the directories
	//are not validated on WinNT. So we see if they exist ourselves, else create a
	//TEMP dir in the Mail directory since we don't want to leave files lying around
	//in the Windows dir incase Eudora crashed.
	if ( ! GetEnvironmentVariable(Temp1, buf, sizeof(buf)) || (!::FileExistsMT(buf, TRUE)) )
	{
		if ( ! GetEnvironmentVariable(Temp2, buf, sizeof(buf)) || (!::FileExistsMT(buf, TRUE)) )
			validTempDir = FALSE;
	}

	//Found a valid TempDir? Then copy it.
	if (validTempDir == TRUE)
		TempDir = buf;

	if (GetModuleFileName(AfxGetInstanceHandle(), buf, sizeof(buf)))
	{
		temp = strrchr(buf, SLASH);
		if (temp)
		{
			temp[1] = 0;
			ExecutableDir = buf;
		}
    }

	if (CmdLine && *CmdLine)
    {
		// If long filename, arguments will be separated by quotes
		if (Ini = strstr(CmdLine, "\" "))
			*(++Ini)++ = 0;
		else if ((*(CmdLine + strlen(CmdLine) - 1) != '\"') && (Ini = strchr(CmdLine, ' ')))
			*Ini++ = 0;

#ifdef WIN32
		::StripQuotesMT(CmdLine);
		if (Ini)
		{
			::TrimWhitespaceMT(Ini);
			::StripQuotesMT(Ini);
			GetShortPathName(Ini,Ini, strlen(Ini));
		}
#endif

		// If the directory doesn't have any path, then assume it's an INI
		if (!Ini && (!strchr(CmdLine, SLASH) && CmdLine[1] != ':'))
		{
			Ini = CmdLine;
		}
		else
		{
			status = CheckMailDirectory(CmdLine, CRString(IDS_FILE_COMMAND_LINE));
		 	done = 1;
		}
	}

	if (!done && GetEnvironmentVariable(EudoraEnvironVar, buf, sizeof(buf)))
	{
		temp = buf;
		// If the directory doesn't have any path, then assume it's an INI
		if (!strchr(temp, SLASH) && temp[1] != ':')
		{
			Ini = temp;
		}
		else
		{
			status = CheckMailDirectory(temp, CRString(IDS_FILE_EUDORA_ENV_VAR));
			done = 1;
		}
	}

	if (!done && EudoraDir.IsEmpty())
	{
		EudoraDir = ExecutableDir;
		status = TRUE;
	}

	if (status)
	{
		// If we don't have an Ini file, but the EudoraDir actually points
		// to a file, then assume it is an Ini file and set the
		// Eudora directory to the directory the file is in.
		struct stat FileAttr;
		if (!Ini && EudoraDir[EudoraDir.GetLength() - 1] != SLASH &&
			(stat(EudoraDir, &FileAttr) == 0) && !(FileAttr.st_mode & S_IFDIR))
		{
			Ini = ::SafeStrdupMT(EudoraDir);
			int Slash = EudoraDir.ReverseFind(SLASH);
			if (Slash >= 0)
				EudoraDir.ReleaseBuffer(Slash + 1);
			SetupINIFilename(Ini);
			delete [] Ini;
			Ini = NULL;
		}
		else
			SetupINIFilename(Ini);
	}

	// If there was no command-line specified, and there is a DEudora.ini file, then look
	// to see if the DEudora.ini file has a DataFolder entry in the [Settings] section.
	if ((!CmdLine || !*CmdLine) && !DefaultINIPath.IsEmpty() &&
		GetPrivateProfileString(CRString(IDS_INISN_SETTINGS), "DataFolder", "", buf, sizeof(buf), DefaultINIPath))
	{
		CheckMailDirectory(buf, CRString(IDS_DEUDORA_DATA_FOLDER));
	}

	EudoraDirLen = EudoraDir.GetLength();
	EudoraDirNoBackslash = EudoraDir.Left(EudoraDirLen - 1);

	//If we did't find a valid TempDir then create a TEMP dir in the Eudora directory
	if ((validTempDir == FALSE) || (TempDir.IsEmpty()) )
	{
		TempDir = EudoraDir + CRString( IDS_TEMP2_ENVIRON_VAR );
		//The following call will create the dir if it does not exist
		CreateDirectoryMT(TempDir);
	}

	if (TempDir.Right(1) != SLASH)
		TempDir += SLASH;

	return (status);
}



// This function is kinda weird.  
//
// It's used is to determine if Eudora needs to be launched with an old command line 
// (as saved in the registry).  This happens if there is an attachment,
// stationery (.sta), URL (/m), or a message (.msg) specified on the cmdline.
//
// As soon as one of the above types is detected, this function bails.
//
// Determining if an attachment is specified requires elimination of all other 
// possibilites.  This is due to the fact that attachments can be named anything,
// as long as they don't end in .msg or .sta.  So the code has to eliminate the
// possibility of directory or ini file before concluding an attachment has been 
// specified.

COMMANDLINE_TYPE GetCommandType(LPCTSTR cmdLine)
{
	// Did we get anything on the command line?
	if (!cmdLine || !*cmdLine)
		return CL_EMPTY;

	// Check for URL, which is a /m option
	if (!strnicmp(cmdLine, "/m ", 3) || !strnicmp(cmdLine, "mailto:", 7))
		return CL_URL;

	// Look for old command lines:
	// 	 Directory 
	// 	 Directory + Ini File
	// 	 Ini File
	// If it's none of the above, assume it's an attachment

	COMMANDLINE_TYPE retVal = CL_ATTACH;
	struct stat FileAttr;
	char test[255];
	char *Line = DEBUG_NEW char[strlen(cmdLine)+1];
	strcpy(Line,cmdLine);
	
	// parse off firstArg
	// If long filename, arguments will be separated by quotes
	char *firstArg = Line;
	{
		char *Ini = NULL;
		if (Ini = strstr(Line, "\" "))
			 *(++Ini) = 0;
		else if ( (*(Line + strlen(Line) - 1) != '\"') && (Ini = strchr(Line, ' ')))
			 *Ini = 0;
	}

	// strip quotes off firstArg.  stat() doesn't expect them.  Further
	// the code that build up the ini path doesn't seem to strip any leading quotes
	if ( firstArg[ 0 ] == '\"' )
	{
		char * p = strchr( &firstArg[ 1 ], '\"' );
		if ( p )
		{
			*p = 0;		// nix trailing quote
			firstArg++;	// skip leading quote
		}
	}


	// if firstArg ends in '\\' (I.E. C:\APPS\EUDORA\ ) remove the trailing '\\'
	// stat() won't see it as a directory with the trailing '\\', except for "x:\"
	int len = strlen( firstArg );
	if ( len )
	{
		char lastchar = firstArg[ len - 1 ];
		if ( lastchar == '\\' )
		{
			if ( ! ( len == 3 && firstArg[ 1 ] == ':' ) )
				firstArg[ len - 1 ] = ' ';
		}
	}

	// Check if we got an existing file/directory
	int status = stat(firstArg, &FileAttr);

	CString StaExt = '.' + CRString(IDS_STATIONERY_EXTENSION);
	CString MsgExt = '.' + CRString(IDS_MESSAGE_EXTENSION);
	CString MbxExt = CRString(IDS_REG_MAILBOX_EXT);

	if (status == 0 && FileAttr.st_mode & S_IFDIR)
		retVal = CL_DIR; 
	else if (status == 0 && len > 4 && !stricmp(StaExt, firstArg + len - 4))
		retVal = CL_STATIONERY;
	else if (status == 0 && len > 4 && !stricmp(MsgExt, firstArg + len - 4))
		retVal = CL_MESSAGE;
	else if(status == 0 && len > 4 && !stricmp(MbxExt, firstArg + len - 4))
		retVal = CL_MAILBOX;
	else
	{
		// it's not a directory, see if it's an ini file
		char app[_MAX_PATH + 1];
		char val[_MAX_PATH + 1];
		char *c = NULL;
		lstrcpy(val,firstArg);
		
		// If it's not a full path, see if this is a ini file local to app
		c = strrchr(firstArg, SLASH);
		if (!c)
		{
			GetModuleFileName(AfxGetInstanceHandle(), app, sizeof(app)); 
 			GetShortPathName(app,val, sizeof(val));
			c = strrchr(val,SLASH);
			if (c)
				*++c = 0;
			lstrcat(val,firstArg);
		}
	
		// Test to see if this is an ini file
		GetPrivateProfileString("Settings", "POPAccount",",",test, 255, val);
		if (test[0] != ',')
			retVal = CL_INI;
	}
	
	delete [] Line;
	return	retVal;
}


////////////////////////////////////////////////////////////////////////
// CopyAttachmentFile [static]
//
// Given a pathname to a MAPI attachment file, make a copy of the
// attachment file in the Eudora attachments directory.  On input, the
// 'pathname' parameter contains the pathname to temporary the source
// file.  Upon successful return, the pathname contains the name of
// copied destination file in the Eudora attachments directory.  Cool,
// huh?
////////////////////////////////////////////////////////////////////////
BOOL CopyAttachmentFile(
	CString& srcPathname,			//(io) on input, pathname to source
									// file; on output, pathname to
									// file in Eudora attachment directory
	const char* destFilename)		//(i) optional filename for dest file
{
	//
	// Determine the "candidate" filename for the copied attachment
	// file.  If this filename conflicts with an existing file in the
	// Eudora attachments directory, then the file copy routine will
	// automatically rename it.
	//
	CString filename(destFilename);
	if (filename.IsEmpty())
	{
		//
		// No candidate filename provided, so just use the filename
		// from the given pathname string (i.e., the part of the
		// pathname string to the right of the last slash separator).
		//
		filename = srcPathname.Right(srcPathname.GetLength() - (srcPathname.ReverseFind(SLASH) + 1));
	}
    
	JJFile srcfile;
	if (FAILED(srcfile.Open(srcPathname, O_RDONLY)))
		return FALSE;
		
	struct stat stat_info;
	if (FAILED(srcfile.Stat(&stat_info)))
		return FALSE;
		
	JJFile* OpenAttachFile(LPTSTR,BOOL);		// prototype for extern function
	JJFile* p_destfile = NULL;
	if (! (p_destfile = OpenAttachFile((LPTSTR)(LPCTSTR)filename, FALSE)))
		return FALSE;
		
	if (FAILED(srcfile.JJBlockMove(0L, stat_info.st_size, p_destfile)))
	{
		p_destfile->Delete();
		delete p_destfile;
		return FALSE;
	}
	
	{
		BSTR bstrFilename = NULL;
		if (SUCCEEDED(p_destfile->GetFName(&bstrFilename)))
		{
			srcPathname = bstrFilename;
			::SysFreeString(bstrFilename);
		}
	}

	delete p_destfile;
	
	return TRUE;
}


void GetRegInfoFilePath(CString & szRegInfoFilePath)
{
	szRegInfoFilePath = EudoraDir + CRString( IDS_REG_INFO_STARTUPREG_NAME );
}


bool GetRegInfoFileAlternatePath(CString & szRegInfoFilePath)
{
	bool	bAltPathIsDifferent = (EudoraDir.Compare(ExecutableDir) != 0);

	if (bAltPathIsDifferent)
		szRegInfoFilePath = ExecutableDir + CRString( IDS_REG_INFO_STARTUPREG_NAME );

	return bAltPathIsDifferent;
}


bool
ChangeFileNameExtension(
	char *					in_szFileName,
	const char *			in_szNewExtension,
	long					in_nFileNameBufferSize)
{
	bool	bFileExtensionChanged = false;

	if (in_szFileName && in_szNewExtension && *in_szNewExtension)
	{
		char *	szFileExt = strrchr(in_szFileName, '.');
		
		// Chop off old extension - if any
		if (szFileExt)
			*szFileExt = '\0';
		
		// File extension should be just extension with no period
		ASSERT(*in_szNewExtension != '.');
		
		// Handle period just to be sure
		if (*in_szNewExtension == '.')
			++in_szNewExtension;
		
		int		nCurrentFileNameLength = strlen(in_szFileName);
		int		nFileExtLength = strlen(in_szNewExtension);
		
		// Make sure that there's enough room
		if ( (nCurrentFileNameLength + nFileExtLength + 1) < in_nFileNameBufferSize )
		{
			// Append period
			*szFileExt++ = '.';

			// Now append new extension (without period)
			strcpy(szFileExt, in_szNewExtension);
			bFileExtensionChanged = true;
		}
		else
		{
			// Notify developer when debugging
			ASSERT(0);
			
			// Unable to fit new extension - restore period
			if (szFileExt)
				*szFileExt = '.';
		}
	}

	return bFileExtensionChanged;
}

//
// CascadeBackupFile - back up a file in cascade fashion
//  in_szFilePath - the file to be backed up
//  in_nBackups - the number of backups to keep, or 0 to remove all backups
//  Each time this routine is called, the oldest backup is removed, the other
//  backups all have one digit added to their extension, and the file itself is
//  backed up as file.001
HRESULT CascadeBackupFile(const char * in_szFilePath, const int nBackup)
{
	HRESULT result = ERROR_SUCCESS;

	// sanity-check the backup value
	ASSERT(nBackup>=0 && nBackup<99);

	CString sBackupPath;	// the backup file we're working with
	int n;								// the backup number we're working with
	CString sErr;					// for convenience of logging error messages

	// first, let's count the backups we already have
	for (n=0;n<99;n++)
	{
		sBackupPath.Format(IDS_BACKUP_FILE_FMT,in_szFilePath,n+1);
		if (!FileExistsMT(sBackupPath)) break;
	}
	
	// now, let's delete the ones we don't need
	for (int i=n;i>0 && i>=nBackup;i--)
	{
		sBackupPath.Format(IDS_BACKUP_FILE_FMT,in_szFilePath,i);

		// Check for existence of file before attempting to remove
		// so that we don't log something that we expect to fail.
		if (FileExistsMT(sBackupPath))
		{
			// if the delete fails, we'll figure that out later when we rename
			// for now, LALALALALALALALALALALA
			result = FileRemoveMT(sBackupPath);

			// At least log that we failed...
			if (FAILED(result))
			{
				sErr.Format("Remove \"%s\" failed (%x)",sBackupPath,result);
				PutDebugLog(DEBUG_MASK_TOC_CORRUPT,sErr);
			}
		}
	}

	// is there anything left to do?
	if (!nBackup) return result;	// if the user passed zero, just kill backups and
													// return the error from the last deletion

	// rename files nBackupPath-1..1 to nBackupPath..2.
	if (nBackup>1 && n>0)
	{
		int		nLastFile = min(n+1, nBackup);
		
		for (i=nLastFile;i>1;i--)
		{
			CString sBackupPath2;

			sBackupPath.Format(IDS_BACKUP_FILE_FMT,in_szFilePath,i-1);

			// Check for existence of file before attempting to rename so that we
			// don't stop or log an error, when we expect the operation to fail.
			if (FileExistsMT(sBackupPath))
			{
				sBackupPath2.Format(IDS_BACKUP_FILE_FMT,in_szFilePath,i);

				result = FileRenameMT(sBackupPath,sBackupPath2);
				if (FAILED(result))
				{
					sErr.Format("Rename \"%s\" to \"%s\" failed (%x)",sBackupPath,sBackupPath2,result);
					PutDebugLog(DEBUG_MASK_TOC_CORRUPT,sErr);
					return result;
				}
			}
		}
	}

	// so far so good.  Now, copy the darn file!
	sBackupPath.Format(IDS_BACKUP_FILE_FMT,in_szFilePath,1);
	if (CopyFile(in_szFilePath,sBackupPath,FALSE))
		result = ERROR_SUCCESS;
	else
		result = GetLastError();

	sErr.Format("Backed up \"%s\" to \"%s\" (%x)",in_szFilePath,sBackupPath,result);
	PutDebugLog(DEBUG_MASK_TOC_CORRUPT,sErr);

	ASSERT(SUCCEEDED(result));

	return result;
}

bool ConvertToLongPathName(CString &csPathName)
{
	char		szLongPathName[_MAX_PATH+1];
	
	DWORD		dwResult = GetLongPathName(csPathName, szLongPathName, _MAX_PATH);

	if ( (dwResult == 0) || (dwResult > _MAX_PATH) )
		return false;

	csPathName = szLongPathName;
	return true;
}
