// $Id: Files.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Deadly Rooms of Death.
 *
 * The Initial Developer of the Original Code is
 * Caravel Software.
 * Portions created by the Initial Developer are Copyright (C) 1995, 1996,
 * 1997, 2000, 2001, 2002 Caravel Software. All Rights Reserved.
 *
 * Contributor(s):
 * Mike Rimer (mrimer)
 *
 * ***** END LICENSE BLOCK ***** */

//********************************************************************************************
//File operations module.
//
//Note: Be careful about infinite loops caused by calling ASSERT, VERIFY, or LogErr in this
//module.
//********************************************************************************************

#ifdef WIN32
#  include <windows.h> //Should be first include.
#endif

#include "Files.h"
#include "Assert.h"
#include "IniFile.h"
#include "Date.h"
#include "Wchar.h"

#include <string.h>
#include <stdio.h>
#include <mk4.h>
#ifdef WIN32
#  include <io.h>
#endif
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
#  include <unistd.h>
#  include <dirent.h>
#  include <fcntl.h>
#endif

#include <assert.h>
#include <string>
#include <set>
using std::string;

#ifndef WIN32
#include <errno.h>
#endif

#include <sys/stat.h>

//Affects what is logged in AppendErrorLog().
string  m_strUnwrittenContext;
UINT    m_wIndentLevel = 0;

char *lpszStartupPath=NULL;

//Initialize static members of class.
WCHAR * CFiles::wszAppPath = NULL;
WCHAR * CFiles::wszDatPath = NULL;
WCHAR * CFiles::wszResPath = NULL;
WCHAR * CFiles::wszGameName = NULL;
WCHAR * CFiles::wszGameBranch = NULL;
WCHAR * CFiles::wszGameVer = NULL;
DWORD CFiles::dwRefCount = 0;
CIniFile CFiles::gameIni;
bool CFiles::bInitIni = false;
bool CFiles::bIsDemo = false;
#ifdef WIN32
bool CFiles::bDrives[NUMDRIVES];
bool CFiles::bDrivesChecked = false;
#endif
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
WSTRING CFiles::wstrHomePath;
#endif

//Some constants
const WCHAR wszDefExportDir[] = {
	We('H'),We('o'),We('m'),We('e'),We('m'),We('a'),We('d'),We('e'),We(0) };
static const WCHAR wszDat[] = {We('.'),We('d'),We('a'),We('t'),We(0)};
static const WCHAR wszData[] = {We('D'),We('a'),We('t'),We('a'),We(0)};
static const WCHAR wszDataPathDotTxt[] = {We('D'),We('a'),We('t'),We('a'),We('P'),We('a'),We('t'),We('h'),We('.'),We('t'),We('x'),We('t'),We(0)};
static const WCHAR wszUniqueResFile[] = {
    We('S'),We('o'),We('u'),We('n'),We('d'),We('s'),We(SLASH),
    We('o'),We('r'),We('b'),We('h'),We('i'),We('t'),We('.'),We('o'),We('g'),We('g'),We(0) };
static const WCHAR wszDemo[] = {We('-'),We('d'),We('e'),We('m'),We('o'),We(0)};

#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
#include "DRODLib/GameConstants.h" // TODO remove this coupling with the DRODLib library.
//Constants used in default path construction/search
#include "../DRODLib/GameConstants.h"
#ifdef KDDBUILD
static const char pszDatEnvVar[] = "DROD_KDD_" DROD_VERSION "_DAT_PATH";
static const char pszResEnvVar[] = "DROD_KDD_" DROD_VERSION "_RES_PATH";
#else
static const char pszDatEnvVar[] = "DROD_" DROD_VERSION "_DAT_PATH";
static const char pszResEnvVar[] = "DROD_" DROD_VERSION "_RES_PATH";
#endif
#ifndef __APPLE__
static const WCHAR wszHomeConfDir[] =
        {We('.'),We('c'),We('a'),We('r'),We('a'),We('v'),We('e'),We('l'),We(0)};
#else
static const WCHAR wszHomeConfDir[] =
        {We('L'),We('i'),We('b'),We('r'),We('a'),We('r'),We('y'),We('/'),
	 We('A'),We('p'),We('p'),We('l'),We('i'),We('c'),We('a'),We('t'),We('i'),We('o'),We('n'),We(' '),We('S'),We('u'),We('p'),We('p'),We('o'),We('r'),We('t'),We('/'),
	 We('C'),We('a'),We('r'),We('a'),We('v'),We('e'),We('l'),
	 We(0)};
#endif
static const WCHAR wszTempPath[] = {We(SLASH),We('t'),We('m'),We('p'),We(0)};

static const WCHAR wszResSearchPath[] = {
	We(SLASH),We('u'),We('s'),We('r'),
	We('?'),We('l'),We('o'),We('c'),We('a'),We('l'),
	We('?'),We('s'),We('h'),We('a'),We('r'),We('e'),
	We('?'),We('g'),We('a'),We('m'),We('e'),We('s'),
	We('<'),We('s'),We('h'),We('a'),We('r'),We('e'),We(0)};
static const WCHAR wszDatSearchPath[] = {
	We(SLASH),We('v'),We('a'),We('r'),
	We('?'),We('g'),We('a'),We('m'),We('e'),We('s'),
	We('>'),We('l'),We('i'),We('b'),
	We('<'),We('g'),We('a'),We('m'),We('e'),We('s'),We(0)};

//FIXME: Are these filenames stored somewhere else ?
static const char *szDatFiles[] = { "drod" DROD_VERSION ".dat", "player.dat", "text.dat",
		"data.dat", "drod.ini", NULL };
#define MAXDATFILELENGTH 16

#endif //#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__

//
//Public methods.
//

//******************************************************************************
CFiles::CFiles(
//Constructor.
//
//Params:
	const WCHAR *wszSetAppPath,      //(in)
	const WCHAR *wszSetGameName,     //(in)
	const WCHAR *wszSetGameBranch,   //(in) (may be NULL for default branch)
	const WCHAR *wszSetGameVer,      //(in)
	const bool bIsDemo)              //(in)
{
	if (this->dwRefCount == 0) {
		ASSERT(wszSetAppPath != NULL);
		ASSERT(wszSetGameName != NULL);
		InitClass(wszSetAppPath, wszSetGameName, wszSetGameBranch, wszSetGameVer, bIsDemo);
	}
	++this->dwRefCount;
}

//******************************************************************************
CFiles::~CFiles()
//Destructor.
{
	--this->dwRefCount;
	if (this->dwRefCount == 0) DeinitClass();
}

//******************************************************************************
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
//******************************************************************************
bool CFiles::CreatePathIfInvalid (
//Creates path and all required parent directories (unless they already exist).
//
//Params:
	const WCHAR *wszPath)   //(in)  The path that should be created.
//
//Returns: True if successful.
{
	const UINT l = WCSlen(wszPath);
	char path[l+1];
	UnicodeToAscii(wszPath, path);

	struct stat st;
	bool bOk;
	UINT i = 0;

	do {
		for (++i; i < l && path[i] != SLASH; ++i);
		if (path[i] == SLASH) path[i] = 0;
		bOk = ((access(path, R_OK | X_OK) || stat(path, &st))
				? !mkdir(path, 0755) : S_ISDIR(st.st_mode));
	} while (bOk && i < l && (path[i] = SLASH));

	return bOk;
}

//******************************************************************************
#define UWP_BUFFER 256
#define UWP_BUFPAD 256
#undef USE_GET_CURRENT_DIR_NAME
static bool UnwrinkleEnvPath (
//Get the environment variable env, transform it into an absolute path without
//any relative bits, and put it in the passed WSTRING.
	const char *env,
	WSTRING &wstr)
//
//Returns:
//True if successful.
{
	char *path = getenv(env), *newpath, prevchar;
	if (!path) return false;

	UINT index = 0, dotlevel = 0;
	bool newelement = true;

	if (*path != SLASH) {
		char *tmp;
#ifdef USE_GET_CURRENT_DIR_NAME
		if (!(newpath = get_current_dir_name()))
			return false;
#else
		if (!(newpath = (char*)malloc(UWP_BUFFER)))
			return false;
		for (index = UWP_BUFFER; getcwd(newpath, index) == NULL; newpath = tmp)
		{
			if ((errno != ERANGE) ||
				(!(tmp = (char*)realloc(newpath, index += UWP_BUFPAD))))
			{
				free(newpath);
				return false;
			}
		}
#endif
		index = strlen(newpath);
		tmp = (char*)realloc(newpath, index + strlen(path) + 2);
		if (!tmp)
		{
			free(newpath);
			return false;
		}
		newpath = tmp;
		newpath[index++] = SLASH;
	}
	else
	{
		newpath = (char*)malloc(strlen(path) + 1);
		if (!newpath)
			return false;
	}

	for (prevchar = 0; (newpath[index] = *path);
		++index, prevchar = *(path++))
	{
		if (newelement)
		{
			if (*path == SLASH)
			{
				if (prevchar == SLASH)
					--index;
				else
				{
					index -= dotlevel;
					while (dotlevel--)
						while (index && newpath[--index] != SLASH);
					dotlevel = 0;
				}
			}
			else if ((*path == '.' && ++dotlevel > 2) || *path != '.')
				newelement = false;
			continue;
		}
		if (*path == SLASH)
		{
			newelement = true;
			dotlevel = 0;
		}
	}
	if (newelement)
	{
		while (dotlevel--)
			while (index && newpath[--index] != SLASH);
		if (!index) ++index;
	}

	if (index > 1 && newpath[index - 1] == SLASH)
		--index;
	newpath[index] = 0;

	AsciiToUnicode(newpath, wstr);
	free(newpath);
	return true;
}

#endif // if defined __linux__ || defined __FreeBSD__ || defined __APPLE__

//******************************************************************************
void CFiles::TryToFixDataPath()
//Error-- try to find the Data dir myself and create a new DataPath.txt.
{
	WSTRING wstrDatPathTxt;

#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	WSTRING wstrDatFile = wszGameName;
	wstrDatFile += wszGameVer;
	wstrDatFile += wszDat;

	const WCHAR *wszHome = wstrHomePath.empty() ? NULL : wstrHomePath.c_str();

	if (!FindPossibleDatFile(wstrDatFile.c_str(), wszDatPath) ||
			!FindPossibleResFile(wszUniqueResFile, wszResPath))
	{
		//Try temporary path first (app path may be read-only)
		if (IsValidPath(wszTempPath))
		{
			WCScpy(wszDatPath, wszTempPath);
			WCScpy(wszResPath, wszTempPath);
		}
		else
		{
			WCScpy(wszDatPath, wszAppPath);
			WCScpy(wszResPath, wszAppPath);
		}

		//Don't try to write DataPath.txt, since we didn't find anything
		return;
	}

	if (wszHome)
	{
		wstrDatPathTxt = wszHome;
		wstrDatPathTxt += wszSlash;
		wstrDatPathTxt += wszHomeConfDir;
		wstrDatPathTxt += wszSlash;
		wstrDatPathTxt += wszGameName;
		if (wszGameBranch)
		{
			wstrDatPathTxt += wszHyphen;
			wstrDatPathTxt += wszGameBranch;
		}
		wstrDatPathTxt += wszHyphen;
		wstrDatPathTxt += wszGameVer;
		if (bIsDemo) wstrDatPathTxt += wszDemo;
		if (!CreatePathIfInvalid(wstrDatPathTxt.c_str())) goto TTFDP_usedat;
	} else {
TTFDP_usedat:
		wszHome = NULL;
		wstrDatPathTxt = wszDatPath; //gah
	}

	wstrDatPathTxt += wszSlash;
	wstrDatPathTxt += wszDataPathDotTxt;

	if (!WriteDataPathTxt(wstrDatPathTxt.c_str(), wszDatPath, wszResPath, true)
			&& wszHome != NULL)
		goto TTFDP_usedat;  //Try again without home
	//Done
#else
	wstrDatPathTxt = wszAppPath;
	wstrDatPathTxt += wszSlash;
	wstrDatPathTxt += wszDataPathDotTxt;

	if (!FindPossibleDatPath(wszAppPath, wszDatPath) ||
			!FindPossibleResPath(wszAppPath, wszResPath) ||
			!WriteDataPathTxt(wstrDatPathTxt.c_str(), wszDatPath, wszResPath, true))
	{
		//Use app path so at least error logging may work later.
		WCScpy(wszDatPath, wszAppPath);
		WCScpy(wszResPath, wszAppPath);
	}
#endif
}

//*****************************************************************************
bool CFiles::DoesFileExist(
//Determines if a specified file exists by trying to open it.
//
//Params:
  const WCHAR *wszFilepath) //(in)
//
//Returns:
//True if file exist, false if not.
{
	FILE *pFile = Open(wszFilepath, "rb");
	if (NULL == pFile) {
		const int nErr = errno;
		return !(ENOTDIR == nErr || ENOENT == nErr || EACCES == nErr || EINVAL == nErr);
	}
	fclose(pFile);
	return true;
}

//ASCII version.
bool CFiles::DoesFileExist(const char *szFilepath)
{
	 WSTRING wstr;
	 AsciiToUnicode(szFilepath, wstr);
	 return DoesFileExist(wstr.c_str());
}

//*****************************************************************************
bool CFiles::FileCopy(const WCHAR *pwzSourceFilepath, const WCHAR *pwzDestFilepath)
{
	 ASSERT(pwzSourceFilepath && pWCv(pwzSourceFilepath));
	 ASSERT(pwzDestFilepath && pWCv(pwzDestFilepath));
	 CStretchyBuffer FileBuf;
	 if (!ReadFileIntoBuffer(pwzSourceFilepath, FileBuf)) return false;
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	 // Get stats from source file, and set the destination file mode to be identical.
	 struct stat st;
	 WSTRING wstrSource = pwzSourceFilepath;
	 char pszSource[wstrSource.length() + 1];
	 UnicodeToAscii(wstrSource, pszSource);
	 stat(pszSource, &st); // The above read succeeded, so assume this succeeds too.
	 mode_t oldmask = umask(st.st_mode ^ 0777);
#endif

	 //Write the buffer without terminating wchar zero at end.
	 bool bSuccess = false;
	 FILE *pFile = Open(pwzDestFilepath, "wb");
	 if (NULL != pFile)
	 {
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
		// Set the group of this file to the same as its owner.
		fchown(fileno(pFile), (uid_t)-1, st.st_gid);
#endif
		bSuccess = (FileBuf.Size() - 2 == fwrite( (BYTE*)FileBuf, 1, FileBuf.Size() - 2, pFile ));
		fclose( pFile );
	 }
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	 // Reset file creation mask
	 umask(oldmask);
#endif
	 return bSuccess;
}

//*****************************************************************************
FILE * CFiles::Open(
//Unicode-friendly function to open a file.
	 const WCHAR *pwzFilepath,   //(in)    Full path to file.
	 const char *pszOptions)     //(in)    fopen()-style options.
//Returns:
//Pointer to file or NULL if an error occurred.  Use fclose() to close file later.
{
	 FILE *pFile = NULL;

	 //Unicode open.
#ifdef HAS_UNICODE
#   ifdef   WIN32
#      define WFOPEN _wfopen
#   endif

	 WSTRING wstrOptions;
	 AsciiToUnicode(pszOptions, wstrOptions);
	 pFile = WFOPEN( pwzFilepath, wstrOptions.c_str() );

	 //If failed, will try again with ASCII conversion in case wfopen() is stubbed on the O/S.
	 if (!pFile)
#endif
	 //ASCII open.
	 {
		  char pszPath[MAX_PATH+1];
		  UnicodeToAscii( pwzFilepath, pszPath );
		  pFile = fopen( pszPath, pszOptions);
	 }

	 return pFile;
}

//*****************************************************************************
bool CFiles::HasReadWriteAccess(
//Determines if a specified file has read/write access by trying to open it.
//
//Params:
  const WCHAR *wszFilepath) //(in)
//
//Returns:
//True if file exists, false if not.
{
	FILE* pFile = Open(wszFilepath, "rb+");
	if (pFile == NULL)
		return false;
	fclose(pFile);
	return true;
}

//******************************************************************************
void CFiles::AppendErrorLog(
//Appends text to the error log.
//
//Params:
	const char *pszText) //(in) Text to write at end of file.
{
	assert(pszText);

	 static CDate LastLog;
	 static bool bFirstLog = true;

	 FILE* pFile;
	 WSTRING wstrTemp;
	 AsciiToUnicode("drod.err", wstrTemp);

	 WSTRING wstrDatPathTxt = GetDatPath();
	 wstrDatPathTxt += wszSlash;
	 wstrDatPathTxt += wstrTemp;

#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	 // Make errorlog world-writable
	 mode_t oldmask = umask(0);
#endif
	 pFile = Open(wstrDatPathTxt.c_str(), "a");
	 if (pFile)
	 {
		  //Write any log context that hasn't been written already.
		  if (!m_strUnwrittenContext.empty())
		  {
				fwrite(m_strUnwrittenContext.c_str(), 1, m_strUnwrittenContext.size(), pFile);
				m_strUnwrittenContext = "";
		  }

		  string strIndent;
		  for (UINT wIndentNo = 0; wIndentNo < m_wIndentLevel; ++wIndentNo) strIndent += "  ";

		  //Write time if this is first log or at least a minute has elapsed since last write.
		  CDate Now;
		  if ((time_t) Now - (time_t) LastLog > 60 || bFirstLog)
		  {
				WSTRING wstrTime;
				string strTime;
				Now.GetLocalFormattedText(DF_SHORT_DATE | DF_SHORT_TIME, wstrTime);

				strTime = strIndent;
				strTime += "*** ";
				if (bFirstLog)
				{
					 strTime += "FIRST LOG IN SESSION ";
					 bFirstLog = false;
				}
				UnicodeToAscii(wstrTime, strTime);
				strTime += " ***\r\n";
				fwrite(strTime.c_str(), 1, strTime.size(), pFile);
		  }

		  //Write the actual text passed to method.
		  fwrite(strIndent.c_str(), 1, strIndent.size(), pFile);
		  fwrite(pszText, 1, strlen(pszText), pFile);
		  fclose(pFile);
		  LastLog = Now;
	 }
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	 // Restore old file creation mask.
	 umask(oldmask);
#endif
}

//******************************************************************************
bool CFiles::WriteGameProfileString(
//Writes a string to <GameName>.INI.
//
//Params:
  const char *pszSection,  //(in)
  const char *pszKey,      //(in)
  const char *pszValue)    //(in)
//
//Returns:
//True if successful, false if not.
{
	gameIni.WriteString(pszSection, pszKey, pszValue);
	return true;
}

//******************************************************************************
bool CFiles::GetGameProfileString(
//Gets a string from <GameName>.INI.
//
//Params:
	const char *pszSection, //(in)
	const char *pszKey,     //(in)
	string& strValue)    //(out)
//
//Returns:
//True if successfully found entry or false if not.
{
	return gameIni.GetString(pszSection, pszKey, strValue);
}

//*********************************
bool CFiles::GetGameProfileString(
	const char *pszSection, const char *pszKey, list<string>& strValue)
{
	return gameIni.GetString(pszSection, pszKey, strValue);
}

//*********************************
bool CFiles::GetGameProfileString(
	const char *pszSection, const char *pszKey, list<WSTRING>& strValue)
{
	return gameIni.GetString(pszSection, pszKey, strValue);
}

//******************************************************************************
bool CFiles::ReadFileIntoBuffer(
//Reads a file into a buffer.  Preferably only be used with small files.
//
//Params:
	const WCHAR *wszFilepath,  //(in)   Full path to file to be read.
	CStretchyBuffer &Buffer,   //(out)  Receives file data.
	bool bBinary)              //(in)   If true, doesn't add a NULL to the end
//
//Returns:
//True if successful, false if not.
{
	bool bSuccess = false;
	ASSERT(wszFilepath);
	ASSERT(Buffer.Size() == 0);

	 FILE* pFile = Open( wszFilepath, "rb" );
	BYTE charBuf[16385];
	if (NULL != pFile)
	{
		//Get size of file.
		fseek(pFile, 0, SEEK_END);
		if (!Buffer.Alloc(ftell(pFile)))
		{
			fclose(pFile);
			return false;
		}
		fseek(pFile, 0, SEEK_SET);

		int bytes;
		do {
			bytes = fread( charBuf, 1, 16384, pFile );
			if (bytes > 0) {
				Buffer.Append( charBuf, bytes );
			}
		} while (bytes > 0);
		if (!bBinary)
			Buffer.Append((const BYTE*)wszEmpty, sizeof(WCHAR));
		fclose( pFile );
		bSuccess = true;
	}
	else
	{
		char buf[MAX_PATH+1];
		UnicodeToAscii(wszFilepath, buf);
		bSuccess = false;
	}

	return bSuccess;
}

//******************************************************************************
bool CFiles::WriteBufferToFile(
//Writes a file from a buffer.  Overwrites an existing file.
//
//Params:
	const char *pszFilepath,      //(in)   Full path to file to be written.
	const CStretchyBuffer &Buffer)   //(in)   Data to write.
//
//Returns:
//True if successful, false if not.
{
	bool bSuccess = false;
	ASSERT(pszFilepath);
	ASSERT(Buffer.Size() != 0);

	FILE* pFile = fopen( pszFilepath, "wb" );
	if (NULL != pFile)
	{
		bSuccess = (Buffer.Size() == fwrite( (BYTE*)Buffer, 1, Buffer.Size(), pFile ));
		fclose( pFile );
	}

	return bSuccess;
}

//******************************************************************************
bool CFiles::WriteBufferToFile(
//Writes a file from a buffer.  Overwrites an existing file.
//
//Params:
	const WCHAR *pwszFilepath,    //(in)   Full path to file to be written.
	const CStretchyBuffer &Buffer)   //(in)   Data to write.
//
//Returns:
//True if successful, false if not.
{
	bool bSuccess = false;
	ASSERT(pwszFilepath);
	ASSERT(Buffer.Size() != 0);

	FILE* pFile = Open( pwszFilepath, "wb" );
	if (NULL != pFile)
	{
		bSuccess = (Buffer.Size() ==
				fwrite( (BYTE*)Buffer, sizeof(BYTE), Buffer.Size(), pFile ));
		fclose( pFile );
	}

	return bSuccess;
}

//******************************************************************************
void CFiles::MutateFileName(
//Unprotected and protected datafiles differ by their last letter.
//This routine changes one variant to the other.
//Params:
	WCHAR *wszFilepath)     //(in/out)  Full path to file to be read.
							//Mutates file name by changing last letter.
{
	const UINT nLength = WCSlen(wszFilepath);
	if (FileIsEncrypted(wszFilepath))
	{
		switch (WCv(wszFilepath[nLength-2]))
		{
		case 'm':
			WCv(wszFilepath[nLength-1]) = 'p';
			break;
		case 'a':
			WCv(wszFilepath[nLength-1]) = 'v';
			break;
		case '3':
			WCv(wszFilepath[nLength-1]) = 'm';
			break;
		default: //the general ".dat"
			WCv(wszFilepath[nLength-1]) = 't';
			break;
		}
	} else {
		WCv(wszFilepath[nLength-1]) = '_';
	}
}

//******************************************************************************
bool CFiles::GetTrueDatafileName(
//Find out true name of file to load.
//It may be protected/unprotected, so check variant (i.e. last char changed).
//
//Returns: whether file was found
//
//Params:
	WCHAR *wszFilepath)  //(in/out)  Full path to file to be read.
										//Filename is mutated (by changing last letter)
										//if file of original name is not found.
{
	//If file exists, return true.
	if (CFiles::DoesFileExist(wszFilepath))
		return true;

	//File not found.  Check variant with last char changed.
	MutateFileName(wszFilepath);
	if (CFiles::DoesFileExist(wszFilepath))
		return true;

	//Didn't find file.
	return false;
}

//******************************************************************************
bool CFiles::ProtectFile(
//Encodes a file.
//
//Params:
	const WCHAR *pszFilepath)  //(in)   Full path to file to be read.
//
//Returns:
//True if successful, false if not.
{
	bool bSuccess = false;
	CStretchyBuffer buffer;

	if (ReadFileIntoBuffer(pszFilepath,buffer))
	{
		buffer.Encode();

		//Get name of destination file.
		WSTRING destFilePath = pszFilepath;
		MutateFileName(&*destFilePath.begin());

		FILE* pFile = Open(destFilePath.c_str(), "wb");
		if (NULL != pFile)
		{
			if (buffer.Size() == fwrite( (BYTE*)buffer, 1, buffer.Size(), pFile ))
				bSuccess = true;
			fclose(pFile);
		}
	}

	return bSuccess;
}

//
//Private methods.
//

//******************************************************************************
void CFiles::InitClass(
//Sets up static members of class.
//
//Params:
	const WCHAR *wszSetAppPath,      //(in)
	const WCHAR *wszSetGameName,     //(in)
	const WCHAR *wszSetGameBranch,   //(in)
	const WCHAR *wszSetGameVer,      //(in)
	const bool bIsDemo)              //(in)
{
	CFiles::bIsDemo = bIsDemo;

	//Get full path to executable.
	ASSERT(!this->wszAppPath);
	this->wszAppPath = new WCHAR[WCSlen(wszSetAppPath) + 1];
	WCScpy(wszAppPath, wszSetAppPath);

	//Get game name and version.
	ASSERT(!this->wszGameName);
	this->wszGameName = new WCHAR[WCSlen(wszSetGameName) + 1];
	WCScpy(wszGameName, wszSetGameName);
	if (wszSetGameBranch)
	{
		this->wszGameBranch = new WCHAR[WCSlen(wszSetGameBranch) + 1];
		WCScpy(wszGameBranch, wszSetGameBranch);
	}
	else
		this->wszGameBranch = NULL;
	ASSERT(!this->wszGameVer);
	this->wszGameVer = new WCHAR[WCSlen(wszSetGameVer) + 1];
	WCScpy(wszGameVer, wszSetGameVer);

	//Truncate app path to just the path portion.
	WCHAR *wszLastBackslash = this->wszAppPath;
	while (*wszLastBackslash != '\0') ++wszLastBackslash; //Search to end.
	while (wszLastBackslash >= this->wszAppPath && *wszLastBackslash != SLASH)
		--wszLastBackslash; //Search to last backslash.
	pWCv(wszLastBackslash) = '\0'; //Truncates the string.
#ifdef __APPLE__
	//if the executable is inside a bundle, look for data files within
	//that bundle's Resources directory
	WSTRING path(this->wszAppPath);
	static const WCHAR wszMacOS[] = {
        	We('M'),We('a'),We('c'),We('O'),We('S'),We(0)
	};
	static const WCHAR wszResources[] = {
        	We('R'),We('e'),We('s'),We('o'),We('u'),We('r'),We('c'),We('e'),We('s'),
        	We(0)
	};
	WSTRING::size_type pos = path.rfind(wszMacOS);
	if (pos != WSTRING::npos)
		path = WSTRING(path,0,pos) + wszResources;
	delete[] this->wszAppPath;
	this->wszAppPath = new WCHAR[WCSlen(wszSetAppPath) + 1];
	WCScpy(wszAppPath, path.c_str());
#endif

#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	//Get $HOME and create conf dir, so we don't have to later.
	UnwrinkleEnvPath("HOME", wstrHomePath);
	CreatePathIfInvalid((wstrHomePath + wszSlash + wszHomeConfDir + wszSlash
			+ wszGameName + (wszGameBranch ? WSTRING(wszHyphen) + wszGameBranch : wszEmpty)
			+ wszHyphen + wszGameVer + (bIsDemo ? wszDemo : wszEmpty)).c_str());
#endif

	//Get dat path from DataPath.txt.
	WSTRING wstrDatPathTxt;
	ASSERT(!this->wszDatPath);
	ASSERT(!this->wszResPath);
	this->wszDatPath = new WCHAR[MAX_PATH + 1];
	this->wszResPath = new WCHAR[MAX_PATH + 1];
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	if (!FindPossibleDatFile(wszDataPathDotTxt, this->wszDatPath))
		TryToFixDataPath();
	wstrDatPathTxt = this->wszDatPath;
#else
	wstrDatPathTxt = this->wszAppPath;
#endif
	wstrDatPathTxt += wszSlash;
	wstrDatPathTxt += wszDataPathDotTxt;
	FILE* pFile = Open(wstrDatPathTxt.c_str(), "r");
	if (!pFile)
		TryToFixDataPath();
	else
	{
#ifdef HAS_UNICODE
		WCHAR buffer[2 * (MAX_PATH + 1)];
		fgetWs(buffer, 2 * (MAX_PATH + 1), pFile);   // line 1: dat path

		//Skip possible header chars inserted by text editor
		UINT wFirstChar = 0;
		if (buffer[wFirstChar] == 0xfeff)
			++wFirstChar;

		UINT i = wFirstChar;
		while (i < MAX_PATH && buffer[i] != ';')
			++i;
		if (buffer[i] != ';' || !buffer[wFirstChar] || !buffer[i + 1] || WCSlen(buffer + i + 1) > MAX_PATH)
		{
			TryToFixDataPath();
		}
		else
		{
			UINT j = i+1;
			buffer[i] = 0;
			while (buffer[j] && buffer[j] != ';')
				++j;
			buffer[j] = 0;
			WCScpy(this->wszDatPath, buffer + wFirstChar);
			WCScpy(this->wszResPath, buffer + i + 1);
		}
#else
		char buffer[2 * (MAX_PATH + 1)];
		fgets(buffer, 2 * (MAX_PATH + 1), pFile);
		UINT i;
		for (i = 0; i < MAX_PATH && buffer[i] != ';'; ++i) ;
		if (buffer[i] != ';' || !buffer[0] || !buffer[i + 1] || strlen(buffer + i + 1) > MAX_PATH)
		{
			TryToFixDataPath();
		}
		else
		{
			WSTRING wstrTemp;
			UINT j;
			buffer[i] = 0;
			for (j = i + 1; buffer[j] && buffer[j] != ';'; ++j) ;
			buffer[j] = 0;
			AsciiToUnicode(buffer, wstrTemp);
			WCScpy(this->wszDatPath, wstrTemp.c_str());
			AsciiToUnicode(buffer + i + 1, wstrTemp);
			WCScpy(this->wszResPath, wstrTemp.c_str());
		}
#endif
		fclose( pFile );
	}
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
	// On OS X, treat the contents of DataPath.txt as merely advisory; if things don't work,
	// regenerate the resource path.  This allows the user to move the application bundle more
	// freely.
	// Let's do this in Linux and FreeBSD as well.  Can't hurt.
	if (!DoesFileExist((WSTRING(wszResPath) + wszSlash + wszUniqueResFile).c_str()))
	{
		TryToFixDataPath();
	}

	// Make sure the default export path exists
	CreatePathIfInvalid((WSTRING(wszDatPath) + wszSlash + wszDefExportDir).c_str());
#endif

	if (!bInitIni)
	{
		WSTRING wstrIniPath = this->wszDatPath;
		wstrIniPath += wszSlash;
		WSTRING wstrTmp;
		AsciiToUnicode(".ini", wstrTmp);
		wstrIniPath += wszGameName;
		wstrIniPath += wstrTmp;

		bInitIni = true;
		gameIni.Load(wstrIniPath.c_str());
	}

}

//******************************************************************************
void CFiles::DeinitClass()
//Frees and zeroes static members of class.
{
	ASSERT(this->dwRefCount==0);

	delete[] this->wszAppPath;
	this->wszAppPath=NULL;

	delete[] this->wszDatPath;
	this->wszDatPath=NULL;

	delete[] this->wszResPath;
	this->wszResPath=NULL;

	delete[] this->wszGameName;
	this->wszGameName=NULL;

	delete[] this->wszGameBranch;
	this->wszGameBranch=NULL;

	delete[] this->wszGameVer;
	this->wszGameVer=NULL;
}

//******************************************************************************
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
//******************************************************************************
bool CFiles::CheckDataAndMakeWritable (
//Check that all the dat-files (and the ini) are present and writable.  If any
//of them are present but not writable, make local writable copies of all files
//in the user's home directory.  If the copy was required and successful,
//wstrPath will be set to the new path of the data.
//
//Params:
	WCHAR *wszPath)   //(in/out)   Path where data is, allocated to MAX_PATH + 1
//
//Returns:
//True if the data is present and writable (regardless if it was copied or not),
//false otherwise.
{
	const WSTRING wstrDest = wstrHomePath + wszSlash + wszHomeConfDir + wszSlash
			+ wszGameName + (wszGameBranch ? WSTRING(wszHyphen) + wszGameBranch : wszEmpty)
			+ wszHyphen + wszGameVer + (bIsDemo ? wszDemo : wszEmpty);
	const unsigned int si = WCSlen(wszPath) + 1, ti = wstrDest.length() + 1;
	char source[si + MAXDATFILELENGTH + 1], target[ti + MAXDATFILELENGTH + 1];

	UnicodeToAscii(wszPath, source);
	UnicodeToAscii(wstrDest, target);
	target[ti - 1] = source[si - 1] = SLASH;

	bool bSamePath = (si == ti && !strncmp(source, target, si));
	int i;

	//First check if all data is writable
	for (i = 0; szDatFiles[i]; ++i) {
		strcpy(source + si, szDatFiles[i]);
		if (access(source, R_OK | W_OK)) {
			if (!bSamePath) break;
			else {
				chmod(source, 0644);  // same path, so we chmod in stead of copy
				if (access(source, R_OK | W_OK)) return false;
			}
		}
	}
#ifndef ALWAYS_COPY_DATA
	if (!szDatFiles[i]) return true;
#endif
	//Some data are read-only (or missing), so we need to make copies (or fail).

	if (wstrHomePath.empty()) return false; //Can't copy if there's no home dir ..

	char buffer[65536 < SSIZE_MAX ? 65536 : SSIZE_MAX]; //read can only handle <= SSIZE_MAX
	for (i = 0; szDatFiles[i]; ++i)
	{
		int sf, tf;
		strcpy(target + ti, szDatFiles[i]);
		strcpy(source + si, szDatFiles[i]);
		if ((sf = open(source, O_RDONLY)) < 0) break;
		if ((tf = creat(target, 0644)) < 0) {
			close(sf);
			break;
		}
		ssize_t l;
		while ((l = read(sf, buffer, 65536 < SSIZE_MAX ? 65536 : SSIZE_MAX))) {
			if (l == (ssize_t)-1) {
				if (errno == EINTR) continue; //read interrupted; try again
				break;
			}
			write (tf, buffer, l);
		}
		close(sf);
		close(tf);
		if (l == (ssize_t)-1) break;
	}

	//If error, unlink copied files
	if (szDatFiles[i]) {
		while (i >= 0) {
			strcpy(target + ti, szDatFiles[i--]);
			unlink(target);
		}
		return false;
	}

	//Success!  Update path, and we're done.
	WCSncpy(wszPath, wstrDest.c_str(), MAX_PATH);
	return true;
}

//******************************************************************************
bool CFiles::CheckForDataFile(
//Check if a file exists in the specified path, and also check the subdirs
//Data (if bTryClean is true), <GameName-GameVer>, and <GameName-GameVer>/Data.
//If the file exists and bData is set, CheckDataAndMakeWritable is called to
//verify that the rest of the data is there and copy the data to a writable
//location if required.
//
//Params:
	WCHAR *wszPath,            // (in/out) Path to file
	const WCHAR *wszFmt,       // (in)     Pathsearch setup string
	const WCHAR *wszFile,      // (in)     Filename (no path)
	const bool bTryClean,      // (in)     Try without adding game name/ver first
	const bool bData)          // (in)     Look for data (req writable or copy)
//
//Returns: true if all data was found and is writable, with wszPath set to the
//path (without filename)
{
	WSTRING wstr = wszFmt;
	wstr += bTryClean ? wszQuestionMark : wszSlash;
	wstr += wszGameName;
	if (wszGameBranch)
	{
		wstr += wszHyphen;
		wstr += wszGameBranch;
	}
	wstr += wszHyphen;
	wstr += wszGameVer;
	if (bIsDemo) wstr += wszDemo;
	wstr += wszQuestionMark;
	wstr += wszData;
	wstr += wszSemicolon; // Break off here
	wstr += wszSlash;
	wstr += wszFile;

	int brkpos;
	CPathGen pg(wszPath, wstr.c_str());
	while ((brkpos = pg.Next())) {
		if (DoesFileExist(wszPath)) {
			WCv(wszPath[brkpos - 1]) = 0;
			if (!bData || CheckDataAndMakeWritable(wszPath))
				return true;
		}
	}
	return false;
}

//******************************************************************************
bool CFiles::FindPossibleDatFile(
//linux/unix specific: Search for a file in some (possibly) writable paths.
//Construct a range of paths from compiled-in defaults (FHS is our friend),
//and see if the specified file exists in one of these.
//
//Params:
	const WCHAR *wszFile,   //(in)   Filename to search for.
	WCHAR *wszPathBuf)      //(out)  Valid path. Preallocate to MAX_PATH + 1
{
	WSTRING wstrTmp;

	// Try home first.
	if (!wstrHomePath.empty())
	{
		wstrTmp = wstrHomePath;
		wstrTmp += wszSlash;
		wstrTmp += wszHomeConfDir;
		if (CheckForDataFile(wszPathBuf, wstrTmp.c_str(), wszFile, false, false)) return true;
	}

	// Then try path in environment variable, app path and the dat search path.
	if ((UnwrinkleEnvPath(pszDatEnvVar, wstrTmp)
			&& CheckForDataFile(wszPathBuf, wstrTmp.c_str(), wszFile, true, true))
			|| CheckForDataFile(wszPathBuf, wszAppPath, wszFile, true, true)
			|| CheckForDataFile(wszPathBuf, wszDatSearchPath, wszFile, false, true))
		return true;

	return false;
}

//******************************************************************************
bool CFiles::FindPossibleResFile(
//As above, but for resource (possibly readonly) files. FHS is still our friend.
//
//Params:
	const WCHAR *wszFile,   //(in)   Filename to search for.
	WCHAR *wszPathBuf)      //(out)  Valid path. Preallocate to MAX_PATH + 1
{
	// Try path in environment variable, then app path and the res search path.
	WSTRING wstrTmp;
	if ((UnwrinkleEnvPath(pszResEnvVar, wstrTmp)
			&& CheckForDataFile(wszPathBuf, wstrTmp.c_str(), wszFile, true, false))
			|| CheckForDataFile(wszPathBuf, wszAppPath, wszFile, true, false)
			|| CheckForDataFile(wszPathBuf, wszResSearchPath, wszFile, false, false))
		return true;

	return false;
}

//******************************************************************************
#else // ifdef __linux__ || defined __FreeBSD__ || defined __APPLE__
//******************************************************************************
bool CFiles::FindPossibleDatPath(
//Finds a directory that could be a data directory because it contains
//"drod1_7.dat".
//
//Params:
	const WCHAR *wszStartPath, //(in)   Directory to begin looking at.  Will
								//    go up levels from this dir.  Expecting
								//    no "\" at end.
	WCHAR *wszPossibleDatPath) //(out)  Prealloced to _MAX_PATH + 1.
//
//Returns:
//True if a possible directory was found, false if not.
{
	const UINT wStartPathLen = WCSlen(wszStartPath);

	WCScpy(wszPossibleDatPath, wszStartPath);

	WSTRING wstrGameDatFilepath;
	WCHAR *pwszEndPath = wszPossibleDatPath + wStartPathLen;

	//Each iteration checks for "<GameName><GameVer>.DAT" in one directory plus up in a "data"
	//directory.
	while (true)
	{
		wstrGameDatFilepath = wszPossibleDatPath;   // "x:/somepath"
		wstrGameDatFilepath += wszSlash;            // "x:/somepath/"
		wstrGameDatFilepath += wszGameName;         // "x:/somepath/drod"
		wstrGameDatFilepath += wszGameVer;          // "x:/somepath/drod<ver>"
		wstrGameDatFilepath += wszDat;              // "x:/somepath/drod<ver>.dat"
		if (DoesFileExist(wstrGameDatFilepath.c_str()))
		{
			//Found DROD1_7.dat so dir is possible dat path.
			return true;
		}

		wstrGameDatFilepath = wszPossibleDatPath;   // "x:/somepath"
		wstrGameDatFilepath += wszSlash;            // "x:/somepath/"
		wstrGameDatFilepath += wszData;             // "x:/somepath/data"
		wstrGameDatFilepath += wszSlash;            // "x:/somepath/data/"
		wstrGameDatFilepath += wszGameName;         // "x:/somepath/data/drod"
		wstrGameDatFilepath += wszGameVer;          // "x:/somepath/data/drod<ver>"
		wstrGameDatFilepath += wszDat;              // "x:/somepath/data/drod<ver>.dat"
		if (DoesFileExist(wstrGameDatFilepath.c_str()))
		{
			//Found DROD<ver>.dat so dir is possible dat path.  Add "data" to end
			//of possible dat path.
			WCv(pwszEndPath[0]) = SLASH;
			WCv(pwszEndPath[1]) = 'D';
			WCv(pwszEndPath[2]) = 'a';
			WCv(pwszEndPath[3]) = 't';
			WCv(pwszEndPath[4]) = 'a';
			WCv(pwszEndPath[5]) = '\0';
			return true;
		}

		//Change file path so that it is specifying parent dir.
		while (pwszEndPath != wszPossibleDatPath && *pwszEndPath != SLASH)
			--pwszEndPath;
		pWCv(pwszEndPath) = '\0';

		if (pwszEndPath == wszPossibleDatPath)
			return false; //Ran out of parent directories to check.
	}

	//Execution never gets here.
	return false;
}

//******************************************************************************
bool CFiles::FindPossibleResPath(
//Finds a directory that could be a resource directory because it contains
//"Bitmaps", "Fonts", "Help", "Music" and "Sounds".
//
//Params:
	const WCHAR *wszStartPath, //(in)   Directory to begin looking at.  Will
								//    go up levels from this dir.  Expecting
								//    no slash at end.
	WCHAR *wszPossibleResPath) //(out)  Prealloced to MAX_PATH + 1.
//
//Returns:
//True if a possible directory was found, false if not.
{
	const UINT wStartPathLen = WCSlen(wszStartPath);

	WCScpy(wszPossibleResPath, wszStartPath);

	WSTRING wstrGameRespath;

	WCHAR *pwszEndPath = wszPossibleResPath + wStartPathLen;

	//Each iteration checks for the resource dirs in the dir and parent dir
	while (true)
	{
		wstrGameRespath = wszPossibleResPath;   // "x:/somepath"
		wstrGameRespath += wszSlash;            // "x:/somepath/"
		wstrGameRespath += wszUniqueResFile;    // "x:/somepath/<res>"
        if (DoesFileExist(wstrGameRespath.c_str()))
			return true;
		wstrGameRespath = wszPossibleResPath;   // "x:/somepath"
		wstrGameRespath += wszSlash;            // "x:/somepath/"
		wstrGameRespath += wszData;             // "x:/somepath/Data"
		wstrGameRespath += wszSlash;            // "x:/somepath/Data/"
		wstrGameRespath += wszUniqueResFile;    // "x:/somepath/Data/<res>"
		if (DoesFileExist(wstrGameRespath.c_str()))
		{
			// Add 'Data' to the end of the path
			WCv(pwszEndPath[0]) = SLASH;
			WCv(pwszEndPath[1]) = 'D';
			WCv(pwszEndPath[2]) = 'a';
			WCv(pwszEndPath[3]) = 't';
			WCv(pwszEndPath[4]) = 'a';
			WCv(pwszEndPath[5]) = '\0';
			return true;
		}

		//Change file path so that it is specifying parent dir.
		while (pwszEndPath != wszPossibleResPath && *pwszEndPath != SLASH)
			--pwszEndPath;
		pWCv(pwszEndPath) = '\0';

		if (pwszEndPath == wszPossibleResPath)
			return false; //Ran out of parent directories to check.
	}

	//Execution never gets here.
	return false;
}

//******************************************************************************
#endif // ifdef __linux__ || defined __FreeBSD__, else

//******************************************************************************
bool CFiles::WriteDataPathTxt(
//Write a DataPath.txt file.  If one already exists, this will fail unless
//overwrite is true.
//
//Params:
	const WCHAR *pwszFilepath, //(in)   Complete filepath to write to.
	const WCHAR *pwszDatPath,  //(in)   Data path to write inside file.
	const WCHAR *pwszResPath,  //(in)   Resource path to write inside file.
	const bool overwrite)      //(in)   Overwrite the bad datapath file.
//
//Returns:
//True if successful, false if not.
{
	ASSERT(pwszFilepath);
	ASSERT(pwszDatPath);
	ASSERT(pwszResPath);

	if (DoesFileExist(pwszFilepath) && !overwrite) return false;

	FILE* pFile = Open(pwszFilepath, "w");
	if (NULL == pFile) {
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
		//Try making the file writable if it exists and is read-only
		string strFilepath;
		UnicodeToAscii(pwszFilepath, strFilepath);
		if (chmod(strFilepath.c_str(), 0644) ||
				(pFile = Open(pwszFilepath, "w")) == NULL);
			return false;
#else
		return false;
#endif
	}

	WSTRING tmp = pwszDatPath;
	tmp += wszSemicolon;
	tmp += pwszResPath;
	tmp += wszSemicolon;
#ifdef HAS_UNICODE
	bool bSuccess = (tmp.length() == fwrite( tmp.c_str(), sizeof(WCHAR), tmp.length(), pFile ));
#else
	char buffer[tmp.length() + 1];
	UnicodeToAscii(tmp, buffer);
	bool bSuccess = (strlen(buffer) == fwrite( buffer, sizeof(char), strlen(buffer), pFile ));
#endif // HAS_UNICODE

	fclose(pFile);

	return bSuccess;
}

//*****************************************************************************
bool CFiles::WindowsCanBrowseUnicode()
// Returns true if the Windows OS has _wfind* functions (i.e. it's NT or better)
{
	 //It might be preferable to make a call to wstat(apppath) instead and see if it works.

#ifdef WIN32
	 OSVERSIONINFO versionInfo;
	 versionInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
	if (!::GetVersionEx (&versionInfo)) {
		// Can't get version - let's assume we can't browse files in unicode
		return false;
	}

	return (
				//At time of writing (8/4/03) this includes: Windows NT 3.51, Windows NT 4.0,
				//Windows 2000, Windows XP, or Windows .NET Server.
				versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ||

				//Any future O/Ss from MS should probably support unicode too.
				versionInfo.dwMajorVersion >= 5);

#else //This isn't even Windows!
	ASSERTP(false, "Bad call to WindowsCanBrowseUnicode().");
	return false;
#endif
}

//*****************************************************************************
bool CFiles::IsValidPath(const WCHAR *pwzPath)
//Returns true if path is valid, false if not.
{
	if (!(pwzPath && pWCv(pwzPath))) return false;
	WSTRING wstrDirPath = pwzPath;

	//Get rid of the trailing slash (stat doesn't like it) UNLESS there is only one slash - i.e. e:\ .
	if (wstrDirPath.size() > 0) {
		int count = 0;
		for (WSTRING::iterator i = wstrDirPath.begin(); i != wstrDirPath.end(); ++i) {
			if (*i == wszSlash[0]) count++;
		}
		if (count > 1) {
			if (wstrDirPath[wstrDirPath.size()-1] == wszSlash[0]) {
				wstrDirPath.erase(wstrDirPath.size()-1);
			}
		}
	}

#ifdef WIN32
	 //Check for read permission.
	 if (CFiles::WindowsCanBrowseUnicode())
		  return (_waccess(wstrDirPath.c_str(), 4) == 0);
	 else
	 {
		  char *aDirPath = new char[wstrDirPath.length()+1];
		UnicodeToAscii(wstrDirPath, aDirPath);
		  bool bValidPath = (_access(aDirPath, 4) == 0);
		  delete [] aDirPath;
		  return bValidPath;
	 }
#else
	char *aDirPath = new char[wstrDirPath.length()+1];
	struct stat buf;
	UnicodeToAscii(wstrDirPath, aDirPath);
	bool bIsValid = !access(aDirPath, R_OK | X_OK)
			&& (!stat(aDirPath, &buf) ? S_ISDIR(buf.st_mode) : false);
	delete[] aDirPath;
	return bIsValid;
#endif
}

//*****************************************************************************
bool CFiles::GetDirectoryList(
//Gets a list of all directories in the given directory.
//
//Params:
	const WCHAR *wszFilepath,  //(in) Directory to look in
	vector<WSTRING>& dirs,     //(out) vector of all subdirectories in wszFilepath
	bool bShowHidden)          //(in)  if true, show hidden directories
//
//Returns:
//True if the directory was found, false if not.
{
	dirs.clear();
	set<WSTRING, WSTRINGicmp> wstrDirs;
#ifdef WIN32
	// If not on Windows NT or better, we need to use non-unicode paths here...
	if (!CFiles::WindowsCanBrowseUnicode()) {
		//Show subdirs.
		char buffer[MAX_PATH+1];
		WSTRING wbuffer;
		struct _finddata_t filedata;
		long hFile;

		//Find first sub-dir in current directory.
		UnicodeToAscii(wszFilepath, buffer);
		string sFileFilter = buffer;

		sFileFilter += "\\*";
		if ((hFile = _findfirst(sFileFilter.c_str(), &filedata )) == -1L) {
			 return (errno == ENOENT); //No files in current directory - fail only if it's not ENOENT
		}
		else
		{
			//Don't display the current directory (i.e., ".").
			if (filedata.attrib & _A_SUBDIR && strcmp(".", filedata.name)) {
				AsciiToUnicode(filedata.name, wbuffer);
				wstrDirs.insert(wbuffer);
			}

			//Find the rest of the sub-dirs.
			while (_findnext( hFile, &filedata ) == 0)
			{
				if (filedata.attrib & _A_SUBDIR) {
					AsciiToUnicode(filedata.name, wbuffer);
					wstrDirs.insert(wbuffer);
			  }
			}

			//Done.
			_findclose( hFile );
		}
	} else {
		// We have access to Unicode file browsing

		struct _wfinddata_t filedata;
		long hFile;

		//Find first sub-dir in current directory.
		WSTRING wFileFilter = wszFilepath;
		wFileFilter += wszSlash;
		wFileFilter += wszAsterisk;
		if ((hFile = _wfindfirst((WCHAR*)wFileFilter.c_str(), &filedata )) == -1L)
			 return (errno == ENOENT); //No files in current directory - fail only if it's not ENOENT

		//Don't display the current directory (i.e., ".").
		if (filedata.attrib & _A_SUBDIR && WCScmp(wszPeriod, filedata.name))
			wstrDirs.insert(filedata.name);

		//Find the rest of the sub-dirs.
		while (_wfindnext( hFile, &filedata ) == 0)
		{
			if (filedata.attrib & _A_SUBDIR)
				wstrDirs.insert(filedata.name);
		}

		//Done.
		_findclose( hFile );
	}
#else
#  ifdef HAS_UNICODE
#     error How does a non-Win32 machine get a Unicode list of directories in a directory?
#  else
	char buffer[MAX_PATH];
	DIR *pDir;
	struct dirent *pDirent;
	UnicodeToAscii(wszFilepath, buffer);
	pDir = opendir(buffer);
	UINT len = strlen(buffer);
	buffer[len] = SLASH;
	buffer[++len] = 0;
	if (pDir)
	{
		for (; (pDirent = readdir(pDir)) != NULL; buffer[len] = 0)
		{
			strcat(buffer, pDirent->d_name);
			struct stat st;
			if ((pDirent->d_name[0] != '.' || (bShowHidden ? pDirent->d_name[1] != 0
						: pDirent->d_name[1] == '.' && pDirent->d_name[2] == 0))
				&& !stat(buffer, &st) && S_ISDIR(st.st_mode)
				&& !access(buffer, R_OK | X_OK))
			{
				// The directory entry is a directory.
				WSTRING wbuffer;
				AsciiToUnicode(pDirent->d_name, wbuffer);
				wstrDirs.insert(wbuffer);
			}
		}
	}

#  endif

#endif
	for (set<WSTRING, WSTRINGicmp>::iterator wstr = wstrDirs.begin(); wstr != wstrDirs.end(); ++wstr)
		dirs.push_back(*wstr);

	return true;
}
//*****************************************************************************
bool CFiles::GetFileList(
// Gets a list of all files that match the mask in the given directory.
//
//Params:
	const WCHAR *wszFilepath,     //(in) the directory to look in
	const WSTRING& mask,          //(in) the file extension to look for (not including the "*.")
	vector<WSTRING>& files,       //(out) the files that matched the mask in the given directory
	bool bFullPath,               //(in) if true, outputs whole path to the file, if false just the filename
	bool bShowHidden)             //(in) if true, shows hidden files
//
//Returns:
//True if the directory was found, false if not.
{
	files.clear();
	set<WSTRING, WSTRINGicmp> wstrFiles;

#ifdef WIN32
	if (!CFiles::WindowsCanBrowseUnicode()) {
      //Get the files in the current directory - ASCII version
		char buffer[MAX_PATH+1];
		WSTRING wbuffer;
		struct _finddata_t filedata;
		long hFile;

		//Find first sub-dir in current directory.
		UnicodeToAscii(wszFilepath, buffer);
		string sFileFilter = buffer;

		sFileFilter += "\\*.";
		UnicodeToAscii(mask, buffer);
		sFileFilter += buffer;
		if ((hFile = _findfirst(sFileFilter.c_str(), &filedata)) == -1L) {
			 return (errno == ENOENT); //No files in current directory - fail only if it's not ENOENT
		}
		else
		{
			if (!(filedata.attrib & _A_SUBDIR || filedata.attrib & _A_SYSTEM ||
				filedata.attrib & _A_HIDDEN)) {
				AsciiToUnicode(filedata.name, wbuffer);
				wstrFiles.insert(wbuffer);
			}

			//Find the rest of the files.
			while (_findnext(hFile, &filedata) == 0)
			{
				if (!(filedata.attrib & _A_SUBDIR || filedata.attrib & _A_SYSTEM ||
					filedata.attrib & _A_HIDDEN)) {
					AsciiToUnicode(filedata.name, wbuffer);
					wstrFiles.insert(wbuffer);
				}
			}

			//Done.
			_findclose( hFile );
		}
	}
	else {
		//Get the files in the current directory - Unicode version
		struct _wfinddata_t filedata;
		long hFile;

		//Find first file in current directory.
		WSTRING wFileFilter = wszFilepath;
		wFileFilter += wszSlash;
		wFileFilter += wszAsterisk;
		wFileFilter += wszPeriod;
		wFileFilter += mask;
		if ((hFile = _wfindfirst((WCHAR*)wFileFilter.c_str(), &filedata)) == -1L)
			 return (errno == ENOENT); //No files in current directory - fail only if it's not ENOENT
		else
		{
			if (!(filedata.attrib & _A_SUBDIR || filedata.attrib & _A_SYSTEM ||
					filedata.attrib & _A_HIDDEN))
				wstrFiles.insert(filedata.name);

			//Find the rest of the files.
			while (_wfindnext(hFile, &filedata) == 0)
			{
				if (!(filedata.attrib & _A_SUBDIR || filedata.attrib & _A_SYSTEM ||
						filedata.attrib & _A_HIDDEN))
					wstrFiles.insert(filedata.name);
			}

			//Done.
			_findclose( hFile );
		}
	}
#else
#  ifdef HAS_UNICODE
#     error How does a non-Win32 machine get a Unicode list of files in a directory?
#  else
	char buffer[MAX_PATH];
	DIR *pDir;
	struct dirent *pDirent;
	UnicodeToAscii(wszFilepath, buffer);
	pDir = opendir(buffer);
	UINT len = strlen(buffer);
	buffer[len] = SLASH;
	buffer[++len] = 0;
	if (pDir)
	{
		char szExt[mask.length() + 1];
		UnicodeToAscii(mask, szExt);

		for (; (pDirent = readdir(pDir)) != NULL; buffer[len] = 0)
		{
			if (!bShowHidden && pDirent->d_name[0] == '.') continue;  //skip hidden

			char *szCurExt = pDirent->d_name + strlen(pDirent->d_name) - strlen(szExt);
			if (szCurExt < pDirent->d_name || (strcmp(szExt, szCurExt)))
				continue;

			strcat(buffer, pDirent->d_name);
			struct stat st;
			if (!stat(buffer, &st) && !S_ISDIR(st.st_mode))
			{
				// The entry is a file.
				WSTRING wbuffer;
				AsciiToUnicode(pDirent->d_name, wbuffer);
				wstrFiles.insert(wbuffer);
			}
		}

		closedir(pDir);
	}
#  endif
#endif

	for (set<WSTRING, WSTRINGicmp>::iterator wstr = wstrFiles.begin(); wstr != wstrFiles.end(); ++wstr)
	{
		if (bFullPath) {
			WSTRING wstrNew = wszFilepath;
			wstrNew += wszSlash;
			wstrNew += *wstr;
			files.push_back(wstrNew);
		}
		else
			files.push_back(*wstr);
	}

	return true;
}

//*****************************************************************************
bool CFiles::GetDriveList(
//Gets a list of all valid drives (Win32) or some useful directories (Linux)
//
//Params:
	vector<WSTRING>& drives)      //(out) vector of strings representing all valid drives
//
//Returns:
//True if it makes sense to call this on this system, false otherwise
{
#ifdef WIN32
	drives.clear();

	if (!CFiles::bDrivesChecked)
	{
		for (UINT drive=0; drive<NUMDRIVES; ++drive)
		{
			char buffer[4] = { drive+'a', ':', '\\', 0};
			UINT uType = GetDriveTypeA(buffer); //Use instead of GetDriveType() for pre-NT compatibility.
			bDrives[drive] = ((uType != DRIVE_UNKNOWN) && (uType != DRIVE_NO_ROOT_DIR));
		}
		bDrivesChecked = true;
	}

	for (UINT drive=0; drive<NUMDRIVES; ++drive)
	{
		if (CFiles::bDrives[drive])
		{
			static const WCHAR wszLeft[] = {'[',' ',0}, wszRight[] = {':',' ',']',0};
			WCHAR wszDrive[] = {'A',0};
			wszDrive[0] = (WCHAR)(drive + 'A');
			WSTRING wstrDrive = wszLeft;
			wstrDrive += wszDrive;
			wstrDrive += wszRight;
			drives.push_back(wstrDrive);
		}
	}

	return true;
#elif defined (__linux__) || defined (__FreeBSD__)
	//Add the DROD dat directory and the home directory first.
	drives.clear();
	static const WCHAR wszLeft[] = {{'['},{' '},{0}}, wszRight[] = {{' '},{']'},{0}};
	WSTRING wstr = wszLeft;
	wstr += wszDatPath;
	wstr += wszRight;
	drives.push_back(wstr);
	wstr = wszLeft;
	wstr += wstrHomePath;
	wstr += wszRight;
	drives.push_back(wstr);

	//Add some mount points
	FILE *fp = fopen("/etc/mtab", "r");
	if (fp) {
		char buf[4096];
		while (fscanf(fp, "%4095s %4094s %*s %*s %*i %*i\n", buf, buf + 1) == 2)
		{
			if (*buf == SLASH) {
				WSTRING wb;
				AsciiToUnicode(buf + 1, wb);
				wstr = wszLeft;
				wstr += wb;
				wstr += wszRight;
				drives.push_back(wstr);
			}
		}
		fclose(fp);
	}

	return true;
#elif defined __APPLE__
	//Add the DROD dat directory and the home directory first.
	drives.clear();
	static const WCHAR wszLeft[] = {{'['},{' '},{0}}, wszRight[] = {{' '},{']'},{0}};
	WSTRING wstr = wszLeft;
	wstr += wszDatPath;
	wstr += wszRight;
	drives.push_back(wstr);
	wstr = wszLeft;
	wstr += wstrHomePath;
	wstr += wszRight;
	drives.push_back(wstr);

	// Add mount points
	char buffer[MAX_PATH];
	DIR *pDir;
	struct dirent *pDirent;
	strcpy(buffer, "/Volumes");
	pDir = opendir(buffer);
	UINT len = strlen(buffer);
	buffer[len] = SLASH;
	buffer[++len] = 0;
	if (pDir)
	{
		dirent *pDirent;
		for (; (pDirent = readdir(pDir)) != NULL; buffer[len] = 0)
		{
			strcat(buffer, pDirent->d_name);
			struct stat st;
			if (pDirent->d_name[0] != '.'
				&& !stat(buffer, &st) && S_ISDIR(st.st_mode)
				&& !access(buffer, R_OK | X_OK))
			{
				// The directory entry is a directory.
				WSTRING wbuffer;
				AsciiToUnicode(buffer, wbuffer);

				wstr = wszLeft;
				wstr += wbuffer;
				wstr += wszRight;
				drives.push_back(wstr);
			}
		}
	}

	return true;
#else
	return false;
#endif
}

//**************************************************************************************
#ifdef USE_LOGCONTEXT
void CFiles::PushLogContext(const char *pszDesc)
{
	 static const UINT MAXLEN_CONTEXT = 20000;
	 static const UINT CHOPLEN = 3000;
	 for (UINT wIndentNo = 0; wIndentNo < m_wIndentLevel; ++wIndentNo)
		  m_strUnwrittenContext += "  ";
	 m_strUnwrittenContext += "BEGIN ";
	 m_strUnwrittenContext += pszDesc;
	 m_strUnwrittenContext += "\r\n";
	 ++m_wIndentLevel;

	 if (m_strUnwrittenContext.size() > MAXLEN_CONTEXT)
	 {
		  const char *pszSeek = m_strUnwrittenContext.c_str() + CHOPLEN;
		  while (*pszSeek && *pszSeek != '\n') ++pszSeek;
		  string strTemp = pszSeek + 1;
		  m_strUnwrittenContext = "(Beginning of log context truncated to free memory.)\r\n";
		  m_strUnwrittenContext += strTemp;
	 }
}

//**************************************************************************************
void CFiles::PopLogContext(const char *pszDesc)
{
	 --m_wIndentLevel;
	 for (UINT wIndentNo = 0; wIndentNo < m_wIndentLevel; ++wIndentNo)
		  m_strUnwrittenContext += "  ";
	 m_strUnwrittenContext += "END ";
	 m_strUnwrittenContext += pszDesc;
	 m_strUnwrittenContext += "\r\n";
}
#endif // #ifdef USE_LOGCONTEXT

//**************************************************************************************

//**************************************************************************************
#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
CPathGen::CPathGen (WCHAR *buffer, const WCHAR *expr)
{
	ASSERT(buffer != NULL);
	ASSERT(expr != NULL);
	wszBuffer = buffer;
	wszExpr = expr;
	UINT i, bits = 0;
	for (i = 0; WCv(expr[i]); ++i)
		if (expr[i] == '?' || expr[i] == '<' || expr[i] == '>')
			++bits;
	this->wBits = bits;
	this->wN = (1 << bits) - 1;
}

//**************************************************************************************
int CPathGen::Next ()
//Fill buffer with the next path to search.
//
//Returns:
//Nonzero if there was more paths, zero otherwise.  If a seperator was used (;), the
//(one-based) position of the last one is returned, otherwise all you get is -1.
{
	if (this->wN == (UINT)-1) {
		WCv(this->wszBuffer[0]) = 0;
		return 0;
	}
	const UINT n = this->wN--;
	UINT m = 1 << (this->wBits - 1), j = 0;
	bool bProcess = true, bWriting = true;
	int ret = -1;
	WCHAR ch;
	for (UINT i = 0; (WCv(ch) = WCv(this->wszExpr[i])); ++i) {
		if (bProcess) switch (WCv(ch)) {
			case ';':
				ret = j + 1;
				bProcess = false;
				bWriting = true;
				continue;
			case '/':
				bWriting = true;
				break;
			case '?':
			case '<':
			case '>':
				if (((bWriting = n & m) && ch != '?')
						&& (((n & (m << 1))?1:0) ^ (ch == '<')?1:0)) // logical xor =)
					return Next();
				WCv(ch) = '/';
				m >>= 1;
				break;
		}
		if (bWriting) this->wszBuffer[j++] = ch;
	}
	WCv(this->wszBuffer[j]) = 0;
	return ret;
}
#endif //#if defined __linux__ || defined __FreeBSD__ || defined __APPLE__
