sin-sdk/archive.cpp

1165 lines
21 KiB
C++

//-----------------------------------------------------------------------------
//
// $Logfile:: /Quake 2 Engine/Sin/code/game/archive.cpp $
// $Revision:: 19 $
// $Author:: Aldie $
// $Date:: 11/12/98 3:46p $
//
// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/archive.cpp $
//
// 19 11/12/98 3:46p Aldie
// Fixed length check for files not found.
//
// 18 11/12/98 2:41p Markd
// added assert for pos in Read
//
// 17 11/12/98 12:19p Markd
// archive file was being loaded as TAG_GAME this was a VERY BAD thing.
//
// 16 11/12/98 2:31a Jimdose
// Added ReadFile. Archives now read from pak files
//
// 15 10/19/98 12:04a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
//
// 14 10/10/98 1:26a Jimdose
// ReadObject no longer cancels objects events
//
// 13 10/07/98 11:41p Jimdose
// Got savegames working!!!
// Rewrote event archiving
//
// 12 9/22/98 7:18p Markd
// forgot to free up a list of objects when closing the file
//
// 11 9/22/98 3:58a Jimdose
// Incremented the archive version number
//
// 10 9/21/98 10:15p Markd
// Putting archiving and unarchiving functions in
//
// 9 9/21/98 4:21p Markd
// Put in archive functions and rewrote all archive routines
//
// 8 7/26/98 2:15a Jimdose
// ReadObject was casting a Class * as a Entity *. Not bad, but wrong.
//
// 7 6/11/98 7:18p Jimdose
// FileError now closes the file before exiting. This prevents an assertion
// when the object is deleted but the file is still open.
//
// 6 5/24/98 8:46p Jimdose
// Made a lot of functions more str-friendly.
// Got rid of a lot of char * based strings
// Cleaned up get spawn arg functions and sound functions
// sound functions now use consistant syntax
//
// 5 5/24/98 4:48p Jimdose
// Made char *'s const
//
// 4 5/09/98 8:04p Jimdose
// Create now creates the path if it doesn't exist
//
// 3 5/08/98 2:50p Jimdose
// fixed bugs
//
// 2 5/07/98 10:39p Jimdose
// created file
//
// 1 5/06/98 8:19p Jimdose
//
// DESCRIPTION:
// Class for archiving objects
//
#include "g_local.h"
#include "archive.h"
#define ARCHIVE_WRITE 0
#define ARCHIVE_READ 1
enum
{
ARC_NULL, ARC_Vector, ARC_Integer, ARC_Unsigned, ARC_Byte, ARC_Char, ARC_Short, ARC_UnsignedShort,
ARC_Float, ARC_Double, ARC_Boolean, ARC_String, ARC_Raw, ARC_Object, ARC_ObjectPointer,
ARC_SafePointer, ARC_Event, ARC_Quat, ARC_Entity,
ARC_NUMTYPES
};
static const char *typenames[] =
{
"NULL", "vector", "int", "unsigned", "byte", "char", "short", "unsigned short",
"float", "double", "qboolean", "string", "raw data", "object", "objectpointer",
"safepointer", "event", "quaternion", "entity"
};
#define ArchiveHeader ( *( int * )"SIN\0" )
#define ArchiveVersion 2 // This must be changed any time the format changes!
#define ArchiveInfo "Sin Archive Version 2" // This must be changed any time the format changes!
CLASS_DECLARATION( Class, ReadFile, NULL );
ResponseDef ReadFile::Responses[] =
{
{ NULL, NULL }
};
ReadFile::ReadFile()
{
length = 0;
buffer = NULL;
pos = 0;
}
ReadFile::~ReadFile()
{
Close();
}
void ReadFile::Close
(
void
)
{
if ( buffer )
{
gi.TagFree( ( void * )buffer );
buffer = NULL;
}
filename = "";
length = 0;
pos = 0;
}
const char *ReadFile::Filename
(
void
)
{
return filename.c_str();
}
size_t ReadFile::Length
(
void
)
{
return length;
}
size_t ReadFile::Pos
(
void
)
{
return pos - buffer;
}
qboolean ReadFile::Seek
(
size_t newpos
)
{
if ( !buffer )
{
return false;
}
if ( newpos < 0 )
{
return false;
}
if ( newpos > length )
{
return false;
}
pos = buffer + newpos;
return true;
}
qboolean ReadFile::Open
(
const char *name
)
{
assert( name );
assert( !buffer );
Close();
if ( !name )
{
return false;
}
length = gi.LoadFile( name, ( void ** )&buffer, 0 );
if ( length == ( size_t )( -1 ) )
{
return false;
}
filename = name;
pos = buffer;
return true;
}
qboolean ReadFile::Read
(
void *dest,
size_t size
)
{
assert( dest );
assert( buffer );
assert( pos );
if ( !dest )
{
return false;
}
if ( size <= 0 )
{
return false;
}
if ( ( pos + size ) > ( buffer + length ) )
{
return false;
}
memcpy( dest, pos, size );
pos += size;
return true;
}
CLASS_DECLARATION( Class, Archiver, NULL );
ResponseDef Archiver::Responses[] =
{
{ NULL, NULL }
};
Archiver::Archiver()
{
file = NULL;
fileerror = false;
assert( ( sizeof( typenames ) / sizeof( typenames[ 0 ] ) ) == ARC_NUMTYPES );
}
Archiver::~Archiver()
{
if ( file )
{
Close();
}
readfile.Close();
}
void Archiver::FileError
(
const char *fmt,
...
)
{
va_list argptr;
char text[ 1024 ];
va_start( argptr, fmt );
vsprintf( text, fmt, argptr );
va_end( argptr );
fileerror = true;
Close();
if ( archivemode == ARCHIVE_READ )
{
gi.error( "Error while loading %s : %s\n", filename.c_str(), text );
}
else
{
gi.error( "Error while writing to %s : %s\n", filename.c_str(), text );
}
}
void Archiver::Close
(
void
)
{
if ( file )
{
if ( archivemode == ARCHIVE_WRITE )
{
// write out the number of classpointers
fseek( file, numclassespos, SEEK_SET );
numclassespos = ftell( file );
WriteInteger( classpointerList.NumObjects() );
}
fclose( file );
file = NULL;
}
readfile.Close();
if ( archivemode == ARCHIVE_READ )
{
int i, num;
Class * classptr;
pointer_fixup_t *fixup;
num = fixupList.NumObjects();
for( i = 1; i <= num; i++ )
{
fixup = fixupList.ObjectAt( i );
classptr = classpointerList.ObjectAt( fixup->index );
if ( fixup->type == pointer_fixup_normal )
{
Class ** fixupptr;
fixupptr = ( Class ** )fixup->ptr;
*fixupptr = classptr;
}
else if ( fixup->type == pointer_fixup_safe )
{
SafePtrBase * fixupptr;
fixupptr = ( SafePtrBase * )fixup->ptr;
fixupptr->InitSafePtr( classptr );
}
delete fixup;
}
fixupList.FreeObjectList();
classpointerList.FreeObjectList();
}
}
/****************************************************************************************
File Read functions
*****************************************************************************************/
void Archiver::Read
(
const char *name
)
{
unsigned header;
unsigned version;
str info;
int num;
int i;
Class *null;
assert( name );
if ( !name )
{
gi.error( "NULL pointer for filename in Archiver::Read.\n" );
}
fileerror = false;
archivemode = ARCHIVE_READ;
filename = name;
if ( !readfile.Open( filename.c_str() ) )
{
FileError( "Couldn't open file." );
}
header = ReadUnsigned();
if ( header != ArchiveHeader )
{
readfile.Close();
FileError( "Not a valid Sin archive." );
}
version = ReadUnsigned();
if ( version > ArchiveVersion )
{
readfile.Close();
FileError( "Archive is from version %.2f. Check http://www.ritual.com for an update.", version );
}
if ( version < ArchiveVersion )
{
readfile.Close();
FileError( "Archive is out of date." );
}
info = ReadString();
gi.dprintf( "%s\n", info.c_str() );
// setup out class pointers
num = ReadInteger();
classpointerList.Resize( num );
null = NULL;
for( i = 1; i <= num; i++ )
{
classpointerList.AddObject( null );
}
}
inline void Archiver::CheckRead
(
void
)
{
assert( archivemode == ARCHIVE_READ );
if ( !fileerror && ( archivemode != ARCHIVE_READ ) )
{
FileError( "File read during a write operation." );
}
}
inline int Archiver::ReadType
(
void
)
{
int t;
if ( !fileerror )
{
readfile.Read( &t, sizeof( t ) );
return t;
}
return ARC_NULL;
}
inline void Archiver::CheckType
(
int type
)
{
int t;
assert( ( type >= 0 ) && ( type < ARC_NUMTYPES ) );
if ( !fileerror )
{
t = ReadType();
if ( t != type )
{
FileError( "Expecting %s", typenames[ type ] );
}
}
}
inline size_t Archiver::ReadSize
(
void
)
{
size_t s;
s = 0;
if ( !fileerror )
{
readfile.Read( &s, sizeof( s ) );
}
return s;
}
inline void Archiver::CheckSize
(
int type,
size_t size
)
{
size_t s;
if ( !fileerror )
{
s = ReadSize();
if ( size != s )
{
FileError( "Invalid data size of %d on %s.", s, typenames[ type ] );
}
}
}
inline void Archiver::ReadData
(
int type,
void *data,
size_t size
)
{
CheckRead();
CheckType( type );
CheckSize( type, size );
if ( !fileerror && size )
{
readfile.Read( data, size );
}
}
#define READ( func, type ) \
type Archiver::Read##func \
( \
void \
) \
\
{ \
type v; \
\
ReadData( ARC_##func, &v, sizeof( type ) ); \
\
return v; \
}
READ( Vector, Vector );
READ( Integer, int );
READ( Unsigned, unsigned );
READ( Byte, byte );
READ( Char, char );
READ( Short, short );
READ( UnsignedShort, unsigned short );
READ( Float, float );
READ( Double, double );
READ( Boolean, qboolean );
READ( Quat, Quat );
#define READPTR( func, type ) \
void Archiver::Read##func \
( \
type * v \
) \
\
{ \
ReadData( ARC_##func, v, sizeof( type ) ); \
}
READPTR( Vector, Vector );
READPTR( Integer, int );
READPTR( Unsigned, unsigned );
READPTR( Byte, byte );
READPTR( Char, char );
READPTR( Short, short );
READPTR( UnsignedShort, unsigned short );
READPTR( Float, float );
READPTR( Double, double );
READPTR( Boolean, qboolean );
READPTR( Quat, Quat );
void Archiver::ReadObjectPointer
(
Class ** ptr
)
{
int index;
pointer_fixup_t *fixup;
ReadData( ARC_ObjectPointer, &index, sizeof( index ) );
// Check for a NULL pointer
assert( ptr );
if ( !ptr )
{
FileError( "NULL pointer in ReadObjectPointer." );
}
//
// see if the variable was NULL
//
if ( index == ARCHIVE_NULL_POINTER )
{
*ptr = NULL;
}
else
{
// init the pointer with NULL until we can fix it
*ptr = NULL;
fixup = new pointer_fixup_t;
fixup->ptr = ( void ** )ptr;
fixup->index = index;
fixup->type = pointer_fixup_normal;
fixupList.AddObject( fixup );
}
}
void Archiver::ReadSafePointer
(
SafePtrBase * ptr
)
{
int index;
pointer_fixup_t *fixup;
ReadData( ARC_SafePointer, &index, sizeof( &index ) );
// Check for a NULL pointer
assert( ptr );
if ( !ptr )
{
FileError( "NULL pointer in ReadSafePointer." );
}
//
// see if the variable was NULL
//
if ( index == ARCHIVE_NULL_POINTER )
{
ptr->InitSafePtr( NULL );
}
else
{
// init the pointer with NULL until we can fix it
ptr->InitSafePtr( NULL );
// Add new fixup
fixup = new pointer_fixup_t;
fixup->ptr = ( void ** )ptr;
fixup->index = index;
fixup->type = pointer_fixup_safe;
fixupList.AddObject( fixup );
}
}
Event Archiver::ReadEvent
(
void
)
{
Event ev;
CheckRead();
CheckType( ARC_Event );
if ( !fileerror )
{
ev.Unarchive( *this );
}
return ev;
}
void Archiver::ReadEvent
(
Event * ev
)
{
CheckRead();
CheckType( ARC_Event );
if ( !fileerror )
{
ev->Unarchive( *this );
}
}
void Archiver::ReadRaw
(
void *data,
size_t size
)
{
ReadData( ARC_Raw, data, size );
}
str Archiver::ReadString
(
void
)
{
size_t s;
char *data;
str string;
CheckRead();
CheckType( ARC_String );
if ( !fileerror )
{
s = ReadSize();
if ( !fileerror )
{
data = new char[ s + 1 ];
if ( s )
{
readfile.Read( data, s );
}
data[ s ] = 0;
string = data;
delete [] data;
}
}
return string;
}
void Archiver::ReadString
(
str * string
)
{
*string = ReadString();
}
Class *Archiver::ReadObject
(
void
)
{
ClassDef *cls;
Class *obj;
str classname;
long objstart;
long endpos;
int index;
size_t size;
qboolean isent;
int type;
CheckRead();
type = ReadType();
if ( ( type != ARC_Object ) && ( type != ARC_Entity ) )
{
FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] );
}
size = ReadSize();
classname = ReadString();
cls = getClass( classname.c_str() );
if ( !cls )
{
FileError( "Invalid class %s.", classname.c_str() );
}
isent = checkInheritance( &Entity::ClassInfo, cls );
if ( type == ARC_Entity )
{
if ( !isent )
{
FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() );
}
game.force_entnum = true;
game.spawn_entnum = ReadInteger();
}
else if ( isent )
{
FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() );
}
index = ReadInteger();
objstart = readfile.Pos();
obj = ( Class * )cls->newInstance();
if ( !obj )
{
FileError( "Failed to on new instance of class %s.", classname.c_str() );
}
else
{
obj->Unarchive( *this );
}
if ( isent )
{
game.force_entnum = false;
}
if ( !fileerror )
{
endpos = readfile.Pos();
if ( ( endpos - objstart ) > size )
{
FileError( "Object read past end of object's data" );
}
else if ( ( endpos - objstart ) < size )
{
FileError( "Object didn't read entire data from file" );
}
}
//
// register this pointer with our list
//
classpointerList.AddObjectAt( index, obj );
return obj;
}
Class *Archiver::ReadObject
(
Class *obj
)
{
ClassDef *cls;
str classname;
long objstart;
long endpos;
int index;
size_t size;
int type;
qboolean isent;
CheckRead();
type = ReadType();
if ( ( type != ARC_Object ) && ( type != ARC_Entity ) )
{
FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] );
}
size = ReadSize();
classname = ReadString();
cls = getClass( classname.c_str() );
if ( !cls )
{
FileError( "Invalid class %s.", classname.c_str() );
}
if ( obj->classinfo() != cls )
{
FileError( "Archive has a '%s' object, but was expecting a '%s' object.", classname.c_str(), obj->getClassname() );
}
isent = obj->isSubclassOf( Entity );
if ( type == ARC_Entity )
{
if ( !isent )
{
FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() );
}
( ( Entity * )obj )->SetEntNum( ReadInteger() );
}
else if ( isent )
{
FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() );
}
index = ReadInteger();
objstart = readfile.Pos();
obj->Unarchive( *this );
if ( !fileerror )
{
endpos = readfile.Pos();
if ( ( endpos - objstart ) > size )
{
FileError( "Object read past end of object's data" );
}
else if ( ( endpos - objstart ) < size )
{
FileError( "Object didn't read entire data from file" );
}
}
//
// register this pointer with our list
//
classpointerList.AddObjectAt( index, obj );
return obj;
}
/****************************************************************************************
File Write functions
*****************************************************************************************/
void Archiver::Create
(
const char *name
)
{
assert( name );
if ( !name )
{
gi.error( "NULL pointer for filename in Archiver::Create.\n" );
}
fileerror = false;
archivemode = ARCHIVE_WRITE;
filename = name;
gi.CreatePath( filename.c_str() );
file = fopen( filename.c_str(), "wb" );
if ( !file )
{
FileError( "Couldn't open file." );
}
WriteUnsigned( ArchiveHeader );
WriteUnsigned( ArchiveVersion );
WriteString( str( ArchiveInfo ) );
numclassespos = ftell( file );
WriteInteger( 0 );
}
inline void Archiver::CheckWrite
(
void
)
{
assert( archivemode == ARCHIVE_WRITE );
if ( !fileerror && ( archivemode != ARCHIVE_WRITE ) )
{
FileError( "File write during a read operation." );
}
}
inline void Archiver::WriteType
(
int type
)
{
fwrite( &type, sizeof( type ), 1, file );
}
inline void Archiver::WriteSize
(
size_t size
)
{
fwrite( &size, sizeof( size ), 1, file );
}
inline void Archiver::WriteData
(
int type,
const void *data,
size_t size
)
{
CheckWrite();
WriteType( type );
WriteSize( size );
if ( !fileerror && size )
{
fwrite( data, size, 1, file );
}
}
#define WRITE( func, type ) \
void Archiver::Write##func \
( \
type v \
) \
\
{ \
WriteData( ARC_##func, &v, sizeof( type ) ); \
}
WRITE( Vector, Vector & );
WRITE( Quat, Quat & );
WRITE( Integer, int );
WRITE( Unsigned, unsigned );
WRITE( Byte, byte );
WRITE( Char, char );
WRITE( Short, short );
WRITE( UnsignedShort, unsigned short );
WRITE( Float, float );
WRITE( Double, double );
WRITE( Boolean, qboolean );
void Archiver::WriteRaw
(
const void *data,
size_t size
)
{
WriteData( ARC_Raw, data, size );
}
void Archiver::WriteString
(
str &string
)
{
WriteData( ARC_String, string.c_str(), string.length() );
}
void Archiver::WriteObject
(
Class *obj
)
{
str classname;
long sizepos;
long objstart;
long endpos;
int index;
size_t size;
qboolean isent;
assert( obj );
if ( !obj )
{
FileError( "NULL object in WriteObject" );
}
isent = obj->isSubclassOf( Entity );
CheckWrite();
if ( isent )
{
WriteType( ARC_Entity );
}
else
{
WriteType( ARC_Object );
}
sizepos = ftell( file );
size = 0;
WriteSize( size );
classname = obj->getClassname();
WriteString( classname );
if ( isent )
{
// Write out the entity number
WriteInteger( ( ( Entity * )obj )->entnum );
}
// write out pointer index for this class pointer
index = classpointerList.AddUniqueObject( obj );
WriteInteger( index );
if ( !fileerror )
{
objstart = ftell( file );
obj->Archive( *this );
}
if ( !fileerror )
{
endpos = ftell( file );
size = endpos - objstart;
fseek( file, sizepos, SEEK_SET );
WriteSize( size );
if ( !fileerror )
{
fseek( file, endpos, SEEK_SET );
}
}
}
void Archiver::WriteObjectPointer
(
Class * ptr
)
{
int index;
if ( ptr )
{
index = classpointerList.AddUniqueObject( ptr );
}
else
{
index = ARCHIVE_NULL_POINTER;
}
WriteData( ARC_ObjectPointer, &index, sizeof( index ) );
}
void Archiver::WriteSafePointer
(
Class * ptr
)
{
int index;
if ( ptr )
{
index = classpointerList.AddUniqueObject( ptr );
}
else
{
index = ARCHIVE_NULL_POINTER;
}
WriteData( ARC_SafePointer, &index, sizeof( index ) );
}
void Archiver::WriteEvent
(
Event &ev
)
{
CheckWrite();
WriteType( ARC_Event );
//FIXME!!!! Make this handle null events
if ( &ev == NULL )
{
NullEvent.Archive( *this );
}
else
{
ev.Archive( *this );
}
}