797 lines
45 KiB
C++

/*
** Command & Conquer Generals Zero Hour(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 <http://www.gnu.org/licenses/>.
*/
/* $Header: /Commando/Code/wwlib/chunkio.cpp 11 7/12/01 7:44p Jani_p $ */
/***********************************************************************************************
*** Confidential - Westwood Studios ***
***********************************************************************************************
* *
* Project Name : Tiberian Sun / Commando / G Library *
* *
* $Archive:: /Commando/Code/wwlib/chunkio.cpp $*
* *
* $Author:: Jani_p $*
* *
* $Modtime:: 7/10/01 7:38p $*
* *
* $Revision:: 11 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* ChunkSaveClass::ChunkSaveClass -- Constructor *
* ChunkSaveClass::Begin_Chunk -- Begin a new chunk in the file *
* ChunkSaveClass::End_Chunk -- Close a chunk, computes the size and adds to the header *
* ChunkSaveClass::Begin_Micro_Chunk -- begins a new "micro-chunk" *
* ChunkSaveClass::End_Micro_Chunk -- close a micro-chunk *
* ChunkSaveClass::Write -- Write data into the current chunk *
* ChunkSaveClass::Write -- write an IOVector2Struct *
* ChunkSaveClass::Write -- write an IOVector3Struct *
* ChunkSaveClass::Write -- write an IOVector4Struct *
* ChunkSaveClass::Write -- write an IOQuaternionStruct *
* ChunkSaveClass::Cur_Chunk_Depth -- returns the current chunk recursion depth (debugging) *
* ChunkLoadClass::ChunkLoadClass -- Constructor *
* ChunkLoadClass::Open_Chunk -- Open a chunk in the file, reads in the chunk header *
* ChunkLoadClass::Close_Chunk -- Close a chunk, seeks to the end if needed *
* ChunkLoadClass::Cur_Chunk_ID -- Returns the ID of the current chunk *
* ChunkLoadClass::Cur_Chunk_Length -- Returns the current length of the current chunk *
* ChunkLoadClass::Cur_Chunk_Depth -- returns the current chunk recursion depth *
* ChunkLoadClass::Contains_Chunks -- Test whether the current chunk contains chunks (or dat *
* ChunkLoadClass::Open_Micro_Chunk -- reads in a micro-chunk header *
* ChunkLoadClass::Close_Micro_Chunk -- closes a micro-chunk *
* ChunkLoadClass::Cur_Micro_Chunk_ID -- returns the ID of the current micro-chunk (asserts *
* ChunkLoadClass::Cur_Micro_Chunk_Length -- returns the size of the current micro chunk *
* ChunkLoadClass::Read -- Read data from the file *
* ChunkLoadClass::Read -- read an IOVector2Struct *
* ChunkLoadClass::Read -- read an IOVector3Struct *
* ChunkLoadClass::Read -- read an IOVector4Struct *
* ChunkLoadClass::Read -- read an IOQuaternionStruct *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "chunkio.h"
#include <string.h>
#include <assert.h>
/***********************************************************************************************
* ChunkSaveClass::ChunkSaveClass -- Constructor *
* *
* INPUT: *
* file - pointer to a FileClass object to write to *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
ChunkSaveClass::ChunkSaveClass(FileClass * file) :
File(file),
StackIndex(0),
InMicroChunk(false),
MicroChunkPosition(0)
{
memset(PositionStack,0,sizeof(PositionStack));
memset(HeaderStack,0,sizeof(HeaderStack));
memset(&MCHeader,0,sizeof(MCHeader));
}
/***********************************************************************************************
* ChunkSaveClass::Begin_Chunk -- Begin a new chunk in the file *
* *
* INPUT: *
* id - id of the chunk *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
bool ChunkSaveClass::Begin_Chunk(uint32 id)
{
ChunkHeader chunkh;
int filepos;
// If we have a parent chunk, set its 'Contains_Chunks' flag
if (StackIndex > 0) {
HeaderStack[StackIndex-1].Set_Sub_Chunk_Flag(true);
}
// Save the current file position and chunk header
// for the call to End_Chunk.
chunkh.Set_Type(id);
chunkh.Set_Size(0);
filepos = File->Seek(0);
PositionStack[StackIndex] = filepos;
HeaderStack[StackIndex] = chunkh;
StackIndex++;
// write a temporary chunk header (size = 0)
if (File->Write(&chunkh,sizeof(chunkh)) != sizeof(chunkh)) {
return false;
}
return true;
}
/***********************************************************************************************
* ChunkSaveClass::End_Chunk -- Close a chunk, computes the size and adds to the header *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
bool ChunkSaveClass::End_Chunk(void)
{
// If the user didn't close his micro chunks bad things are gonna happen
assert(!InMicroChunk);
// Save the current position
int curpos = File->Seek(0);
// Pop the position and chunk header off the stacks
StackIndex--;
int chunkpos = PositionStack[StackIndex];
ChunkHeader chunkh = HeaderStack[StackIndex];
// write the completed header
File->Seek(chunkpos,SEEK_SET);
if (File->Write(&chunkh,sizeof(chunkh)) != sizeof(chunkh)) {
return false;
}
// Add the total bytes written to any encompasing chunk
if (StackIndex != 0) {
HeaderStack[StackIndex-1].Add_Size(chunkh.Get_Size() + sizeof(chunkh));
}
// Go back to the end of the file
File->Seek(curpos,SEEK_SET);
return true;
}
/***********************************************************************************************
* ChunkSaveClass::Begin_Micro_Chunk -- begins a new "micro-chunk" *
* *
* Micro chunks are used to wrap individual variables. They aren't hierarchical so if you *
* attempt to open a micro chunk while already in one, an assert will occur. *
* *
* INPUT: *
* id - 8bit id *
* *
* OUTPUT: *
* *
* WARNINGS: *
* id is asserted to be between 0 and 255 *
* cannot nest micro chunks so it asserts that you are currently not in another micro-chunk *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
bool ChunkSaveClass::Begin_Micro_Chunk(uint32 id)
{
assert(id < 256);
assert(!InMicroChunk);
// Save the current file position and chunk header
// for the call to End_Micro_Chunk.
MCHeader.Set_Type(id);
MCHeader.Set_Size(0);
MicroChunkPosition = File->Seek(0);
// Write a temporary chunk header
// NOTE: I'm calling the ChunkSaveClass::Write method so that the bytes for
// this header are tracked in the wrapping chunk. This is because micro-chunks
// are simply data inside the normal chunks...
if (Write(&MCHeader,sizeof(MCHeader)) != sizeof(MCHeader)) {
return false;
}
InMicroChunk = true;
return true;
}
/***********************************************************************************************
* ChunkSaveClass::End_Micro_Chunk -- close a micro-chunk *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
bool ChunkSaveClass::End_Micro_Chunk(void)
{
assert(InMicroChunk);
// Save the current position
int curpos = File->Seek(0);
// Seek back and write the micro chunk header
File->Seek(MicroChunkPosition,SEEK_SET);
if (File->Write(&MCHeader,sizeof(MCHeader)) != sizeof(MCHeader)) {
return false;
}
// Go back to the end of the file
File->Seek(curpos,SEEK_SET);
InMicroChunk = false;
return true;
}
/***********************************************************************************************
* ChunkSaveClass::Write -- Write data into the current chunk *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
uint32 ChunkSaveClass::Write(const void * buf, uint32 nbytes)
{
// If this assert hits, you mixed data and chunks within the same chunk NO NO!
assert(HeaderStack[StackIndex-1].Get_Sub_Chunk_Flag() == 0);
// If this assert hits, you didnt open any chunks yet
assert(StackIndex > 0);
// write the bytes into the file
if (File->Write(buf,nbytes) != (int)nbytes) return 0;
// track them in the wrapping chunk
HeaderStack[StackIndex-1].Add_Size(nbytes);
// track them if you are using a micro-chunk too.
if (InMicroChunk) {
assert(MCHeader.Get_Size() < 255 - nbytes); // micro chunks can only be 255 bytes
MCHeader.Add_Size(nbytes);
}
return nbytes;
}
/***********************************************************************************************
* ChunkSaveClass::Write -- write an IOVector2Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkSaveClass::Write(const IOVector2Struct & v)
{
return Write(&v,sizeof(v));
}
/***********************************************************************************************
* ChunkSaveClass::Write -- write an IOVector3Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkSaveClass::Write(const IOVector3Struct & v)
{
return Write(&v,sizeof(v));
}
/***********************************************************************************************
* ChunkSaveClass::Write -- write an IOVector4Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkSaveClass::Write(const IOVector4Struct & v)
{
return Write(&v,sizeof(v));
}
/***********************************************************************************************
* ChunkSaveClass::Write -- write an IOQuaternionStruct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkSaveClass::Write(const IOQuaternionStruct & q)
{
return Write(&q,sizeof(q));
}
/***********************************************************************************************
* ChunkSaveClass::Cur_Chunk_Depth -- returns the current chunk recursion depth (debugging) *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
int ChunkSaveClass::Cur_Chunk_Depth(void)
{
return StackIndex;
}
/***********************************************************************************************
* ChunkLoadClass::ChunkLoadClass -- Constructor *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
ChunkLoadClass::ChunkLoadClass(FileClass * file) :
File(file),
StackIndex(0),
InMicroChunk(false),
MicroChunkPosition(0)
{
memset(PositionStack,0,sizeof(PositionStack));
memset(HeaderStack,0,sizeof(HeaderStack));
memset(&MCHeader,0,sizeof(MCHeader));
}
/***********************************************************************************************
* ChunkLoadClass::Open_Chunk -- Open a chunk in the file, reads in the chunk header *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
bool ChunkLoadClass::Open_Chunk()
{
// if user didn't close any micro chunks that he opened, bad things could happen
assert(InMicroChunk == false);
// check for stack overflow
assert(StackIndex < MAX_STACK_DEPTH-1);
// if the parent chunk has been completely eaten, return false
if ((StackIndex > 0) && (PositionStack[StackIndex-1] == HeaderStack[StackIndex-1].Get_Size())) {
return false;
}
// read the chunk header
if (File->Read(&HeaderStack[StackIndex],sizeof(ChunkHeader)) != sizeof(ChunkHeader)) {
return false;
}
PositionStack[StackIndex] = 0;
StackIndex++;
return true;
}
/***********************************************************************************************
* ChunkLoadClass::Close_Chunk -- Close a chunk, seeks to the end if needed *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
bool ChunkLoadClass::Close_Chunk()
{
// if user didn't close any micro chunks that he opened, bad things could happen
assert(InMicroChunk == false);
// check for stack overflow
assert(StackIndex > 0);
int csize = HeaderStack[StackIndex-1].Get_Size();
int pos = PositionStack[StackIndex-1];
if (pos < csize) {
File->Seek(csize - pos,SEEK_CUR);
}
StackIndex--;
if (StackIndex > 0) {
PositionStack[StackIndex - 1] += csize + sizeof(ChunkHeader);
}
return true;
}
/***********************************************************************************************
* ChunkLoadClass::Cur_Chunk_ID -- Returns the ID of the current chunk *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Cur_Chunk_ID()
{
assert(StackIndex >= 1);
return HeaderStack[StackIndex-1].Get_Type();
}
/***********************************************************************************************
* ChunkLoadClass::Cur_Chunk_Length -- Returns the current length of the current chunk *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Cur_Chunk_Length()
{
assert(StackIndex >= 1);
return HeaderStack[StackIndex-1].Get_Size();
}
/***********************************************************************************************
* ChunkLoadClass::Cur_Chunk_Depth -- returns the current chunk recursion depth *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
int ChunkLoadClass::Cur_Chunk_Depth()
{
return StackIndex;
}
/***********************************************************************************************
* ChunkLoadClass::Contains_Chunks -- Test whether the current chunk contains chunks (or data) *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/24/99 GTH : Created. *
*=============================================================================================*/
int ChunkLoadClass::Contains_Chunks()
{
return HeaderStack[StackIndex-1].Get_Sub_Chunk_Flag();
}
/***********************************************************************************************
* ChunkLoadClass::Open_Micro_Chunk -- reads in a micro-chunk header *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
bool ChunkLoadClass::Open_Micro_Chunk()
{
assert(!InMicroChunk);
// read the chunk header
// calling the ChunkLoadClass::Read fn so that if we exhaust the chunk, the read will fail
if (Read(&MCHeader,sizeof(MCHeader)) != sizeof(MCHeader)) {
return false;
}
InMicroChunk = true;
MicroChunkPosition = 0;
return true;
}
/***********************************************************************************************
* ChunkLoadClass::Close_Micro_Chunk -- closes a micro-chunk (seeks to end) *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
bool ChunkLoadClass::Close_Micro_Chunk()
{
assert(InMicroChunk);
InMicroChunk = false;
int csize = MCHeader.Get_Size();
int pos = MicroChunkPosition;
// seek the file past this micro chunk
if (pos < csize) {
File->Seek(csize - pos,SEEK_CUR);
// update the tracking variables for where we are in the normal chunk.
if (StackIndex > 0) {
PositionStack[StackIndex-1] += csize - pos;
}
}
return true;
}
/***********************************************************************************************
* ChunkLoadClass::Cur_Micro_Chunk_ID -- returns the ID of the current micro-chunk (asserts if *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* Asserts if you are not currently inside a micro-chunk *
* Micro chunks have an id between 0 and 255 *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Cur_Micro_Chunk_ID()
{
assert(InMicroChunk);
return MCHeader.Get_Type();
}
/***********************************************************************************************
* ChunkLoadClass::Cur_Micro_Chunk_Length -- returns the size of the current micro chunk *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* Asserts if you are not currently inside a micro-chunk *
* Micro chunks have a maximum size of 255 bytes *
* *
* HISTORY: *
* 9/3/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Cur_Micro_Chunk_Length()
{
assert(InMicroChunk);
return MCHeader.Get_Size();
}
// Seek over nbytes in the stream
uint32 ChunkLoadClass::Seek(uint32 nbytes)
{
assert(StackIndex >= 1);
// Don't seek if we would go past the end of the current chunk
if (PositionStack[StackIndex-1] + nbytes > (int)HeaderStack[StackIndex-1].Get_Size()) {
return 0;
}
// Don't read if we are in a micro chunk and would go past the end of it
if (InMicroChunk && MicroChunkPosition + nbytes > MCHeader.Get_Size()) {
return 0;
}
uint32 curpos=File->Tell();
if (File->Seek(nbytes,SEEK_CUR)-curpos != (int)nbytes) {
return 0;
}
// Update our position in the chunk
PositionStack[StackIndex-1] += nbytes;
// Update our position in the micro chunk if we are in one
if (InMicroChunk) {
MicroChunkPosition += nbytes;
}
return nbytes;
}
/***********************************************************************************************
* ChunkLoadClass::Read -- Read data from the file *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/17/1997 GH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Read(void * buf,uint32 nbytes)
{
assert(StackIndex >= 1);
// Don't read if we would go past the end of the current chunk
if (PositionStack[StackIndex-1] + nbytes > (int)HeaderStack[StackIndex-1].Get_Size()) {
return 0;
}
// Don't read if we are in a micro chunk and would go past the end of it
if (InMicroChunk && MicroChunkPosition + nbytes > MCHeader.Get_Size()) {
return 0;
}
if (File->Read(buf,nbytes) != (int)nbytes) {
return 0;
}
// Update our position in the chunk
PositionStack[StackIndex-1] += nbytes;
// Update our position in the micro chunk if we are in one
if (InMicroChunk) {
MicroChunkPosition += nbytes;
}
return nbytes;
}
/***********************************************************************************************
* ChunkLoadClass::Read -- read an IOVector2Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Read(IOVector2Struct * v)
{
assert(v != NULL);
return Read(v,sizeof(v));
}
/***********************************************************************************************
* ChunkLoadClass::Read -- read an IOVector3Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Read(IOVector3Struct * v)
{
assert(v != NULL);
return Read(v,sizeof(v));
}
/***********************************************************************************************
* ChunkLoadClass::Read -- read an IOVector4Struct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Read(IOVector4Struct * v)
{
assert(v != NULL);
return Read(v,sizeof(v));
}
/***********************************************************************************************
* ChunkLoadClass::Read -- read an IOQuaternionStruct *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/4/99 GTH : Created. *
*=============================================================================================*/
uint32 ChunkLoadClass::Read(IOQuaternionStruct * q)
{
assert(q != NULL);
return Read(q,sizeof(q));
}