Serious-Engine/Sources/Engine/Base/Stream.cpp
Ryan C. Gordon c752e7cace Fixed a mismerge.
(But I still need to figure out what I'm doing between SetAbsolutePath() and
whatever I had before...)
2016-04-04 22:40:30 -04:00

1616 lines
43 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd.
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
#include "Engine/StdH.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// !!! FIXME : rcg10162001 Need this anymore, since _findfirst() is abstracted?
#ifdef PLATFORM_WIN32
#include <io.h>
#endif
#include <Engine/Base/Protection.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Memory.h>
#include <Engine/Base/Console.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Base/ProgressHook.h>
#include <Engine/Base/Unzip.h>
#include <Engine/Base/CRC.h>
#include <Engine/Base/Shell.h>
#include <Engine/Base/FileSystem.h>
#include <Engine/Templates/NameTable_CTFileName.h>
#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/DynamicStackArray.cpp>
#include <Engine/Templates/Stock_CTextureData.h>
#include <Engine/Templates/Stock_CModelData.h>
// default size of page used for stream IO operations (4Kb)
ULONG _ulPageSize = 0;
// maximum length of file that can be saved (default: 128Mb)
ULONG _ulMaxLenghtOfSavingFile = (1UL<<20)*128;
INDEX fil_bPreferZips = FALSE;
// set if current thread has currently enabled stream handling
THREADLOCAL(BOOL, _bThreadCanHandleStreams, FALSE);
// list of currently opened streams
ULONG _ulVirtuallyAllocatedSpace = 0;
ULONG _ulVirtuallyAllocatedSpaceTotal = 0;
THREADLOCAL(CListHead *, _plhOpenedStreams, NULL);
// global string with application path
CTFileName _fnmApplicationPath;
// global string with filename of the started application
CTFileName _fnmApplicationExe;
// global string with user-specific writable directory.
CTFileName _fnmUserDir;
// global string with current MOD path
CTFileName _fnmMod;
// global string with current name (the parameter that is passed on cmdline)
CTString _strModName;
// global string with url to be shown to users that don't have the mod installed
// (should be set by game.dll)
CTString _strModURL;
// global string with current MOD extension (for adding to dlls)
CTString _strModExt;
// global string with CD path (for minimal installations)
CTFileName _fnmCDPath;
// include/exclude lists for base dir writing/browsing
CDynamicStackArray<CTFileName> _afnmBaseWriteInc;
CDynamicStackArray<CTFileName> _afnmBaseWriteExc;
CDynamicStackArray<CTFileName> _afnmBaseBrowseInc;
CDynamicStackArray<CTFileName> _afnmBaseBrowseExc;
// list of paths or patterns that are not included when making CRCs for network connection
// this is used to enable connection between different localized versions
CDynamicStackArray<CTFileName> _afnmNoCRC;
// load a filelist
static BOOL LoadFileList(CDynamicStackArray<CTFileName> &afnm, const CTFileName &fnmList)
{
afnm.PopAll();
try {
CTFileStream strm;
strm.Open_t(fnmList);
while(!strm.AtEOF()) {
CTString strLine;
strm.GetLine_t(strLine);
strLine.TrimSpacesLeft();
strLine.TrimSpacesRight();
if (strLine!="") {
afnm.Push() = strLine;
}
}
return TRUE;
} catch(char *strError) {
CPrintF("%s\n", strError);
return FALSE;
}
}
extern BOOL FileMatchesList(CDynamicStackArray<CTFileName> &afnm, const CTFileName &fnm)
{
for(INDEX i=0; i<afnm.Count(); i++) {
if (fnm.Matches(afnm[i]) || fnm.HasPrefix(afnm[i])) {
return TRUE;
}
}
return FALSE;
}
static CTFileName _fnmApp;
void InitStreams(void)
{
// obtain information about system
// !!! FIXME: Move this into an abstraction of some sort...
#ifdef PLATFORM_WIN32
SYSTEM_INFO siSystemInfo;
GetSystemInfo( &siSystemInfo);
// and remember page size
_ulPageSize = siSystemInfo.dwPageSize*16; // cca. 64kB on WinNT/Win95
#else
_ulPageSize = PAGESIZE;
#endif
// keep a copy of path for setting purposes
_fnmApp = _fnmApplicationPath;
// if no mod defined yet
if (_fnmMod=="") {
// check for 'default mod' file
LoadStringVar(CTString("DefaultMod.txt"), _fnmMod);
}
CPrintF(TRANSV("Current mod: %s\n"),
(_fnmMod=="") ? TRANS("<none>") :
(const char *) (CTString&)_fnmMod);
// if there is a mod active
if (_fnmMod!="") {
// load mod's include/exclude lists
CPrintF(TRANSV("Loading mod include/exclude lists...\n"));
BOOL bOK = FALSE;
bOK |= LoadFileList(_afnmBaseWriteInc , CTString("BaseWriteInclude.lst"));
bOK |= LoadFileList(_afnmBaseWriteExc , CTString("BaseWriteExclude.lst"));
bOK |= LoadFileList(_afnmBaseBrowseInc, CTString("BaseBrowseInclude.lst"));
bOK |= LoadFileList(_afnmBaseBrowseExc, CTString("BaseBrowseExclude.lst"));
// if none found
if (!bOK) {
// the mod is not valid
_fnmMod = CTString("");
CPrintF(TRANSV("Error: MOD not found!\n"));
// if mod is ok
} else {
// remember mod name (the parameter that is passed on cmdline)
_strModName = _fnmMod;
_strModName.DeleteChar(_strModName.Length()-1);
_strModName = CTFileName(_strModName).FileName();
}
}
// find eventual extension for the mod's dlls
_strModExt = "";
LoadStringVar(CTString("ModExt.txt"), _strModExt);
CPrintF(TRANSV("Loading group files...\n"));
CDynamicArray<CTString> *files = NULL;
// for each group file in base directory
files = _pFileSystem->FindFiles(_fnmApplicationPath, "*.gro");
int max = files->Count();
int i;
// for each .gro file in the directory
for (i = 0; i < max; i++) {
// add it to active set
UNZIPAddArchive( _fnmApplicationPath+((*files)[i]) );
}
delete files;
// if there is a mod active
if (_fnmMod!="") {
// for each group file in mod directory
files = _pFileSystem->FindFiles(_fnmApplicationPath+_fnmMod, "*.gro");
max = files->Count();
for (i = 0; i < max; i++) {
UNZIPAddArchive( _fnmApplicationPath + _fnmMod + ((*files)[i]) );
}
delete files;
}
// if there is a CD path
if (_fnmCDPath!="") {
// for each group file on the CD
files = _pFileSystem->FindFiles(_fnmCDPath, "*.gro");
max = files->Count();
for (i = 0; i < max; i++) {
UNZIPAddArchive( _fnmCDPath + ((*files)[i]) );
}
delete files;
// if there is a mod active
if (_fnmMod!="") {
// for each group file in mod directory
files = _pFileSystem->FindFiles(_fnmCDPath+_fnmMod, "*.gro");
max = files->Count();
for (i = 0; i < max; i++) {
UNZIPAddArchive( _fnmCDPath + _fnmMod + ((*files)[i]) );
}
delete files;
}
}
// try to
try {
// read the zip directories
UNZIPReadDirectoriesReverse_t();
// if failed
} catch( char *strError) {
// report warning
CPrintF( TRANS("There were group file errors:\n%s"), strError);
}
CPrintF("\n");
const char *dirsep = CFileSystem::GetDirSeparator();
LoadFileList(_afnmNoCRC, CTFILENAME("Data") + CTString(dirsep) + CTString("NoCRC.lst"));
_pShell->SetINDEX(CTString("sys")+"_iCPU"+"Misc", 1);
}
void EndStreams(void)
{
}
void UseApplicationPath(void)
{
_fnmApplicationPath = _fnmApp;
}
void IgnoreApplicationPath(void)
{
_fnmApplicationPath = CTString("");
}
/////////////////////////////////////////////////////////////////////////////
// Helper functions
/* Static function enable stream handling. */
void CTStream::EnableStreamHandling(void)
{
ASSERT(!_bThreadCanHandleStreams && _plhOpenedStreams == NULL);
_bThreadCanHandleStreams = TRUE;
_plhOpenedStreams = new CListHead;
}
/* Static function disable stream handling. */
void CTStream::DisableStreamHandling(void)
{
ASSERT(_bThreadCanHandleStreams && _plhOpenedStreams != NULL);
_bThreadCanHandleStreams = FALSE;
delete _plhOpenedStreams;
_plhOpenedStreams = NULL;
}
/*
* Throw an exception of formatted string.
*/
void CTStream::Throw_t(const char *strFormat, ...) // throws char *
{
const SLONG slBufferSize = 256;
char strFormatBuffer[slBufferSize];
static char *strBuffer = NULL;
// ...and yes, you are screwed if you call this in a catch block and
// try to access the previous text again.
delete[] strBuffer;
strBuffer = new char[slBufferSize];
// add the stream description to the format string
_snprintf(strFormatBuffer, slBufferSize, "%s (%s)", strFormat, (const char *) strm_strStreamDescription);
// format the message in buffer
va_list arg;
va_start(arg, strFormat); // variable arguments start after this argument
_vsnprintf(strBuffer, slBufferSize, strFormatBuffer, arg);
va_end(arg);
throw strBuffer;
}
/////////////////////////////////////////////////////////////////////////////
// Binary access methods
/* Get CRC32 of stream */
ULONG CTStream::GetStreamCRC32_t(void)
{
// remember where stream is now
SLONG slOldPos = GetPos_t();
// go to start of file
SetPos_t(0);
// get size of file
SLONG slFileSize = GetStreamSize();
ULONG ulCRC;
CRC_Start(ulCRC);
// for each block in file
const SLONG slBlockSize = 4096;
for(SLONG slPos=0; slPos<slFileSize; slPos+=slBlockSize) {
// read the block
UBYTE aubBlock[slBlockSize];
SLONG slThisBlockSize = Min(slFileSize-slPos, slBlockSize);
Read_t(aubBlock, slThisBlockSize);
// checksum it
CRC_AddBlock(ulCRC, aubBlock, slThisBlockSize);
}
// restore position
SetPos_t(slOldPos);
CRC_Finish(ulCRC);
return ulCRC;
}
/////////////////////////////////////////////////////////////////////////////
// Text access methods
/* Get a line of text from file. */
// throws char *
void CTStream::GetLine_t(char *strBuffer, SLONG slBufferSize, char cDelimiter /*='\n'*/ )
{
// check parameters
ASSERT(strBuffer!=NULL && slBufferSize>0);
// check that the stream can be read
ASSERT(IsReadable());
// letters slider
INDEX iLetters = 0;
// test if EOF reached
if(AtEOF()) {
ThrowF_t(TRANS("EOF reached, file %s"), (const char *) strm_strStreamDescription);
}
// get line from istream
FOREVER
{
char c;
Read_t(&c, 1);
if(AtEOF()) {
// cut off
strBuffer[ iLetters] = 0;
break;
}
// don't read "\r" characters but rather act like they don't exist
if( c != '\r') {
strBuffer[ iLetters] = c;
// stop reading when delimiter loaded
if( strBuffer[ iLetters] == cDelimiter) {
// convert delimiter to zero
strBuffer[ iLetters] = 0;
// jump over delimiter
//Seek_t(1, SD_CUR);
break;
}
// jump to next destination letter
iLetters++;
}
// test if maximum buffer lenght reached
if( iLetters==slBufferSize) {
return;
}
}
}
void CTStream::GetLine_t(CTString &strLine, char cDelimiter/*='\n'*/) // throw char *
{
char strBuffer[1024];
GetLine_t(strBuffer, sizeof(strBuffer)-1, cDelimiter);
strLine = strBuffer;
}
/* Put a line of text into file. */
void CTStream::PutLine_t(const char *strBuffer) // throws char *
{
// check parameters
ASSERT(strBuffer!=NULL);
// check that the stream is writteable
ASSERT(IsWriteable());
// get string length
INDEX iStringLength = strlen(strBuffer);
// put line into stream
Write_t(strBuffer, iStringLength);
// write "\r\n" into stream
Write_t("\r\n", 2);
}
void CTStream::PutString_t(const char *strString) // throw char *
{
// check parameters
ASSERT(strString!=NULL);
// check that the stream is writteable
ASSERT(IsWriteable());
// get string length
INDEX iStringLength = strlen(strString);
// put line into stream
for( INDEX iLetter=0; iLetter<iStringLength; iLetter++)
{
if (*strString=='\n') {
// write "\r\n" into stream
Write_t("\r\n", 2);
strString++;
} else {
Write_t(strString++, 1);
}
}
}
void CTStream::FPrintF_t(const char *strFormat, ...) // throw char *
{
const SLONG slBufferSize = 2048;
char strBuffer[slBufferSize];
// format the message in buffer
va_list arg;
va_start(arg, strFormat); // variable arguments start after this argument
_vsnprintf(strBuffer, slBufferSize, strFormat, arg);
va_end(arg);
// print the buffer
PutString_t(strBuffer);
}
/////////////////////////////////////////////////////////////////////////////
// Chunk reading/writing methods
CChunkID CTStream::GetID_t(void) // throws char *
{
CChunkID cidToReturn;
Read_t( &cidToReturn.cid_ID[0], CID_LENGTH);
return( cidToReturn);
}
CChunkID CTStream::PeekID_t(void) // throw char *
{
// read the chunk id
CChunkID cidToReturn;
Read_t( &cidToReturn.cid_ID[0], CID_LENGTH);
// return the stream back
Seek_t(-CID_LENGTH, SD_CUR);
return( cidToReturn);
}
void CTStream::ExpectID_t(const CChunkID &cidExpected) // throws char *
{
CChunkID cidToCompare;
Read_t( &cidToCompare.cid_ID[0], CID_LENGTH);
if( !(cidToCompare == cidExpected))
{
ThrowF_t(TRANS("Chunk ID validation failed.\nExpected ID \"%s\" but found \"%s\"\n"),
cidExpected.cid_ID, cidToCompare.cid_ID);
}
}
void CTStream::ExpectKeyword_t(const CTString &strKeyword) // throw char *
{
// check that the keyword is present
const INDEX total = (INDEX)strlen(strKeyword);
for(INDEX iKeywordChar=0; iKeywordChar<total; iKeywordChar++) {
SBYTE chKeywordChar;
(*this)>>chKeywordChar;
if (chKeywordChar!=strKeyword[iKeywordChar]) {
ThrowF_t(TRANS("Expected keyword %s not found"), (const char *) strKeyword);
}
}
}
SLONG CTStream::GetSize_t(void) // throws char *
{
SLONG chunkSize;
Read_t( (char *) &chunkSize, sizeof( SLONG));
return( chunkSize);
}
void CTStream::ReadRawChunk_t(void *pvBuffer, SLONG slSize) // throws char *
{
Read_t((char *)pvBuffer, slSize);
}
void CTStream::ReadChunk_t(void *pvBuffer, SLONG slExpectedSize) // throws char *
{
if( slExpectedSize != GetSize_t())
throw TRANS("Chunk size not equal as expected size");
Read_t((char *)pvBuffer, slExpectedSize);
}
void CTStream::ReadFullChunk_t(const CChunkID &cidExpected, void *pvBuffer,
SLONG slExpectedSize) // throws char *
{
ExpectID_t( cidExpected);
ReadChunk_t( pvBuffer, slExpectedSize);
};
void* CTStream::ReadChunkAlloc_t(SLONG slSize) // throws char *
{
UBYTE *buffer;
SLONG chunkSize;
if( slSize != 0)
chunkSize = slSize;
else
chunkSize = GetSize_t(); // throws char *
buffer = (UBYTE *) AllocMemory( chunkSize);
if( buffer == NULL)
throw TRANS("ReadChunkAlloc: Unable to allocate needed amount of memory.");
Read_t((char *)buffer, chunkSize); // throws char *
return buffer;
}
void CTStream::ReadStream_t(CTStream &strmOther) // throw char *
{
// implement this !!!! @@@@
}
void CTStream::WriteID_t(const CChunkID &cidSave) // throws char *
{
Write_t( &cidSave.cid_ID[0], CID_LENGTH);
}
void CTStream::WriteSize_t(SLONG slSize) // throws char *
{
Write_t( (char *)&slSize, sizeof( SLONG));
}
void CTStream::WriteRawChunk_t(void *pvBuffer, SLONG slSize) // throws char *
{
Write_t( (char *)pvBuffer, slSize);
}
void CTStream::WriteChunk_t(void *pvBuffer, SLONG slSize) // throws char *
{
WriteSize_t( slSize);
WriteRawChunk_t( pvBuffer, slSize);
}
void CTStream::WriteFullChunk_t(const CChunkID &cidSave, void *pvBuffer,
SLONG slSize) // throws char *
{
WriteID_t( cidSave); // throws char *
WriteChunk_t( pvBuffer, slSize); // throws char *
}
void CTStream::WriteStream_t(CTStream &strmOther) // throw char *
{
// implement this !!!! @@@@
}
// whether or not the given pointer is coming from this stream (mainly used for exception handling)
BOOL CTStream::PointerInStream(void* pPointer)
{
// safe to return FALSE, we're using virtual functions anyway
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// filename dictionary operations
// enable dictionary in writable file from this point
void CTStream::DictionaryWriteBegin_t(const CTFileName &fnmImportFrom, SLONG slImportOffset)
{
ASSERT(strm_slDictionaryPos==0);
ASSERT(strm_dmDictionaryMode == DM_NONE);
strm_ntDictionary.SetAllocationParameters(100, 5, 5);
strm_ctDictionaryImported = 0;
// if importing an existing dictionary to start with
if (fnmImportFrom!="") {
// open that file
CTFileStream strmOther;
strmOther.Open_t(fnmImportFrom);
// read the dictionary in that stream
strmOther.ReadDictionary_intenal_t(slImportOffset);
// copy the dictionary here
CopyDictionary(strmOther);
// write dictionary importing data
WriteID_t("DIMP"); // dictionary import
*this<<fnmImportFrom<<slImportOffset;
// remember how many filenames were imported
strm_ctDictionaryImported = strm_afnmDictionary.Count();
}
// write dictionary position chunk id
WriteID_t("DPOS"); // dictionary position
// remember where position will be placed
strm_slDictionaryPos = GetPos_t();
// leave space for position
*this<<SLONG(0);
// start dictionary
strm_dmDictionaryMode = DM_ENABLED;
}
// write the dictionary (usually at the end of file)
void CTStream::DictionaryWriteEnd_t(void)
{
ASSERT(strm_dmDictionaryMode == DM_ENABLED);
ASSERT(strm_slDictionaryPos>0);
// remember the dictionary position chunk position
SLONG slDictPos = strm_slDictionaryPos;
// mark that now saving dictionary
strm_slDictionaryPos = -1;
// remember where dictionary begins
SLONG slDictBegin = GetPos_t();
// start dictionary processing
strm_dmDictionaryMode = DM_PROCESSING;
WriteID_t("DICT"); // dictionary
// write number of used filenames
INDEX ctFileNames = strm_afnmDictionary.Count();
INDEX ctFileNamesNew = ctFileNames-strm_ctDictionaryImported;
*this<<ctFileNamesNew;
// for each filename
for(INDEX iFileName=strm_ctDictionaryImported; iFileName<ctFileNames; iFileName++) {
// write it to disk
*this<<strm_afnmDictionary[iFileName];
}
WriteID_t("DEND"); // dictionary end
// remember where is end of dictionary
SLONG slContinue = GetPos_t();
// write the position back to dictionary position chunk
SetPos_t(slDictPos);
*this<<slDictBegin;
// stop dictionary processing
strm_dmDictionaryMode = DM_NONE;
strm_ntDictionary.Clear();
strm_afnmDictionary.Clear();
// return to end of dictionary
SetPos_t(slContinue);
strm_slDictionaryPos=0;
}
// read the dictionary from given offset in file (internal function)
void CTStream::ReadDictionary_intenal_t(SLONG slOffset)
{
// remember where to continue loading
SLONG slContinue = GetPos_t();
// go to dictionary beginning
SetPos_t(slOffset);
// start dictionary processing
strm_dmDictionaryMode = DM_PROCESSING;
ExpectID_t("DICT"); // dictionary
// read number of new filenames
INDEX ctFileNamesOld = strm_afnmDictionary.Count();
INDEX ctFileNamesNew;
*this>>ctFileNamesNew;
// if there are any new filenames
if (ctFileNamesNew>0) {
// create that much space
strm_afnmDictionary.Push(ctFileNamesNew);
// for each filename
for(INDEX iFileName=ctFileNamesOld; iFileName<ctFileNamesOld+ctFileNamesNew; iFileName++) {
// read it
*this>>strm_afnmDictionary[iFileName];
}
}
ExpectID_t("DEND"); // dictionary end
// remember where end of dictionary is
strm_slDictionaryPos = GetPos_t();
// return to continuing position
SetPos_t(slContinue);
}
// copy filename dictionary from another stream
void CTStream::CopyDictionary(CTStream &strmOther)
{
strm_afnmDictionary = strmOther.strm_afnmDictionary;
for (INDEX i=0; i<strm_afnmDictionary.Count(); i++) {
strm_ntDictionary.Add(&strm_afnmDictionary[i]);
}
}
SLONG CTStream::DictionaryReadBegin_t(void)
{
ASSERT(strm_dmDictionaryMode == DM_NONE);
ASSERT(strm_slDictionaryPos==0);
strm_ntDictionary.SetAllocationParameters(100, 5, 5);
SLONG slImportOffset = 0;
// if there is imported dictionary
if (PeekID_t()==CChunkID("DIMP")) { // dictionary import
// read dictionary importing data
ExpectID_t("DIMP"); // dictionary import
CTFileName fnmImportFrom;
*this>>fnmImportFrom>>slImportOffset;
// open that file
CTFileStream strmOther;
strmOther.Open_t(fnmImportFrom);
// read the dictionary in that stream
strmOther.ReadDictionary_intenal_t(slImportOffset);
// copy the dictionary here
CopyDictionary(strmOther);
}
// if the dictionary is not here
if (PeekID_t()!=CChunkID("DPOS")) { // dictionary position
// do nothing
return 0;
}
// read dictionary position
ExpectID_t("DPOS"); // dictionary position
SLONG slDictBeg;
*this>>slDictBeg;
// read the dictionary from that offset in file
ReadDictionary_intenal_t(slDictBeg);
// stop dictionary processing - go to dictionary using
strm_dmDictionaryMode = DM_ENABLED;
// return offset of dictionary for later cross-file importing
if (slImportOffset!=0) {
return slImportOffset;
} else {
return slDictBeg;
}
}
void CTStream::DictionaryReadEnd_t(void)
{
if (strm_dmDictionaryMode == DM_ENABLED) {
ASSERT(strm_slDictionaryPos>0);
// just skip the dictionary (it was already read)
SetPos_t(strm_slDictionaryPos);
strm_slDictionaryPos=0;
strm_dmDictionaryMode = DM_NONE;
strm_ntDictionary.Clear();
// for each filename
INDEX ctFileNames = strm_afnmDictionary.Count();
for(INDEX iFileName=0; iFileName<ctFileNames; iFileName++) {
CTFileName &fnm = strm_afnmDictionary[iFileName];
// if not preloaded
if (fnm.fnm_pserPreloaded==NULL) {
// skip
continue;
}
// free preloaded instance
CTString strExt = fnm.FileExt();
if (strExt==".tex") {
_pTextureStock->Release((CTextureData*)fnm.fnm_pserPreloaded);
} else if (strExt==".mdl") {
_pModelStock->Release((CModelData*)fnm.fnm_pserPreloaded);
}
}
strm_afnmDictionary.Clear();
}
}
void CTStream::DictionaryPreload_t(void)
{
INDEX ctFileNames = strm_afnmDictionary.Count();
// for each filename
for(INDEX iFileName=0; iFileName<ctFileNames; iFileName++) {
// preload it
CTFileName &fnm = strm_afnmDictionary[iFileName];
CTString strExt = fnm.FileExt();
CallProgressHook_t(FLOAT(iFileName)/ctFileNames);
try {
if (strExt==".tex") {
fnm.fnm_pserPreloaded = _pTextureStock->Obtain_t(fnm);
} else if (strExt==".mdl") {
fnm.fnm_pserPreloaded = _pModelStock->Obtain_t(fnm);
}
} catch (char *strError) {
CPrintF( TRANS("Cannot preload %s: %s\n"), (const char *) (CTString&)fnm, strError);
}
}
}
/////////////////////////////////////////////////////////////////////////////
// General construction/destruction
/* Default constructor. */
CTStream::CTStream(void) : strm_ntDictionary(*new CNameTable_CTFileName)
{
strm_strStreamDescription = "";
strm_slDictionaryPos = 0;
strm_dmDictionaryMode = DM_NONE;
}
/* Destructor. */
CTStream::~CTStream(void)
{
strm_ntDictionary.Clear();
strm_afnmDictionary.Clear();
delete &strm_ntDictionary;
}
/////////////////////////////////////////////////////////////////////////////
// File stream opening/closing methods
/*
* Default constructor.
*/
CTFileStream::CTFileStream(void)
{
fstrm_pFile = NULL;
// mark that file is created for writing
fstrm_bReadOnly = TRUE;
fstrm_iZipHandle = -1;
fstrm_iZipLocation = 0;
fstrm_pubZipBuffer = NULL;
}
/*
* Destructor.
*/
CTFileStream::~CTFileStream(void)
{
// close stream
if (fstrm_pFile != NULL || fstrm_iZipHandle!=-1) {
Close();
}
}
/*
* Open an existing file.
*/
// throws char *
void CTFileStream::Open_t(const CTFileName &fnFileName, CTStream::OpenMode om/*=OM_READ*/)
{
// if current thread has not enabled stream handling
if (!_bThreadCanHandleStreams) {
// error
::ThrowF_t(TRANS("Cannot open file `%s', stream handling is not enabled for this thread"),
(const char *) (CTString&)fnFileName);
}
// check parameters
ASSERT(strlen(fnFileName)>0);
// check that the file is not open
ASSERT(fstrm_pFile==NULL && fstrm_iZipHandle==-1);
// expand the filename to full path
CTFileName fnmFullFileName;
INDEX iFile = ExpandFilePath((om == OM_READ)?EFP_READ:EFP_WRITE, fnFileName, fnmFullFileName);
// if read only mode requested
if( om == OM_READ) {
// initially, no physical file
fstrm_pFile = NULL;
// if zip file
if( iFile==EFP_MODZIP || iFile==EFP_BASEZIP) {
// open from zip
fstrm_iZipHandle = UNZIPOpen_t(fnmFullFileName);
fstrm_slZipSize = UNZIPGetSize(fstrm_iZipHandle);
// load the file from the zip in the buffer
fstrm_pubZipBuffer = new UBYTE[fstrm_slZipSize];
UNZIPReadBlock_t(fstrm_iZipHandle, fstrm_pubZipBuffer, 0, fstrm_slZipSize);
// if it is a physical file
} else if (iFile==EFP_FILE) {
// open file in read only mode
fstrm_pFile = fopen(fnmFullFileName, "rb");
}
fstrm_bReadOnly = TRUE;
// if write mode requested
} else if( om == OM_WRITE) {
// open file for reading and writing
fstrm_pFile = fopen(fnmFullFileName, "rb+");
fstrm_bReadOnly = FALSE;
// if unknown mode
} else {
FatalError(TRANS("File stream opening requested with unknown open mode: %d\n"), om);
}
// if openning operation was not successfull
if(fstrm_pFile == NULL && fstrm_iZipHandle==-1) {
// throw exception
Throw_t(TRANS("Cannot open file `%s' (%s)"), (const char *) (CTString&)fnmFullFileName,
strerror(errno));
}
// if file opening was successfull, set stream description to file name
strm_strStreamDescription = fnmFullFileName;
// add this newly opened file into opened stream list
_plhOpenedStreams->AddTail( strm_lnListNode);
}
static void MakeSureDirectoryPathExists(const CTFileName &fnmFullFileName)
{
STUBBED("!!! FIXME: get the code back in from Ryan's original port.");
}
/*
* Create a new file or overwrite existing.
*/
void CTFileStream::Create_t(const CTFileName &fnFileName,
enum CTStream::CreateMode cm) // throws char *
{
(void)cm; // OBSOLETE!
CTFileName fnFileNameAbsolute = fnFileName;
fnFileNameAbsolute.SetAbsolutePath();
// if current thread has not enabled stream handling
if (!_bThreadCanHandleStreams) {
// error
::ThrowF_t(TRANS("Cannot create file `%s', stream handling is not enabled for this thread"),
(const char *) (CTString&)fnFileNameAbsolute);
}
CTFileName fnmFullFileName;
INDEX iFile = ExpandFilePath(EFP_WRITE, fnFileNameAbsolute, fnmFullFileName);
// check parameters
ASSERT(strlen(fnFileNameAbsolute)>0);
// check that the file is not open
ASSERT(fstrm_pFile == NULL);
// create the directory for the new file if it doesn't exist yet
MakeSureDirectoryPathExists(fnmFullFileName);
// open file stream for writing (destroy file context if file existed before)
fstrm_pFile = fopen(fnmFullFileName, "wb+");
// if not successfull
if(fstrm_pFile == NULL)
{
// throw exception
Throw_t(TRANS("Cannot create file `%s' (%s)"), (const char *) (CTString&)fnmFullFileName,
strerror(errno));
}
// if file creation was successfull, set stream description to file name
strm_strStreamDescription = fnFileNameAbsolute;
// mark that file is created for writing
fstrm_bReadOnly = FALSE;
// add this newly created file into opened stream list
_plhOpenedStreams->AddTail( strm_lnListNode);
}
/*
* Close an open file.
*/
void CTFileStream::Close(void)
{
// if file is not open
if (fstrm_pFile==NULL && fstrm_iZipHandle==-1) {
ASSERT(FALSE);
return;
}
// clear stream description
strm_strStreamDescription = "";
// remove file from list of curently opened streams
strm_lnListNode.Remove();
// if file on disk
if (fstrm_pFile != NULL) {
// close file
fclose( fstrm_pFile);
fstrm_pFile = NULL;
// if file in zip
} else if (fstrm_iZipHandle>=0) {
// close zip entry
UNZIPClose(fstrm_iZipHandle);
fstrm_iZipHandle = -1;
delete[] fstrm_pubZipBuffer;
_ulVirtuallyAllocatedSpace -= fstrm_slZipSize;
//CPrintF("Freed virtual memory with size ^c00ff00%d KB^C (now %d KB)\n", (fstrm_slZipSize / 1000), (_ulVirtuallyAllocatedSpace / 1000));
}
// clear dictionary vars
strm_dmDictionaryMode = DM_NONE;
strm_ntDictionary.Clear();
strm_afnmDictionary.Clear();
strm_slDictionaryPos=0;
}
/* Get CRC32 of stream */
ULONG CTFileStream::GetStreamCRC32_t(void)
{
// if file on disk
if (fstrm_pFile != NULL) {
// use base class implementation (really calculates the CRC)
return CTStream::GetStreamCRC32_t();
// if file in zip
} else if (fstrm_iZipHandle >=0) {
return UNZIPGetCRC(fstrm_iZipHandle);
} else {
ASSERT(FALSE);
return 0;
}
}
/* Read a block of data from stream. */
void CTFileStream::Read_t(void *pvBuffer, SLONG slSize)
{
if(fstrm_iZipHandle != -1) {
memcpy(pvBuffer, fstrm_pubZipBuffer + fstrm_iZipLocation, slSize);
fstrm_iZipLocation += slSize;
return;
}
fread(pvBuffer, slSize, 1, fstrm_pFile);
}
/* Write a block of data to stream. */
void CTFileStream::Write_t(const void *pvBuffer, SLONG slSize)
{
if(fstrm_bReadOnly || fstrm_iZipHandle != -1) {
throw "Stream is read-only!";
}
fwrite(pvBuffer, slSize, 1, fstrm_pFile);
}
/* Seek in stream. */
void CTFileStream::Seek_t(SLONG slOffset, enum SeekDir sd)
{
if(fstrm_iZipHandle != -1) {
switch(sd) {
case SD_BEG: fstrm_iZipLocation = slOffset; break;
case SD_CUR: fstrm_iZipLocation += slOffset; break;
case SD_END: fstrm_iZipLocation = GetSize_t() + slOffset; break;
}
} else {
fseek(fstrm_pFile, slOffset, sd);
}
}
/* Set absolute position in stream. */
void CTFileStream::SetPos_t(SLONG slPosition)
{
Seek_t(slPosition, SD_BEG);
}
/* Get absolute position in stream. */
SLONG CTFileStream::GetPos_t(void)
{
if(fstrm_iZipHandle != -1) {
return fstrm_iZipLocation;
} else {
return ftell(fstrm_pFile);
}
}
/* Get size of stream */
SLONG CTFileStream::GetStreamSize(void)
{
if(fstrm_iZipHandle != -1) {
return UNZIPGetSize(fstrm_iZipHandle);
} else {
long lCurrentPos = ftell(fstrm_pFile);
fseek(fstrm_pFile, 0, SD_END);
long lRet = ftell(fstrm_pFile);
fseek(fstrm_pFile, lCurrentPos, SD_BEG);
return lRet;
}
}
/* Check if file position points to the EOF */
BOOL CTFileStream::AtEOF(void)
{
if(fstrm_iZipHandle != -1) {
return fstrm_iZipLocation >= fstrm_slZipSize;
} else {
int eof = feof(fstrm_pFile);
return eof != 0;
}
}
// whether or not the given pointer is coming from this stream (mainly used for exception handling)
BOOL CTFileStream::PointerInStream(void* pPointer)
{
// we're not using virtual allocation buffers so it's fine to return FALSE here.
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// Memory stream construction/destruction
/*
* Create dynamically resizing stream for reading/writing.
*/
CTMemoryStream::CTMemoryStream(void)
{
// if current thread has not enabled stream handling
if (!_bThreadCanHandleStreams) {
// error
::FatalError(TRANS("Can create memory stream, stream handling is not enabled for this thread"));
}
mstrm_ctLocked = 0;
mstrm_bReadable = TRUE;
mstrm_bWriteable = TRUE;
mstrm_slLocation = 0;
// set stream description
strm_strStreamDescription = "dynamic memory stream";
// add this newly created memory stream into opened stream list
_plhOpenedStreams->AddTail( strm_lnListNode);
// allocate amount of memory needed to hold maximum allowed file length (when saving)
mstrm_pubBuffer = new UBYTE[_ulMaxLenghtOfSavingFile];
mstrm_pubBufferEnd = mstrm_pubBuffer + _ulMaxLenghtOfSavingFile;
mstrm_pubBufferMax = mstrm_pubBuffer;
}
/*
* Create static stream from given buffer.
*/
CTMemoryStream::CTMemoryStream(void *pvBuffer, SLONG slSize,
CTStream::OpenMode om /*= CTStream::OM_READ*/)
{
// if current thread has not enabled stream handling
if (!_bThreadCanHandleStreams) {
// error
::FatalError(TRANS("Can create memory stream, stream handling is not enabled for this thread"));
}
// allocate amount of memory needed to hold maximum allowed file length (when saving)
mstrm_pubBuffer = new UBYTE[_ulMaxLenghtOfSavingFile];
mstrm_pubBufferEnd = mstrm_pubBuffer + _ulMaxLenghtOfSavingFile;
mstrm_pubBufferMax = mstrm_pubBuffer + slSize;
// copy given block of memory into memory file
memcpy( mstrm_pubBuffer, pvBuffer, slSize);
mstrm_ctLocked = 0;
mstrm_bReadable = TRUE;
mstrm_slLocation = 0;
// if stram is opened in read only mode
if( om == OM_READ)
{
mstrm_bWriteable = FALSE;
}
// otherwise, write is enabled
else
{
mstrm_bWriteable = TRUE;
}
// set stream description
strm_strStreamDescription = "dynamic memory stream";
// add this newly created memory stream into opened stream list
_plhOpenedStreams->AddTail( strm_lnListNode);
}
/* Destructor. */
CTMemoryStream::~CTMemoryStream(void)
{
ASSERT(mstrm_ctLocked==0);
delete[] mstrm_pubBuffer;
// remove memory stream from list of curently opened streams
strm_lnListNode.Remove();
}
/////////////////////////////////////////////////////////////////////////////
// Memory stream buffer operations
/*
* Lock the buffer contents and it's size.
*/
void CTMemoryStream::LockBuffer(void **ppvBuffer, SLONG *pslSize)
{
mstrm_ctLocked++;
ASSERT(mstrm_ctLocked>0);
*ppvBuffer = mstrm_pubBuffer;
*pslSize = GetSize_t();
}
/*
* Unlock buffer.
*/
void CTMemoryStream::UnlockBuffer()
{
mstrm_ctLocked--;
ASSERT(mstrm_ctLocked>=0);
}
/////////////////////////////////////////////////////////////////////////////
// Memory stream overrides from CTStream
BOOL CTMemoryStream::IsReadable(void)
{
return mstrm_bReadable && (mstrm_ctLocked==0);
}
BOOL CTMemoryStream::IsWriteable(void)
{
return mstrm_bWriteable && (mstrm_ctLocked==0);
}
BOOL CTMemoryStream::IsSeekable(void)
{
return TRUE;
}
/* Read a block of data from stream. */
void CTMemoryStream::Read_t(void *pvBuffer, SLONG slSize)
{
memcpy(pvBuffer, mstrm_pubBuffer + mstrm_slLocation, slSize);
mstrm_slLocation += slSize;
}
/* Write a block of data to stream. */
void CTMemoryStream::Write_t(const void *pvBuffer, SLONG slSize)
{
memcpy(mstrm_pubBuffer + mstrm_slLocation, pvBuffer, slSize);
mstrm_slLocation += slSize;
if(mstrm_pubBuffer + mstrm_slLocation > mstrm_pubBufferMax) {
mstrm_pubBufferMax = mstrm_pubBuffer + mstrm_slLocation;
}
}
/* Seek in stream. */
void CTMemoryStream::Seek_t(SLONG slOffset, enum SeekDir sd)
{
switch(sd) {
case SD_BEG: mstrm_slLocation = slOffset; break;
case SD_CUR: mstrm_slLocation += slOffset; break;
case SD_END: mstrm_slLocation = GetStreamSize() + slOffset; break;
}
}
/* Set absolute position in stream. */
void CTMemoryStream::SetPos_t(SLONG slPosition)
{
mstrm_slLocation = slPosition;
}
/* Get absolute position in stream. */
SLONG CTMemoryStream::GetPos_t(void)
{
return mstrm_slLocation;
}
/* Get size of stream. */
SLONG CTMemoryStream::GetSize_t(void)
{
return GetStreamSize();
}
/* Get size of stream */
SLONG CTMemoryStream::GetStreamSize(void)
{
return mstrm_pubBufferMax - mstrm_pubBuffer;
}
/* Get CRC32 of stream */
ULONG CTMemoryStream::GetStreamCRC32_t(void)
{
return CTStream::GetStreamCRC32_t();
}
/* Check if file position points to the EOF */
BOOL CTMemoryStream::AtEOF(void)
{
return mstrm_slLocation >= GetStreamSize();
}
// whether or not the given pointer is coming from this stream (mainly used for exception handling)
BOOL CTMemoryStream::PointerInStream(void* pPointer)
{
return pPointer >= mstrm_pubBuffer && pPointer < mstrm_pubBufferEnd;
}
// Test if a file exists.
BOOL FileExists(const CTFileName &fnmFile)
{
// if no file
if (fnmFile=="") {
// it doesn't exist
return FALSE;
}
// try to
try {
// open the file for reading
CTFileStream strmFile;
strmFile.Open_t(fnmFile);
// if successful, it means that it exists,
return TRUE;
// if failed, it means that it doesn't exist
} catch (char *strError) {
(void) strError;
return FALSE;
}
}
// Test if a file exists for writing.
// (this is can be diferent than normal FileExists() if a mod uses basewriteexclude.lst
BOOL FileExistsForWriting(const CTFileName &fnmFile)
{
// if no file
if (fnmFile=="") {
// it doesn't exist
return FALSE;
}
// expand the filename to full path for writing
CTFileName fnmFullFileName;
INDEX iFile = ExpandFilePath(EFP_WRITE, fnmFile, fnmFullFileName);
// check if it exists
FILE *f = fopen(fnmFullFileName, "rb");
if (f!=NULL) {
fclose(f);
return TRUE;
} else {
return FALSE;
}
}
// Get file timestamp
SLONG GetFileTimeStamp_t(const CTFileName &fnm)
{
// expand the filename to full path
CTFileName fnmExpanded;
INDEX iFile = ExpandFilePath(EFP_READ, fnm, fnmExpanded);
if (iFile!=EFP_FILE) {
return FALSE;
}
int file_handle;
// try to open file for reading
file_handle = _open( fnmExpanded, _O_RDONLY | _O_BINARY);
if(file_handle==-1) {
ThrowF_t(TRANS("Cannot open file '%s' for reading"), (const char *) CTString(fnm));
return -1;
}
struct stat statFileStatus;
// get file status
fstat( file_handle, &statFileStatus);
_close( file_handle);
ASSERT(statFileStatus.st_mtime<=time(NULL));
return statFileStatus.st_mtime;
}
// Get CRC32 of a file
ULONG GetFileCRC32_t(const CTFileName &fnmFile) // throw char *
{
// open the file
CTFileStream fstrm;
fstrm.Open_t(fnmFile);
// return the checksum
return fstrm.GetStreamCRC32_t();
}
// Test if a file is read only (also returns FALSE if file does not exist)
BOOL IsFileReadOnly(const CTFileName &fnm)
{
// expand the filename to full path
CTFileName fnmExpanded;
INDEX iFile = ExpandFilePath(EFP_READ, fnm, fnmExpanded);
if (iFile!=EFP_FILE) {
return FALSE;
}
int file_handle;
// try to open file for reading
file_handle = _open( fnmExpanded, _O_RDONLY | _O_BINARY);
if(file_handle==-1) {
return FALSE;
}
struct stat statFileStatus;
// get file status
fstat( file_handle, &statFileStatus);
_close( file_handle);
ASSERT(statFileStatus.st_mtime<=time(NULL));
return !(statFileStatus.st_mode&_S_IWRITE);
}
// Delete a file
BOOL RemoveFile(const CTFileName &fnmFile)
{
// expand the filename to full path
CTFileName fnmExpanded;
INDEX iFile = ExpandFilePath(EFP_WRITE, fnmFile, fnmExpanded);
if (iFile==EFP_FILE) {
int ires = remove(fnmExpanded);
return ires==0;
} else {
return FALSE;
}
}
static BOOL IsFileReadable_internal(CTFileName &fnmFullFileName)
{
FILE *pFile = fopen(fnmFullFileName, "rb");
if (pFile!=NULL) {
fclose(pFile);
return TRUE;
} else {
return FALSE;
}
}
// check for some file extensions that can be substituted
static BOOL SubstExt_internal(CTFileName &fnmFullFileName)
{
if (fnmFullFileName.FileExt()==".mp3") {
fnmFullFileName = fnmFullFileName.NoExt()+".ogg";
return TRUE;
} else if (fnmFullFileName.FileExt()==".ogg") {
fnmFullFileName = fnmFullFileName.NoExt()+".mp3";
return TRUE;
} else {
return TRUE;
}
}
static INDEX ExpandFilePath_read(ULONG ulType, const CTFileName &fnmFile, CTFileName &fnmExpanded)
{
// search for the file in zips
INDEX iFileInZip = UNZIPGetFileIndex(fnmFile);
const BOOL userdir_not_basedir = (_fnmUserDir != _fnmApplicationPath);
// if a mod is active
if (_fnmMod!="") {
// first try in the mod's dir
if (!fil_bPreferZips) {
fnmExpanded = _fnmApplicationPath+_fnmMod+fnmFile;
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
// if not disallowing zips
if (!(ulType&EFP_NOZIPS)) {
// if exists in mod's zip
if (iFileInZip>=0 && UNZIPIsFileAtIndexMod(iFileInZip)) {
// use that one
fnmExpanded = fnmFile;
return EFP_MODZIP;
}
}
// try in the mod's dir after
if (fil_bPreferZips) {
fnmExpanded = _fnmApplicationPath+_fnmMod+fnmFile;
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
}
// try in the app's base dir
if (!fil_bPreferZips) {
CTFileName fnmAppPath = _fnmApplicationPath;
fnmAppPath.SetAbsolutePath();
if(fnmFile.HasPrefix(fnmAppPath)) {
fnmExpanded = fnmFile;
} else {
fnmExpanded = _fnmApplicationPath+fnmFile;
}
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
// if not disallowing zips
if (!(ulType&EFP_NOZIPS)) {
// if exists in any zip
if (iFileInZip>=0) {
// use that one
fnmExpanded = fnmFile;
return EFP_BASEZIP;
}
}
// try in the app's base dir
if (fil_bPreferZips) {
fnmExpanded = _fnmApplicationPath+fnmFile;
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
// finally, try in the CD path
if (_fnmCDPath!="") {
// if a mod is active
if (_fnmMod!="") {
// first try in the mod's dir
fnmExpanded = _fnmCDPath+_fnmMod+fnmFile;
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
fnmExpanded = _fnmCDPath+fnmFile;
if (IsFileReadable_internal(fnmExpanded)) {
return EFP_FILE;
}
}
return EFP_NONE;
}
// rcg01042002 User dir and children may need to be created on the fly...
static void VerifyDirsExist(const char *_path)
{
char *path = (char *) AllocMemory(strlen(_path) + 1);
strcpy(path, _path);
const char *dirsep = CFileSystem::GetDirSeparator();
// skip first dirsep. This assumes an absolute path and some other
// fundamentals of how a filepath is specified.
char *ptr = strstr(path, dirsep);
ASSERT(ptr != NULL);
if (ptr == NULL)
return;
for (ptr = strstr(ptr+1, dirsep); ptr != NULL; ptr = strstr(ptr+1, dirsep)) {
char ch = *ptr;
*ptr = '\0'; // terminate the path.
if (!_pFileSystem->IsDirectory(path)) {
if (_pFileSystem->Exists(path)) {
CPrintF("Expected %s to be a directory, but it's a file!\n", path);
break;
} else {
CPrintF("Creating directory %s ...\n", path);
_mkdir(path);
if (!_pFileSystem->IsDirectory(path)) {
CPrintF("Creation of directory %s FAILED!\n", path);
break;
}
}
}
*ptr = ch; // put path char back...
}
FreeMemory(path);
}
// Expand a file's filename to full path
INDEX ExpandFilePath(ULONG ulType, const CTFileName &fnmFile, CTFileName &fnmExpanded)
{
CTFileName fnmFileAbsolute = fnmFile;
fnmFileAbsolute.SetAbsolutePath();
// if writing
if (ulType&EFP_WRITE) {
// if should write to mod dir
if (_fnmMod!="" && (!FileMatchesList(_afnmBaseWriteInc, fnmFileAbsolute) || FileMatchesList(_afnmBaseWriteExc, fnmFileAbsolute))) {
// do that
fnmExpanded = _fnmApplicationPath+_fnmMod+fnmFileAbsolute;
fnmExpanded.SetAbsolutePath();
VerifyDirsExist(fnmExpanded.FileDir());
return EFP_FILE;
// if should not write to mod dir
} else {
// write to base dir
fnmExpanded = _fnmApplicationPath+fnmFileAbsolute;
fnmExpanded.SetAbsolutePath();
VerifyDirsExist(fnmExpanded.FileDir());
return EFP_FILE;
}
// if reading
} else if (ulType&EFP_READ) {
// check for expansions for reading
INDEX iRes = ExpandFilePath_read(ulType, fnmFileAbsolute, fnmExpanded);
// if not found
if (iRes==EFP_NONE) {
//check for some file extensions that can be substituted
CTFileName fnmSubst = fnmFileAbsolute;
if (SubstExt_internal(fnmSubst)) {
iRes = ExpandFilePath_read(ulType, fnmSubst, fnmExpanded);
}
}
fnmExpanded.SetAbsolutePath();
if (iRes!=EFP_NONE) {
return iRes;
}
// in other cases
} else {
ASSERT(FALSE);
fnmExpanded = _fnmApplicationPath+fnmFileAbsolute;
fnmExpanded.SetAbsolutePath();
return EFP_FILE;
}
fnmExpanded = _fnmApplicationPath+fnmFileAbsolute;
fnmExpanded.SetAbsolutePath();
return EFP_NONE;
}