398 lines
12 KiB
C++
398 lines
12 KiB
C++
/*
|
|
* HLLib
|
|
* Copyright (C) 2006-2010 Ryan Gregg
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later
|
|
* version.
|
|
*/
|
|
|
|
#include "HLLib.h"
|
|
#include "GCFStream.h"
|
|
|
|
using namespace HLLib;
|
|
using namespace HLLib::Streams;
|
|
|
|
CGCFStream::CGCFStream(const CGCFFile &GCFFile, hlUInt uiFileID) : bOpened(hlFalse), uiMode(HL_MODE_INVALID), GCFFile(GCFFile), uiFileID(uiFileID), pView(0), uiPointer(0), uiLength(0)
|
|
{
|
|
|
|
}
|
|
|
|
CGCFStream::~CGCFStream()
|
|
{
|
|
this->Close();
|
|
}
|
|
|
|
HLStreamType CGCFStream::GetType() const
|
|
{
|
|
return HL_STREAM_GCF;
|
|
}
|
|
|
|
const CGCFFile &CGCFStream::GetPackage() const
|
|
{
|
|
return this->GCFFile;
|
|
}
|
|
|
|
const hlChar *CGCFStream::GetFileName() const
|
|
{
|
|
return this->GCFFile.lpDirectoryNames + this->GCFFile.lpDirectoryEntries[this->uiFileID].uiNameOffset;
|
|
}
|
|
|
|
hlBool CGCFStream::GetOpened() const
|
|
{
|
|
return this->bOpened;
|
|
}
|
|
|
|
hlUInt CGCFStream::GetMode() const
|
|
{
|
|
return this->uiMode;
|
|
}
|
|
|
|
hlBool CGCFStream::Open(hlUInt uiMode)
|
|
{
|
|
this->Close();
|
|
|
|
if(!this->GCFFile.GetOpened())
|
|
{
|
|
LastError.SetErrorMessage("GCF file not opened.");
|
|
return hlFalse;
|
|
}
|
|
|
|
if((uiMode & (HL_MODE_READ | HL_MODE_WRITE)) == 0)
|
|
{
|
|
LastError.SetErrorMessageFormated("Invalid open mode (%#.8x).", uiMode);
|
|
return hlFalse;
|
|
}
|
|
|
|
if((uiMode & HL_MODE_READ) != 0 && (this->GCFFile.pMapping->GetMode() & HL_MODE_READ) == 0)
|
|
{
|
|
LastError.SetErrorMessage("GCF file does not have read permissions.");
|
|
return hlFalse;
|
|
}
|
|
|
|
if((uiMode & HL_MODE_WRITE) != 0 && (this->GCFFile.pMapping->GetMode() & HL_MODE_WRITE) == 0)
|
|
{
|
|
LastError.SetErrorMessage("GCF file does not have write permissions.");
|
|
return hlFalse;
|
|
}
|
|
|
|
this->uiPointer = 0;
|
|
this->uiLength = (uiMode & HL_MODE_READ) ? this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize : 0;
|
|
|
|
this->bOpened = hlTrue;
|
|
this->uiMode = uiMode;
|
|
|
|
this->uiBlockEntryIndex = this->GCFFile.lpDirectoryMapEntries[this->uiFileID].uiFirstBlockIndex;
|
|
this->uiBlockEntryOffset = 0;
|
|
this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
|
|
this->uiDataBlockOffset = 0;
|
|
|
|
return hlTrue;
|
|
}
|
|
|
|
hlVoid CGCFStream::Close()
|
|
{
|
|
this->bOpened = hlFalse;
|
|
this->uiMode = HL_MODE_INVALID;
|
|
|
|
this->GCFFile.pMapping->Unmap(this->pView);
|
|
|
|
this->uiPointer = 0;
|
|
this->uiLength = 0;
|
|
}
|
|
|
|
hlULongLong CGCFStream::GetStreamSize() const
|
|
{
|
|
return this->uiLength;
|
|
}
|
|
|
|
hlULongLong CGCFStream::GetStreamPointer() const
|
|
{
|
|
return this->uiPointer;
|
|
}
|
|
|
|
hlULongLong CGCFStream::Seek(hlLongLong iOffset, HLSeekMode eSeekMode)
|
|
{
|
|
if(!this->bOpened)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
switch(eSeekMode)
|
|
{
|
|
case HL_SEEK_BEGINNING:
|
|
this->uiPointer = 0;
|
|
break;
|
|
case HL_SEEK_CURRENT:
|
|
|
|
break;
|
|
case HL_SEEK_END:
|
|
this->uiPointer = this->uiLength;
|
|
break;
|
|
}
|
|
|
|
hlLongLong iPointer = static_cast<hlLongLong>(this->uiPointer) + iOffset;
|
|
|
|
if(iPointer < 0)
|
|
{
|
|
iPointer = 0;
|
|
}
|
|
else if(iPointer > static_cast<hlLongLong>(this->uiLength))
|
|
{
|
|
iPointer = static_cast<hlLongLong>(this->uiLength);
|
|
}
|
|
|
|
this->uiPointer = static_cast<hlULongLong>(iPointer);
|
|
|
|
return this->uiPointer;
|
|
}
|
|
|
|
hlBool CGCFStream::Read(hlChar &cChar)
|
|
{
|
|
if(!this->bOpened)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if((this->uiMode & HL_MODE_READ) == 0)
|
|
{
|
|
LastError.SetErrorMessage("Stream not in read mode.");
|
|
return 0;
|
|
}
|
|
|
|
if(this->uiPointer < this->uiLength)
|
|
{
|
|
if(!this->Map(this->uiPointer))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
|
|
hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
|
|
|
|
if(uiViewBytes >= 1)
|
|
{
|
|
cChar = *(static_cast<const hlChar *>(this->pView->GetView()) + uiViewPointer);
|
|
this->uiPointer++;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
hlUInt CGCFStream::Read(hlVoid *lpData, hlUInt uiBytes)
|
|
{
|
|
if(!this->bOpened)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if((this->uiMode & HL_MODE_READ) == 0)
|
|
{
|
|
LastError.SetErrorMessage("Stream not in read mode.");
|
|
return 0;
|
|
}
|
|
|
|
if(this->uiPointer == this->uiLength)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
hlULongLong uiOffset = 0;
|
|
while(uiBytes && this->uiPointer < this->uiLength)
|
|
{
|
|
if(!this->Map(this->uiPointer))
|
|
{
|
|
break;
|
|
}
|
|
|
|
hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
|
|
hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
|
|
|
|
if(uiViewBytes >= static_cast<hlULongLong>(uiBytes))
|
|
{
|
|
memcpy(static_cast<hlByte *>(lpData) + uiOffset, static_cast<const hlByte *>(this->pView->GetView()) + uiViewPointer, uiBytes);
|
|
this->uiPointer += static_cast<hlULongLong>(uiBytes);
|
|
uiOffset += uiBytes;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
memcpy(static_cast<hlByte *>(lpData) + uiOffset, static_cast<const hlByte *>(this->pView->GetView()) + uiViewPointer, static_cast<size_t>(uiViewBytes));
|
|
this->uiPointer += uiViewBytes;
|
|
uiOffset += uiViewBytes;
|
|
uiBytes -= static_cast<hlUInt>(uiViewBytes);
|
|
}
|
|
}
|
|
|
|
return static_cast<hlUInt>(uiOffset);
|
|
}
|
|
}
|
|
|
|
hlBool CGCFStream::Write(hlChar cChar)
|
|
{
|
|
if(!this->bOpened)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if((this->uiMode & HL_MODE_WRITE) == 0)
|
|
{
|
|
LastError.SetErrorMessage("Stream not in write mode.");
|
|
return 0;
|
|
}
|
|
|
|
if(this->uiPointer < this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
|
|
{
|
|
if(!this->Map(this->uiPointer))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
|
|
hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
|
|
|
|
if(uiViewBytes >= 1)
|
|
{
|
|
*(static_cast<hlChar *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer) = cChar;
|
|
this->uiPointer++;
|
|
|
|
if(this->uiPointer > this->uiLength)
|
|
{
|
|
this->uiLength = this->uiPointer;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
hlUInt CGCFStream::Write(const hlVoid *lpData, hlUInt uiBytes)
|
|
{
|
|
if(!this->bOpened)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if((this->uiMode & HL_MODE_WRITE) == 0)
|
|
{
|
|
LastError.SetErrorMessage("Stream not in write mode.");
|
|
return 0;
|
|
}
|
|
|
|
if(this->uiPointer == this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
hlULongLong uiOffset = 0;
|
|
while(uiBytes && this->uiPointer < this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
|
|
{
|
|
if(!this->Map(this->uiPointer))
|
|
{
|
|
break;
|
|
}
|
|
|
|
hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
|
|
hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
|
|
|
|
if(uiViewBytes >= uiBytes)
|
|
{
|
|
memcpy(static_cast<hlByte *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer, static_cast<const hlByte *>(lpData) + uiOffset, uiBytes);
|
|
this->uiPointer += static_cast<hlULongLong>(uiBytes);
|
|
uiOffset += uiBytes;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
memcpy(static_cast<hlByte *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer, static_cast<const hlByte *>(lpData) + uiOffset, static_cast<size_t>(uiViewBytes));
|
|
this->uiPointer += uiViewBytes;
|
|
uiOffset += uiViewBytes;
|
|
uiBytes -= static_cast<hlUInt>(uiViewBytes);
|
|
}
|
|
}
|
|
|
|
if(this->uiPointer > this->uiLength)
|
|
{
|
|
this->uiLength = this->uiPointer;
|
|
}
|
|
|
|
return static_cast<hlUInt>(uiOffset);
|
|
}
|
|
}
|
|
|
|
hlBool CGCFStream::Map(hlULongLong uiPointer)
|
|
{
|
|
if(uiPointer < this->uiBlockEntryOffset + this->uiDataBlockOffset)
|
|
{
|
|
this->uiBlockEntryIndex = this->GCFFile.lpDirectoryMapEntries[this->uiFileID].uiFirstBlockIndex;
|
|
this->uiBlockEntryOffset = 0;
|
|
this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
|
|
this->uiDataBlockOffset = 0;
|
|
}
|
|
|
|
hlULongLong uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize - this->uiDataBlockOffset : this->GCFFile.pDataBlockHeader->uiBlockSize;
|
|
//hlUInt uiDataBlockTerminator = this->pDataBlockHeader->uiBlockCount >= 0x0000ffff ? 0xffffffff : 0x0000ffff;
|
|
hlUInt uiDataBlockTerminator = this->GCFFile.pFragmentationMapHeader->uiTerminator == 0 ? 0x0000ffff : 0xffffffff;
|
|
|
|
while((uiPointer >= this->uiBlockEntryOffset + this->uiDataBlockOffset + uiLength) && (this->uiBlockEntryIndex != this->GCFFile.pDataBlockHeader->uiBlockCount))
|
|
{
|
|
// Loop through each data block fragment.
|
|
while((uiPointer >= this->uiBlockEntryOffset + this->uiDataBlockOffset + uiLength) && (this->uiDataBlockIndex < uiDataBlockTerminator && this->uiDataBlockOffset < this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize))
|
|
{
|
|
// Get the next data block fragment.
|
|
this->uiDataBlockIndex = this->GCFFile.lpFragmentationMap[this->uiDataBlockIndex].uiNextDataBlockIndex;
|
|
this->uiDataBlockOffset += static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
|
|
|
|
uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize) - this->uiDataBlockOffset : static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
|
|
}
|
|
|
|
if(this->uiDataBlockOffset >= static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize))
|
|
{
|
|
// Get the next data block.
|
|
this->uiBlockEntryOffset += static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize);
|
|
this->uiBlockEntryIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiNextBlockEntryIndex;
|
|
|
|
this->uiDataBlockOffset = 0;
|
|
if(this->uiBlockEntryIndex != this->GCFFile.pDataBlockHeader->uiBlockCount)
|
|
{
|
|
this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
|
|
}
|
|
|
|
uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize) - this->uiDataBlockOffset : static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
|
|
}
|
|
}
|
|
|
|
if(this->uiBlockEntryIndex == this->GCFFile.pDataBlockHeader->uiBlockCount || this->uiDataBlockIndex >= uiDataBlockTerminator)
|
|
{
|
|
if(this->uiBlockEntryOffset + this->uiDataBlockOffset < static_cast<hlULongLong>(this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize))
|
|
{
|
|
#ifdef _WIN32
|
|
LastError.SetErrorMessageFormated("Unexpected end of GCF stream (%I64u B of %u B). Has the GCF file been completely acquired?", this->uiBlockEntryOffset + this->uiDataBlockOffset, this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize);
|
|
#else
|
|
LastError.SetErrorMessageFormated("Unexpected end of GCF stream (%llu B of %u B). Has the GCF file been completely acquired?", this->uiBlockEntryOffset + this->uiDataBlockOffset, this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize);
|
|
#endif
|
|
}
|
|
|
|
this->GCFFile.pMapping->Unmap(this->pView);
|
|
return hlFalse;
|
|
}
|
|
|
|
if(this->pView)
|
|
{
|
|
if(this->pView->GetAllocationOffset() == this->GCFFile.pDataBlockHeader->uiFirstBlockOffset + this->uiDataBlockIndex * this->GCFFile.pDataBlockHeader->uiBlockSize)
|
|
{
|
|
return hlTrue;
|
|
}
|
|
}
|
|
|
|
return this->GCFFile.pMapping->Map(this->pView, static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiFirstBlockOffset) + static_cast<hlULongLong>(this->uiDataBlockIndex) * static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize), uiLength);
|
|
}
|