//----------------------------------------------------------------------------- // // $Logfile:: /EF2/Code/DLLs/game/archive.cpp $ // $Revision:: 12 $ // $Author:: Singlis $ // $Date:: 9/26/03 2:35p $ // // 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. // // // DESCRIPTION: // Class for archiving objects // #include "_pch_cpp.h" #include "archive.h" enum { ARC_NULL, ARC_Vector, ARC_Vec3, ARC_Vec4, 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_EventPointer, ARC_Quat, ARC_Entity, ARC_Bool, ARC_NUMTYPES }; static const char *typenames[] = { "NULL", "vector", "vec3", "vec4", "int", "unsigned", "byte", "char", "short", "unsigned short", "float", "double", "qboolean", "string", "raw data", "object", "objectpointer", "safepointer", "event", "eventpointer", "quaternion", "entity", "bool" }; #define ArchiveHeader ( *( int * )"EF2" ) #define ArchiveVersion 2 // This must be changed any time the format changes! #define ArchiveInfo "EF2 Archive Version 2" // This must be changed any time the format changes! CLASS_DECLARATION( Class, FileRead, NULL ) { { NULL, NULL } }; FileRead::FileRead() { length = 0; buffer = NULL; pos = 0; } FileRead::~FileRead() { Close(); } void FileRead::Close( void ) { if ( buffer ) { gi.Free( ( void * )buffer ); buffer = NULL; } filename = ""; length = 0; pos = 0; } const char *FileRead::Filename( void ) { return filename.c_str(); } size_t FileRead::Length( void ) { return length; } size_t FileRead::Pos( void ) { return pos - buffer; } qboolean FileRead::Seek( size_t newpos ) { if ( !buffer ) { return false; } if ( newpos > length ) { return false; } pos = buffer + newpos; return true; } qboolean FileRead::Open( const char *name ) { byte *tempbuf; assert( name ); assert( !buffer ); Close(); if ( !name ) { return false; } length = gi.FS_ReadFile( name, ( void ** )&tempbuf, true ); if ( length == ( size_t )( -1 ) ) { return false; } // create our own space buffer = ( byte * )gi.Malloc( length ); // copy the file over to our space memcpy( buffer, tempbuf, length ); // free the file gi.FS_FreeFile( tempbuf ); filename = name; pos = buffer; return true; } qboolean FileRead::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 ) { { NULL, NULL } }; Archiver::Archiver() { file = 0; fileerror = false; harderror = true; archivemode = ARCHIVE_READ; numclassespos = 0; 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 ) { if ( harderror ) { gi.Error( ERR_DROP, "Error while loading %s : %s\n", filename.c_str(), text ); } else { gi.WPrintf( "Error while loading %s : %s\n", filename.c_str(), text ); } } else { if ( harderror ) { gi.Error( ERR_DROP, "Error while writing to %s : %s\n", filename.c_str(), text ); } else { gi.WPrintf( "Error while writing to %s : %s\n", filename.c_str(), text ); } } } void Archiver::Close( void ) { if ( file ) { if ( archivemode == ARCHIVE_WRITE ) { int numobjects; // write out the number of classpointers gi.FS_FSeek( file, numclassespos, FS_SEEK_SET ); numclassespos = gi.FS_FTell( file ); numobjects = classpointerList.NumObjects(); ArchiveInteger( &numobjects ); } gi.FS_FCloseFile( file ); file = 0; } 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 ); // Make sure the ptr isn't screwed up assert( !( ( classptr == NULL ) && ( fixup->index != ARCHIVE_NULL_POINTER ) ) ); 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/Write functions *****************************************************************************************/ qboolean Archiver::Read( const char *name, qboolean file_harderror ) { unsigned header; unsigned version; str info; int num; int i; Class *null; harderror = file_harderror; assert( name ); if ( !name ) { FileError( "NULL pointer for filename in Archiver::Read.\n" ); return false; } fileerror = false; archivemode = ARCHIVE_READ; filename = name; if ( !readfile.Open( filename.c_str() ) ) { if ( file_harderror ) { FileError( "Couldn't open file." ); } return false; } ArchiveUnsigned( &header ); if ( header != ArchiveHeader ) { readfile.Close(); FileError( "Not a valid archive." ); return false; } ArchiveUnsigned( &version ); if ( version > ArchiveVersion ) { readfile.Close(); FileError( "Archive is from version %.2f. Check http://www.ritual.com for an update.", version ); return false; } if ( version < ArchiveVersion ) { readfile.Close(); FileError( "Archive is out of date." ); return false; } ArchiveString( &info ); gi.DPrintf( "%s\n", info.c_str() ); // setup out class pointers ArchiveInteger( &num ); classpointerList.Resize( num ); null = NULL; for( i = 1; i <= num; i++ ) { classpointerList.AddObject( null ); } return true; } qboolean Archiver::Create( const char *name, qboolean file_harderror ) { unsigned header; unsigned version; str info; int numZero = 0; harderror = file_harderror; assert( name ); if ( !name ) { FileError( "NULL pointer for filename in Archiver::Create.\n" ); return false; } fileerror = false; archivemode = ARCHIVE_WRITE; filename = name; file = gi.FS_FOpenFileWrite( filename.c_str() ); if ( !file ) { FileError( "Couldn't open file." ); return false; } header = ArchiveHeader; ArchiveUnsigned( &header ); version = ArchiveVersion; ArchiveUnsigned( &version ); info = ArchiveInfo; ArchiveString( &info ); numclassespos = gi.FS_FTell( file ); ArchiveInteger( &numZero ); return true; } inline void Archiver::CheckRead( void ) { assert( archivemode == ARCHIVE_READ ); if ( !fileerror && ( archivemode != ARCHIVE_READ ) ) { FileError( "File read during a write operation." ); } } inline void Archiver::CheckWrite( void ) { assert( archivemode == ARCHIVE_WRITE ); if ( !fileerror && ( archivemode != ARCHIVE_WRITE ) ) { FileError( "File write during a read operation." ); } } 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::WriteSize( size_t size ) { gi.FS_Write( &size, sizeof( size ), file ); } inline int Archiver::ReadType( void ) { int t; if ( !fileerror ) { readfile.Read( &t, sizeof( t ) ); return t; } return ARC_NULL; } inline void Archiver::WriteType( int type ) { gi.FS_Write( &type, sizeof( type ), file ); } 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 ] ); } } } /**************************************************************************************** File Archive functions *****************************************************************************************/ //#define ARCHIVE_USE_TYPES 1 inline void Archiver::ArchiveData( int type, void *data, size_t size ) { if ( archivemode == ARCHIVE_READ ) { #ifndef NDEBUG CheckRead(); #endif #ifdef ARCHIVE_USE_TYPES CheckType( type ); #endif if ( !fileerror && size ) { readfile.Read( data, size ); } } else { #ifndef NDEBUG CheckWrite(); #endif #ifdef ARCHIVE_USE_TYPES WriteType( type ); #endif if ( !fileerror && size ) { gi.FS_Write( data, size, file ); } } } #define ARCHIVE( func, type ) \ void Archiver::Archive##func \ ( \ type * v \ ) \ \ { \ ArchiveData( ARC_##func, v, sizeof( type ) ); \ } ARCHIVE( Vector, Vector ); ARCHIVE( Integer, int ); ARCHIVE( Unsigned, unsigned ); ARCHIVE( Byte, byte ); ARCHIVE( Char, char ); ARCHIVE( Short, short ); ARCHIVE( UnsignedShort, unsigned short ); ARCHIVE( Float, float ); ARCHIVE( Double, double ); ARCHIVE( Boolean, qboolean ); ARCHIVE( Quat, Quat ); ARCHIVE( Bool, bool ); void Archiver::ArchiveVec3( vec3_t vec ) { ArchiveData( ARC_Vec3, vec, sizeof( vec3_t ) ); } void Archiver::ArchiveVec4( vec4_t vec ) { ArchiveData( ARC_Vec4, vec, sizeof( vec4_t ) ); } void Archiver::ArchiveObjectPointer( Class ** ptr ) { int index = 0; if ( archivemode == ARCHIVE_READ ) { pointer_fixup_t *fixup; ArchiveData( ARC_ObjectPointer, &index, sizeof( index ) ); // Check for a NULL pointer assert( ptr ); if ( !ptr ) { FileError( "NULL pointer in ArchiveObjectPointer." ); } // // 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 ); } } else { if ( *ptr ) { index = classpointerList.AddUniqueObject( *ptr ); } else { index = ARCHIVE_NULL_POINTER; } ArchiveData( ARC_ObjectPointer, &index, sizeof( index ) ); } } void Archiver::ArchiveSafePointer( SafePtrBase * ptr ) { int index = 0; if ( archivemode == ARCHIVE_READ ) { pointer_fixup_t *fixup; ArchiveData( 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 ); } } else { if ( ptr->Pointer() ) { Class * obj; obj = ptr->Pointer(); index = classpointerList.AddUniqueObject( obj ); } else { index = ARCHIVE_NULL_POINTER; } ArchiveData( ARC_SafePointer, &index, sizeof( index ) ); } } void Archiver::ArchiveEvent( Event * ev ) { if ( archivemode == ARCHIVE_READ ) { #ifndef NDEBUG CheckRead(); #endif #ifdef ARCHIVE_USE_TYPES CheckType( ARC_Event ); #endif if ( !fileerror ) { ev->Archive( *this ); } } else { #ifndef NDEBUG CheckWrite(); #endif #ifdef ARCHIVE_USE_TYPES WriteType( ARC_Event ); #endif //FIXME!!!! Make this handle null events if ( ev == NULL ) { NullEvent.Archive( *this ); } else { ev->Archive( *this ); } } } void Archiver::ArchiveEventPointer( Event ** ev ) { int index; if ( archivemode == ARCHIVE_READ ) { #ifndef NDEBUG CheckRead(); #endif #ifdef ARCHIVE_USE_TYPES CheckType( ARC_EventPointer ); #endif ArchiveInteger( &index ); if ( !fileerror ) { if ( index == ARCHIVE_POINTER_VALID ) { *ev = new Event; (*ev)->Archive( *this ); } else { (*ev) = NULL; } } } else { #ifndef NDEBUG CheckWrite(); #endif if ( *ev ) { index = ARCHIVE_POINTER_VALID; } else { index = ARCHIVE_NULL_POINTER; } #ifdef ARCHIVE_USE_TYPES WriteType( ARC_EventPointer ); #endif ArchiveInteger( &index ); if ( *ev ) { (*ev)->Archive( *this ); } } } void Archiver::ArchiveRaw( void *data, size_t size ) { ArchiveData( ARC_Raw, data, size ); } void Archiver::ArchiveString( str * string ) { if ( archivemode == ARCHIVE_READ ) { size_t s; char *data; #ifndef NDEBUG CheckRead(); #endif #ifdef ARCHIVE_USE_TYPES CheckType( ARC_String ); #endif if ( !fileerror ) { s = ReadSize(); if ( !fileerror ) { data = new char[ s + 1 ]; if ( data ) { if ( s ) { readfile.Read( data, s ); } data[ s ] = 0; *string = data; delete [] data; } } } } else { #ifndef NDEBUG CheckWrite(); #endif #ifdef ARCHIVE_USE_TYPES WriteType( ARC_String ); #endif WriteSize( string->length() ); //gi.FS_Write( string->m_data->data, string->length(), file ); gi.FS_Write( string->c_str(), string->length(), file ); } } 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(); ArchiveString( &classname ); 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() ); } ArchiveInteger( &level.spawn_entnum ); // // make sure to setup spawnflags properly // ArchiveInteger( &level.spawnflags ); } else if ( isent ) { FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); } ArchiveInteger( &index ); objstart = readfile.Pos(); obj = ( Class * )cls->newInstance(); if ( !obj ) { FileError( "Failed to on new instance of class %s.", classname.c_str() ); } else { obj->Archive( *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; } void Archiver::ArchiveObject( Class *obj ) { str classname; int index; size_t size; qboolean isent; if ( archivemode == ARCHIVE_READ ) { ClassDef *cls; long objstart; long endpos; 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(); ArchiveString( &classname ); 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 ) { int entnum; if ( !isent ) { FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() ); } ArchiveInteger( &entnum ); ( ( Entity * )obj )->SetEntNum( entnum ); // // make sure to setup spawnflags properly // ArchiveInteger( &level.spawnflags ); } else if ( isent ) { FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); } ArchiveInteger( &index ); objstart = readfile.Pos(); obj->Archive( *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 ); } else { long sizepos; long objstart = 0; long endpos; assert( obj ); if ( !obj ) { FileError( "NULL object in WriteObject" ); } isent = obj->isSubclassOf( Entity ); CheckWrite(); if ( isent ) { WriteType( ARC_Entity ); } else { WriteType( ARC_Object ); } sizepos = gi.FS_FTell( file ); size = 0; WriteSize( size ); classname = obj->getClassname(); ArchiveString( &classname ); if ( isent ) { // Write out the entity number ArchiveInteger( &( ( Entity * )obj )->entnum ); // // make sure to setup spawnflags properly // ArchiveInteger( &( ( Entity * )obj )->spawnflags ); } // write out pointer index for this class pointer index = classpointerList.AddUniqueObject( obj ); ArchiveInteger( &index ); if ( !fileerror ) { objstart = gi.FS_FTell( file ); obj->Archive( *this ); } if ( !fileerror ) { endpos = gi.FS_FTell( file ); size = endpos - objstart; gi.FS_FSeek( file, sizepos, FS_SEEK_SET ); WriteSize( size ); if ( !fileerror ) { gi.FS_FSeek( file, endpos, FS_SEEK_SET ); } } } }