/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program 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.
**
** 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, see .
*/
/* $Header: /G/wwlib/tagblock.cpp 5 11/30/99 3:46p Scott_b $ */
/***********************************************************************************************
*** 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 : WWLib *
* *
* $Archive:: /G/wwlib/tagblock.cpp $*
* *
* $Author:: Scott_b $*
* *
* $Modtime:: 11/29/99 6:42p $*
* *
* $Revision:: 5 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* TagBlockFile::TagBlockFile -- Create/open tag file *
* TagBlockFile::~TagBlockFile -- Close down the tag file. *
* TagBlockFile::Create_Index -- Create a index into the IndexList sorted by CRC. *
* TagBlockFile::Find_Block -- Find block assocated with name. *
* TagBlockFile::Open_Tag -- Open an existing tag block. *
* TagBlockFile::Create_Tag -- Create a new tag at the end of the block. *
* TagBlockFile::Close_Tag -- Close the handle that Create or Open made. *
* TagBlockFile::Destroy_Handle -- Shut down a handle. *
* TagBlockFile::End_Write_Access -- Stop write access for handle - flushes data bug keeps ha*
* TagBlockFile::Reset_File -- Clear file so no blocks exist. *
* TagBlockFile::Empty_Index_List -- Clear out tag block list in memory *
*---------------------------------------------------------------------------------------------*
* TagBlockHandle::Write -- Write data to the block. *
* TagBlockHandle::Read -- Read from a tag block. *
* TagBlockHandle::Seek -- Seek within the file. *
* TagBlockHandle::~TagBlockHandle -- Destroy handle. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "tagblock.h"
#include "realcrc.h"
#include
int TagBlockHandle::_InDestructor = 0;
////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// Start of TagBlockIndex ///////////////////////////////////////
class TagBlockIndex
{
public:
TagBlockIndex(const char *tagname, int blockoffset):
CRC(CRC_Stringi(tagname)),
BlockOffset(blockoffset),
DataOffset(TagBlockFile::Calc_Data_Offset(blockoffset, tagname))
{}
unsigned Get_CRC() {
return(CRC);
}
int Get_BlockOffset() {
return(BlockOffset);
}
int Get_TagOffset() {
return(TagBlockFile::Calc_Tag_Offset(BlockOffset));
}
int Get_TagSize() {
return(DataOffset - Get_TagOffset());
}
int Get_DataOffset() {
return(DataOffset);
}
private:
// The index file is sorted by the CRC of the file name for
// quicker retrieval. The filename is saved in the texture file.
unsigned long CRC;
// Start of the block - this is the start of TagBlockFile::BlockHeader.
int BlockOffset;
// Offset of block inside of TagFile.
// This is first byte after header and TagName.
// It is actual data used by external methods.
int DataOffset;
};
///////////////////////////////////// End of TagBlockIndex /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// Start of TagBlockHandle/////////////////////////////////////////
/***********************************************************************************************
* RawFileClass -- Open up the tag file (it may not exist). *
* *
* INPUT: *
* const char *fname - name of file that is or wants to be a TagBlockFile. *
* *
* OUTPUT: *
* Will assume a file that has invalid data to be corrupt and will write over it. *
* So don't pass in a file that is not a tag file. *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockFile::TagBlockFile(const char *fname):
RawFileClass(),
Header(),
CreateHandle(NULL),
NumOpenHandles(0),
IndexList()
{
// Open file up, create it if it does not exist.
// Pass in name to Open function so that the file name will be strdup'd.
Open(fname, READ|WRITE);
FileTime = RawFileClass::Get_Date_Time();
// Read in header so we can tell if it is proper file.
Read(&Header, sizeof(Header));
// See if there is any data in file (or was it just created?)
if (Header.Version == FILE_VERSION) {
TagBlockIndex *lasttag = NULL;
int curpos = sizeof(Header);
// Loop through each block in file and create an in memory index for it.
int block;
for (block = 0; block < Header.NumBlocks; block++) {
BlockHeader blockheader;
// Read in next header.
Seek(curpos, SEEK_SET);
Read(&blockheader, sizeof(blockheader));
// Make sure things are in order.
if (blockheader.Index == block) {
char tagname[MAX_TAG_NAME_SIZE];
// Read in tag name, this includes the NULL at end of string.
Read(tagname, blockheader.TagSize);
// Create new tag index for fast retrievel.
lasttag = Create_Index(tagname, curpos);
// Now get past the data.
curpos = Calc_Tag_Offset(curpos) + blockheader.TagSize + blockheader.DataSize;
} else {
break;
}
}
// See if there is a difference between the header and actual data.
if ((curpos != Header.FileSize) || (block != Header.NumBlocks)) {
Header.NumBlocks = block;
Header.FileSize = curpos;
// Start at begining of file and write out our new header.
Seek(0, SEEK_SET);
Write(&Header, sizeof(Header));
}
} else {
Reset_File();
}
}
/***********************************************************************************************
* TagBlockFile::~TagBlockFile -- Close down the tag file. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* Any TagBlockHandles that have not been deleted are now invalide but cannot be deleted.*
* You must delete any handles associated with this before closing the TagFile. *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockFile::~TagBlockFile()
{
Empty_Index_List();
}
/***********************************************************************************************
* TagBlockFile::Reset_File -- Clear file so no blocks exist. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 11/29/1999 SKB : Created. *
*=============================================================================================*/
void TagBlockFile::Reset_File()
{
Empty_Index_List();
// Save a clean header out.
Header.Version = FILE_VERSION;
Header.NumBlocks = 0;
Header.FileSize = sizeof(Header);
Save_Header();
// Close, then open file so we get a new time stamp on it.
Close();
Open(READ|WRITE);
// Reget file creation time.
FileTime = RawFileClass::Get_Date_Time();
}
/***********************************************************************************************
* *TagBlockFile::Open_Tag -- Open an existing tag block. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockHandle *TagBlockFile::Open_Tag(const char *tagname)
{
// Find tag to open up.
TagBlockIndex *index = Find_Block(tagname);
if (!index) {
return(NULL);
}
// Load up the block header information.
BlockHeader *blockheader = new BlockHeader();
Seek(index->Get_BlockOffset(), SEEK_SET);
Read(blockheader, sizeof(*blockheader));
// Now that we have all that we need, create the
TagBlockHandle *handle = new TagBlockHandle(this, index, blockheader);
// Keep track of how many handles there are so we can assert if they are not all shut down.
NumOpenHandles++;
// Return with our new handle.
return(handle);
}
/***********************************************************************************************
* *TagBlockFile::Create_Tag -- Create a new tag at the end of the block. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockHandle *TagBlockFile::Create_Tag(const char *tagname)
{
// Only allow one handle to be creating open at a time.
if (CreateHandle) {
return(NULL);
}
// Create a new index that we can write too.
TagBlockIndex *index = Create_Index(tagname, Header.FileSize);
// An index may not be created if a tag of the same name already exists.
if (!index) {
return(NULL);
}
// Create a header.
// Use -1 for index to indecate that block is not yet written out competely.
BlockHeader *blockheader = new BlockHeader(-1, index->Get_TagSize(), 0);
// Write out the block header and the tag.
Seek(index->Get_BlockOffset(), SEEK_SET);
Write(blockheader, sizeof(*blockheader));
Write(tagname, strlen(tagname) + 1);
// Now that we have all that we need, create the
CreateHandle = new TagBlockHandle(this, index, blockheader);
// Keep track of how many handles there are so we can assert if they are not all shut down.
NumOpenHandles++;
return(CreateHandle);
}
/***********************************************************************************************
* TagBlockFile::Close_Tag -- Close the handle that Create or Open made. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
void TagBlockFile::Close_Tag(TagBlockHandle *handle)
{
delete handle;
}
/***********************************************************************************************
* TagBlockFile::Destroy_Handle -- Shut down a handle. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
void TagBlockFile::Destroy_Handle(TagBlockHandle *handle)
{
// Make sure those sneaky programmers aren't trying to fool me.
assert(handle->Called_By_Destructor());
// If we had write access to handle, flush it out.
End_Write_Access(handle);
// This was allocated by TagBlockFile::Create_Tag() or Open_Tag().
delete handle->BlockHeader;
// Keep track of how many handles there are so we can assert if they are not all shut down.
NumOpenHandles--;
}
/***********************************************************************************************
* TagBlockFile::End_Write_Access -- Stop write access for handle - flushes data bug keeps han *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/02/1999 SKB : Created. *
*=============================================================================================*/
int TagBlockFile::End_Write_Access(TagBlockHandle *handle)
{
// Make sure this handle is the proper one.
if (CreateHandle == handle) {
// Update file header and block header.
handle->BlockHeader->Index = Header.NumBlocks;
Header.NumBlocks++;
Header.FileSize = handle->Index->Get_DataOffset();
Header.FileSize += handle->BlockHeader->DataSize;
// Write both headers out.
Seek(handle->Index->Get_BlockOffset(), SEEK_SET);
Write(handle->BlockHeader, sizeof(*handle->BlockHeader));
Save_Header();
// Don't allow writing with this handle anymore.
CreateHandle = NULL;
return(true);
}
return(false);
}
/***********************************************************************************************
* *TagBlockFile::Create_Index -- Create a index into the IndexList sorted by CRC. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockIndex *TagBlockFile::Create_Index(const char *tagname, int blockoffset)
{
// Don't allow duplicate tags.
if (Find_Block(tagname)) {
return(NULL);
}
TagBlockIndex *index;
index = new TagBlockIndex(tagname, blockoffset);
// Put it into the list.
if (IndexList.Is_Empty()) {
IndexList.Add_Head(index);
} else {
// Put in list sorted by CRC, smallest to largest.
SLNode *node = IndexList.Head();
while (node) {
TagBlockIndex *cur = node->Data();
if (index->Get_CRC() > cur->Get_CRC()) {
IndexList.Insert_Before(index, cur);
break;
}
node = node->Next();
}
// If we did not find a place, then add at end of list.
if (!node) {
IndexList.Add_Tail(index);
}
}
return (index);
}
/***********************************************************************************************
* *TagBlockFile::Find_Block -- Find block assocated with name. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/11/1999 SKB : Created. *
*=============================================================================================*/
TagBlockIndex *TagBlockFile::Find_Block(const char *tagname)
{
if (IndexList.Is_Empty()) {
return(NULL);
}
unsigned long crc = CRC_Stringi(tagname);
SLNode *node = IndexList.Head();
while (node) {
TagBlockIndex *cur = node->Data();
// Did we find it?
if (cur->Get_CRC() == crc) {
// Now read from file to verify that it is the right name.
char name[MAX_TAG_NAME_SIZE];
Seek(cur->Get_TagOffset(), SEEK_SET);
// Read in the name.
Read(name, cur->Get_TagSize());
// Is it a match?
assert(name != NULL);
assert(tagname != NULL);
if (!strcmpi(name, tagname)) {
return(cur);
}
}
// Are we out of range?
if (cur->Get_CRC() < crc) {
break;
}
// Next in line please.
node = node->Next();
}
return(NULL);
}
/***********************************************************************************************
* TagBlockFile::Empty_Index_List -- Clear out tag block list in memory *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 11/29/1999 SKB : Created. *
*=============================================================================================*/
void TagBlockFile::Empty_Index_List()
{
assert(!NumOpenHandles);
// Get rid of index list in memory.
while (!IndexList.Is_Empty()) {
TagBlockIndex *index = IndexList.Remove_Head();
delete index;
}
}
///////////////////////////////////////// End of TagBlockFile /////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////// Start of TagBlockHandle/////////////////////////////////////
/***********************************************************************************************
* Position -- Create a handle for user to access the TagBlock. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
TagBlockHandle::TagBlockHandle(TagBlockFile *tagfile, TagBlockIndex *tagindex, TagBlockFile::BlockHeader *blockheader):
File(tagfile),
Index(tagindex),
BlockHeader(blockheader),
Position(0)
{
}
/***********************************************************************************************
* TagBlockHandle::~TagBlockHandle -- Destroy handle. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
TagBlockHandle::~TagBlockHandle()
{
_InDestructor++;
File->Destroy_Handle(this);
_InDestructor--;
}
/***********************************************************************************************
* TagBlockHandle::Write -- Write data to the block. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
int TagBlockHandle::Write(const void *buf, int nbytes)
{
// Make sure this handle is the proper one.
if (!File->Handle_Can_Write(this)) {
return(-1);
}
// Get to correct position to write out and write the buffer.
File->Seek(Index->Get_DataOffset() + Position, SEEK_SET);
nbytes = File->Write(buf, nbytes);
// Advance the EOF marker for the block.
Position += nbytes;
if (Position > BlockHeader->DataSize) {
BlockHeader->DataSize = Position;
}
// Return about written out.
return(nbytes);
}
/***********************************************************************************************
* TagBlockHandle::Read -- Read from a tag block. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
int TagBlockHandle::Read(void *buf, int nbytes)
{
// Make sure user does not read past end of buffer.
if ((Position + nbytes) > BlockHeader->DataSize) {
nbytes = BlockHeader->DataSize - Position;
}
// Get to correct position to write out and read in the buffer.
File->Seek(Index->Get_DataOffset() + Position, SEEK_SET);
nbytes = File->Read(buf, nbytes);
// Adjust the read head.
Position += nbytes;
// Tell user how much was read from the file.
return(nbytes);
}
/***********************************************************************************************
* TagBlockHandle::Seek -- Seek within the file. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/12/1999 SKB : Created. *
*=============================================================================================*/
int TagBlockHandle::Seek(int pos, int dir)
{
switch (dir) {
case SEEK_CUR:
Position += pos;
break;
case SEEK_SET:
Position = pos;
break;
case SEEK_END:
Position = BlockHeader->DataSize + pos;
break;
}
return(Position);
}
// EOF