//----------------------------------------------------------------------------- // // $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 ); } }