640 lines
16 KiB
C++
640 lines
16 KiB
C++
/**********************************************************************
|
|
|
|
Filename : GFile_sys.cpp
|
|
Content : GFile wrapper class implementation (Win32)
|
|
|
|
Created : April 5, 1999
|
|
Authors : Michael Antonov
|
|
|
|
History : 8/27/2001 MA Reworked file and directory interface
|
|
|
|
Copyright : (c) 1999-2006 Scaleform Corp. All Rights Reserved.
|
|
|
|
Licensees may use this file in accordance with the valid Scaleform
|
|
Commercial License Agreement provided with the software.
|
|
|
|
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
|
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
|
|
|
|
**********************************************************************/
|
|
|
|
#define GFILE_CXX
|
|
|
|
// Standard C library
|
|
#include <stdio.h>
|
|
|
|
// GKernel
|
|
#include "GFile.h"
|
|
|
|
// ***** GFile interface
|
|
|
|
|
|
// ***** GBufferedFile
|
|
|
|
// Buffered file adds buffering to an existing file
|
|
// FILEBUFFER_SIZE defines the size of internal buffer, while
|
|
// FILEBUFFER_TOLERANCE controls the amount of data we'll effectively try to buffer
|
|
#ifdef GFC_OS_WII
|
|
#define FILEBUFFER_SIZE 8192
|
|
#else
|
|
#define FILEBUFFER_SIZE (8192-8)
|
|
#endif
|
|
#define FILEBUFFER_TOLERANCE 4096
|
|
|
|
// ** Constructor/Destructor
|
|
|
|
// Hidden constructor
|
|
// Not supposed to be used
|
|
GBufferedFile::GBufferedFile() : GDelegatedFile(0)
|
|
{
|
|
// WII OS requires file buffer to be 32-byte aligned.
|
|
pBuffer = (UByte*)GMEMALIGN(FILEBUFFER_SIZE, 32, GStat_Default_Mem);
|
|
BufferMode = NoBuffer;
|
|
FilePos = 0;
|
|
Pos = 0;
|
|
DataSize = 0;
|
|
}
|
|
|
|
// Takes another file as source
|
|
GBufferedFile::GBufferedFile(GFile *pfile) : GDelegatedFile(pfile)
|
|
{
|
|
// WII OS requires file buffer to be 32-byte aligned.
|
|
pBuffer = (UByte*)GMEMALIGN(FILEBUFFER_SIZE, 32, GStat_Default_Mem);
|
|
BufferMode = NoBuffer;
|
|
FilePos = pfile->LTell();
|
|
Pos = 0;
|
|
DataSize = 0;
|
|
}
|
|
|
|
|
|
// Destructor
|
|
GBufferedFile::~GBufferedFile()
|
|
{
|
|
// Flush in case there's data
|
|
if (pFile)
|
|
FlushBuffer();
|
|
// Get rid of buffer
|
|
if (pBuffer)
|
|
GFREE_ALIGN(pBuffer);
|
|
}
|
|
|
|
/*
|
|
bool GBufferedFile::VCopy(const GObject &source)
|
|
{
|
|
if (!GDelegatedFile::VCopy(source))
|
|
return 0;
|
|
|
|
// Data members
|
|
GBufferedFile *psource = (GBufferedFile*)&source;
|
|
|
|
// Buffer & the mode it's in
|
|
pBuffer = psource->pBuffer;
|
|
BufferMode = psource->BufferMode;
|
|
Pos = psource->Pos;
|
|
DataSize = psource->DataSize;
|
|
return 1;
|
|
}
|
|
*/
|
|
|
|
// Initializes buffering to a certain mode
|
|
bool GBufferedFile::SetBufferMode(BufferModeType mode)
|
|
{
|
|
if (!pBuffer)
|
|
return false;
|
|
if (mode == BufferMode)
|
|
return true;
|
|
|
|
FlushBuffer();
|
|
|
|
// Can't set write mode if we can't write
|
|
if ((mode==WriteBuffer) && (!pFile || !pFile->IsWritable()) )
|
|
return 0;
|
|
|
|
// And SetMode
|
|
BufferMode = mode;
|
|
Pos = 0;
|
|
DataSize = 0;
|
|
return 1;
|
|
}
|
|
|
|
// Flushes buffer
|
|
void GBufferedFile::FlushBuffer()
|
|
{
|
|
switch(BufferMode)
|
|
{
|
|
case WriteBuffer:
|
|
// Write data in buffer
|
|
FilePos += pFile->Write(pBuffer,Pos);
|
|
Pos = 0;
|
|
break;
|
|
|
|
case ReadBuffer:
|
|
// Seek back & reset buffer data
|
|
if ((DataSize-Pos)>0)
|
|
FilePos = pFile->LSeek(-(SInt)(DataSize-Pos), Seek_Cur);
|
|
DataSize = 0;
|
|
Pos = 0;
|
|
break;
|
|
default:
|
|
// not handled!
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reloads data for ReadBuffer
|
|
void GBufferedFile::LoadBuffer()
|
|
{
|
|
if (BufferMode == ReadBuffer)
|
|
{
|
|
// We should only reload once all of pre-loaded buffer is consumed.
|
|
GASSERT(Pos == DataSize);
|
|
|
|
// WARNING: Right now LoadBuffer() assumes the buffer's empty
|
|
SInt sz = pFile->Read(pBuffer,FILEBUFFER_SIZE);
|
|
DataSize = sz<0 ? 0 : (UInt)sz;
|
|
Pos = 0;
|
|
FilePos += DataSize;
|
|
}
|
|
}
|
|
|
|
|
|
// ** Overridden functions
|
|
|
|
// We override all the functions that can possibly
|
|
// require buffer mode switch, flush, or extra calculations
|
|
|
|
// Tell() requires buffer adjustment
|
|
SInt GBufferedFile::Tell()
|
|
{
|
|
if (BufferMode == ReadBuffer)
|
|
return SInt(FilePos - DataSize + Pos);
|
|
|
|
SInt pos = pFile->Tell();
|
|
// Adjust position based on buffer mode & data
|
|
if (pos!=-1)
|
|
{
|
|
GASSERT(BufferMode != ReadBuffer);
|
|
if (BufferMode == WriteBuffer)
|
|
pos += Pos;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
SInt64 GBufferedFile::LTell()
|
|
{
|
|
if (BufferMode == ReadBuffer)
|
|
return FilePos - DataSize + Pos;
|
|
|
|
SInt64 pos = pFile->LTell();
|
|
if (pos!=-1)
|
|
{
|
|
GASSERT(BufferMode != ReadBuffer);
|
|
if (BufferMode == WriteBuffer)
|
|
pos += Pos;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
SInt GBufferedFile::GetLength()
|
|
{
|
|
SInt len = pFile->GetLength();
|
|
// If writing through buffer, file length may actually be bigger
|
|
if ((len!=-1) && (BufferMode==WriteBuffer))
|
|
{
|
|
SInt currPos = pFile->Tell() + Pos;
|
|
if (currPos>len)
|
|
len = currPos;
|
|
}
|
|
return len;
|
|
}
|
|
SInt64 GBufferedFile::LGetLength()
|
|
{
|
|
SInt64 len = pFile->LGetLength();
|
|
// If writing through buffer, file length may actually be bigger
|
|
if ((len!=-1) && (BufferMode==WriteBuffer))
|
|
{
|
|
SInt64 currPos = pFile->LTell() + Pos;
|
|
if (currPos>len)
|
|
len = currPos;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
bool GBufferedFile::Stat(GFileStats *pfs)
|
|
{
|
|
// Have to fix up length is stat
|
|
if (pFile->Stat(pfs))
|
|
{
|
|
if (BufferMode==WriteBuffer)
|
|
{
|
|
SInt64 currPos = pFile->LTell() + Pos;
|
|
if (currPos > pfs->Size)
|
|
{
|
|
pfs->Size = currPos;
|
|
// ??
|
|
pfs->Blocks = (pfs->Size+511) >> 9;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
SInt GBufferedFile::Write(const UByte *psourceBuffer, SInt numBytes)
|
|
{
|
|
if ( (BufferMode==WriteBuffer) || SetBufferMode(WriteBuffer))
|
|
{
|
|
// If not data space in buffer, flush
|
|
if ((FILEBUFFER_SIZE-(SInt)Pos)<numBytes)
|
|
{
|
|
FlushBuffer();
|
|
// If bigger then tolerance, just write directly
|
|
if (numBytes>FILEBUFFER_TOLERANCE)
|
|
{
|
|
SInt sz = pFile->Write(psourceBuffer,numBytes);
|
|
if (sz > 0)
|
|
FilePos += sz;
|
|
return sz;
|
|
}
|
|
}
|
|
|
|
// Enough space in buffer.. so copy to it
|
|
memcpy(pBuffer+Pos, psourceBuffer, numBytes);
|
|
Pos += numBytes;
|
|
return numBytes;
|
|
}
|
|
SInt sz = pFile->Write(psourceBuffer,numBytes);
|
|
if (sz > 0)
|
|
FilePos += sz;
|
|
return sz;
|
|
}
|
|
|
|
SInt GBufferedFile::Read(UByte *pdestBuffer, SInt numBytes)
|
|
{
|
|
if ( (BufferMode==ReadBuffer) || SetBufferMode(ReadBuffer))
|
|
{
|
|
#ifdef GFC_OS_WII
|
|
SInt totalRead = 0;
|
|
while (numBytes > 0)
|
|
{
|
|
// Data in buffer... copy it
|
|
if (((SInt)DataSize-(SInt)Pos)>=numBytes)
|
|
{
|
|
memcpy(pdestBuffer, pBuffer+Pos, numBytes);
|
|
Pos += numBytes;
|
|
return totalRead + numBytes;
|
|
}
|
|
|
|
// Not enough data in buffer, copy buffer
|
|
SInt readBytes = DataSize-Pos;
|
|
memcpy(pdestBuffer, pBuffer+Pos, readBytes);
|
|
numBytes -= readBytes;
|
|
pdestBuffer += readBytes;
|
|
totalRead += readBytes;
|
|
Pos = DataSize;
|
|
|
|
LoadBuffer();
|
|
if (FilePos >= GetLength() && Pos == DataSize)
|
|
return totalRead;
|
|
}
|
|
return totalRead;
|
|
#else
|
|
|
|
// Data in buffer... copy it
|
|
if ((SInt)(DataSize-Pos) >= numBytes)
|
|
{
|
|
memcpy(pdestBuffer, pBuffer+Pos, numBytes);
|
|
Pos += numBytes;
|
|
return numBytes;
|
|
}
|
|
|
|
// Not enough data in buffer, copy buffer
|
|
SInt readBytes = DataSize-Pos;
|
|
memcpy(pdestBuffer, pBuffer+Pos, readBytes);
|
|
numBytes -= readBytes;
|
|
pdestBuffer += readBytes;
|
|
Pos = DataSize;
|
|
|
|
// Don't reload buffer if more then tolerance
|
|
// (No major advantage, and we don't want to write a loop)
|
|
if (numBytes>FILEBUFFER_TOLERANCE)
|
|
{
|
|
numBytes = pFile->Read(pdestBuffer,numBytes);
|
|
if (numBytes > 0)
|
|
{
|
|
FilePos += numBytes;
|
|
Pos = DataSize = 0;
|
|
}
|
|
return readBytes + ((numBytes==-1) ? 0 : numBytes);
|
|
}
|
|
|
|
// Reload the buffer
|
|
// WARNING: Right now LoadBuffer() assumes the buffer's empty
|
|
LoadBuffer();
|
|
if ((SInt)(DataSize-Pos) < numBytes)
|
|
numBytes = (SInt)DataSize-Pos;
|
|
|
|
memcpy(pdestBuffer, pBuffer+Pos, numBytes);
|
|
Pos += numBytes;
|
|
return numBytes + readBytes;
|
|
|
|
/*
|
|
// Alternative Read implementation. The one above is probably better
|
|
// due to FILEBUFFER_TOLERANCE.
|
|
SInt total = 0;
|
|
|
|
do {
|
|
SInt bufferBytes = (SInt)(DataSize-Pos);
|
|
SInt copyBytes = (bufferBytes > numBytes) ? numBytes : bufferBytes;
|
|
|
|
memcpy(pdestBuffer, pBuffer+Pos, copyBytes);
|
|
numBytes -= copyBytes;
|
|
pdestBuffer += copyBytes;
|
|
Pos += copyBytes;
|
|
total += copyBytes;
|
|
|
|
if (numBytes == 0)
|
|
break;
|
|
LoadBuffer();
|
|
|
|
} while (DataSize > 0);
|
|
|
|
return total;
|
|
*/
|
|
|
|
#endif
|
|
}
|
|
SInt sz = pFile->Read(pdestBuffer,numBytes);
|
|
if (sz > 0)
|
|
FilePos += sz;
|
|
return sz;
|
|
}
|
|
|
|
|
|
SInt GBufferedFile::SkipBytes(SInt numBytes)
|
|
{
|
|
SInt skippedBytes = 0;
|
|
|
|
// Special case for skipping a little data in read buffer
|
|
if (BufferMode==ReadBuffer)
|
|
{
|
|
skippedBytes = (((SInt)DataSize-(SInt)Pos) >= numBytes) ? numBytes : (DataSize-Pos);
|
|
Pos += skippedBytes;
|
|
numBytes -= skippedBytes;
|
|
}
|
|
|
|
if (numBytes)
|
|
{
|
|
numBytes = pFile->SkipBytes(numBytes);
|
|
// Make sure we return the actual number skipped, or error
|
|
if (numBytes!=-1)
|
|
{
|
|
skippedBytes += numBytes;
|
|
FilePos += numBytes;
|
|
Pos = DataSize = 0;
|
|
}
|
|
else if (skippedBytes <= 0)
|
|
skippedBytes = -1;
|
|
}
|
|
return skippedBytes;
|
|
}
|
|
|
|
SInt GBufferedFile::BytesAvailable()
|
|
{
|
|
SInt available = pFile->BytesAvailable();
|
|
// Adjust available size based on buffers
|
|
switch(BufferMode)
|
|
{
|
|
case ReadBuffer:
|
|
available += DataSize-Pos;
|
|
break;
|
|
case WriteBuffer:
|
|
available -= Pos;
|
|
if (available<0)
|
|
available= 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return available;
|
|
}
|
|
|
|
bool GBufferedFile::Flush()
|
|
{
|
|
FlushBuffer();
|
|
return pFile->Flush();
|
|
}
|
|
|
|
// Seeking could be optimized better..
|
|
SInt GBufferedFile::Seek(SInt offset, SInt origin)
|
|
{
|
|
if (BufferMode == ReadBuffer)
|
|
{
|
|
if (origin == Seek_Cur)
|
|
{
|
|
// Seek can fall either before or after Pos in the buffer,
|
|
// but it must be within bounds.
|
|
if (((UInt(offset) + Pos)) <= DataSize)
|
|
{
|
|
Pos += offset;
|
|
return SInt(FilePos - DataSize + Pos);
|
|
}
|
|
|
|
// Lightweight buffer "Flush". We do this to avoid an extra seek
|
|
// back operation which would take place if we called FlushBuffer directly.
|
|
origin = Seek_Set;
|
|
GASSERT(((FilePos - DataSize + Pos) + (UInt64)offset) < ~(UInt64)0);
|
|
offset = (SInt)(FilePos - DataSize + Pos) + offset;
|
|
Pos = DataSize = 0;
|
|
}
|
|
else if (origin == Seek_Set)
|
|
{
|
|
if (((UInt)offset - (FilePos-DataSize)) <= DataSize)
|
|
{
|
|
GASSERT((FilePos-DataSize) < ~(UInt64)0);
|
|
Pos = (UInt)offset - (UInt)(FilePos-DataSize);
|
|
return offset;
|
|
}
|
|
Pos = DataSize = 0;
|
|
}
|
|
else
|
|
{
|
|
FlushBuffer();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FlushBuffer();
|
|
}
|
|
|
|
/*
|
|
// Old Seek Logic
|
|
if (origin == Seek_Cur && offset + Pos < DataSize)
|
|
{
|
|
//GASSERT((FilePos - DataSize) >= (FilePos - DataSize + Pos + offset));
|
|
Pos += offset;
|
|
GASSERT(SInt(Pos) >= 0);
|
|
return SInt(FilePos - DataSize + Pos);
|
|
}
|
|
else if (origin == Seek_Set && UInt(offset) >= FilePos - DataSize && UInt(offset) < FilePos)
|
|
{
|
|
Pos = UInt(offset - FilePos + DataSize);
|
|
GASSERT(SInt(Pos) >= 0);
|
|
return SInt(FilePos - DataSize + Pos);
|
|
}
|
|
|
|
FlushBuffer();
|
|
*/
|
|
|
|
|
|
FilePos = pFile->Seek(offset,origin);
|
|
return SInt(FilePos);
|
|
}
|
|
|
|
SInt64 GBufferedFile::LSeek(SInt64 offset, SInt origin)
|
|
{
|
|
if (BufferMode == ReadBuffer)
|
|
{
|
|
if (origin == Seek_Cur)
|
|
{
|
|
// Seek can fall either before or after Pos in the buffer,
|
|
// but it must be within bounds.
|
|
if (((UInt(offset) + Pos)) <= DataSize)
|
|
{
|
|
Pos += (UInt)offset;
|
|
return SInt64(FilePos - DataSize + Pos);
|
|
}
|
|
|
|
// Lightweight buffer "Flush". We do this to avoid an extra seek
|
|
// back operation which would take place if we called FlushBuffer directly.
|
|
origin = Seek_Set;
|
|
offset = (SInt64)(FilePos - DataSize + Pos) + offset;
|
|
Pos = DataSize = 0;
|
|
}
|
|
else if (origin == Seek_Set)
|
|
{
|
|
if (((UInt64)offset - (FilePos-DataSize)) <= DataSize)
|
|
{
|
|
Pos = (UInt)((UInt64)offset - (FilePos-DataSize));
|
|
return offset;
|
|
}
|
|
Pos = DataSize = 0;
|
|
}
|
|
else
|
|
{
|
|
FlushBuffer();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FlushBuffer();
|
|
}
|
|
|
|
/*
|
|
GASSERT(BufferMode != NoBuffer);
|
|
|
|
if (origin == Seek_Cur && offset + Pos < DataSize)
|
|
{
|
|
Pos += SInt(offset);
|
|
return FilePos - DataSize + Pos;
|
|
}
|
|
else if (origin == Seek_Set && offset >= SInt64(FilePos - DataSize) && offset < SInt64(FilePos))
|
|
{
|
|
Pos = UInt(offset - FilePos + DataSize);
|
|
return FilePos - DataSize + Pos;
|
|
}
|
|
|
|
FlushBuffer();
|
|
*/
|
|
|
|
FilePos = pFile->LSeek(offset,origin);
|
|
return FilePos;
|
|
}
|
|
|
|
bool GBufferedFile::ChangeSize(SInt newSize)
|
|
{
|
|
FlushBuffer();
|
|
return pFile->ChangeSize(newSize);
|
|
}
|
|
|
|
SInt GBufferedFile::CopyFromStream(GFile *pstream, SInt byteSize)
|
|
{
|
|
// We can't rely on overridden Write()
|
|
// because delegation doesn't override virtual pointers
|
|
// So, just re-implement
|
|
UByte buff[0x4000];
|
|
SInt count = 0;
|
|
SInt szRequest, szRead, szWritten;
|
|
|
|
while(byteSize)
|
|
{
|
|
szRequest = (byteSize > SInt(sizeof(buff))) ? SInt(sizeof(buff)) : byteSize;
|
|
|
|
szRead = pstream->Read(buff,szRequest);
|
|
szWritten = 0;
|
|
if (szRead > 0)
|
|
szWritten = Write(buff,szRead);
|
|
|
|
count +=szWritten;
|
|
byteSize-=szWritten;
|
|
if (szWritten < szRequest)
|
|
break;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// Closing files
|
|
bool GBufferedFile::Close()
|
|
{
|
|
switch(BufferMode)
|
|
{
|
|
case WriteBuffer:
|
|
FlushBuffer();
|
|
break;
|
|
case ReadBuffer:
|
|
// No need to seek back on close
|
|
BufferMode = NoBuffer;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return pFile->Close();
|
|
}
|
|
|
|
/*
|
|
bool GBufferedFile::CloseCancel()
|
|
{
|
|
// If operation's being canceled
|
|
// There's not need to flush buffers at all
|
|
BufferMode = NoBuffer;
|
|
return pFile->CloseCancel();
|
|
}
|
|
*/
|
|
|
|
// ***** Global path helpers
|
|
|
|
// Find trailing short filename in a path.
|
|
const char* G_GetShortFilename(const char* purl)
|
|
{
|
|
// RAGE hack - if the url starts with "memory:" skip up to after the first _
|
|
if (!strncmp(purl, "memory:", 7))
|
|
{
|
|
const char* realNameStart = strchr(purl, '_');
|
|
if (realNameStart)
|
|
{
|
|
return realNameStart+1;
|
|
}
|
|
}
|
|
|
|
UPInt len = G_strlen(purl);
|
|
for (UPInt i=len; i>0; i--)
|
|
if (purl[i]=='\\' || purl[i]=='/')
|
|
return purl+i+1;
|
|
return purl;
|
|
}
|
|
|
|
|
|
|