/* ======================== idProfileMgr::OnLoadSettingsCompleted ======================== */ void idProfileMgr::OnLoadSettingsCompleted( idSaveLoadParms* parms ) { // Don't process if error already detected if( parms->errorCode != SAVEGAME_E_NONE ) { return; } // Serialize the loaded profile idFile_SaveGame** profileFileContainer = FindFromGenericPtr( parms->files, SAVEGAME_PROFILE_FILENAME ); idFile_SaveGame* profileFile = profileFileContainer == NULL ? NULL : *profileFileContainer; bool foundProfile = profileFile != NULL && profileFile->Length() > 0; if( foundProfile ) { idTempArray< byte > buffer( MAX_PROFILE_SIZE ); // Serialize settings from this buffer profileFile->MakeReadOnly(); unsigned int originalChecksum; profileFile->ReadBig( originalChecksum ); int dataLength = profileFile->Length() - ( int )sizeof( originalChecksum ); profileFile->ReadBigArray( buffer.Ptr(), dataLength ); // Validate the checksum before we let the game serialize the settings unsigned int checksum = MD5_BlockChecksum( buffer.Ptr(), dataLength ); if( originalChecksum != checksum ) { idLib::Warning( "Checksum: 0x%08x, originalChecksum: 0x%08x, size = %d", checksum, originalChecksum, dataLength ); parms->errorCode = SAVEGAME_E_CORRUPTED; } else { idBitMsg msg; msg.InitRead( buffer.Ptr(), ( int )buffer.Size() ); idSerializer ser( msg, false ); if( !profile->Serialize( ser ) ) { parms->errorCode = SAVEGAME_E_CORRUPTED; } } } else { parms->errorCode = SAVEGAME_E_FILE_NOT_FOUND; } }
/* ============================ idFile_SaveGamePipelined::CompressBlock Called when an uncompressed block fills up, and also to flush the final partial block. Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes) Modifies: dataZlib bytesZlib compressed compressedProducedBytes zStream zStreamEndHit ============================ */ void idFile_SaveGamePipelined::CompressBlock() { zStream.next_in = ( Bytef* )dataZlib; zStream.avail_in = ( uInt ) bytesZlib; dataZlib = NULL; bytesZlib = 0; // if this is the finish block, we may need to write // multiple buffers even after all input has been consumed while( zStream.avail_in > 0 || zLibFlushType == Z_FINISH ) { const int zstat = deflate( &zStream, zLibFlushType ); if( zstat != Z_OK && zstat != Z_STREAM_END ) { idLib::FatalError( "idFile_SaveGamePipelined::CompressBlock: deflate() returned %i", zstat ); } if( zStream.avail_out == 0 || zLibFlushType == Z_FINISH ) { if( sgf_checksums.GetBool() ) { size_t blockSize = zStream.total_out + numChecksums * sizeof( uint32 ) - compressedProducedBytes; uint32 checksum = MD5_BlockChecksum( zStream.next_out - blockSize, blockSize ); zStream.next_out[0] = ( ( checksum >> 0 ) & 0xFF ); zStream.next_out[1] = ( ( checksum >> 8 ) & 0xFF ); zStream.next_out[2] = ( ( checksum >> 16 ) & 0xFF ); zStream.next_out[3] = ( ( checksum >> 24 ) & 0xFF ); numChecksums++; } // flush the output buffer IO compressedProducedBytes = zStream.total_out + numChecksums * sizeof( uint32 ); FlushCompressedBlock(); if( zstat == Z_STREAM_END ) { assert( zLibFlushType == Z_FINISH ); zStreamEndHit = true; return; } assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) ); zStream.avail_out = COMPRESSED_BLOCK_SIZE; zStream.next_out = ( Bytef* )&compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ]; if( sgf_checksums.GetBool() ) { zStream.avail_out -= sizeof( uint32 ); } } }
void rvInstance::BuildInstanceMessage( void ) { // Build the client join instance msg mapEntityMsg.BeginWriting(); mapEntityMsg.Init( mapEntityMsgBuf, sizeof( byte ) * MAX_GAME_MESSAGE_SIZE ); mapEntityMsg.WriteByte( GAME_RELIABLE_MESSAGE_SET_INSTANCE ); mapEntityMsg.WriteByte( instanceID ); mapEntityMsg.WriteShort( initialSpawnCount ); // we need to send that down for tourney so the index will match mapEntityMsg.WriteLong( gameLocal.GetStartingIndexForInstance( instanceID ) ); LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); //common->Printf( "pop: server checksum: 0x%x\n", checksum ); mapEntityMsg.WriteLong( checksum ); }
/* ======================== idSaveGameProcessorSaveProfile::InitSaveProfile ======================== */ bool idSaveGameProcessorSaveProfile::InitSaveProfile( idPlayerProfile* profile_, const char* folder ) { // Serialize the profile and pass a file to the processor profileFile = new( TAG_SAVEGAMES ) idFile_SaveGame( SAVEGAME_PROFILE_FILENAME, SAVEGAMEFILE_BINARY | SAVEGAMEFILE_AUTO_DELETE ); profileFile->MakeWritable(); profileFile->SetMaxLength( MAX_PROFILE_SIZE ); // Create a serialization object and let the game serialize the settings into the buffer const int serializeSize = MAX_PROFILE_SIZE - 8; // -8 for checksum (all platforms) and length (on 360) idTempArray< byte > buffer( serializeSize ); idBitMsg msg; msg.InitWrite( buffer.Ptr(), serializeSize ); idSerializer ser( msg, true ); profile_->Serialize( ser ); // Get and write the checksum & length first unsigned int checksum = MD5_BlockChecksum( msg.GetReadData(), msg.GetSize() ); profileFile->WriteBig( checksum ); idLib::PrintfIf( profile_verbose.GetBool(), "checksum: 0x%08x, length: %d\n", checksum, msg.GetSize() ); // Add data to the file and prepare for save profileFile->Write( msg.GetReadData(), msg.GetSize() ); profileFile->MakeReadOnly(); saveFileEntryList_t files; files.Append( profileFile ); idSaveGameDetails description; if( !idSaveGameProcessorSaveFiles::InitSave( folder, files, description, idSaveGameManager::PACKAGE_PROFILE ) ) { return false; } profile = profile_; return true; }
/* ======================== idSaveGameThread::SaveGame ======================== */ int idSaveGameThread::Save() { idLocalUserWin * user = GetLocalUserFromSaveParms( data ); if ( user == NULL ) { data.saveLoadParms->errorCode = SAVEGAME_E_INVALID_USER; return -1; } idSaveLoadParms * callback = data.saveLoadParms; idStr saveFolder = "savegame"; saveFolder.AppendPath( callback->directory ); // Check for the required storage space. int64 requiredSizeBytes = 0; { for ( int i = 0; i < callback->files.Num(); i++ ) { idFile_SaveGame * file = callback->files[i]; requiredSizeBytes += ( file->Length() + sizeof( unsigned int ) ); // uint for checksum if ( file->type == SAVEGAMEFILE_PIPELINED ) { requiredSizeBytes += MIN_SAVEGAME_SIZE_BYTES; } } } int ret = ERROR_SUCCESS; // Check size of previous files if needed // ALL THE FILES RIGHT NOW---- could use pattern later... idStrList filesToDelete; if ( ( callback->mode & SAVEGAME_MBF_DELETE_FILES ) && !callback->cancelled ) { if ( fileSystem->IsFolder( saveFolder.c_str(), "fs_savePath" ) == FOLDER_YES ) { idFileList * files = fileSystem->ListFilesTree( saveFolder.c_str(), "*.*" ); for ( int i = 0; i < files->GetNumFiles(); i++ ) { requiredSizeBytes -= fileSystem->GetFileLength( files->GetFile( i ) ); filesToDelete.Append( files->GetFile( i ) ); } fileSystem->FreeFileList( files ); } } // Inform user about size required if necessary if ( requiredSizeBytes > 0 && !callback->cancelled ) { user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); if ( callback->requiredSpaceInBytes > 0 ) { // check to make sure savepath actually exists before erroring idStr directory = fs_savepath.GetString(); directory += "\\"; // so it doesn't think the last part is a file and ignores in the directory creation fileSystem->CreateOSPath( directory ); // we can't actually check FileExists in production builds, so just try to create it user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); if ( callback->requiredSpaceInBytes > 0 ) { callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; // safe to return, haven't written any files yet return -1; } } } // Delete all previous files if needed // ALL THE FILES RIGHT NOW---- could use pattern later... for ( int i = 0; i < filesToDelete.Num() && !callback->cancelled; i++ ) { fileSystem->RemoveFile( filesToDelete[i].c_str() ); } // Save the raw files. for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { idFile_SaveGame * file = callback->files[i]; idStr fileName = saveFolder; fileName.AppendPath( file->GetName() ); idStr tempFileName = va( "%s.temp", fileName.c_str() ); idFile * outputFile = fileSystem->OpenFileWrite( tempFileName, "fs_savePath" ); if ( outputFile == NULL ) { idLib::Warning( "[%s]: Couldn't open file for writing, %s. Error = %08x", __FUNCTION__, tempFileName.c_str(), GetLastError() ); file->error = true; callback->errorCode = SAVEGAME_E_UNKNOWN; ret = -1; continue; } if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { idFile_SaveGamePipelined * inputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); assert( inputFile != NULL ); blockForIO_t block; while ( inputFile->NextWriteBlock( & block ) ) { if ( (size_t)outputFile->Write( block.data, block.bytes ) != block.bytes ) { idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); file->error = true; callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; ret = -1; break; } } } else { if ( ( file->type & SAVEGAMEFILE_BINARY ) || ( file->type & SAVEGAMEFILE_COMPRESSED ) ) { if ( saveGame_checksum.GetBool() ) { unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); size_t size = outputFile->WriteBig( checksum ); if ( size != sizeof( checksum ) ) { idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); file->error = true; callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; ret = -1; } } } size_t size = outputFile->Write( file->GetDataPtr(), file->Length() ); if ( size != (size_t)file->Length() ) { idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); file->error = true; callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; ret = -1; } else { idLib::PrintfIf( saveGame_verbose.GetBool(), "Saved %s (%s)\n", fileName.c_str(), outputFile->GetFullPath() ); } } delete outputFile; if ( ret == ERROR_SUCCESS ) { // Remove the old file if ( !fileSystem->RenameFile( tempFileName, fileName, "fs_savePath" ) ) { idLib::Warning( "Could not start to rename temporary file %s to %s.", tempFileName.c_str(), fileName.c_str() ); } } else { fileSystem->RemoveFile( tempFileName ); idLib::Warning( "Invalid write to temporary file %s.", tempFileName.c_str() ); } } if ( data.saveLoadParms->cancelled ) { data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; } // Removed because it seemed a bit drastic #if 0 // If there is an error, delete the partially saved folder if ( callback->errorCode != SAVEGAME_E_NONE ) { if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); for ( int i = 0; i < files->GetNumFiles(); i++ ) { fileSystem->RemoveFile( files->GetFile( i ) ); } fileSystem->FreeFileList( files ); fileSystem->RemoveDir( saveFolder ); } } #endif return ret; }
/* ======================== idSessionLocal::LoadGame ======================== */ int idSaveGameThread::Load() { idSaveLoadParms * callback = data.saveLoadParms; idStr saveFolder = "savegame"; saveFolder.AppendPath( callback->directory ); if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) != FOLDER_YES ) { callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; return -1; } int ret = ERROR_SUCCESS; for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { idFile_SaveGame * file = callback->files[i]; idStr filename = saveFolder; filename.AppendPath( file->GetName() ); idFile * inputFile = fileSystem->OpenFileRead( filename.c_str() ); if ( inputFile == NULL ) { file->error = true; if ( !( file->type & SAVEGAMEFILE_OPTIONAL ) ) { callback->errorCode = SAVEGAME_E_CORRUPTED; ret = -1; } continue; } if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { idFile_SaveGamePipelined * outputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); assert( outputFile != NULL ); size_t lastReadBytes = 0; blockForIO_t block; while ( outputFile->NextReadBlock( &block, lastReadBytes ) && !callback->cancelled ) { lastReadBytes = inputFile->Read( block.data, block.bytes ); if ( lastReadBytes != block.bytes ) { // Notify end-of-file to the save game file which will cause all reads on the // other end of the pipeline to return zero bytes after the pipeline is drained. outputFile->NextReadBlock( NULL, lastReadBytes ); break; } } } else { size_t size = inputFile->Length(); unsigned int originalChecksum = 0; if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { if ( saveGame_checksum.GetBool() ) { if ( size >= sizeof( originalChecksum ) ) { inputFile->ReadBig( originalChecksum ); size -= sizeof( originalChecksum ); } } } file->SetLength( size ); size_t sizeRead = inputFile->Read( (void *)file->GetDataPtr(), size ); if ( sizeRead != size ) { file->error = true; callback->errorCode = SAVEGAME_E_CORRUPTED; ret = -1; } if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { if ( saveGame_checksum.GetBool() ) { unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); if ( checksum != originalChecksum ) { file->error = true; callback->errorCode = SAVEGAME_E_CORRUPTED; ret = -1; } } } } delete inputFile; } if ( data.saveLoadParms->cancelled ) { data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; } return ret; }
/* ================ idDeclParticle::Parse ================ */ bool idDeclParticle::Parse( const char* text, const int textLength, bool allowBinaryVersion ) { if( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { fileSystem->AddParticlePreload( GetName() ); } idLexer src; idToken token; unsigned int sourceChecksum = 0; idStrStatic< MAX_OSPATH > generatedFileName; if( allowBinaryVersion ) { // Try to load the generated version of it // If successful, // - Create an MD5 of the hash of the source // - Load the MD5 of the generated, if they differ, create a new generated generatedFileName = "generated/particles/"; generatedFileName.AppendPath( GetName() ); generatedFileName.SetFileExtension( ".bprt" ); idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); sourceChecksum = MD5_BlockChecksum( text, textLength ); if( binaryLoadParticles.GetBool() && LoadBinary( file, sourceChecksum ) ) { return true; } } src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); src.SetFlags( DECL_LEXER_FLAGS ); src.SkipUntilString( "{" ); depthHack = 0.0f; while( 1 ) { if( !src.ReadToken( &token ) ) { break; } if( !token.Icmp( "}" ) ) { break; } if( !token.Icmp( "{" ) ) { if( stages.Num() >= MAX_PARTICLE_STAGES ) { src.Error( "Too many particle stages" ); MakeDefault(); return false; } idParticleStage* stage = ParseParticleStage( src ); if( !stage ) { src.Warning( "Particle stage parse failed" ); MakeDefault(); return false; } stages.Append( stage ); continue; } if( !token.Icmp( "depthHack" ) ) { depthHack = src.ParseFloat(); continue; } src.Warning( "bad token %s", token.c_str() ); MakeDefault(); return false; } // don't calculate bounds or write binary files for defaulted ( non-existent ) particles in resource builds if( fileSystem->UsingResourceFiles() ) { bounds = idBounds( vec3_origin ).Expand( 8.0f ); return true; } // // calculate the bounds // bounds.Clear(); for( int i = 0; i < stages.Num(); i++ ) { GetStageBounds( stages[i] ); bounds.AddBounds( stages[i]->bounds ); } if( bounds.GetVolume() <= 0.1f ) { bounds = idBounds( vec3_origin ).Expand( 8.0f ); } if( allowBinaryVersion && binaryLoadParticles.GetBool() ) { idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); WriteBinary( outputFile, sourceChecksum ); } return true; }
/* ======================== idSnapShot::WriteObject ======================== */ void idSnapShot::WriteObject( idFile* file, int visIndex, objectState_t* newState, objectState_t* oldState, int& lastobjectNum ) { assert( newState != NULL || oldState != NULL ); bool visChange = false; // visibility changes will be signified with a 0xffff state size bool visSendState = false; // the state is sent when an entity is no longer stale // Compute visibility changes // (we need to do this before writing out object id, because we may not need to write out the id if we early out) // (when we don't write out the id, we assume this is an "ack" when we deserialize the objects) if( newState != NULL && oldState != NULL ) { // Check visibility assert( newState->objectNum == oldState->objectNum ); if( visIndex > 0 ) { bool oldVisible = ( oldState->visMask & ( 1 << visIndex ) ) != 0; bool newVisible = ( newState->visMask & ( 1 << visIndex ) ) != 0; // Force visible if we need to either create or destroy this object newVisible |= ( newState->buffer.Size() == 0 ) != ( oldState->buffer.Size() == 0 ); if( !oldVisible && !newVisible ) { // object is stale and ack'ed for this client, write nothing (see 'same object' below) return; } else if( oldVisible && !newVisible ) { NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex ); visChange = true; visSendState = false; } else if( !oldVisible && newVisible ) { NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex ); visChange = true; visSendState = true; } } // Same object, write a delta (never early out during vis changes) if( !visChange && newState->buffer.Size() == oldState->buffer.Size() && ( ( newState->buffer.Ptr() == oldState->buffer.Ptr() ) || memcmp( newState->buffer.Ptr(), oldState->buffer.Ptr(), newState->buffer.Size() ) == 0 ) ) { // same state, write nothing return; } } // Get the id of the object we are writing out uint16 objectNum; if( newState != NULL ) { objectNum = newState->objectNum; } else if( oldState != NULL ) { objectNum = oldState->objectNum; } else { objectNum = 0; } assert( objectNum == 0 || objectNum > lastobjectNum ); // Write out object id (using delta) uint16 objectDelta = objectNum - lastobjectNum; file->WriteBig( objectDelta ); lastobjectNum = objectNum; if( newState == NULL ) { // Deleted, write 0 size assert( oldState != NULL ); file->WriteBig<objectSize_t>( 0 ); } else if( oldState == NULL ) { // New object, write out full state assert( newState != NULL ); // delta against an empty snap file->WriteBig( newState->buffer.Size() ); file->Write( newState->buffer.Ptr(), newState->buffer.Size() ); } else { // Compare to last object assert( newState != NULL && oldState != NULL ); assert( newState->objectNum == oldState->objectNum ); if( visChange ) { // fake size indicates vis state change // NOTE: we may still send a real size and a state below, for 'no longer stale' transitions // TMP: send 0xFFFF for going stale and 0xFFFF - 1 for no longer stale file->WriteBig<objectSize_t>( visSendState ? SIZE_NOT_STALE : SIZE_STALE ); } if( !visChange || visSendState ) { objectSize_t compareSize = Min( newState->buffer.Size(), oldState->buffer.Size() ); // Get the number of bytes that overlap file->WriteBig( newState->buffer.Size() ); // Write new size // Compare bytes that overlap for( objectSize_t b = 0; b < compareSize; b++ ) { file->WriteBig<byte>( ( 0xFF + 1 + newState->buffer[b] - oldState->buffer[b] ) & 0xFF ); } // Write leftover if( newState->buffer.Size() > compareSize ) { file->Write( newState->buffer.Ptr() + oldState->buffer.Size(), newState->buffer.Size() - compareSize ); } } } #ifdef SNAPSHOT_CHECKSUMS if( ( !visChange || visSendState ) && newState != NULL ) { assert( newState->buffer.Size() > 0 ); unsigned int checksum = MD5_BlockChecksum( newState->buffer.Ptr(), newState->buffer.Size() ); file->WriteBig( checksum ); } #endif }
/* ======================== idSnapShot::ReadDelta ======================== */ bool idSnapShot::ReadDelta( idFile* file, int visIndex ) { file->ReadBig( time ); int objectNum = 0; uint16 delta = 0; while( file->ReadBig( delta ) == sizeof( delta ) ) { objectNum += delta; if( objectNum >= 0xFFFF ) { // full delta return true; } objectState_t& state = FindOrCreateObjectByID( objectNum ); objectSize_t newsize = 0; file->ReadBig( newsize ); if( newsize == SIZE_STALE ) { NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum ); // sanity bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; if( !oldVisible ) { NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" ); } state.visMask &= ~( 1 << visIndex ); state.stale = true; // We need to make sure we haven't freed stale objects. assert( state.buffer.Size() > 0 ); // no more data continue; } else if( newsize == SIZE_NOT_STALE ) { NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum ); // sanity bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; if( oldVisible ) { NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" ); } state.visMask |= ( 1 << visIndex ); state.stale = false; // the latest state is packed in, get the new size and continue reading the new state file->ReadBig( newsize ); } if( newsize == 0 ) { // object deleted state.buffer._Release(); } else { objectBuffer_t newbuffer( newsize ); objectSize_t compareSize = Min( newsize, state.buffer.Size() ); for( objectSize_t i = 0; i < compareSize; i++ ) { uint8 delta = 0; file->ReadBig<byte>( delta ); newbuffer[i] = state.buffer[i] + delta; } if( newsize > compareSize ) { file->Read( newbuffer.Ptr() + compareSize, newsize - compareSize ); } state.buffer = newbuffer; state.changedCount++; } #ifdef SNAPSHOT_CHECKSUMS if( state.buffer.Size() > 0 ) { unsigned int checksum = 0; file->ReadBig( checksum ); assert( checksum == MD5_BlockChecksum( state.buffer.Ptr(), state.buffer.Size() ) ); } #endif } // partial delta return false; }
void rvInstance::Populate( int serverChecksum ) { gameState_t currentState = gameLocal.GameState(); // disable the minSpawnIndex lock out int latchMinSpawnIndex = gameLocal.minSpawnIndex; gameLocal.minSpawnIndex = MAX_CLIENTS; if ( currentState != GAMESTATE_STARTUP ) { gameLocal.SetGameState( GAMESTATE_RESTART ); } if ( gameLocal.isServer ) { // When populating on a server, record the entity numbers numMapEntities = gameLocal.GetNumMapEntities(); // mwhitlock: Dynamic memory consolidation RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); if ( mapEntityNumbers ) { delete[] mapEntityNumbers; } mapEntityNumbers = new unsigned short[ numMapEntities ]; RV_POP_HEAP(); memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); // read the index we should start populating at as transmitted by the server gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); //common->Printf( "pos: get starting index for instance %d sets firstFreeIndex to %d\n", instanceID, gameLocal.firstFreeIndex ); // remember the spawnCount ahead of time, so that the client can accurately reconstruct its spawnIds gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers, &initialSpawnCount ); // only build the message in MP if ( gameLocal.isMultiplayer ) { BuildInstanceMessage(); // force joins of anyone in our instance so they get potentially new map entitynumbers for( int i = 0; i < MAX_CLIENTS; i++ ) { PACIFIER_UPDATE; idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; if( player && player->GetInstance() == instanceID ) { networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg ); } } } } else { bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); // When populating on a client, spawn the map using existing numbers // check for good state // OK to spawn w/o specific entity numbers if we're in the startup process. Otherwise, // we need entity numbers from the server. // TTimo: only valid for backward 1.2 playback now assert( !proto69 || ( mapEntityNumbers || ( instanceID == 0 && gameLocal.GameState() == GAMESTATE_STARTUP ) ) ); if ( !proto69 ) { // have the client produce a log of the entity layout so we can match it with the server's // this is also going to be used to issue the EV_FindTargets below if ( mapEntityNumbers ) { delete []mapEntityNumbers; } numMapEntities = gameLocal.GetNumMapEntities(); RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); mapEntityNumbers = new unsigned short[ numMapEntities ]; RV_POP_HEAP(); memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); } gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); gameLocal.SetSpawnCount( initialSpawnCount ); // that was transmitted through the instance msg if ( proto69 ) { gameLocal.SpawnMapEntities( spawnInstanceID, mapEntityNumbers, NULL ); } else { gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers ); LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); if ( serverChecksum != 0 && checksum != serverChecksum ) { common->Error( "client side map populate checksum ( 0x%x ) doesn't match server's ( 0x%x )", checksum, serverChecksum ); } } } for ( int i = 0; i < numMapEntities; i++ ) { if ( mapEntityNumbers[ i ] < 0 || mapEntityNumbers[ i ] >= MAX_GENTITIES ) { continue; } if ( (i % 100) == 0 ) { PACIFIER_UPDATE; } idEntity* ent = gameLocal.entities[ mapEntityNumbers[ i ] ]; if ( ent ) { ent->PostEventMS( &EV_FindTargets, 0 ); ent->PostEventMS( &EV_PostSpawn, 0 ); } } if ( currentState != GAMESTATE_STARTUP ) { gameLocal.SetGameState( currentState ); } // re-enable the min spawn index assert( latchMinSpawnIndex == MAX_CLIENTS || gameLocal.firstFreeIndex <= latchMinSpawnIndex ); gameLocal.minSpawnIndex = latchMinSpawnIndex; }