// // Copyright 2020 Electronic Arts Inc. // // TiberianDawn.DLL and RedAlert.dll and corresponding source code is free // software: you can redistribute it and/or modify it under the terms of // the GNU General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection /* $Header: /CounterStrike/BFIOFILE.CPP 1 3/03/97 10:24a Joe_bostic $ */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Westwood Library * * * * File Name : RAMFILE.CPP * * * * Programmer : David R. Dettmer * * * * Start Date : November 10, 1995 * * * * Last Update : November 10, 1995 [DRD] * * * *---------------------------------------------------------------------------------------------* * Functions: * * BufferIOFileClass::BufferIOFileClass -- Filename based constructor for a file object. * * BufferIOFileClass::BufferIOFileClass -- default constructor for a file object. * * BufferIOFileClass::Cache -- Load part or all of a file data into RAM. * * BufferIOFileClass::Close -- Perform a closure of the file. * * BufferIOFileClass::Commit -- Writes the cache to the file if it has changed. * * BufferIOFileClass::Free -- Frees the allocated buffer. * * BufferIOFileClass::Is_Available -- Checks for existence of file cached or on disk. * * BufferIOFileClass::Is_Open -- Determines if the file is open. * * BufferIOFileClass::Open -- Assigns name and opens file in one operation. * * BufferIOFileClass::Open -- Opens the file object with the rights specified. * * BufferIOFileClass::Read -- Reads data from the file cache. * * BufferIOFileClass::Seek -- Moves the current file pointer in the file. * * BufferIOFileClass::Set_Name -- Checks for name changed for a cached file. * * BufferIOFileClass::Size -- Determines size of file (in bytes). * * BufferIOFileClass::Write -- Writes data to the file cache. * * BufferIOFileClass::~BufferIOFileClass -- Destructor for the file object. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "bfiofile.h" #include /*********************************************************************************************** * BufferIOFileClass::BufferIOFileClass -- Filename based constructor for a file object. * * * * This constructor is called when a file object is created with a supplied filename, but * * not opened at the same time. In this case, an assumption is made that the supplied * * filename is a constant string. A duplicate of the filename string is not created since * * it would be wasteful in that case. * * * * INPUT: filename -- The filename to assign to this file object. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/10/1995 DRD : Created. * *=============================================================================================*/ BufferIOFileClass::BufferIOFileClass(char const * filename) : IsAllocated(false), IsOpen(false), IsDiskOpen(false), IsCached(false), IsChanged(false), UseBuffer(false), BufferRights(0), Buffer(0), BufferSize(0), BufferPos(0), BufferFilePos(0), BufferChangeBeg(-1), BufferChangeEnd(-1), FileSize(0), FilePos(0), TrueFileStart(0) { BufferIOFileClass::Set_Name(filename); } /*********************************************************************************************** * BufferIOFileClass::BufferIOFileClass -- default constructor for a file object. * * * * This is the default constructor for a file object. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/10/1995 DRD : Created. * *=============================================================================================*/ BufferIOFileClass::BufferIOFileClass(void) : IsAllocated(false), IsOpen(false), IsDiskOpen(false), IsCached(false), IsChanged(false), UseBuffer(false), BufferRights(0), Buffer(0), BufferSize(0), BufferPos(0), BufferFilePos(0), BufferChangeBeg(-1), BufferChangeEnd(-1), FileSize(0), FilePos(0), TrueFileStart(0) { } /*********************************************************************************************** * BufferIOFileClass::~BufferIOFileClass -- Destructor for the file object. * * * * This destructor will free all memory allocated thru using Cache routines. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/10/1995 DRD : Created. * *=============================================================================================*/ BufferIOFileClass::~BufferIOFileClass(void) { Free(); } /*********************************************************************************************** * BufferIOFileClass::Cache -- Load part or all of a file data into RAM. * * * * INPUT: none * * * * OUTPUT: bool; Was the file load successful? It could fail if there wasn't enough room * * to allocate the raw data block. * * * * WARNINGS: This routine goes to disk for a potentially very long time. * * * * HISTORY: * * 11/10/1995 DRD : Created. * *=============================================================================================*/ bool BufferIOFileClass::Cache( long size, void * ptr ) { if (Buffer) { // // if trying to cache again with size or ptr fail // if (size || ptr) { return( false ); } else { return( true ); } } if ( Is_Available() ) { FileSize = Size(); } else { FileSize = 0; } if (size) { // // minimum buffer size for performance // if (size < MINIMUM_BUFFER_SIZE) { size = MINIMUM_BUFFER_SIZE; /* ** Specifying a size smaller than the minimum is an error ** IF a buffer pointer was also specified. In such a case the ** system cannot use the buffer. */ if (ptr) { Error(EINVAL); } } BufferSize = size; } else { BufferSize = FileSize; } // // if size == 0 and a ptr to a buffer is specified then that is invalid. // if the BufferSize is 0 then this must be a new file and no size was // specified so exit. // if ( (size == 0 && ptr) || !BufferSize) { return( false ); } if (ptr) { Buffer = ptr; } else { Buffer = new char [BufferSize]; } if (Buffer) { IsAllocated = true; IsDiskOpen = false; BufferPos = 0; BufferFilePos = 0; BufferChangeBeg = -1; BufferChangeEnd = -1; FilePos = 0; TrueFileStart = 0; // // the file was checked for availability then set the FileSize // if (FileSize) { long readsize; int opened = false; long prevpos = 0; if (FileSize <= BufferSize) { readsize = FileSize; } else { readsize = BufferSize; } if ( Is_Open() ) { // // get previous file position // prevpos = Seek(0); // // get true file position // if ( RawFileClass::Is_Open() ) { TrueFileStart = RawFileClass::Seek(0); } else { TrueFileStart = prevpos; } if (FileSize <= BufferSize) { // // if previous position is non-zero seek to the beginning // if (prevpos) { Seek(0, SEEK_SET); } // // set the buffer position for future reads/writes // BufferPos = prevpos; } else { BufferFilePos = prevpos; } FilePos = prevpos; } else { if ( Open() ) { TrueFileStart = RawFileClass::Seek(0); opened = true; } } long actual = Read(Buffer, readsize); if (actual != readsize) { Error(EIO); } if (opened) { Close(); } else { // // seek to the previous position in the file // Seek(prevpos, SEEK_SET); } IsCached = true; } UseBuffer = true; return(true); } Error(ENOMEM); return(false); } /*********************************************************************************************** * BufferIOFileClass::Free -- Frees the allocated buffer. * * * * This routine will free the buffer. By using this in conjunction with the * * Cache() function, one can maintain tight control of memory usage. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/10/1995 DRD : Created. * *=============================================================================================*/ void BufferIOFileClass::Free(void) { if (Buffer) { if (IsAllocated) { delete [] Buffer; IsAllocated = false; } Buffer = 0; } BufferSize = 0; IsOpen = false; IsCached = false; IsChanged = false; UseBuffer = false; } /*********************************************************************************************** * BufferIOFileClass::Commit -- Writes the cache to the file if it has changed. * * * * * * INPUT: none * * * * OUTPUT: false, did not need to write the buffer. * * true, wrote the buffer. * * * * WARNINGS: none * * * * HISTORY: * * 11/15/1995 DRD : Created. * *=============================================================================================*/ bool BufferIOFileClass::Commit( void ) { long size; if (UseBuffer) { if (IsChanged) { size = BufferChangeEnd - BufferChangeBeg; if (IsDiskOpen) { RawFileClass::Seek( TrueFileStart + BufferFilePos + BufferChangeBeg, SEEK_SET ); RawFileClass::Write( Buffer, size ); RawFileClass::Seek( TrueFileStart + FilePos, SEEK_SET ); } else { RawFileClass::Open(); RawFileClass::Seek( TrueFileStart + BufferFilePos + BufferChangeBeg, SEEK_SET ); RawFileClass::Write( Buffer, size ); RawFileClass::Close(); } IsChanged = false; return( true ); } else { return( false ); } } else { return( false ); } } /*********************************************************************************************** * BufferIOFileClass::Set_Name -- Checks for name changed for a cached file. * * * * Checks for a previous filename and that it is cached. If so, then check the * * new filename against the old. If they are the same then return that filename. * * Otherwise, the file object's name is set with just the raw filename as passed * * to this routine. * * * * INPUT: filename -- Pointer to the filename to set as the name of this file object. * * * * OUTPUT: Returns a pointer to the final and complete filename of this file object. This * * may have a path attached to the file. * * * * WARNINGS: none * * * * HISTORY: * * 11/15/1995 DRD : Created. * *=============================================================================================*/ char const * BufferIOFileClass::Set_Name(char const * filename) { if ( File_Name() && UseBuffer) { if ( strcmp(filename, File_Name() ) == 0) { return( File_Name() ); } else { Commit(); IsCached = false; } } RawFileClass::Set_Name(filename); return( File_Name() ); } /*********************************************************************************************** * BufferIOFileClass::Is_Available -- Checks for existence of file cached or on disk. * * * * * * INPUT: none * * * * OUTPUT: bool; Is the file available for opening? * * * * WARNINGS: none * * * * HISTORY: * * 11/16/1995 DRD : Created. * *=============================================================================================*/ int BufferIOFileClass::Is_Available(int ) { if (UseBuffer) { return(true); } return( RawFileClass::Is_Available() ); } /*********************************************************************************************** * BufferIOFileClass::Is_Open -- Determines if the file is open. * * * * If part or all of the file is cached, then return that it is opened. A closed file * * doesn't have a valid pointer. * * * * INPUT: none * * * * OUTPUT: bool; Is the file open? * * * * WARNINGS: none * * * * HISTORY: * * 11/14/1995 DRD : Created. * *=============================================================================================*/ int BufferIOFileClass::Is_Open(void) const { if (IsOpen && UseBuffer) { return( true ); } return( RawFileClass::Is_Open() ); } /*********************************************************************************************** * BufferIOFileClass::Open -- Assigns name and opens file in one operation. * * * * This routine will assign the specified filename to the file object and open it at the * * same time. If the file object was already open, then it will be closed first. If the * * file object was previously assigned a filename, then it will be replaced with the new * * name. Typically, this routine is used when an anonymous file object has been crated and * * now it needs to be assigned a name and opened. * * * * INPUT: filename -- The filename to assign to this file object. * * * * rights -- The open file access rights to use. * * * * OUTPUT: bool; Was the file opened? The return value of this is moot, since the open file * * is designed to never return unless it succeeded. * * * * WARNINGS: none * * * * HISTORY: * * 11/14/1995 DRD : Created. * *=============================================================================================*/ int BufferIOFileClass::Open(char const * filename, int rights) { Set_Name(filename); return( BufferIOFileClass::Open( rights ) ); } /*********************************************************************************************** * BufferIOFileClass::Open -- Opens the file object with the rights specified. * * * * This routine is used to open the specified file object with the access rights indicated. * * This only works if the file has already been assigned a filename. It is guaranteed, by * * the error handler, that this routine will always return with success. * * * * INPUT: rights -- The file access rights to use when opening this file. This is a * * combination of READ and/or WRITE bit flags. * * * * OUTPUT: bool; Was the file opened successfully? This will always return true by reason of * * the error handler. * * * * WARNINGS: none * * * * HISTORY: * * 11/14/1995 DRD : Created. * *=============================================================================================*/ int BufferIOFileClass::Open(int rights) { BufferIOFileClass::Close(); if (UseBuffer) { BufferRights = rights; // save rights requested for checks later if (rights != READ || (rights == READ && FileSize > BufferSize) ) { if (rights == WRITE) { RawFileClass::Open( rights ); RawFileClass::Close(); rights = READ | WRITE; TrueFileStart = 0; // now writing to single file } if (TrueFileStart) { UseBuffer = false; Open( rights ); UseBuffer = true; } else { RawFileClass::Open( rights ); } IsDiskOpen = true; if (BufferRights == WRITE) { FileSize = 0; } } else { IsDiskOpen = false; } BufferPos = 0; BufferFilePos = 0; BufferChangeBeg = -1; BufferChangeEnd = -1; FilePos = 0; IsOpen = true; } else { RawFileClass::Open( rights ); } return( true ); } /*********************************************************************************************** * BufferIOFileClass::Write -- Writes data to the file cache. * * * * * * INPUT: buffer -- Pointer to the buffer that holds the data to be written. * * * * size -- The number of bytes to write. * * * * OUTPUT: Returns the number of bytes actually written. * * * * WARNINGS: none * * * * HISTORY: * * 11/15/1995 DRD : Created. * *=============================================================================================*/ long BufferIOFileClass::Write(void const * buffer, long size) { int opened = false; if ( !Is_Open() ) { if (!Open(WRITE)) { return(0); } TrueFileStart = RawFileClass::Seek(0); opened = true; } if (UseBuffer) { long sizewritten = 0; if (BufferRights != READ) { while (size) { long sizetowrite; if (size >= (BufferSize - BufferPos) ) { sizetowrite = (BufferSize - BufferPos); } else { sizetowrite = size; } if (sizetowrite != BufferSize) { if ( !IsCached ) { long readsize; if (FileSize < BufferSize) { readsize = FileSize; BufferFilePos = 0; } else { readsize = BufferSize; BufferFilePos = FilePos; } if (TrueFileStart) { UseBuffer = false; Seek( FilePos, SEEK_SET ); Read( Buffer, BufferSize ); Seek( FilePos, SEEK_SET ); UseBuffer = true; } else { RawFileClass::Seek( BufferFilePos, SEEK_SET ); RawFileClass::Read( Buffer, readsize ); } BufferPos = 0; BufferChangeBeg = -1; BufferChangeEnd = -1; IsCached = true; } } memmove((char *)Buffer + BufferPos, (char *)buffer + sizewritten, sizetowrite); IsChanged = true; sizewritten += sizetowrite; size -= sizetowrite; if (BufferChangeBeg == -1) { BufferChangeBeg = BufferPos; BufferChangeEnd = BufferPos; } else { if (BufferChangeBeg > BufferPos) { BufferChangeBeg = BufferPos; } } BufferPos += sizetowrite; if (BufferChangeEnd < BufferPos) { BufferChangeEnd = BufferPos; } FilePos = BufferFilePos + BufferPos; if (FileSize < FilePos) { FileSize = FilePos; } // // end of buffer reached? // if (BufferPos == BufferSize) { Commit(); BufferPos = 0; BufferFilePos = FilePos; BufferChangeBeg = -1; BufferChangeEnd = -1; if (size && FileSize > FilePos) { if (TrueFileStart) { UseBuffer = false; Seek( FilePos, SEEK_SET ); Read( Buffer, BufferSize ); Seek( FilePos, SEEK_SET ); UseBuffer = true; } else { RawFileClass::Seek( FilePos, SEEK_SET ); RawFileClass::Read( Buffer, BufferSize ); } } else { IsCached = false; } } } } else { Error(EACCES); } size = sizewritten; } else { size = RawFileClass::Write(buffer, size); } if (opened) { Close(); } return( size ); } /*********************************************************************************************** * BufferIOFileClass::Read -- Reads data from the file cache. * * * * * * INPUT: buffer -- Pointer to the buffer to place the read data. * * * * size -- The number of bytes to read. * * * * OUTPUT: Returns the actual number of bytes read (this could be less than requested). * * * * WARNINGS: none * * * * HISTORY: * * 11/15/1995 DRD : Created. * *=============================================================================================*/ long BufferIOFileClass::Read(void * buffer, long size) { int opened = false; if ( !Is_Open() ) { if ( Open() ) { TrueFileStart = RawFileClass::Seek(0); opened = true; } } if (UseBuffer) { long sizeread = 0; if (BufferRights != WRITE) { while (size) { long sizetoread; if (size >= (BufferSize - BufferPos) ) { sizetoread = (BufferSize - BufferPos); } else { sizetoread = size; } if ( !IsCached ) { long readsize; if (FileSize < BufferSize) { readsize = FileSize; BufferFilePos = 0; } else { readsize = BufferSize; BufferFilePos = FilePos; } if (TrueFileStart) { UseBuffer = false; Seek( FilePos, SEEK_SET ); Read( Buffer, BufferSize ); Seek( FilePos, SEEK_SET ); UseBuffer = true; } else { RawFileClass::Seek( BufferFilePos, SEEK_SET ); RawFileClass::Read( Buffer, readsize ); } BufferPos = 0; BufferChangeBeg = -1; BufferChangeEnd = -1; IsCached = true; } memmove((char *)buffer + sizeread, (char *)Buffer + BufferPos, sizetoread); sizeread += sizetoread; size -= sizetoread; BufferPos += sizetoread; FilePos = BufferFilePos + BufferPos; // // end of buffer reached? // if (BufferPos == BufferSize) { Commit(); BufferPos = 0; BufferFilePos = FilePos; BufferChangeBeg = -1; BufferChangeEnd = -1; if (size && FileSize > FilePos) { if (TrueFileStart) { UseBuffer = false; Seek( FilePos, SEEK_SET ); Read( Buffer, BufferSize ); Seek( FilePos, SEEK_SET ); UseBuffer = true; } else { RawFileClass::Seek( FilePos, SEEK_SET ); RawFileClass::Read( Buffer, BufferSize ); } } else { IsCached = false; } } } } else { Error(EACCES); } size = sizeread; } else { size = RawFileClass::Read(buffer, size); } if (opened) { Close(); } return( size ); } /*********************************************************************************************** * BufferIOFileClass::Seek -- Moves the current file pointer in the file. * * * * This routine will change the current file pointer to the position specified. It follows * * the same rules the a normal Seek() does, but if the file is part of the mixfile system, * * then only the position value needs to be updated. * * * * INPUT: pos -- The position to move the file to relative to the position indicated * * by the "dir" parameter. * * * * dir -- The direction to affect the position change against. This can be * * either SEEK_CUR, SEEK_END, or SEEK_SET. * * * * OUTPUT: Returns with the position of the new location. * * * * WARNINGS: none * * * * HISTORY: * * 11/15/1995 DRD : Created. * *=============================================================================================*/ long BufferIOFileClass::Seek(long pos, int dir) { if (UseBuffer) { bool adjusted = false; switch (dir) { case SEEK_END: FilePos = FileSize; break; case SEEK_SET: FilePos = 0; break; case SEEK_CUR: default: break; } if (TrueFileStart) { if (pos >= TrueFileStart) { pos -= TrueFileStart; adjusted = true; } } FilePos += pos; if (FilePos < 0) { FilePos = 0; } if (FilePos > FileSize ) { FilePos = FileSize; } if (FileSize <= BufferSize) { BufferPos = FilePos; } else { if (FilePos >= BufferFilePos && FilePos < (BufferFilePos + BufferSize) ) { BufferPos = FilePos - BufferFilePos; } else { Commit(); // check!! if (TrueFileStart) { UseBuffer = false; Seek(FilePos, SEEK_SET); UseBuffer = true; } else { RawFileClass::Seek(FilePos, SEEK_SET); } IsCached = false; } } if (TrueFileStart && adjusted) { return( FilePos + TrueFileStart ); } return( FilePos ); } return( RawFileClass::Seek(pos, dir) ); } /*********************************************************************************************** * BufferIOFileClass::Size -- Determines size of file (in bytes). * * * * If part or all of the file is cached, then the size of the file is already * * determined and available. Otherwise, go to the low level system to find the file * * size. * * * * INPUT: none * * * * OUTPUT: Returns with the number of bytes in the file. * * * * WARNINGS: none * * * * HISTORY: * * 11/14/1995 DRD : Created. * *=============================================================================================*/ long BufferIOFileClass::Size(void) { if (IsOpen && UseBuffer) { return( FileSize ); } return( RawFileClass::Size() ); } /*********************************************************************************************** * BufferIOFileClass::Close -- Perform a closure of the file. * * * * Call Commit() to write the buffer if the file is cached and the buffer has changed, * * then call lower level Close(). * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/14/1995 DRD : Created. * *=============================================================================================*/ void BufferIOFileClass::Close(void) { if (UseBuffer) { Commit(); if (IsDiskOpen) { if (TrueFileStart) { UseBuffer = false; Close(); UseBuffer = true; } else { RawFileClass::Close(); } IsDiskOpen = false; } IsOpen = false; } else { RawFileClass::Close(); } }