HLLib/HLLib/GCFFile.cpp

1050 lines
37 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 "GCFFile.h"
#include "Streams.h"
#include "Checksum.h"
using namespace HLLib;
#define HL_GCF_FLAG_FILE 0x00004000 // The item is a file.
#define HL_GCF_FLAG_ENCRYPTED 0x00000100 // The item is encrypted.
#define HL_GCF_FLAG_BACKUP_LOCAL 0x00000040 // Backup the item before overwriting it.
#define HL_GCF_FLAG_COPY_LOCAL 0x0000000a // The item is to be copied to the disk.
#define HL_GCF_FLAG_COPY_LOCAL_NO_OVERWRITE 0x00000001 // Don't overwrite the item if copying it to the disk and the item already exists.
#define HL_GCF_CHECKSUM_LENGTH 0x00008000 // The maximum data allowed in a 32 bit checksum.
const char *CGCFFile::lpAttributeNames[] = { "Version", "Cache ID", "Allocated Blocks", "Used Blocks", "Block Length", "Last Version Played" };
const char *CGCFFile::lpItemAttributeNames[] = { "Encrypted", "Copy Locally", "Overwrite Local Copy", "Backup Local Copy", "Flags", "Fragmentation" };
CGCFFile::CGCFFile() : CPackage(), pHeaderView(0)
{
this->pHeader = 0;
this->pBlockEntryHeader = 0;
this->lpBlockEntries = 0;
this->pFragmentationMapHeader = 0;
this->lpFragmentationMap = 0;
this->pBlockEntryMapHeader = 0;
this->lpBlockEntryMap = 0;
this->pDirectoryHeader = 0;
this->lpDirectoryEntries = 0;
this->lpDirectoryNames = 0;
this->lpDirectoryInfo1Entries = 0;
this->lpDirectoryInfo2Entries = 0;
this->lpDirectoryCopyEntries = 0;
this->lpDirectoryLocalEntries = 0;
this->pDirectoryMapHeader = 0;
this->lpDirectoryMapEntries = 0;
this->pChecksumHeader = 0;
this->pChecksumMapHeader = 0;
this->lpChecksumMapEntries = 0;
this->lpChecksumEntries = 0;
this->pDataBlockHeader = 0;
this->lpDirectoryItems = 0;
}
CGCFFile::~CGCFFile()
{
this->Close();
}
HLPackageType CGCFFile::GetType() const
{
return HL_PACKAGE_GCF;
}
const hlChar *CGCFFile::GetExtension() const
{
return "gcf";
}
const hlChar *CGCFFile::GetDescription() const
{
return "Half-Life Game Cache File";
}
hlBool CGCFFile::MapDataStructures()
{
//
// Determine the size of the header and validate it.
//
hlUInt uiVersion = 0;
hlUInt uiHeaderSize = 0;
// Header.
if(sizeof(GCFHeader) > this->pMapping->GetMappingSize())
{
LastError.SetErrorMessage("Invalid file: the file map is too small for it's header.");
return hlFalse;
}
if(!this->pMapping->Map(this->pHeaderView, 0, sizeof(GCFHeader)))
{
return hlFalse;
}
this->pHeader = (GCFHeader *)this->pHeaderView->GetView();
hlBool bNull = hlTrue;
for(hlByte *pTest = (hlByte *)this->pHeader; pTest < (hlByte *)this->pHeader + sizeof(GCFHeader); pTest++)
{
if(*pTest != 0)
{
bNull = hlFalse;
break;
}
}
if(bNull)
{
LastError.SetErrorMessage("Invalid file: the file's header is null (contains no data).");
return hlFalse;
}
if(this->pHeader->uiMajorVersion != 1 || (this->pHeader->uiMinorVersion != 3 && this->pHeader->uiMinorVersion != 5 && this->pHeader->uiMinorVersion != 6))
{
LastError.SetErrorMessageFormated("Invalid GCF version (v%u): you have a version of a GCF file that HLLib does not know how to read. Check for product updates.", this->pHeader->uiMinorVersion);
return hlFalse;
}
uiVersion = this->pHeader->uiMinorVersion;
uiHeaderSize += sizeof(GCFHeader);
// Block entries.
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFBlockEntryHeader)))
{
return hlFalse;
}
this->pBlockEntryHeader = (GCFBlockEntryHeader *)this->pHeaderView->GetView();
uiHeaderSize += sizeof(GCFBlockEntryHeader) + this->pBlockEntryHeader->uiBlockCount * sizeof(GCFBlockEntry);
// Fragmentation map.
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFFragmentationMapHeader)))
{
return hlFalse;
}
this->pFragmentationMapHeader = (GCFFragmentationMapHeader *)this->pHeaderView->GetView();
uiHeaderSize += sizeof(GCFFragmentationMapHeader) + this->pFragmentationMapHeader->uiBlockCount * sizeof(GCFFragmentationMap);
// Block entry map.
if(uiVersion < 6)
{
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFBlockEntryMapHeader)))
{
return hlFalse;
}
this->pBlockEntryMapHeader = (GCFBlockEntryMapHeader *)this->pHeaderView->GetView();
uiHeaderSize += sizeof(GCFBlockEntryMapHeader) + this->pBlockEntryMapHeader->uiBlockCount * sizeof(GCFBlockEntryMap);
}
// Directory.
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFDirectoryHeader)))
{
return hlFalse;
}
this->pDirectoryHeader = (GCFDirectoryHeader *)this->pHeaderView->GetView();
uiHeaderSize += this->pDirectoryHeader->uiDirectorySize;/*sizeof(GCFDirectoryHeader);
+ this->pDirectoryHeader->uiItemCount * sizeof(GCFDirectoryEntry)
+ this->pDirectoryHeader->uiNameSize
+ this->pDirectoryHeader->uiInfo1Count * sizeof(GCFDirectoryInfo1Entry)
+ this->pDirectoryHeader->uiItemCount * sizeof(GCFDirectoryInfo2Entry)
+ this->pDirectoryHeader->uiCopyCount * sizeof(GCFDirectoryCopyEntry)
+ this->pDirectoryHeader->uiLocalCount * sizeof(GCFDirectoryLocalEntry);*/
if(uiVersion >= 5)
{
uiHeaderSize += sizeof(GCFDirectoryMapHeader);
}
uiHeaderSize += this->pDirectoryHeader->uiItemCount * sizeof(GCFDirectoryMapEntry);
// Checksums.
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFChecksumHeader)))
{
return hlFalse;
}
this->pChecksumHeader = (GCFChecksumHeader *)this->pHeaderView->GetView();
uiHeaderSize += sizeof(GCFChecksumHeader) + this->pChecksumHeader->uiChecksumSize;
// Data blocks.
if(!this->pMapping->Map(this->pHeaderView, uiHeaderSize, sizeof(GCFDataBlockHeader)))
{
return hlFalse;
}
this->pDataBlockHeader = (GCFDataBlockHeader *)this->pHeaderView->GetView();
// It seems that some GCF files may have allocated only the data blocks that are used. Extraction,
// validation and defragmentation should fail cleanly if an unallocated data block is indexed so
// leave this check out for now.
/*if(this->pDataBlockHeader->uiFirstBlockOffset + this->pDataBlockHeader->uiBlocksUsed * this->pDataBlockHeader->uiBlockSize > this->pMapping->GetMappingSize())
{
LastError.SetErrorMessage("Invalid file: the file map is too small for it's data blocks.");
return hlFalse;
}*/
if(uiVersion < 5)
{
// See note below.
uiHeaderSize += sizeof(GCFDataBlockHeader) - sizeof(hlUInt);
}
else
{
uiHeaderSize += sizeof(GCFDataBlockHeader);
}
//
// Map the header.
//
if(!this->pMapping->Map(this->pHeaderView, 0, uiHeaderSize))
{
return hlFalse;
}
this->pHeader = (GCFHeader *)this->pHeaderView->GetView();
this->pBlockEntryHeader = (GCFBlockEntryHeader *)((hlByte *)this->pHeader + sizeof(GCFHeader));
this->lpBlockEntries = (GCFBlockEntry *)((hlByte *)this->pBlockEntryHeader + sizeof(GCFBlockEntryHeader));
this->pFragmentationMapHeader = (GCFFragmentationMapHeader *)((hlByte *)this->lpBlockEntries + sizeof(GCFBlockEntry) * this->pBlockEntryHeader->uiBlockCount);
this->lpFragmentationMap = (GCFFragmentationMap *)((hlByte *)this->pFragmentationMapHeader + sizeof(GCFFragmentationMapHeader));
if(uiVersion < 6)
{
this->pBlockEntryMapHeader = (GCFBlockEntryMapHeader *)((hlByte *)this->lpFragmentationMap + sizeof(GCFFragmentationMap) * this->pFragmentationMapHeader->uiBlockCount);
this->lpBlockEntryMap = (GCFBlockEntryMap *)((hlByte *)this->pBlockEntryMapHeader + sizeof(GCFBlockEntryMapHeader));
this->pDirectoryHeader = (GCFDirectoryHeader *)((hlByte *)this->lpBlockEntryMap + sizeof(GCFBlockEntryMap) * this->pBlockEntryMapHeader->uiBlockCount);
}
else
{
this->pBlockEntryMapHeader = 0;
this->lpBlockEntryMap = 0;
this->pDirectoryHeader = (GCFDirectoryHeader *)((hlByte *)this->lpFragmentationMap + sizeof(GCFFragmentationMap) * this->pFragmentationMapHeader->uiBlockCount);
}
this->lpDirectoryEntries = (GCFDirectoryEntry *)((hlByte *)this->pDirectoryHeader + sizeof(GCFDirectoryHeader));
this->lpDirectoryNames = (hlChar *)((hlByte *)this->lpDirectoryEntries + sizeof(GCFDirectoryEntry) * this->pDirectoryHeader->uiItemCount);
this->lpDirectoryInfo1Entries = (GCFDirectoryInfo1Entry *)((hlByte *)this->lpDirectoryNames + this->pDirectoryHeader->uiNameSize);
this->lpDirectoryInfo2Entries = (GCFDirectoryInfo2Entry *)((hlByte *)this->lpDirectoryInfo1Entries + sizeof(GCFDirectoryInfo1Entry) * this->pDirectoryHeader->uiInfo1Count);
this->lpDirectoryCopyEntries = (GCFDirectoryCopyEntry *)((hlByte *)this->lpDirectoryInfo2Entries + sizeof(GCFDirectoryInfo2Entry) * this->pDirectoryHeader->uiItemCount);
this->lpDirectoryLocalEntries = (GCFDirectoryLocalEntry *)((hlByte *)this->lpDirectoryCopyEntries + sizeof(GCFDirectoryCopyEntry) * this->pDirectoryHeader->uiCopyCount);
if(uiVersion < 5)
{
this->pDirectoryMapHeader = 0;
this->lpDirectoryMapEntries = (GCFDirectoryMapEntry *)((hlByte *)this->pDirectoryHeader + this->pDirectoryHeader->uiDirectorySize);
}
else
{
this->pDirectoryMapHeader = (GCFDirectoryMapHeader *)((hlByte *)this->pDirectoryHeader + this->pDirectoryHeader->uiDirectorySize);
this->lpDirectoryMapEntries = (GCFDirectoryMapEntry *)((hlByte *)this->pDirectoryMapHeader + sizeof(GCFDirectoryMapHeader));
}
this->pChecksumHeader = (GCFChecksumHeader *)((hlByte *)this->lpDirectoryMapEntries + sizeof(GCFDirectoryMapEntry) * this->pDirectoryHeader->uiItemCount);
this->pChecksumMapHeader = (GCFChecksumMapHeader *)((hlByte *)(this->pChecksumHeader) + sizeof(GCFChecksumHeader));
this->lpChecksumMapEntries = (GCFChecksumMapEntry *)((hlByte *)(this->pChecksumMapHeader) + sizeof(GCFChecksumMapHeader));
this->lpChecksumEntries = (GCFChecksumEntry *)((hlByte *)(this->lpChecksumMapEntries) + sizeof(GCFChecksumMapEntry) * this->pChecksumMapHeader->uiItemCount);
if(uiVersion < 5)
{
// In version 3 the GCFDataBlockHeader is missing the uiLastVersionPlayed field.
// The below hack makes the file map correctly.
this->pDataBlockHeader = (GCFDataBlockHeader *)((hlByte *)this->pChecksumMapHeader + this->pChecksumHeader->uiChecksumSize - sizeof(hlUInt));
}
else
{
this->pDataBlockHeader = (GCFDataBlockHeader *)((hlByte *)this->pChecksumMapHeader + this->pChecksumHeader->uiChecksumSize);
}
return hlTrue;
}
hlVoid CGCFFile::UnmapDataStructures()
{
delete []this->lpDirectoryItems;
this->lpDirectoryItems = 0;
this->pHeader = 0;
this->pBlockEntryHeader = 0;
this->lpBlockEntries = 0;
this->pFragmentationMapHeader = 0;
this->lpFragmentationMap = 0;
this->pBlockEntryMapHeader = 0;
this->lpBlockEntryMap = 0;
this->pDirectoryHeader = 0;
this->lpDirectoryEntries = 0;
this->lpDirectoryNames = 0;
this->lpDirectoryInfo1Entries = 0;
this->lpDirectoryInfo2Entries = 0;
this->lpDirectoryCopyEntries = 0;
this->lpDirectoryLocalEntries = 0;
this->pDirectoryMapHeader = 0;
this->lpDirectoryMapEntries = 0;
this->pChecksumHeader = 0;
this->pChecksumMapHeader = 0;
this->lpChecksumMapEntries = 0;
this->lpChecksumEntries = 0;
this->pDataBlockHeader = 0;
this->pMapping->Unmap(this->pHeaderView);
}
hlBool CGCFFile::DefragmentInternal()
{
hlBool bError = hlFalse, bCancel = hlFalse;
hlUInt uiFilesDefragmented = 0, uiFilesTotal = 0;
hlULongLong uiBytesDefragmented = 0, uiBytesTotal = 0;
// Check the current fragmentation state.
{
hlUInt uiBlocksFragmented = 0;
hlUInt uiBlocksUsed = 0;
for(hlUInt i = 0; i < this->pDirectoryHeader->uiItemCount; i++)
{
if(this->lpDirectoryEntries[i].uiDirectoryFlags & HL_GCF_FLAG_FILE)
{
hlUInt uiFileBlocksFragmented = 0;
hlUInt uiFileBlocksUsed = 0;
this->GetItemFragmentation(i, uiFileBlocksFragmented, uiFileBlocksUsed);
uiBlocksFragmented += uiFileBlocksFragmented;
uiBlocksUsed += uiFileBlocksUsed;
uiFilesTotal++;
uiBytesTotal += static_cast<hlULongLong>(uiFileBlocksUsed * this->pDataBlockHeader->uiBlockSize);
}
}
// If there are no data blocks to defragment, and we don't want to sort the data blocks
// lexicographically, then we're done.
if((uiBlocksFragmented == 0 && !bForceDefragment) || uiBlocksUsed == 0)
{
hlDefragmentProgress(0, uiFilesTotal, uiFilesTotal, uiBytesTotal, uiBytesTotal, &bCancel);
return hlTrue;
}
}
Mapping::CView *pCurrentView = 0, *pIncrementedView = 0;
hlUInt uiIncrement = 0;
//hlUInt uiDataBlockTerminator = this->pDataBlockHeader->uiBlockCount >= 0x0000ffff ? 0xffffffff : 0x0000ffff;
hlUInt uiDataBlockTerminator = this->pFragmentationMapHeader->uiTerminator == 0 ? 0x0000ffff : 0xffffffff;
hlByte *lpDataBlock = new hlByte[this->pDataBlockHeader->uiBlockSize];
// Step through each data block in each file as it appears in the directory and defragment (order each data
// block sequentially). This is a slow brute force approach, but since related half-life 2 files
// often start with the same name we *may* expect a performance boost from odering files lexicographically
// (as they appear in the directory). That's my justification at least...
for(hlUInt i = 0; i < this->pDirectoryHeader->uiItemCount && !bError && !bCancel; i++)
{
if(this->lpDirectoryEntries[i].uiDirectoryFlags & HL_GCF_FLAG_FILE)
{
hlUInt uiCurrentBlockEntryIndex = this->lpDirectoryMapEntries[i].uiFirstBlockIndex;
while(uiCurrentBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
hlUInt uiCurrentBlockEntrySize = 0;
hlUInt uiCurrentDataBlockIndex = this->lpBlockEntries[uiCurrentBlockEntryIndex].uiFirstDataBlockIndex;
hlUInt uiLastDataBlockIndex = this->pDataBlockHeader->uiBlockCount;
while(uiCurrentDataBlockIndex < uiDataBlockTerminator && uiCurrentBlockEntrySize < this->lpBlockEntries[uiCurrentBlockEntryIndex].uiFileDataSize)
{
hlUInt uiNextDataBlockIndex = this->lpFragmentationMap[uiCurrentDataBlockIndex].uiNextDataBlockIndex;
// If this data block is not ordered sequentially, swap it with the sequential data block.
if(uiCurrentDataBlockIndex != uiIncrement)
{
// Make sure we can map the two data blocks before we alter any tables.
if(this->pMapping->Map(pCurrentView, static_cast<hlULongLong>(this->pDataBlockHeader->uiFirstBlockOffset) + static_cast<hlULongLong>(uiCurrentDataBlockIndex) * static_cast<hlULongLong>(this->pDataBlockHeader->uiBlockSize), static_cast<hlULongLong>(this->pDataBlockHeader->uiBlockSize)) &&
this->pMapping->Map(pIncrementedView, static_cast<hlULongLong>(this->pDataBlockHeader->uiFirstBlockOffset) + static_cast<hlULongLong>(uiIncrement) * static_cast<hlULongLong>(this->pDataBlockHeader->uiBlockSize), static_cast<hlULongLong>(this->pDataBlockHeader->uiBlockSize)))
{
// Search to see if the sequential data block is in use, we only need to check
// files after ours in the directory because everything before is sequential.
hlBool bFound = hlFalse;
for(hlUInt j = i; j < this->pDirectoryHeader->uiItemCount && !bFound; j++)
{
if(this->lpDirectoryEntries[j].uiDirectoryFlags & HL_GCF_FLAG_FILE)
{
hlUInt uiIncrementedBlockEntryIndex = this->lpDirectoryMapEntries[j].uiFirstBlockIndex;
while(uiIncrementedBlockEntryIndex != this->pDataBlockHeader->uiBlockCount && !bFound)
{
hlUInt uiIncrementedDataBlockIndex = this->lpBlockEntries[uiIncrementedBlockEntryIndex].uiFirstDataBlockIndex;
if(uiIncrementedDataBlockIndex == uiIncrement)
{
// The sequential data block is the first data block in a block entry,
// update the tables preserving the sequence for the file we are fragmenting.
this->lpBlockEntries[uiIncrementedBlockEntryIndex].uiFirstDataBlockIndex = uiCurrentDataBlockIndex;
this->lpFragmentationMap[uiCurrentDataBlockIndex].uiNextDataBlockIndex = this->lpFragmentationMap[uiIncrementedDataBlockIndex].uiNextDataBlockIndex;
// We found it.
bFound = hlTrue;
break;
}
else
{
// The sequential data block is in the middle of a block entry,
// update the tables preserving the sequence for the file we are fragmenting.
hlUInt uiIncrementedBlockEntrySize = 0;
hlUInt uiIncrementedLastDataBlockIndex = this->pDataBlockHeader->uiBlockCount;
while(uiIncrementedDataBlockIndex < uiDataBlockTerminator && uiIncrementedBlockEntrySize < this->lpBlockEntries[uiIncrementedBlockEntryIndex].uiFileDataSize)
{
if(uiIncrementedDataBlockIndex == uiIncrement)
{
// If the data blocks are side by side, prevent circular maps.
if(uiIncrement != uiNextDataBlockIndex)
{
this->lpFragmentationMap[uiIncrementedLastDataBlockIndex].uiNextDataBlockIndex = uiCurrentDataBlockIndex;
}
this->lpFragmentationMap[uiCurrentDataBlockIndex].uiNextDataBlockIndex = this->lpFragmentationMap[uiIncrementedDataBlockIndex].uiNextDataBlockIndex;
// We found it.
bFound = hlTrue;
break;
}
uiIncrementedLastDataBlockIndex = uiIncrementedDataBlockIndex;
uiIncrementedDataBlockIndex = this->lpFragmentationMap[uiIncrementedDataBlockIndex].uiNextDataBlockIndex;
uiIncrementedBlockEntrySize += this->pDataBlockHeader->uiBlockSize;
}
}
uiIncrementedBlockEntryIndex = this->lpBlockEntries[uiIncrementedBlockEntryIndex].uiNextBlockEntryIndex;
}
}
}
// Swap the data blocks if necessary.
if(bFound)
{
memcpy(lpDataBlock, pIncrementedView->GetView(), this->pDataBlockHeader->uiBlockSize);
}
memcpy(const_cast<hlVoid *>(pIncrementedView->GetView()), pCurrentView->GetView(), this->pDataBlockHeader->uiBlockSize);
if(!this->pMapping->Commit(*pIncrementedView))
{
// At this point the GCF will be corrupt and require validating by Steam for repair.
bError = hlTrue;
}
if(bFound)
{
memcpy(const_cast<hlVoid *>(pCurrentView->GetView()), lpDataBlock, this->pDataBlockHeader->uiBlockSize);
if(!this->pMapping->Commit(*pCurrentView))
{
// At this point the GCF will be corrupt and require validating by Steam for repair.
bError = hlTrue;
}
}
// Update the tables preserving the sequence of the file we are defragmenting.
if(uiLastDataBlockIndex == this->pDataBlockHeader->uiBlockCount)
{
this->lpBlockEntries[uiCurrentBlockEntryIndex].uiFirstDataBlockIndex = uiIncrement;
}
else
{
this->lpFragmentationMap[uiLastDataBlockIndex].uiNextDataBlockIndex = uiIncrement;
}
if(uiIncrement != this->pDataBlockHeader->uiBlockCount)
{
// If the data blocks are side by side, prevent circular maps.
if(uiIncrement != uiNextDataBlockIndex)
{
this->lpFragmentationMap[uiIncrement].uiNextDataBlockIndex = uiNextDataBlockIndex;
}
else
{
this->lpFragmentationMap[uiIncrement].uiNextDataBlockIndex = uiCurrentDataBlockIndex;
}
}
}
else
{
bError = hlTrue;
break;
}
}
// Move to the next data block.
uiLastDataBlockIndex = uiIncrement;
uiIncrement++;
uiCurrentDataBlockIndex = this->lpFragmentationMap[uiLastDataBlockIndex].uiNextDataBlockIndex;
uiCurrentBlockEntrySize += this->pDataBlockHeader->uiBlockSize;
// Update the progress.
uiBytesDefragmented += static_cast<hlULongLong>(this->pDataBlockHeader->uiBlockSize);
hlDefragmentProgress(this->lpDirectoryItems != 0 ? static_cast<CDirectoryFile *>(this->lpDirectoryItems[i]) : 0, uiFilesDefragmented, uiFilesTotal, uiBytesDefragmented, uiBytesTotal, &bCancel);
if(bCancel)
{
break;
}
}
if(bError || bCancel)
{
break;
}
// Update the tables to make sure the last data block points to nothing.
// It would seem that this is only necessary if there is an eror in the GCF
// in which case we should just let it be (Steam knows better)...
/*if(uiLastDataBlockIndex != this->pDataBlockHeader->uiBlockCount)
{
this->lpFragmentationMap[uiLastDataBlockIndex].uiNextDataBlockIndex = uiDataBlockTerminator;
}*/
uiCurrentBlockEntryIndex = this->lpBlockEntries[uiCurrentBlockEntryIndex].uiNextBlockEntryIndex;
}
uiFilesDefragmented++;
}
}
if(!bError && !bCancel)
{
// Store the first unused fragmentation map entry.
if(uiIncrement < this->pFragmentationMapHeader->uiBlockCount)
{
// Note: usually if there are no unused data blocks, uiFirstUnusedEntry is 0, but
// sometimes it isn't (and I don't know why) so I'm unsure if I should set it to
// 0 in the else case.
this->pFragmentationMapHeader->uiFirstUnusedEntry = uiIncrement;
this->pFragmentationMapHeader->uiChecksum = this->pFragmentationMapHeader->uiBlockCount +
this->pFragmentationMapHeader->uiFirstUnusedEntry +
this->pFragmentationMapHeader->uiTerminator;
}
// Fill in the unused fragmentation map entries with uiBlockCount.
for(hlUInt i = uiIncrement; i < this->pFragmentationMapHeader->uiBlockCount; i++)
{
this->lpFragmentationMap[i].uiNextDataBlockIndex = this->pFragmentationMapHeader->uiBlockCount;
}
}
else
{
hlByte *lpTouched = new hlByte[this->pFragmentationMapHeader->uiBlockCount];
memset(lpTouched, 0, sizeof(hlByte) * this->pFragmentationMapHeader->uiBlockCount);
// Figure out which fragmentation map entries are used.
for(hlUInt i = 0; i < this->pDirectoryHeader->uiItemCount; i++)
{
if(this->lpDirectoryEntries[i].uiDirectoryFlags & HL_GCF_FLAG_FILE)
{
hlUInt uiBlockEntryIndex = this->lpDirectoryMapEntries[i].uiFirstBlockIndex;
while(uiBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
hlUInt uiBlockEntrySize = 0;
hlUInt uiDataBlockIndex = this->lpBlockEntries[uiBlockEntryIndex].uiFirstDataBlockIndex;
while(uiDataBlockIndex < uiDataBlockTerminator && uiBlockEntrySize < this->lpBlockEntries[uiBlockEntryIndex].uiFileDataSize)
{
lpTouched[uiDataBlockIndex] = hlTrue;
uiDataBlockIndex = this->lpFragmentationMap[uiDataBlockIndex].uiNextDataBlockIndex;
uiBlockEntrySize += this->pDataBlockHeader->uiBlockSize;
}
uiBlockEntryIndex = this->lpBlockEntries[uiBlockEntryIndex].uiNextBlockEntryIndex;
}
}
}
// Fill in the unused fragmentation map entries with uiBlockCount.
hlBool bFirst = hlFalse;
for(hlUInt i = 0; i < this->pFragmentationMapHeader->uiBlockCount; i++)
{
if(!lpTouched[i])
{
if(!bFirst)
{
// Store the first unused fragmentation map entry.
this->pFragmentationMapHeader->uiFirstUnusedEntry = i;
this->pFragmentationMapHeader->uiChecksum = this->pFragmentationMapHeader->uiBlockCount +
this->pFragmentationMapHeader->uiFirstUnusedEntry +
this->pFragmentationMapHeader->uiTerminator;
bFirst = hlTrue;
}
this->lpFragmentationMap[i].uiNextDataBlockIndex = this->pFragmentationMapHeader->uiBlockCount;
}
}
delete []lpTouched;
}
delete []lpDataBlock;
this->pMapping->Unmap(pCurrentView);
this->pMapping->Unmap(pIncrementedView);
// Commit header changes to mapping.
this->pMapping->Commit(*this->pHeaderView, (hlUInt)((const hlByte *)this->lpBlockEntries - (const hlByte *)this->pHeaderView->GetView()), sizeof(GCFBlockEntry) * this->pBlockEntryHeader->uiBlockCount);
this->pMapping->Commit(*this->pHeaderView, (hlUInt)((const hlByte *)this->pFragmentationMapHeader - (const hlByte *)this->pHeaderView->GetView()), sizeof(GCFFragmentationMapHeader));
this->pMapping->Commit(*this->pHeaderView, (hlUInt)((const hlByte *)this->lpFragmentationMap - (const hlByte *)this->pHeaderView->GetView()), sizeof(GCFFragmentationMap) * this->pFragmentationMapHeader->uiBlockCount);
return !bError;
}
CDirectoryFolder *CGCFFile::CreateRoot()
{
this->lpDirectoryItems = new CDirectoryItem *[this->pDirectoryHeader->uiItemCount];
this->lpDirectoryItems[0] = new CDirectoryFolder("root", 0, 0, this, 0);
this->CreateRoot(static_cast<CDirectoryFolder *>(this->lpDirectoryItems[0]));
return static_cast<CDirectoryFolder *>(this->lpDirectoryItems[0]);
}
hlVoid CGCFFile::CreateRoot(CDirectoryFolder *pFolder)
{
// Get the first directory item.
hlUInt uiIndex = this->lpDirectoryEntries[pFolder->GetID()].uiFirstIndex;
// Loop through directory items.
while(uiIndex && uiIndex != 0xffffffff)
{
// Check if the item is a folder.
if((this->lpDirectoryEntries[uiIndex].uiDirectoryFlags & HL_GCF_FLAG_FILE) == 0)
{
// Add the directory item to the current folder.
this->lpDirectoryItems[uiIndex] = pFolder->AddFolder(this->lpDirectoryNames + this->lpDirectoryEntries[uiIndex].uiNameOffset, uiIndex);
// Build the new folder.
this->CreateRoot(static_cast<CDirectoryFolder *>(this->lpDirectoryItems[uiIndex]));
}
else
{
// Add the directory item to the current folder.
this->lpDirectoryItems[uiIndex] = pFolder->AddFile(this->lpDirectoryNames + this->lpDirectoryEntries[uiIndex].uiNameOffset, uiIndex);
}
// Get the next directory item.
uiIndex = this->lpDirectoryEntries[uiIndex].uiNextIndex;
}
}
hlUInt CGCFFile::GetAttributeCountInternal() const
{
return HL_GCF_PACKAGE_COUNT;
}
const hlChar *CGCFFile::GetAttributeNameInternal(HLPackageAttribute eAttribute) const
{
if(eAttribute < HL_GCF_PACKAGE_COUNT)
{
return this->lpAttributeNames[eAttribute];
}
return 0;
}
hlBool CGCFFile::GetAttributeInternal(HLPackageAttribute eAttribute, HLAttribute &Attribute) const
{
switch(eAttribute)
{
case HL_GCF_PACKAGE_VERSION:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pHeader->uiMinorVersion, hlFalse);
return hlTrue;
case HL_GCF_PACKAGE_ID:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pHeader->uiCacheID, hlFalse);
return hlTrue;
case HL_GCF_PACKAGE_ALLOCATED_BLOCKS:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pDataBlockHeader->uiBlockCount, hlFalse);
return hlTrue;
case HL_GCF_PACKAGE_USED_BLOCKS:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pDataBlockHeader->uiBlocksUsed, hlFalse);
return hlTrue;
case HL_GCF_PACKAGE_BLOCK_LENGTH:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pDataBlockHeader->uiBlockSize, hlFalse);
return hlTrue;
case HL_GCF_PACKAGE_LAST_VERSION_PLAYED:
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pHeader->uiLastVersionPlayed, hlFalse);
return hlTrue;
default:
return hlFalse;
}
}
hlUInt CGCFFile::GetItemAttributeCountInternal() const
{
return HL_GCF_ITEM_COUNT;
}
const hlChar *CGCFFile::GetItemAttributeNameInternal(HLPackageAttribute eAttribute) const
{
if(eAttribute < HL_GCF_ITEM_COUNT)
{
return this->lpItemAttributeNames[eAttribute];
}
return 0;
}
hlBool CGCFFile::GetItemAttributeInternal(const CDirectoryItem *pItem, HLPackageAttribute eAttribute, HLAttribute &Attribute) const
{
switch(pItem->GetType())
{
case HL_ITEM_FILE:
{
const CDirectoryFile *pFile = static_cast<const CDirectoryFile *>(pItem);
switch(eAttribute)
{
case HL_GCF_ITEM_ENCRYPTED:
{
hlAttributeSetBoolean(&Attribute, this->lpItemAttributeNames[eAttribute], (this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_ENCRYPTED) != 0);
return hlTrue;
}
case HL_GCF_ITEM_COPY_LOCAL:
{
hlAttributeSetBoolean(&Attribute, this->lpItemAttributeNames[eAttribute], (this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_COPY_LOCAL) != 0);
return hlTrue;
}
case HL_GCF_ITEM_OVERWRITE_LOCAL:
{
hlAttributeSetBoolean(&Attribute, this->lpItemAttributeNames[eAttribute], (this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_COPY_LOCAL_NO_OVERWRITE) == 0);
return hlTrue;
}
case HL_GCF_ITEM_BACKUP_LOCAL:
{
hlAttributeSetBoolean(&Attribute, this->lpItemAttributeNames[eAttribute], (this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_BACKUP_LOCAL) != 0);
return hlTrue;
}
case HL_GCF_ITEM_FLAGS:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags, hlTrue);
return hlTrue;
}
case HL_GCF_ITEM_FRAGMENTATION:
{
hlUInt uiBlocksFragmented = 0;
hlUInt uiBlocksUsed = 0;
this->GetItemFragmentation(pFile->GetID(), uiBlocksFragmented, uiBlocksUsed);
if(uiBlocksUsed == 0)
{
hlAttributeSetFloat(&Attribute, this->lpItemAttributeNames[eAttribute], 0.0f);
}
else
{
hlAttributeSetFloat(&Attribute, this->lpItemAttributeNames[eAttribute], ((hlFloat)uiBlocksFragmented / (hlFloat)uiBlocksUsed) * 100.0f);
}
return hlTrue;
}
default:
break;
}
break;
}
case HL_ITEM_FOLDER:
{
const CDirectoryFolder *pFolder = static_cast<const CDirectoryFolder *>(pItem);
switch(eAttribute)
{
case HL_GCF_ITEM_FLAGS:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], this->lpDirectoryEntries[pFolder->GetID()].uiDirectoryFlags, hlTrue);
return hlTrue;
}
case HL_GCF_ITEM_FRAGMENTATION:
{
hlUInt uiBlocksFragmented = 0;
hlUInt uiBlocksUsed = 0;
this->GetItemFragmentation(pFolder->GetID(), uiBlocksFragmented, uiBlocksUsed);
if(uiBlocksUsed == 0)
{
hlAttributeSetFloat(&Attribute, this->lpItemAttributeNames[eAttribute], 0.0f);
}
else
{
hlAttributeSetFloat(&Attribute, this->lpItemAttributeNames[eAttribute], ((hlFloat)uiBlocksFragmented / (hlFloat)uiBlocksUsed) * 100.0f);
}
return hlTrue;
}
default:
break;
}
break;
}
default:
break;
}
return hlFalse;
}
hlBool CGCFFile::GetFileExtractableInternal(const CDirectoryFile *pFile, hlBool &bExtractable) const
{
if(this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_ENCRYPTED)
{
bExtractable = hlFalse;
}
else
{
// Do we have enough data to extract?
hlUInt uiSize = 0;
// Get the first data block.
hlUInt uiBlockEntryIndex = this->lpDirectoryMapEntries[pFile->GetID()].uiFirstBlockIndex;
// Loop through each data block.
while(uiBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
uiSize += this->lpBlockEntries[uiBlockEntryIndex].uiFileDataSize;
// Get the next data block.
uiBlockEntryIndex = this->lpBlockEntries[uiBlockEntryIndex].uiNextBlockEntryIndex;
}
bExtractable = uiSize >= this->lpDirectoryEntries[pFile->GetID()].uiItemSize;
}
return hlTrue;
}
hlBool CGCFFile::GetFileValidationInternal(const CDirectoryFile *pFile, HLValidation &eValidation) const
{
// Do we have enough data to validate?
{
hlUInt uiSize = 0;
// Get the first data block.
hlUInt uiBlockEntryIndex = this->lpDirectoryMapEntries[pFile->GetID()].uiFirstBlockIndex;
// Loop through each data block.
while(uiBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
uiSize += this->lpBlockEntries[uiBlockEntryIndex].uiFileDataSize;
// Get the next data block.
uiBlockEntryIndex = this->lpBlockEntries[uiBlockEntryIndex].uiNextBlockEntryIndex;
}
if(uiSize != this->lpDirectoryEntries[pFile->GetID()].uiItemSize)
{
// File is incomplete.
eValidation = HL_VALIDATES_INCOMPLETE;
return hlTrue;
}
}
if((this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_ENCRYPTED) != 0)
{
// No way of checking, assume it's ok.
eValidation = HL_VALIDATES_ASSUMED_OK;
return hlTrue;
}
// File has no checksum.
if(this->lpDirectoryEntries[pFile->GetID()].uiChecksumIndex == 0xffffffff)
{
eValidation = HL_VALIDATES_ASSUMED_OK;
return hlTrue;
}
Streams::IStream *pStream = 0;
if(this->CreateStreamInternal(pFile, pStream))
{
if(pStream->Open(HL_MODE_READ))
{
eValidation = HL_VALIDATES_OK;
hlULongLong uiTotalBytes = 0, uiFileBytes = pStream->GetStreamSize();
hlUInt uiBufferSize;
hlByte lpBuffer[HL_GCF_CHECKSUM_LENGTH];
const GCFChecksumMapEntry *pChecksumMapEntry = this->lpChecksumMapEntries + this->lpDirectoryEntries[pFile->GetID()].uiChecksumIndex;
hlBool bCancel = hlFalse;
hlValidateFileProgress(const_cast<CDirectoryFile *>(pFile), uiTotalBytes, uiFileBytes, &bCancel);
hlUInt i = 0;
while((uiBufferSize = pStream->Read(lpBuffer, HL_GCF_CHECKSUM_LENGTH)) != 0)
{
if(bCancel)
{
// User canceled.
eValidation = HL_VALIDATES_CANCELED;
break;
}
if(i >= pChecksumMapEntry->uiChecksumCount)
{
// Something bad happened.
eValidation = HL_VALIDATES_ERROR;
break;
}
hlULong uiChecksum = Adler32(lpBuffer, uiBufferSize) ^ CRC32(lpBuffer, uiBufferSize);
if(uiChecksum != this->lpChecksumEntries[pChecksumMapEntry->uiFirstChecksumIndex + i].uiChecksum)
{
eValidation = HL_VALIDATES_CORRUPT;
break;
}
uiTotalBytes += static_cast<hlULongLong>(uiBufferSize);
hlValidateFileProgress(const_cast<CDirectoryFile *>(pFile), uiTotalBytes, uiFileBytes, &bCancel);
i++;
}
pStream->Close();
}
else
{
eValidation = HL_VALIDATES_ERROR;
}
this->ReleaseStreamInternal(*pStream);
delete pStream;
}
else
{
eValidation = HL_VALIDATES_ERROR;
}
return hlTrue;
}
hlBool CGCFFile::GetFileSizeInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
{
uiSize = this->lpDirectoryEntries[pFile->GetID()].uiItemSize;
return hlTrue;
}
hlBool CGCFFile::GetFileSizeOnDiskInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
{
// Get the first data block.
hlUInt uiBlockEntryIndex = this->lpDirectoryMapEntries[pFile->GetID()].uiFirstBlockIndex;
// Loop through each data block.
while(uiBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
uiSize += ((this->lpBlockEntries[uiBlockEntryIndex].uiFileDataSize + this->pDataBlockHeader->uiBlockSize - 1) / this->pDataBlockHeader->uiBlockSize) * this->pDataBlockHeader->uiBlockSize;
// Get the next data block.
uiBlockEntryIndex = this->lpBlockEntries[uiBlockEntryIndex].uiNextBlockEntryIndex;
}
return hlTrue;
}
hlBool CGCFFile::CreateStreamInternal(const CDirectoryFile *pFile, Streams::IStream *&pStream) const
{
if(!bReadEncrypted && this->lpDirectoryEntries[pFile->GetID()].uiDirectoryFlags & HL_GCF_FLAG_ENCRYPTED)
{
LastError.SetErrorMessage("File is encrypted.");
return hlFalse;
}
pStream = new Streams::CGCFStream(*this, pFile->GetID());
return hlTrue;
}
hlVoid CGCFFile::GetItemFragmentation(hlUInt uiDirectoryItemIndex, hlUInt &uiBlocksFragmented, hlUInt &uiBlocksUsed) const
{
if((this->lpDirectoryEntries[uiDirectoryItemIndex].uiDirectoryFlags & HL_GCF_FLAG_FILE) == 0)
{
uiDirectoryItemIndex = this->lpDirectoryEntries[uiDirectoryItemIndex].uiFirstIndex;
while(uiDirectoryItemIndex && uiDirectoryItemIndex != 0xffffffff)
{
this->GetItemFragmentation(uiDirectoryItemIndex, uiBlocksFragmented, uiBlocksUsed);
uiDirectoryItemIndex = this->lpDirectoryEntries[uiDirectoryItemIndex].uiNextIndex;
}
}
else
{
hlUInt uiDataBlockTerminator = this->pFragmentationMapHeader->uiTerminator == 0 ? 0x0000ffff : 0xffffffff;
hlUInt uiLastDataBlockIndex = this->pDataBlockHeader->uiBlockCount;
hlUInt uiBlockEntryIndex = this->lpDirectoryMapEntries[uiDirectoryItemIndex].uiFirstBlockIndex;
while(uiBlockEntryIndex != this->pDataBlockHeader->uiBlockCount)
{
hlUInt uiBlockEntrySize = 0;
hlUInt uiDataBlockIndex = this->lpBlockEntries[uiBlockEntryIndex].uiFirstDataBlockIndex;
while(uiDataBlockIndex < uiDataBlockTerminator && uiBlockEntrySize < this->lpBlockEntries[uiBlockEntryIndex].uiFileDataSize)
{
if(uiLastDataBlockIndex != this->pDataBlockHeader->uiBlockCount && uiLastDataBlockIndex + 1 != uiDataBlockIndex)
{
uiBlocksFragmented++;
}
uiBlocksUsed++;
uiLastDataBlockIndex = uiDataBlockIndex;
uiDataBlockIndex = this->lpFragmentationMap[uiDataBlockIndex].uiNextDataBlockIndex;
uiBlockEntrySize += this->pDataBlockHeader->uiBlockSize;
}
uiBlockEntryIndex = this->lpBlockEntries[uiBlockEntryIndex].uiNextBlockEntryIndex;
}
}
}